第四次课程提交 认证登录和用户的初始化

This commit is contained in:
Y1NanPing 2025-07-22 19:00:12 +08:00
parent aabcd49dfd
commit 1378baeac6
20 changed files with 362 additions and 9 deletions

View File

@ -14,9 +14,8 @@ public class AuthContextHolder {
} }
public static Long getUserId() { public static Long getUserId() {
//return userId.get(); return userId.get();
//TODO 暂时硬编码为1后续做完登录验证后再修改
return 1L;
} }
public static void removeUserId() { public static void removeUserId() {

View File

@ -0,0 +1,15 @@
package com.atguigu.tingshu.common.login;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface GuiGuLogin {
/**
* 改接口调用是否需要必须登录
* @return
*/
boolean required() default true;
}

View File

@ -0,0 +1,80 @@
package com.atguigu.tingshu.common.login;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* @author: atguigu
* @create: 2025-07-21 09:49
*/
@Slf4j
@Aspect
@Component
public class GuiGuLoginAspect {
@Autowired
private RedisTemplate redisTemplate;
/**
* 为了代码规范认证状态校验注解只能用在api包下且只有使用@GuiGuLogin注解的方法才能被增强
*
* @param pjp 目标方法对象
* @return
* @throws Throwable
*/
@Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)")
public Object doBasicProfiling(ProceedingJoinPoint pjp, GuiGuLogin guiGuLogin) throws Throwable {
log.info("一、前置逻辑");
//1.获取用户令牌用于查询redis中用户基本信息
//1.1 获取请求对象
//1.1.1 获取请求上下文对象 RequestAttributes是接口
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//1.1.2 获取请求对象 ServletRequestAttributes是实现类
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
//1.2 获取请求头token
String token = request.getHeader("token");
//2.查询当时在登录成功后存入redis中用户基本信息 UserInfoVo
//2.1. 构建查询用户信息Redis的Key 注意一定要跟登录成功存入Redis一致
String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
//2.2 查询Redis中用户信息
UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKey);
//3.如果目标方法要求必须登录但查询Redis用户信息为空抛出登录异常响应业务状态码208
if (guiGuLogin.required() && userInfoVo == null) {
log.error("用户未登录,引导用户登录");
throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
}
//4.如果查询Redis存在用户信息将用户ID存入ThreadLocal中方便JavaEE三层任意层获取用户ID
if (userInfoVo != null) {
AuthContextHolder.setUserId(userInfoVo.getId());
}
log.info("二、目标方法执行");
Object retVal = pjp.proceed();
log.info("三、后置逻辑");
//5.清理ThreadLocal防止内存泄漏
AuthContextHolder.removeUserId();
return retVal;
}
}

View File

