it_lv 1 місяць тому
батько
коміт
58f429ef18
1 змінених файлів з 161 додано та 158 видалено
  1. 161 158
      第3章 用户登录.md

+ 161 - 158
第3章 用户登录.md

@@ -145,57 +145,54 @@ import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
 /**
- * 对所有业务微服务模块中com.atguigu.tingshu.*.api包下所有使用@GuiGuLogin注解方法所在类进行增强
- *
  * @author: atguigu
- * @create: 2024-08-06 10:39
+ * @create: 2025-05-30 09:51
  */
 @Slf4j
 @Aspect
 @Component
-public class GuiguLoginAspect {
+public class GuiGuLoginAspect {
 
     @Autowired
     private RedisTemplate redisTemplate;
 
-    /**
-     * 进行验证用户登录状态环绕通知逻辑
-     *
-     * @param pjp        切入点对象
-     * @param guiGuLogin 方法使用使用注解对象
-     * @return
-     * @throws Throwable
-     */
     @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)")
-    public Object doBasicProfiling(ProceedingJoinPoint pjp, GuiGuLogin guiGuLogin) throws Throwable {
-        //1.前置逻辑
-        log.info("[认证注解切面]前置逻辑...");
-        //1.1 获取请求对象;获取请求头名称为token的用户令牌
+    public Object around(ProceedingJoinPoint pjp, GuiGuLogin guiGuLogin) throws Throwable {
+        //一、前置逻辑
+        log.info("前置逻辑...");
+        //1.获取到请求对象
+        //1.1 通过请求上下文(底层ThreadLocal)对象获取请求对象 RequestAttributes接口
         RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+        //1.2 将接口类型强转为ServletRequestAttributes实现类
         ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
+        //1.3 获取请求对象
         HttpServletRequest request = sra.getRequest();
+
+        //2.获取请求头token中令牌
         String token = request.getHeader("token");
-        //1.2 将token作为Key查询Redis中用户信息
-        //1.2.1 构建登录成功存入Redis用户令牌Key查询 形式:user:login:token值
+
+        //3.查询Redis中用户基本信息
+        //3.1 构建在登录成功后存入Redis用户信息Key 形式=前缀+用户令牌
         String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
-        //1.2.2 获取存在Redis中用户信息UserInfoVo
+        //3.2 查询在登录成功后存入Redis用户基本信息 UserInfoVo
         UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKey);
 
-        //1.3 如果认证注解对象属性required=true且用户信息为空抛出异常:状态码=208 引导用户登录
-        if (guiGuLogin.required() && userInfoVo == null) {
+        //4.如果Redis中登录用户信息为空切目标方法要求必须登录 抛出异常 前端要求响应业务状态码为208,前端引导用户登录
+        if (userInfoVo == null && guiGuLogin.required()) {
             throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
         }
-        //1.4 如果用户信息有值,将用户ID存入ThreadLocal中
+
+        //5.如果Redis中用户信息存在,获取用户ID,将用户ID存入ThreadLocal 方便后续javaEE三层获取用户ID
         if (userInfoVo != null) {
             AuthContextHolder.setUserId(userInfoVo.getId());
         }
-        //2.执行目标方法(controller->service-mapper)
-        Object retVal = pjp.proceed();
-        //3.后置逻辑
-        log.info("[认证注解切面]后置逻辑...");
-        //3.1 手动清理ThreadLocal避免出现使用不当导致内存泄漏,不断内存泄漏导致内存溢出
+        //二、目标方法 controller使用使用自定义认证注解的方法
+        Object result = pjp.proceed();
+        //三、后置逻辑
+        log.info("后置逻辑...");
+        //6.避免ThreadLocal引发的内存泄漏,清理ThreadLocal
         AuthContextHolder.removeUserId();
-        return retVal;
+        return result;
     }
 }
 ```
@@ -376,12 +373,12 @@ public class WxLoginApiController {
 
 
     /**
-     * 提供给小程序微信登录接口
+     * 微信一键登录
      *
-     * @param code 小程序集成SDK后获取临时票据(基于当前微信用户产生的)
-     * @return
+     * @param code 小程序端根据当前微信,生成访问为微信服务端临时凭据
+     * @return {token:令牌}
      */
-    @Operation(summary = "提供给小程序微信登录接口")
+    @Operation(summary = "微信一键登录")
     @GetMapping("/wxLogin/{code}")
     public Result<Map<String, String>> wxLogin(@PathVariable String code) {
         Map<String, String> map = userInfoService.wxLogin(code);
@@ -461,61 +458,66 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
     @Autowired
     private RabbitService rabbitService;
 
-    /**
-     * 提供给小程序微信登录接口
+        /**
+     * 微信一键登录
      *
-     * @param code 小程序集成SDK后获取临时票据(基于当前微信用户产生的)
+     * @param code 小程序端根据当前微信,生成访问为微信服务端临时凭据
      * @return
      */
     @Override
     public Map<String, String> wxLogin(String code) {
         try {
-            //1.根据小程序提交code再加appid+appsecret获取微信账户唯一标识 wxOpenId
-            WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(code);
-            String wxOpenid = sessionInfo.getOpenid();
-            if (StringUtils.isBlank(wxOpenid)) {
-                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
-            }
-            //2.根据微信唯一标识查询用户记录
-            LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
-            queryWrapper.eq(UserInfo::getWxOpenId, wxOpenid);
-            UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
-
-            //3.判断用户记录是否存在(用户是否绑定过该微信账号)
+            //1.拿着临时凭据+应用ID+应用秘钥 调用微信接口 获取当前微信账户唯一标识:openId
+            //1.1 微信账户信息业务类
+            WxMaUserService userService = wxMaService.getUserService();
+            //1.2 获取会话信息
+            WxMaJscode2SessionResult sessionInfo = userService.getSessionInfo(code);
+            //1.3 获取微信账号唯一标识
+            String openid = sessionInfo.getOpenid();
+
+            //2.根据微信账户唯一标识,查询数据库,看当前微信是否已经注册
+            UserInfo userInfo = userInfoMapper.selectOne(
+                    new LambdaQueryWrapper<UserInfo>()
+                            .eq(UserInfo::getWxOpenId, openid)
+            );
+
+            //3.如果微信账户首次注册,则新增用户记录,为用户初始化账户记录用于后续订单支付
             if (userInfo == null) {
-                //TODO 如果不存在,新增用户记录绑定微信账户唯一标识,隐式初始化账户记录
-                //3.1 构建保存用户信息记录绑定微信唯一标识
+                //3.1 新增用户记录 绑定微信账户唯一标识
                 userInfo = new UserInfo();
-                userInfo.setWxOpenId(wxOpenid);
-                userInfo.setNickname("听友"+IdUtil.getSnowflakeNextId());
-                userInfo.setAvatarUrl("https://thirdwx.qlogo.cn/mmopen/vi_32/hFKeRpRQU4wG…axvke5nueicggowdBricR4pspWbp6dwFtLSCWJKyZGJoQ/132");
+                userInfo.setWxOpenId(openid);
+                userInfo.setNickname(IdUtil.nanoId());
+                userInfo.setAvatarUrl("http://192.168.200.6:9000/tingshu/2024-04-02/0b033705-4603-4fb2-bd0f-db84076aef84.jpg");
                 userInfoMapper.insert(userInfo);
-                //3.2 隐式初始化账户记录
-                //3.2.1 构建初始化账户所需参数对象
-                Map<String, Object> mapData = new HashMap<>();
-                mapData.put("userId", userInfo.getId());
-                mapData.put("title", "首次登录赠送体验金");
-                mapData.put("amount", new BigDecimal("10"));
-                mapData.put("orderNo", "zs"+IdUtil.getSnowflakeNextId());
-                //3.2.2 调用发送消息工具类方法发送消息
-                rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, mapData);
+                //3.2 TODO 为当前注册用户初始化账户记录
+                // 方案一:Openfeign远程调用 分布式事务问题  方案二:采用MQ可靠性消息队列实现数据最终一致
+                //3.2.1 构建消息对象 注意:如果是VO对象一定要实现序列化接口以及生成序列化版本号
+                Map<String, Object> map = new HashMap<>();
+                map.put("userId", userInfo.getId());
+                map.put("amount", new BigDecimal("100"));
+                map.put("title", "新用户专项体验金活动");
+                map.put("orderNo", "ZS"+IdUtil.getSnowflakeNextId());
+                //3.2.2 发送消息到MQ
+                rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_REGISTER, map);
             }
-            //4.为用户生成令牌存入Redis 其中Redis中Key=前缀+token Value=用户基本信息UserInfoVo
-            //4.1 生成token值
+
+            //4.基于当前用户信息生成令牌
+            //4.1 创建令牌
             String token = IdUtil.randomUUID();
-            //4.2 构建用户登录Key 形式=user:login:token值
+            //4.2 构建登录成功后Redis的Key 形式为:user:login:token
             String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
-            //4.3 将用户登录信息UserInfoVo存入Redis
+            //4.3 构建登录成功后Redis的Value 形式为:userInfo
             UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class);
+            //4.4 存入Redis 设置有效期:7天
             redisTemplate.opsForValue().set(loginKey, userInfoVo, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
 
-            //5.封装token到对象响应给前端
+            //5.封装令牌返回给前端
             Map<String, String> map = new HashMap<>();
             map.put("token", token);
             return map;
-        } catch (Exception e) {
-            log.error("[用户服务]微信登录接口异常:{}", e);
-            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
+        } catch (WxErrorException e) {
+            log.error("微信登录失败");
+            throw new GuiguException(500, "微信登录失败");
         }
     }
 }
@@ -546,7 +548,7 @@ spring:
 ```java
 package com.atguigu.tingshu.account.receiver;
 
-import cn.hutool.core.collection.CollectionUtil;
+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;
@@ -564,21 +566,21 @@ import java.util.Map;
 
 /**
  * @author: atguigu
- * @create: 2024-08-06 15:48
+ * @create: 2025-05-30 14:11
  */
 @Slf4j
 @Component
 public class AccountReceiver {
 
-
     @Autowired
     private UserAccountService userAccountService;
 
     /**
-     * 监听到注册业务数据,为用户隐式初始化账户信息
-     * @param mapData 初始化账户相关数据 {"userId",1,"title":"","amount":10,"orderNo":"cz001"}
-     * @param message 消息对象
-     * @param channel 连接对象
+     * 用户首次注册成功后,为用户初始账户余额记录
+     *
+     * @param map     消息对象 包含:用户ID,赠送体验金额,标题,订单编号
+     * @param channel
+     * @param message
      */
     @SneakyThrows
     @RabbitListener(bindings = @QueueBinding(
@@ -586,15 +588,12 @@ public class AccountReceiver {
             value = @Queue(value = MqConst.QUEUE_USER_REGISTER, durable = "true"),
             key = MqConst.ROUTING_USER_REGISTER
     ))
-    public void registerAndInitAccount(Map mapData, Message message, Channel channel) {
-        if (CollectionUtil.isNotEmpty(mapData)) {
-            //1. 处理业务
-            log.info("[账户服务]监听到用户首次注册初始化账户消息:{}", mapData);
-            userAccountService.saveUserAccount(mapData);
+    public void initUserAccount(Map<String, Object> map, Channel channel, Message message) {
+        log.info("[账户服务]用户首次注册成功后,为用户初始账户余额记录:{}", map);
+        if(CollUtil.isNotEmpty(map)){
+            userAccountService.initUserAccount(map);
         }
-        //2.手动确认消息 参数1:为投递消息分配唯一标识 参数2:
         channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
-
     }
 }
 ```
@@ -614,20 +613,22 @@ public interface UserAccountService extends IService<UserAccount> {
 
 
     /**
-     * 初始化账户记录;新增账户变动日志
-     * @param mapData
+     * 用户首次注册成功后,为用户初始账户余额记录
+     *
+     * @param map 消息对象 包含:用户ID,赠送体验金额,标题,订单编号
      */
-    void saveUserAccount(Map mapData);
+    void initUserAccount(Map<String, Object> map);
+
 
     /**
-     * 保存账户变动日志
+     * 新增账户变动日志
      * @param userId 用户ID
-     * @param titile 内容
-     * @param tradeType 交易类型 1201-充值 1202-锁定 1203-解锁 1204-消费
+     * @param title 内容
+     * @param tradeType 交易类型
      * @param amount 金额
      * @param orderNo 订单编号
      */
-    void saveUserAccountDetail(Long userId, String titile, String tradeType, BigDecimal amount, String orderNo);
+    void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo);
 }
 ```
 
@@ -656,54 +657,56 @@ import java.util.Map;
 @SuppressWarnings({"all"})
 public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {
 
-	@Autowired
-	private UserAccountMapper userAccountMapper;
-
-	@Autowired
-	private UserAccountDetailMapper userAccountDetailMapper;
-	/**
-	 * 初始化账户记录;新增账户变动日志
-	 * @param mapData {"userId",1,"title":"","amount":10,"orderNo":"cz001"}
-	 */
-	@Override
-	@Transactional(rollbackFor = Exception.class)
-	public void saveUserAccount(Map mapData) {
-		//1.获取从MQ中获取到参数
-		Long userId = (Long) mapData.get("userId");
-		String title = (String) mapData.get("title");
-		BigDecimal amount = (BigDecimal) mapData.get("amount");
-		String orderNo = (String) mapData.get("orderNo");
-
-		//2.保存账户记录
-		UserAccount userAccount = new UserAccount();
-		userAccount.setUserId(userId);
-		userAccount.setTotalAmount(amount);
-		userAccount.setAvailableAmount(amount);
-		userAccount.setTotalIncomeAmount(amount);
-		userAccountMapper.insert(userAccount);
-		//3.新增账户变动日志
-		this.saveUserAccountDetail(userId, title, SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT, amount, orderNo);
-	}
-
-
-	/**
-	 * 保存账户变动日志
-	 * @param userId 用户ID
-	 * @param titile 内容
-	 * @param tradeType 交易类型 1201-充值 1202-锁定 1203-解锁 1204-消费
-	 * @param amount 金额
-	 * @param orderNo 订单编号
-	 */
-	@Override
-	public void saveUserAccountDetail(Long userId, String titile, String tradeType, BigDecimal amount, String orderNo) {
-		UserAccountDetail userAccountDetail = new UserAccountDetail();
-		userAccountDetail.setUserId(userId);
-		userAccountDetail.setTitle(titile);
-		userAccountDetail.setTradeType(tradeType);
-		userAccountDetail.setAmount(amount);
-		userAccountDetail.setOrderNo(orderNo);
-		userAccountDetailMapper.insert(userAccountDetail);
-	}
+    @Autowired
+    private UserAccountMapper userAccountMapper;
+
+    @Autowired
+    private UserAccountDetailMapper userAccountDetailMapper;
+
+    /**
+     * 用户首次注册成功后,为用户初始账户余额记录
+     *
+     * @param map 消息对象 包含:用户ID,赠送体验金额,标题,订单编号
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void initUserAccount(Map<String, Object> map) {
+        //1.新增账户记录
+        Long userId = (Long) map.get("userId");
+        BigDecimal amount = (BigDecimal) map.get("amount");
+        String title = (String) map.get("title");
+        String orderNo = (String) map.get("orderNo");
+        UserAccount userAccount = new UserAccount();
+        userAccount.setUserId(userId);
+        userAccount.setTotalAmount(amount);
+        userAccount.setAvailableAmount(amount);
+        userAccount.setTotalIncomeAmount(amount);
+        userAccount.setLockAmount(new BigDecimal("0.00"));
+        userAccount.setTotalPayAmount(new BigDecimal("0.00"));
+        userAccountMapper.insert(userAccount);
+
+        //2.新增账户变动日志
+        this.saveUserAccountDetail( userId, title, SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT, amount, orderNo);
+    }
+
+    /**
+     * 新增账户变动日志
+     * @param userId 用户ID
+     * @param title 内容
+     * @param tradeType 交易类型 1201-充值 1202-锁定 1203-解锁 1204-消费',
+     * @param amount 金额
+     * @param orderNo 订单编号
+     */
+    @Override
+    public void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
+        UserAccountDetail userAccountDetail = new UserAccountDetail();
+        userAccountDetail.setUserId(userId);
+        userAccountDetail.setTitle(title);
+        userAccountDetail.setTradeType(tradeType);
+        userAccountDetail.setAmount(amount);
+        userAccountDetail.setOrderNo(orderNo);
+        userAccountDetailMapper.insert(userAccountDetail);
+    }
 }
 ```
 
@@ -722,15 +725,14 @@ TIPS:以前在**AuthContextHolder**硬编码的用户ID可以修改为动态
 ```java
 /**
  * 获取当前登录用户基本信息
+ *
  * @return
  */
 @GuiGuLogin
 @Operation(summary = "获取当前登录用户基本信息")
 @GetMapping("/getUserInfo")
-public Result<UserInfoVo> getUserInfo(){
-    //1.获取用户ID
+public Result<UserInfoVo> getUserInfo() {
     Long userId = AuthContextHolder.getUserId();
-    //2.获取用户信息
     UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
     return Result.ok(userInfoVo);
 }
@@ -740,7 +742,8 @@ public Result<UserInfoVo> getUserInfo(){
 
 ```java
 /**
- * 获取指定用户基本信息
+ * 根据用户ID查询用户基本信息
+ * @param userId
  * @return
  */
 UserInfoVo getUserInfo(Long userId);
@@ -750,16 +753,16 @@ UserInfoVo getUserInfo(Long userId);
 
 ```java
 /**
- * 获取指定用户基本信息
+ * 根据用户ID查询用户基本信息
  *
+ * @param userId
  * @return
  */
 @Override
 public UserInfoVo getUserInfo(Long userId) {
     UserInfo userInfo = userInfoMapper.selectById(userId);
     if (userInfo != null) {
-        UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class);
-        return userInfoVo;
+        return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
     }
     return null;
 }
@@ -767,7 +770,7 @@ public UserInfoVo getUserInfo(Long userId) {
 
 # 4、更新用户信息方法
 
-![登录-修改用户信息](assets/登录-修改用户信息.gif)
+ ![登录-修改用户信息](assets/登录-修改用户信息.gif)
 
 需求:登录成功之后,可以修改用户基本信息。
 
@@ -777,17 +780,15 @@ public UserInfoVo getUserInfo(Long userId) {
 
 ```java
 /**
- * 更新当前登录用户基本信息
+ * 更新当前用户信息
  * @param userInfoVo
  * @return
  */
 @GuiGuLogin
+@Operation(summary = "更新当前用户信息")
 @PostMapping("/updateUser")
-public Result updateUser(@RequestBody UserInfoVo userInfoVo){
-    //1.获取用户ID
-    Long userId = AuthContextHolder.getUserId();
-    //2.更新用户信息
-    userInfoService.updateUser(userId, userInfoVo);
+public Result updateUser(@RequestBody UserInfoVo userInfoVo) {
+    userInfoService.updateUser(userInfoVo);
     return Result.ok();
 }
 ```
@@ -796,23 +797,25 @@ public Result updateUser(@RequestBody UserInfoVo userInfoVo){
 
 ```java
 /**
- * 更新用户基本信息
- * @param userId
+ * 更新用户信息方法
  * @param userInfoVo
  */
-void updateUser(Long userId, UserInfoVo userInfoVo);
+void updateUser(UserInfoVo userInfoVo);
 ```
 
 **UserInfoServiceImpl**实现类
 
 ```java
 /**
- * 更新用户基本信息(昵称、头像)
- * @param userId
+ * 更新用户信息方法
+ * 只允许修改昵称头像
  * @param userInfoVo
  */
 @Override
-public void updateUser(Long userId, UserInfoVo userInfoVo) {
+public void updateUser(UserInfoVo userInfoVo) {
+    //1.获取用户ID
+    Long userId = AuthContextHolder.getUserId();
+    //2.更新用户昵称跟头像
     UserInfo userInfo = new UserInfo();
     userInfo.setId(userId);
     userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());