**谷粒随享** ## 第3章 用户登录 **学习目标:** - 了解小程序/移动端登录业务需求 - 认证状态校验(鉴权) - **自定义注解** - 认证**切面**类 - 用户登录 - **微信**登录 - 初次登录(初始化账户(余额)记录) - 非初次登录 - 完成登录(响应登录token) - 获取用户信息 - 用户信息修改 # 1、认证状态校验 ![登录-认证拦截](assets/登录-认证拦截.gif) 关键点:客户端发起请求后响应业务状态码code是**208**,自动到登录页面引导用户进行登录 ## 1.1 自定义注解 ​ 当用户在查询专辑列表的时候,要求用户必须是登录状态。就应该让用户登录,所以在此我们自定义一个注解来表示访问此功能时必须要登录。 注解作用:哪些需要登录才能访问必须要添加,那些需要获取到用户Id控制层方法也必须加这个注解. 在`service-util` 模块中添加登录注解 ```java package com.atguigu.tingshu.common.login; import java.lang.annotation.*; /** * 修饰controller层方法,被注解标识方法对其所在类进行增强 * 元注解: * * @Target:指定使用位置,ElementType.TYPE:类上 ElementType.METHOD:方法上 * @Retention:注解保留阶段 * @Inherited:是否可以被继承 * @Documented:通过javadoc生成类文档时候是否显示类的注解信息 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface GuiGuLogin { /** * 该注解修饰方法是否必须登录,默认为必须登录 * @return */ boolean required() default true; } ``` ## 1.2 切面类 RequestContextHolder类持有上下文的Request容器。 用法: ```java // 获取请求对象 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); // 转化为ServletRequestAttributes ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes; // 获取到HttpServletRequest 对象 HttpServletRequest request = sra.getRequest(); // 获取到HttpServletResponse 对象 HttpServletResponse response = sra.getResponse(); ``` request 和 response 如何与 当前进行挂钩的?看底层源码 首先分析RequestContextHolder这个类,里面有两个ThreadLocal保存当前线程下的request ```java public abstract class RequestContextHolder { // 得到存储进去的request private static final ThreadLocal requestAttributesHolder = new NamedThreadLocal("Request attributes"); //可被子线程继承的reques private static final ThreadLocal inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context"); ``` 再看getRequestAttributes() 方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request. ```java @Nullable public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get(); if (attributes == null) { attributes = (RequestAttributes)inheritableRequestAttributesHolder.get(); } return attributes; } ``` request和response等是什么时候设置进去的? springMVC 核心类DispatcherServlet 继承关系 ![](assets/tingshu007.png) 1. `HttpServletBean` 进行初始化工作 2. `FrameworkServlet` 初始化 `WebApplicationContext`,并提供service方法预处理请 3. `DispatcherServlet` 具体分发处理. 那么就可以在`FrameworkServlet`查看到该类重写了service(),doGet(),doPost()...等方法,这些实现里面都有一个预处理方法`processRequest(request, response);`,所以定位到了我们要找的位置 查看`processRequest(request, response);`的实现,具体可以分为以下几步: 1. 获取上一个请求的参数 2. 重新建立新的参数 3. 设置到XXContextHolder 4. 父类的service()处理请求 5. 恢复request(恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter 6. 发布事件(发布ServletRequestHandlerEvent消息,这个请求是否执行成功都会发布消息) 自定义切面类 ```java 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-05-30 09:51 */ @Slf4j @Aspect @Component public class GuiGuLoginAspect { @Autowired private RedisTemplate redisTemplate; @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)") 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"); //3.查询Redis中用户基本信息 //3.1 构建在登录成功后存入Redis用户信息Key 形式=前缀+用户令牌 String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token; //3.2 查询在登录成功后存入Redis用户基本信息 UserInfoVo UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKey); //4.如果Redis中登录用户信息为空切目标方法要求必须登录 抛出异常 前端要求响应业务状态码为208,前端引导用户登录 if (userInfoVo == null && guiGuLogin.required()) { throw new GuiguException(ResultCodeEnum.LOGIN_AUTH); } //5.如果Redis中用户信息存在,获取用户ID,将用户ID存入ThreadLocal 方便后续javaEE三层获取用户ID if (userInfoVo != null) { AuthContextHolder.setUserId(userInfoVo.getId()); } //二、目标方法 controller使用使用自定义认证注解的方法 Object result = pjp.proceed(); //三、后置逻辑 log.info("后置逻辑..."); //6.避免ThreadLocal引发的内存泄漏,清理ThreadLocal AuthContextHolder.removeUserId(); return result; } } ``` 在查看专辑列表的时候,添加注解。在点击查看专辑列表的时候,就会提示我们需要进行登录! ## 1.3 使用认证校验注解 在所有业务微服务中,com.atguigu.tingshu.*.api.XXXXController类中下所有需要登录才可以访问方法上加注解@GuiGuLogin 以下接口测试需要完成登录代码后才进行测试 **AlbumInfoApiController** ```java /** * 该接口登录才能访问,目前并未实现登录无法获取用户ID,动态获取到用户ID * 提供给内容创作者/运营人员保存专辑 * @param albumInfoVo * @return */ @Operation(summary = "保存专辑") @PostMapping("/albumInfo/saveAlbumInfo") @GuiGuLogin//(required = true) public Result saveAlbumInfo(@RequestBody @Validated AlbumInfoVo albumInfoVo){//...} /** * 该接口登录才能访问 * 查询当前用户专辑分页列表 * @param page 页码 * @param limit 页大小 * @param albumInfoQuery 查询条件对象 * @return */ @Operation(summary = "查询当前用户专辑分页列表") @PostMapping("/albumInfo/findUserAlbumPage/{page}/{limit}") @GuiGuLogin public Result> getUserAlbumPage( @PathVariable int page, @PathVariable int limit, @RequestBody AlbumInfoQuery albumInfoQuery){//...} /** * 该接口登录才能访问 * 查询当前登录用户专辑列表 * @return */ @GuiGuLogin @Operation(summary = "查询当前登录用户专辑列表") @GetMapping("/albumInfo/findUserAllAlbumList") public Result> getUserAllAlbumList(){//...} ``` **TrackInfoApiController** ```java /** * 该接口需要登录才能访问 * 保存专辑下声音 * * @param trackInfoVo 声音信息VO对象 * @return */ @GuiGuLogin @Operation(summary = "保存专辑下声音") @PostMapping("/trackInfo/saveTrackInfo") public Result saveTrackInfo(@RequestBody @Validated TrackInfoVo trackInfoVo){//...} /** * 该接口登录才能访问 * 分页获取当前登录用户声音列表 * * @param page 页码 * @param limit 页大小 * @param trackInfoQuery 查询条件 * @return 分页对象 */ @GuiGuLogin @Operation(summary = "分页获取当前登录用户声音列表") @PostMapping("/trackInfo/findUserTrackPage/{page}/{limit}") public Result> getUserTrackPage(@PathVariable int page, @PathVariable int limit, @RequestBody TrackInfoQuery trackInfoQuery){//...} ``` # 2、用户登录 ![登录-认证登录](assets/登录-认证登录.gif) ## 2.1 微信登录 [小程序登录 | 微信开放文档 (qq.com)](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html) ![](assets/tingshu008.png) ### 2.1.1 集成微信JavaSDK 说明 1. 小程序微信授权登录(学生测试使用) 测试账号申请入口https://mp.weixin.qq.com/wxamp/sandbox?doc=1 2. 小程序调用 [wx.login()](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html) 获取 **临时登录凭证code** ,并回传到开发者服务器(已完成)。 3. 应用服务器端调用 [auth.code2Session](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html) 接口,换取 **用户唯一标识 OpenID** 、 用户在微信开放平台账号下的**唯一标识UnionID**(若当前小程序已绑定到微信开放平台账号) 和 **会话密钥 session_key**。 之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。 注意事项 1. 会话密钥 `session_key` 是对用户数据进行 [加密签名](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html) 的密钥。为了应用自身的数据安全,开发者服务器**不应该把会话密钥下发到小程序,也不应该对外提供这个密钥**。 2. 临时登录凭证 code 只能使用一次 这里我们采用**微信开发 Java SDK(非官方提供)**简化开发。支持微信支付、开放平台、公众号、企业号/企业微信、小程序等的后端开发,官方地址:http://wxjava.fly2you.cn/zh-CN/ 1. 在`service-user`模块中导入依赖 ```xml com.github.binarywang wx-java-miniapp-spring-boot-starter 4.5.0 ``` 2. 在nacos注册中心增加配置信息(已添加-**注意:一定要修改为自己申请微信小程序测试号信息**) ```yaml wx: miniapp: appid: # 小程序微信公众平台appId 改成同学申请测试号应用id secret: # 小程序微信公众平台api秘钥 改成同学申请测试号秘钥 msgDataFormat: JSON ``` 3. 在IOC容器中会自动产生对象类型,可以自动注入 - `WxMaService` - `WxMaConfig` 4. 当时导入小程序,选择测试号,产生用与测试的APPID(微信用不了) ![image-20231020154902093](assets/image-20231020154902093.png) 5. 进入微信开发者工具,编辑器->project.config.json文件修改appId: **wxcc651fcbab275e33** 改为自己申请的应用ID:appid ![image-20231020155121005](assets/image-20231020155121005.png) ### 2.1.2 登录接口 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/41 `service-user`模块完成微信登录**WxLoginApiController** ```java package com.atguigu.tingshu.user.api; import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.user.service.UserInfoService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @Tag(name = "微信授权登录接口") @RestController @RequestMapping("/api/user/wxLogin") @Slf4j public class WxLoginApiController { @Autowired private UserInfoService userInfoService; /** * 微信一键登录 * * @param code 小程序端根据当前微信,生成访问为微信服务端临时凭据 * @return {token:令牌} */ @Operation(summary = "微信一键登录") @GetMapping("/wxLogin/{code}") public Result> wxLogin(@PathVariable String code) { Map map = userInfoService.wxLogin(code); return Result.ok(map); } } ``` **UserInfoService** ```java package com.atguigu.tingshu.user.service; import com.atguigu.tingshu.model.user.UserInfo; import com.baomidou.mybatisplus.extension.service.IService; import java.util.Map; public interface UserInfoService extends IService { /** * 提供给小程序微信登录接口 * * @param code 小程序集成SDK后获取临时票据(基于当前微信用户产生的) * @return */ Map wxLogin(String code); } ``` **UserInfoServiceImpl** ```java 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.util.IdUtil; import com.atguigu.tingshu.common.constant.RedisConstant; import com.atguigu.tingshu.common.execption.GuiguException; import com.atguigu.tingshu.common.rabbit.constant.MqConst; import com.atguigu.tingshu.common.rabbit.service.RabbitService; import com.atguigu.tingshu.common.result.ResultCodeEnum; import com.atguigu.tingshu.model.user.UserInfo; import com.atguigu.tingshu.user.mapper.UserInfoMapper; 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 lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @Slf4j @Service @SuppressWarnings({"all"}) public class UserInfoServiceImpl extends ServiceImpl implements UserInfoService { @Autowired private UserInfoMapper userInfoMapper; @Autowired private WxMaService wxMaService; @Autowired private RedisTemplate redisTemplate; @Autowired private RabbitService rabbitService; /** * 微信一键登录 * * @param code 小程序端根据当前微信,生成访问为微信服务端临时凭据 * @return */ @Override public Map wxLogin(String code) { try { //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() .eq(UserInfo::getWxOpenId, openid) ); //3.如果微信账户首次注册,则新增用户记录,为用户初始化账户记录用于后续订单支付 if (userInfo == null) { //3.1 新增用户记录 绑定微信账户唯一标识 userInfo = new UserInfo(); 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 TODO 为当前注册用户初始化账户记录 // 方案一:Openfeign远程调用 分布式事务问题 方案二:采用MQ可靠性消息队列实现数据最终一致 //3.2.1 构建消息对象 注意:如果是VO对象一定要实现序列化接口以及生成序列化版本号 Map 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.基于当前用户信息生成令牌 //4.1 创建令牌 String token = IdUtil.randomUUID(); //4.2 构建登录成功后Redis的Key 形式为:user:login:token String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token; //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.封装令牌返回给前端 Map map = new HashMap<>(); map.put("token", token); return map; } catch (WxErrorException e) { log.error("微信登录失败"); throw new GuiguException(500, "微信登录失败"); } } } ``` ## 2.2 初始化账户(余额) 在Nacos配置中心修改`common.yaml`配置文件新增RabbitMQ相关信息 ```yaml spring: rabbitmq: host: 192.168.200.6 port: 5672 username: admin password: admin virtual-host: /tingshu #作用隔离环境 ``` ![image-20240806161546427](assets/image-20240806161546427.png) 在RabbitMQ新增虚拟主机**/tingshu** ![image-20240806154633782](assets/image-20240806154633782.png) 在`service-acount` 微服务中监听消息: ```java 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-05-30 14:11 */ @Slf4j @Component public class AccountReceiver { @Autowired private UserAccountService userAccountService; /** * 用户首次注册成功后,为用户初始账户余额记录 * * @param map 消息对象 包含:用户ID,赠送体验金额,标题,订单编号 * @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 initUserAccount(Map map, Channel channel, Message message) { log.info("[账户服务]用户首次注册成功后,为用户初始账户余额记录:{}", map); if(CollUtil.isNotEmpty(map)){ userAccountService.initUserAccount(map); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } } ``` **UserAccountService接口 **初始化账户信息: ```java package com.atguigu.tingshu.account.service; import com.atguigu.tingshu.model.account.UserAccount; import com.baomidou.mybatisplus.extension.service.IService; import java.math.BigDecimal; import java.util.Map; public interface UserAccountService extends IService { /** * 用户首次注册成功后,为用户初始账户余额记录 * * @param map 消息对象 包含:用户ID,赠送体验金额,标题,订单编号 */ void initUserAccount(Map map); /** * 新增账户变动日志 * @param userId 用户ID * @param title 内容 * @param tradeType 交易类型 * @param amount 金额 * @param orderNo 订单编号 */ void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo); } ``` **UserAccountServiceImpl实现类:** ```java 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.service.UserAccountService; import com.atguigu.tingshu.common.constant.SystemConstant; import com.atguigu.tingshu.model.account.UserAccount; import com.atguigu.tingshu.model.account.UserAccountDetail; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.Map; @Slf4j @Service @SuppressWarnings({"all"}) public class UserAccountServiceImpl extends ServiceImpl implements UserAccountService { @Autowired private UserAccountMapper userAccountMapper; @Autowired private UserAccountDetailMapper userAccountDetailMapper; /** * 用户首次注册成功后,为用户初始账户余额记录 * * @param map 消息对象 包含:用户ID,赠送体验金额,标题,订单编号 */ @Override @Transactional(rollbackFor = Exception.class) public void initUserAccount(Map 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); } } ``` TIPS:以前在**AuthContextHolder**硬编码的用户ID可以修改为动态获取 ![image-20231014214718202](assets/image-20231014214718202.png) # 3、获取登录用户信息 获取用户信息 ,将查询到的信息放入这个实体类UserInfoVo中 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/43 **WxLoginApiController 控制器** ```java /** * 获取当前登录用户基本信息 * * @return */ @GuiGuLogin @Operation(summary = "获取当前登录用户基本信息") @GetMapping("/getUserInfo") public Result getUserInfo() { Long userId = AuthContextHolder.getUserId(); UserInfoVo userInfoVo = userInfoService.getUserInfo(userId); return Result.ok(userInfoVo); } ``` **UserInfoService接口:** ```java /** * 根据用户ID查询用户基本信息 * @param userId * @return */ UserInfoVo getUserInfo(Long userId); ``` **UserInfoServiceImpl**实现类: ```java /** * 根据用户ID查询用户基本信息 * * @param userId * @return */ @Override public UserInfoVo getUserInfo(Long userId) { UserInfo userInfo = userInfoMapper.selectById(userId); if (userInfo != null) { return BeanUtil.copyProperties(userInfo, UserInfoVo.class); } return null; } ``` # 4、更新用户信息方法 ![登录-修改用户信息](assets/登录-修改用户信息.gif) 需求:登录成功之后,可以修改用户基本信息。 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/45 **WxLoginApiController**控制器 ```java /** * 更新当前用户信息 * @param userInfoVo * @return */ @GuiGuLogin @Operation(summary = "更新当前用户信息") @PostMapping("/updateUser") public Result updateUser(@RequestBody UserInfoVo userInfoVo) { userInfoService.updateUser(userInfoVo); return Result.ok(); } ``` **UserInfoService接口** ```java /** * 更新用户信息方法 * @param userInfoVo */ void updateUser(UserInfoVo userInfoVo); ``` **UserInfoServiceImpl**实现类 ```java /** * 更新用户信息方法 * 只允许修改昵称头像 * @param userInfoVo */ @Override public void updateUser(UserInfoVo userInfoVo) { //1.获取用户ID Long userId = AuthContextHolder.getUserId(); //2.更新用户昵称跟头像 UserInfo userInfo = new UserInfo(); userInfo.setId(userId); userInfo.setAvatarUrl(userInfoVo.getAvatarUrl()); userInfo.setNickname(userInfoVo.getNickname()); userInfoMapper.updateById(userInfo); } ```