@ -0,0 +1,49 @@
package com.atguigu.tingshu.account.receiver;
import cn.hutool.core.collection.CollUtil;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.rabbitmq.client.Channel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author: atguigu
* @create: 2025-07-21 14:31
*/
@Slf4j
@Component
public class AccountReceiver {
@Autowired
private UserAccountService userAccountService;
/**
* 用户首次登录初始化账户信息
* @param map
* @param channel
* @param message
*/
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
exchange = @Exchange(value = MqConst.EXCHANGE_USER, durable = "true"),
value = @Queue(value = MqConst.QUEUE_USER_REGISTER, durable = "true"),
key = MqConst.ROUTING_USER_REGISTER
))
public void initUserAccountListener(Map<String, Object> map, Channel channel, Message message) {
if (CollUtil.isNotEmpty(map)) {
log.info("用户首次登录,初始化账户信息,消息:{}", map);
userAccountService.saveUserAccount(map);
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}

View File

@ -3,7 +3,23 @@ package com.atguigu.tingshu.account.service;
import com.atguigu.tingshu.model.account.UserAccount; import com.atguigu.tingshu.model.account.UserAccount;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import java.math.BigDecimal;
import java.util.Map;
public interface UserAccountService extends IService<UserAccount> { public interface UserAccountService extends IService<UserAccount> {
/**
* 保存账户记录
* @param map
*/
void saveUserAccount(Map<String, Object> map);
/**
* 保存账户变动日志例如充值消费
* @param userId 用户ID
* @param title 内容
* @param amount 金额
* @param orderNo 订单编号
*/
void saveUserAccountDetail(Long userId, String title, BigDecimal amount, String orderNo);
} }

View File

@ -1,12 +1,19 @@
package com.atguigu.tingshu.account.service.impl; package com.atguigu.tingshu.account.service.impl;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper; import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService; import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.account.UserAccount; import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.Map;
@Slf4j @Slf4j
@Service @Service
@ -15,5 +22,40 @@ public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserA
@Autowired @Autowired
private UserAccountMapper userAccountMapper; private UserAccountMapper userAccountMapper;
@Autowired
private UserAccountDetailMapper userAccountDetailMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveUserAccount(Map<String, Object> map) {
//1.新增账户记录
Long userId = (Long) map.get("userId");
BigDecimal amount = (BigDecimal) map.get("amount");
UserAccount userAccount = new UserAccount();
userAccount.setUserId(userId);
userAccount.setTotalAmount(amount);
userAccount.setLockAmount(BigDecimal.ZERO);
userAccount.setAvailableAmount(amount);
userAccount.setTotalIncomeAmount(amount);
userAccount.setTotalPayAmount(BigDecimal.ZERO);
int rows = userAccountMapper.insert(userAccount);
if (rows > 0) {
String title = (String) map.get("title");
String orderNo = (String) map.get("orderNo");
//2.保存账户变动日志
this.saveUserAccountDetail(userId, title, amount, orderNo);
}
} }
@Override
public void saveUserAccountDetail(Long userId, String title, BigDecimal amount, String orderNo) {
UserAccountDetail userAccountDetail = new UserAccountDetail();
userAccountDetail.setUserId(userId);
userAccountDetail.setTitle(title);
userAccountDetail.setTradeType(SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT);
userAccountDetail.setAmount(amount);
userAccountDetail.setOrderNo(orderNo);
userAccountDetailMapper.insert(userAccountDetail);
}
}

View File

@ -1,6 +1,7 @@
package com.atguigu.tingshu.album.api; package com.atguigu.tingshu.album.api;
import com.atguigu.tingshu.album.service.AlbumInfoService; import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.common.login.GuiGuLogin;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.AuthContextHolder; import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.AlbumInfo;
@ -28,6 +29,7 @@ public class AlbumInfoApiController {
@Operation(summary = "新建专辑信息") @Operation(summary = "新建专辑信息")
@GuiGuLogin
@PostMapping("/albumInfo/saveAlbumInfo") @PostMapping("/albumInfo/saveAlbumInfo")
public Result saveAlbumInfo(@Validated @RequestBody AlbumInfoVo albumInfoVo) { public Result saveAlbumInfo(@Validated @RequestBody AlbumInfoVo albumInfoVo) {
Long userId = AuthContextHolder.getUserId(); Long userId = AuthContextHolder.getUserId();
@ -37,11 +39,11 @@ public class AlbumInfoApiController {
} }
//这个是在登录到专辑页面的时候会调用这个接口获取当前登录用户的专辑信息 //这个是在登录到专辑页面的时候会调用这个接口获取当前登录用户的专辑信息
@GuiGuLogin
@Operation(summary = "分页条件查询当前登录用户发布专辑") @Operation(summary = "分页条件查询当前登录用户发布专辑")
@PostMapping("/albumInfo/findUserAlbumPage/{page}/{limit}") @PostMapping("/albumInfo/findUserAlbumPage/{page}/{limit}")
public Result<IPage<AlbumListVo>> findUserAlbumPage(@PathVariable Long page, public Result<IPage<AlbumListVo>> findUserAlbumPage(@PathVariable Long page,
@PathVariable Long limit, @PathVariable Long limit,@RequestBody AlbumInfoQuery albumInfoQuery){
@RequestBody AlbumInfoQuery albumInfoQuery){
//先从线程中取出来用户id //先从线程中取出来用户id
Long userId = AuthContextHolder.getUserId(); Long userId = AuthContextHolder.getUserId();
//将用户id放进到查询条件中 //将用户id放进到查询条件中
@ -81,6 +83,7 @@ public class AlbumInfoApiController {
* 查询当前用户专辑列表 * 查询当前用户专辑列表
* @return * @return
*/ */
@GuiGuLogin
@Operation(summary = "查询当前用户专辑列表") @Operation(summary = "查询当前用户专辑列表")
@GetMapping("/albumInfo/findUserAllAlbumList") @GetMapping("/albumInfo/findUserAllAlbumList")
public Result<List<AlbumInfo>> findUserAllAlbumList(){ public Result<List<AlbumInfo>> findUserAllAlbumList(){

View File

@ -2,6 +2,7 @@ package com.atguigu.tingshu.album.api;
import com.atguigu.tingshu.album.service.TrackInfoService; import com.atguigu.tingshu.album.service.TrackInfoService;
import com.atguigu.tingshu.album.service.VodService; import com.atguigu.tingshu.album.service.VodService;
import com.atguigu.tingshu.common.login.GuiGuLogin;
import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.AuthContextHolder; import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.album.TrackInfo; import com.atguigu.tingshu.model.album.TrackInfo;
@ -47,6 +48,7 @@ public class TrackInfoApiController {
* @param trackInfoVo * @param trackInfoVo
* @return * @return
*/ */
@GuiGuLogin
@Operation(summary = "保存声音") @Operation(summary = "保存声音")
@PostMapping("/trackInfo/saveTrackInfo") @PostMapping("/trackInfo/saveTrackInfo")
public Result saveTrackInfo(@Validated @RequestBody TrackInfoVo trackInfoVo) { public Result saveTrackInfo(@Validated @RequestBody TrackInfoVo trackInfoVo) {
@ -63,6 +65,7 @@ public class TrackInfoApiController {
* @param trackInfoQuery * @param trackInfoQuery
* @return * @return
*/ */
@GuiGuLogin
@Operation(summary = "分页条件查询当前用户声音列表") @Operation(summary = "分页条件查询当前用户声音列表")
@PostMapping("/trackInfo/findUserTrackPage/{page}/{limit}") @PostMapping("/trackInfo/findUserTrackPage/{page}/{limit}")
public Result<Page<TrackListVo>> findUserTrackPage( public Result<Page<TrackListVo>> findUserTrackPage(

View File

@ -32,6 +32,11 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId> <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,11 +1,17 @@
package com.atguigu.tingshu.user.api; package com.atguigu.tingshu.user.api;
import com.atguigu.tingshu.common.login.GuiGuLogin;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.user.service.UserInfoService; import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@Tag(name = "微信授权登录接口") @Tag(name = "微信授权登录接口")
@RestController @RestController
@ -16,5 +22,47 @@ public class WxLoginApiController {
@Autowired @Autowired
private UserInfoService userInfoService; private UserInfoService userInfoService;
/**
* 微信一键登录
*
* @param code 小程序集成微信基于当前微信用户生成临时票据code
* @return {token:"令牌"}
*/
@Operation(summary = "小程序端微信一键登录")
@GetMapping("/wxLogin/{code}")
public Result<Map<String, String>> wxLogin(@PathVariable String code) {
Map<String, String> map = userInfoService.wxLogin(code);
return Result.ok(map);
}
/**
* 获取当前登录用户基本信息
* @return
*/
@GuiGuLogin
@GetMapping("/getUserInfo")
@Operation(summary = "获取当前登录用户基本信息")
public Result<UserInfoVo> getUserInfo() {
Long userId = AuthContextHolder.getUserId();
UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
return Result.ok(userInfoVo);
}
/**
* 更新当前用户基本信息
* @param userInfoVo
* @return
*/
@GuiGuLogin
@Operation(summary = "更新当前用户基本信息")
@PostMapping("/updateUser")
public Result updateUser(@RequestBody UserInfoVo userInfoVo) {
Long userId = AuthContextHolder.getUserId();
userInfoService.updateUser(userInfoVo, userId);
return Result.ok();
}
} }

View File

@ -1,8 +1,16 @@
package com.atguigu.tingshu.user.service; package com.atguigu.tingshu.user.service;
import com.atguigu.tingshu.model.user.UserInfo; import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Map;
public interface UserInfoService extends IService<UserInfo> { public interface UserInfoService extends IService<UserInfo> {
Map<String, String> wxLogin(String code);
UserInfoVo getUserInfo(Long userId);
void updateUser(UserInfoVo userInfoVo, Long userId);
} }

View File

@ -1,13 +1,30 @@
package com.atguigu.tingshu.user.service.impl; package com.atguigu.tingshu.user.service.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.model.user.UserInfo; import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.user.mapper.UserInfoMapper; import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.service.UserInfoService; import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@Service @Service
@SuppressWarnings({"all"}) @SuppressWarnings({"all"})
@ -15,5 +32,73 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
@Autowired @Autowired
private UserInfoMapper userInfoMapper; private UserInfoMapper userInfoMapper;
@Autowired
private WxMaService wxMaService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RabbitService rabbitService;
@Override
public Map<String, String> wxLogin(String code) {
try {
//1.根据小程序提交的临时票据code调用微信接口服务获取当前微信用户唯一标识openid
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
String wxOpenid = sessionInfo.getOpenid();
//2.根据openid查询数据库用户表判断微信账户是否关联用户
UserInfo userInfo = userInfoMapper.selectOne(
new LambdaQueryWrapper<UserInfo>()
.eq(UserInfo::getWxOpenId, wxOpenid)
);//3.如果不存在用户记录说明该微信是首次登录
if (userInfo == null) {
//3.1 新增用户记录
userInfo = new UserInfo();
userInfo.setWxOpenId(wxOpenid);
userInfo.setNickname("听友" + IdUtil.nanoId());
userInfo.setAvatarUrl("http://192.168.200.6:9000/tingshu/2025-07-21/1ce194e3-4212-4ff8-948b-babd9a7b8a02.jpg");
userInfoMapper.insert(userInfo);
//3.2 TODO 隐式初始化账户记录用于后续订单消费,采用MQ
//3.2.1 创建初始化账户MQ消息对象 注意如果使用的是VO对象该对象必须得实现序列化接口 Serializable
Map<String, Object> mqData = new HashMap<>();
mqData.put("userId", userInfo.getId());
mqData.put("amount", BigDecimal.valueOf(100));
mqData.put("orderNo", "ZS"+ DateUtil.today().replaceAll("-", "")+IdUtil.getSnowflakeNextId());
mqData.put("title", "首次注册,赠送体验金活动");
//3.2.2 发送消息到RabbitMQ交换机 todo 使用RabbitService
rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, mqData);
} }
//4.无论是否首次登录都需要生成令牌返回给小程序
//4.1 生成随机令牌
String token = IdUtil.randomUUID();
//4.2 将用户令牌作为Key用户基本信息UserInfoVo作为Value保存到Redis中
String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class);
redisTemplate.opsForValue().set(loginKey, userInfoVo, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
//4.3 返回用户令牌
return Map.of("token", token);
} catch (WxErrorException e) {
log.error("微信登录失败:{}", e.getMessage());
throw new RuntimeException(e);
}
}
@Override
public UserInfoVo getUserInfo(Long userId) {
UserInfo userInfo = userInfoMapper.selectById(userId);
return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
}
@Override
public void updateUser(UserInfoVo userInfoVo, Long userId) {
UserInfo userInfo = new UserInfo();
userInfo.setId(userId);
userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
userInfo.setNickname(userInfoVo.getNickname());
userInfoMapper.updateById(userInfo);
}
}