springboot--实战--大事件--用户接口开发
开发模式&环境搭建
开发模式:
前后端分离开发
前端程序员写前端页面,后端程序员写后端的接口,前端工程发送请求来访问后台,后台处理完请求后要给前端相应对应的数据。
还需要一套标准来约束即接口文档,在接口文档中会对每一个接口的访问路径,请求方式以及请求参数还有响应数据进行明确的说明。
一般这样的接口文档就需要提供好,有了接口文档就有了开发的标准。前端程序员参照接口文档进行开发,后端程序员也参考同一份接口文档开发,最终开发好的项目就不会出现接口出错的情况了。
环境搭建
准备数据库表
创建springboot工程,引入对应的依赖(web,MyBatis,MySQL驱动)
在配置文件中引入MyBatis的配置信息
创建包结构,并准备实体类
注册接口
用户
-
注册
查看用户表及其实体类:
在数据库表中user_pic应该为图片,但为什么数据类型为varchar,因为将来将图片放在第三方服务器上,这里存放的是一个URL地址。
开发接口流程:
首先我们要明确一下需求,需要知道该功能将来用户是如何使用的,
接下来需要去阅读接口文档明确该接口的输入以及输出
接着要进行思路分析,分析将来如何写代码逻辑,再接着就是开发写代码,代码写完之后需要测试接口的正确性
在写注册接口时,依然是按照三层架构的方式书写:
Controller层:
@Controller
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/register")public Result register(String username,String password){//查询用户User user = userService.findByUsername(username);if(user != null){return Result.error("用户已存在");}else {userService.Register(username,password);return Result.success();}}
}
Service层:
//根据用户名查询用户User findByUsername(String username);
//注册void Register(User user);
实现类:
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User findByUsername(String username) {return userMapper.findByUsername(username);}@Overridepublic void Register(String username,String password) {//加密password = MD5Util.MD5Lower(password);userMapper.addUser(username,password);}
}
Mapper层:
@Mapper
public interface UserMapper {@Select("select * from user where username = #{username}")User findByUsername(String username);@Insert("insert into user(username,password,create_time,update_time) values(#{username},#{password},now(),now())")void addUser(String username, String password);
}
执行:发现错误,无法扫描到mapperBean对象,检查得知,没有添加MyBatis-spring的starter,导入之后可正常启动。
接口测试:
检查数据库数据
测试成功。
注册接口参数校验
我们在接口文档中对username和password有明确的说明,这两个参数必须是5-16位的非空字符,那接口就必须保证,如果前端传递的参数不符合规则,就不能够完成注册。而在我们之前的代码中并没有对username与password的校验,就会导致无论什么样的数据都会被无差别传入数据库。
所以我们需要对参数做校验
@RestController@RequestMapping("/user")public class UserController {@Resourceprivate UserService userService;@PostMapping("/register")public Result register(String username,String password){//检验用户名与密码必须满足5到16位且不能为空if(username.length() < 5 || username.length() > 16 || password.length() < 5 || password.length() > 16){return Result.error("用户名或密码长度不符合要求");}else {User user = userService.findByUsername(username);if(user != null){return Result.error("用户已存在");}else {userService.Register(username,password);return Result.success();}}}}
接口测试得:
证明代码执行成功。
但是该业务代码太过繁琐,且在只有两个参数的情况下,就已经如此,在访问量高的情况下,更不用想。
解决方案:
Spring Validation
spring 提供的一个参数校验框架,是用预定义的注解完成参数校验。
案例要求:使用spring Validation 对注册接口的参数进行合法性校验。
使用步骤:
-
引入Spring Validation 起步依赖
-
在参数前面添加@Pattern注解
-
在Controller类上添加@Validated
代码展示:
pom.xml
<!-- validation依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
UserController
@RestController@RequestMapping("/user")@Validatedpublic class UserController {@Resourceprivate UserService userService;@PostMapping("/register")public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//检验用户名与密码必须满足5到16位且不能为空if(username.length() < 5 || username.length() > 16 || password.length() < 5 || password.length() > 16){return Result.error("用户名或密码长度不符合要求");}else {User user = userService.findByUsername(username);if(user != null){return Result.error("用户已存在");}else {userService.Register(username,password);return Result.success();}}}}
测试接口结果:
发现500异常,在idea内部也报错
使用全局异常处理器进行参数校验失败异常处理,
代码展示:
// 全局异常处理类@RestControllerAdvicepublic class GlobalExceptionHandler {// 捕获所有异常@ExceptionHandler(Exception.class)// 返回值类型为Result,因为全局异常处理类返回值类型为Resultpublic Result handleException(Exception e){e.printStackTrace();// 三元运算符 :如果e.getMessage()不为空,则返回e.getMessage(),否则返回"服务器异常"return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "服务器异常");}}
效果展示:
小结:
Spring Validation
-
导入 validation 起步依赖
-
在参数上添加@Pattern注解,指定校验规则
-
在Controller类上添加@Validated注解
-
在全局异常处理器中处理参数校验注解失败的异常
登录
登录需求:
用户在登录界面输入用户名还有密码,点击登录按钮,访问后台的登录接口,如果登录成功,要跳转到首页,登录失败则会给出对应的提示。
根据接口文档
分析实现思路
首先在UserController中添加方法。
@PostMapping("/login")public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//根据用户名查询用户User user = userService.findByUsername(username);//判断用户是否存在if (user == null) {return Result.error("用户不存在");}// 判断密码是否正确if (MD5Util.MD5Lower(password).equals(user.getPassword())){return Result.success("登录成功 JWT 令牌");}return Result.error("密码错误");}}
测试成功
登录认证
在实际项目中,会有许多接口,在提供服务之前需要对用户的登录状态进行检查,检查的过程被称为登录认证。
而在当前项目中并没有登录认证,因此我们要为项目添加登录验证。
如果要实现登录认证,要借助于令牌的技术,即我们在登录页面登录成功之后,需要生成一个JWT令牌,再将令牌响应给浏览器,浏览器在访问其它接口或资源是都需要携带该令牌,则服务器能够得到该令牌,还需要去验证令牌的合法性,如果令牌合法,则正常提供服务,如果不是,则不提供服务。
令牌就是一段字符串
-
承载业务数据,减少后续请求查询数据库的次数(系统需要得知本次操作是哪个用户操作的,方便以后回收,如果每次都需要到数据库去查询用户信息,则会极大影响性能,因此可以将用户信息放在令牌中,而浏览器每次访问接口都会携带令牌,就可以直接从令牌中获取,由此减少了数据库的访问次数,提高了性能)
-
防止篡改,保证信息的合法性与有效性。
而在实际开发中,我们所用到的常用令牌规范为JWT令牌规范
JWT
简介
全称:JSON Web Token(官网地址:JSON Web Tokens - jwt.io)
定义了一种简洁的、自包含的格式,用于通信双方以JSON数据格式安全的传输信息。
组成:
-
第一部分:Header(头),记录令牌类型、签名算法等。例如
{"alg":"HS256","type":"JWT"}
,算法用于防篡改 -
第二部分:Payload(有效载荷),携带一些自定义信息,默认信息。例如
{"id":"1","username":"Tom"}
。而外在表示为一段无规律的64个可打印字符
原因:借助base64编码方式(Base64:是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式)来完成,将json字符串变为64个可打印字符,
优点:提高token的实用性:假如JSON字符串中含有空格、中文等特殊字符,cookie就不能支持,因此在JWT中将JS字符串通过base64编码转换。
注意事项:Base64仅仅是编码方式,不是加密方式,且编码方式是公开的,因此安全性不高,所以在Payload中不要存放私密数据。
(Base64特点:通用,在任意场景下都被支持)
-
第三部分:Signature(签名),防止token被篡改,确保安全性。将header,payload,借助于密钥以及加密算法通过加密得来的。
加密算法是通过第一部分的alg
指定,而密钥可以在程序中单独的配置。
主要作用:防篡改,保证token的安全性,即使篡改了前两部分,也无法篡改第三部分,因为是加密后的字符串,之后JWT解析token令牌时,会通过解密第三部分数字签名从而得到头部与载荷,在拿解密的内容与用户传递的内容作对比,如果不一样,则说明数据被篡改过,就会禁止访问。
注意事项:JWT就是生成token的一种规则,包含了token的生成,校验,是一种约定俗成。
生成令牌
JWT是令牌的规范,我们可以自己手动书写,也可以借助生成JWT令牌的工具。
生成步骤:
导入依赖:
<!-- JWT依赖--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency>
生成令牌(大多数实际开发直接使用工具包生成即可,在这里手动编译是加强记忆):
public class JwtTest {@Testpublic void test() {Map<String, Object> claims = new HashMap<>();claims.put("userId",1);claims.put("username","lyc");//生成JWT的代码String token = JWT.create().withClaim("user", claims)//添加载荷.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))//设置过期时间.sign(Algorithm.HMAC256("lyc"));//指定签名算法System.out.println(token);}
}
验证令牌:
@Test
public void testParse(){//定义字符串,模拟从数据库中取出的JWTString token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +".eyJ1c2VyIjp7InVzZXJJZCI6MSwidXNlcm5hbWUiOiJseWMifSwiZXhwIjoxNzQ4MzAwMDI4fQ" +".f5LzYorp0U4RBiKt7DzBgYDm_SRanjHPpb1zCMYJszQ";JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("lyc")).build();DecodedJWT decodedJWT = jwtVerifier.verify(token);//解析JWT,生成一个DecodedJWT对象decodedJWT.getClaims().forEach((k,v)->{System.out.println(k + ":" + v.asMap());});}
-
如果篡改了头部与载荷,就会抛出异常
-
如果修改了密钥,就会抛出异常
-
如果过期了,就会抛出异常
注意事项:
-
JWT校验使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的。
-
如果JWT令牌解析校验时报错,则说明令牌被篡改或失效,令牌非法。
登录验证实现
操作流程:在USerController中生成JWT令牌,在其他接口中去验证该令牌
先编译一个JWT工具类
public class JWTUtil {private static final String SIGNATURE = "lyc";/*** 生成token* @param map //传入payload* @return 返回token*/public static String getToken(Map<String,Object> map){JWTCreator.Builder builder = JWT.create();builder.withClaim("claims",map);Calendar instance = Calendar.getInstance();instance.add(Calendar.HOUR,12);builder.withExpiresAt(instance.getTime());return builder.sign(Algorithm.HMAC256(SIGNATURE));}//接收token,验证token,返回业务数据public static Map<String,Object> verifyToken(String token){DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);return verify.getClaims().get("claims").asMap();}}
在userController中的login接口里,新建JWT令牌,
@PostMapping("/login")
public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//根据用户名查询用户User user = userService.findByUsername(username);//判断用户是否存在if (user == null) {return Result.error("用户不存在");}// 判断密码是否正确if (MD5Util.MD5Lower(password).equals(user.getPassword())){// 生成JWT令牌Map<String, Object> claims = new HashMap<>();claims.put("username", username);claims.put("password", password);String token = JWTUtil.getToken(claims);return Result.success(token);}return Result.error("密码错误");}
测试,看接口是否能接收到token
在ArticleController中验证令牌
代码如下:
@RestController
@RequestMapping("/article")
public class ArticleController {@RequestMapping("/list")public Result<String> list(@RequestHeader(name="Authorization") String token, HttpServletResponse response){try {Map<String, Object> claims = JWTUtil.verifyToken(token);return Result.success("文章列表");} catch (Exception e) {response.setStatus(401);return Result.error("未登录");}}
}
测试结果
在我们实际项目中会有很多的接口需要验证用户的登录状态,我们不能在每一个接口中书写业务代码,所以我们应该使用拦截器来进行验证令牌,让所有请求都经过拦截器,在拦截器里统一完成令牌验证,如果生效则通行,失效则拦截。
代码如下:
JWTInterceptor:
public class JWTInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//验证令牌String token = request.getHeader("Authorization");if (token == null || token.isEmpty()) {response.setStatus(401);return false;}try {Map<String, Object> claims = JWTUtil.verifyToken(token);return true;} catch (Exception e) {response.setStatus(401);return false;}}}
MvcConfig(将拦截器注册进IOC容器中):
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JWTInterceptor()).excludePathPatterns("/user/login","/user/register");}
}
测试结果:
不携带token
携带token
注意事项:
-
使用拦截器统一验证令牌
-
登录与注册接口需要放行
获取用户详细信息
明确需求:当用户登陆成功后需要跳转到首页,在首页顶部导航栏需要展示用户的昵称,头像等,以及个人中心得基本资料,更换头像,重置密码等都需要先查询数据库来获取用户的详细信息,再去更新。
根据接口文档分析实现思路
我们还是需要在UserController中声明userInfo方法。在接口文档中并没有携带参数,但是浏览器中携带的token里有用户的username,因此我们可以取出username,在根据用户名查询用户的详细信息。
代码展示:
@GetMapping("/userInfo")
public Result<User> userInfo(@RequestHeader(name = "Authorization") String token){//根据用户名获取当前用户信息Map<String, Object> claims = JWTUtil.verifyToken(token);String username = (String)claims.get("username");User user = userService.findByUsername(username);return Result.success(user);
}
效果展示:
发现问题:在返回的数据中将用户的密码也返回,在接口文档中并没有返回密码。
解决方案:在实体类上的密码属性添加@JsonIgnore,让springmvc把当前对象转换成json字符串时,忽略password,最终的json字符串中就没有password了。
注意事项:一定要检查导入的依赖为com.fasterxml.jackson.annotation.JsonIgnore
测试效果:
发现问题:在创建数据库表示规定createTime以及updateTime非空,而在获取的信息中却为空。
原因:因为在实体类中的createTime与updateTime命名为驼峰命名,而数据库中的命名为下划线命名,这样就导致MyBatis无法自动识别,需要在配置文件中设置
mybatis:configuration:map-underscore-to-camel-case: true # 开启驼峰映射
这时开启驼峰命名与下划线命名的相互转换。
测试结果:
优化获取用户信息接口
问题引入:
在userInfo接口中解析前端的接收头的业务代码在拦截器中已经写过一遍,不应该在重复造轮子,我们可以复用在拦截器里解析到的结果。
需要知识:ThreadLocal
ThreadLocal(线程本地存储)是Java中用于解决线程间数据共享问题的机制。
作用:提供线程局部变量,为每个线程提供独立的数据副本,防止因共享资源造成的竞态条件。
竞态条件:
多个线程或者进程没有正确的同步的访问共享资源的时候,其执行结果可能依赖于一些不可控的执行顺序导致一些不可预测的错误行为。简单来讲,就是并发安全性问题。
-
ThreadLocal中用来存取数据(set()/get())
-
TreadLocal可以做到线程隔离。
代码展示:
@Testpublic void threadLocalTest1() {// 创建ThreadLocal对象ThreadLocal threadLocal = new ThreadLocal();//开启两个线程new Thread(()->{threadLocal.set("thread1");System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());},"thread1").start();new Thread(()->{threadLocal.set("thread2");System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());},"thread2").start();}}
如何实现?
首先定义ThreadLocal对象,定义之后再去在对应线程中设置value(set()方法),并且将value保存在每个线程中的独立存储数据的空间ThreadLocalMap
,ThreadLocal对象会去维护一个ThreadLocalMap对象。
对于项目的作用:
假如项目中的Mapper层,Service层,Controller层都需要传入username参数,如果还有其他业务代码要用到username,还需要去传参,那有没有一种方式,不需要声明,也可以使用参数。此时就可以使用ThreadLocal来进行优化,可以维护一个全局的ThreadLocal对象,用来存储这类经常使用的数据,这时就可以在请求到达拦截器之后,调用Threadlocal对象的set()方法存储变量,在请求到达接口之后,可以直接定义ThreadLocal对象,在调用get方法调用参数即可。且不会造成相同参数名混淆的情况,因为客户端在访问服务端时,服务端会为用户单独开启一条线程用来提供服务,而ThreadLocal为每一个线程都提供了独立的存储空间,并且做到了数据隔离。同时做到了统一线程数据共享,不同线程数据隔离。
作用总结:减少参数的传递,同一个线程之间共享数据,不同线程之间数据隔离。
代码优化:
新建ThreadLocal工具类:
package com.lyc.utils;// 线程工具类public class ThreadUtil {//提供ThreadLocal对象private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();//根据建获取值//泛型 可以直接强转public static <T> T get(){return (T) THREAD_LOCAL.get();}//存储键值对public static void set(Object obj){THREAD_LOCAL.set(obj);}//清除Thread Local 防止内存泄露public static void remove(){THREAD_LOCAL.remove();}}
再在拦截器中将参数存入ThreadLocal中。
最后在UserController中调用get方法获取user参数。
@GetMapping("/userInfo")public Result<User> userInfo(/*@RequestHeader(name = "Authorization") String token*/){//根据用户名获取当前用户信息Map<String, Object> claims = ThreadUtil.get();String username = (String)claims.get("username");User user = userService.findByUsername(username);return Result.success(user);}
}
测试结果:
注意事项:ThreadLocal的生命周期与线程的生命周期一致,因此不使用时就应该将ThreadLocal对象移除,防止内存泄漏
所以在拦截器中重写afterCompletion方法,该方法是在请求结束后执行,在方法中移除ThreadLocal对象。
@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清除ThreadLocal中存储的数据ThreadUtil.remove();}
更新用户基本信息
明确需求:当用户在个人中心点击基本资料后,当前页面会展示用户的详细信息,用户可以再次修改信息并点击提交修改按钮,从而让访问后台的接口更新当前用户的信息,
注意事项:当前用户的登录名称无法修改。
根据接口文档分析实现思路
首先在UserController中声明一个新的方法,使用put请求方式提交,所以需要添加注解@PutMapping,浏览器向服务端请求时携带的信息是用户想要修改的信息,并放在了请求体中以json格式携带,因此可以将这些数据封装在user对象接收,为了让框架能够自动把请求体里面的JSON数据给转换成一个实体类对象,因此使用@RequestBody注解。
注意事项:在Mapper层中要注意,SQL修改语句中要将updateTime一并修改,且不能修改username。
代码展示:
USerController:
@PutMapping("/update")public Result update(@RequestBody User user){userService.update(user);return Result.success();}
UserServiceImpl:
@Overridepublic void update(User user) {//获取当前时间user.setUpdateTime(LocalDateTime.now());userMapper.update(user);}
测试结果,发现错误:
org.springframework.web.servlet.resource.NoResourceFoundException: No static resource user/update.
表示 Spring MVC 在尝试访问静态资源 /user/update
时没有找到对应的文件。
如果 Spring Boot 错误地认为 /user/update
是静态资源,所以关闭静态资源处理
在配置文件中 配置spring.web.resources.add-mappings=false。
web:resources:add-mappings: false
再次测试:
检查数据库:
优化:参数校验
在前面只是将user属性更新,但是并没有对属性进行校验,
首先ID必须传递,不能为null,nickname必须传递,不能为null,且必须是1-10位非空字符,以及email也是必须传递,不能为null,需要满足邮箱的格式。
可以继续使用Spring Validation,如果要对实体参数进行校验,首先要在实体类的成员变量上添加validation提供的注解,对指定的属性值完成校验。
使用注解详情:
注解 | 作用 |
---|---|
@NotNull | 值不能为null |
@NotEmpty | 值不能为null,且内容不为空 |
满足邮箱格式 |
代码展示:
public class User {@NotNullprivate Integer id;// 主键IDprivate String username;// 用户名@JsonIgnore// 让springmvc把当前对象转换成json字符串时,忽略password,最终的json字符串中就没有password了。private String password;// 密码@NotEmpty@Pattern(regexp = "^\\S{1,10}$")private String nickname;// 昵称@NotEmpty@Emailprivate String email;// 邮箱private String userPic;// 用户头像地址private LocalDateTime createTime;// 创建时间private LocalDateTime updateTime;// 修改时间}
其次需要在Controller层的方法参数上声明@Validated注解
@PutMapping("/update")public Result update(@RequestBody @Validated User user){userService.update(user);return Result.success();}}
测试结果:
查看数据库:
当不添加ID时:
小结:实体参数校验:
-
实体类的成员变量上添加注解(@NotNUll,@NotEmpty,@Email等)
-
接口方法的实体参数上添加@Validated注解
更新用户头像
明确需求:当用户在个人中心点击更换头像,然后会在页面的主区域展示当前用户的头像,此时用户可以点击选择头像,选择一张本地的图片,再次点击上传头像按钮,访问后台的接口完成头像更新。
根据接口文档分析实现思路:
请求方式为patch(因为更新用户头像仅仅是更新用户信息里的局部)
请求参数需要一个URL,头像地址。
首先需要在UserController中新声明一个方法,在该方式中需要声明@PatchMapping注解,且需要从前端中拿到请求体中的参数,需要使用@RequestParam注解。
还需要在Service层以及Mapper层都需要修改。
注意事项:修改SQL语句时依旧要修改updateTime。
代码实现:
UserController
@PatchMapping("/updateAvatar")public Result updateAvatar(@RequestParam("avatar") String avatarUrl){userService.updateAvatar(avatarUrl);return Result.success();}
serviceImpl
@Overridepublic void updateAvatar(String avatarUrl) {Map<String,Object> map = ThreadUtil.get();String username = (String) map.get("username");System.out.println("username = " + username);Integer id = userMapper.findByUsername(username).getId();userMapper.updateAvatar(avatarUrl,id);}
注意事项:这里是在登录时并没有存储user的ID,因此只能拿到username,在通过username拿到user,在通过user拿到userid。
测试结果:
查看数据库:
优化:参数校验
此时无论前端传入任何值都会直接传入,但是用户头像要求为URL格式,因此我们可以调用validation提供的参数校验注解@URL,直接放在参数上面即可。
代码展示:
@PatchMapping("/updateAvatar")public Result updateAvatar(@RequestParam("avatar") @URL String avatarUrl){userService.updateAvatar(avatarUrl);return Result.success();}
测试结果:
更新用户密码
明确需求:当用户在个人中心点击重置密码时,会在刷新一个表单,在该表单用户需要填写三项内容,原密码、新密码、确认新密码,如果没问题,点击修改密码,最终需要访问后台的接口完成密码的更新。
根据接口文档分析实现思路
请求方式依旧为patch(更新用户密码仅仅是更新用户对象中的一个字段)。
首先在UserController新声明一个方法,用于完成密码的更新,在该方法上需要声明一个map类型的参数用于接手前端提交的JSON参数
注意:在之前更新用户基本信息的时候,也接收过JSON参数,当时声明了一个user实体对象来接受,这是因为当时传递的JSON中的键名刚好和user实体类的属性名一样,但此时接受的JSON中的键名以实体类属性名不一致,需要使用map类型的参数来接受,MVC框架会自动的将JSON数据转换成map。
依旧是要修改Service层以及Mapper层的业务代码。
代码展示:
UserController:
@PatchMapping("/updatePwd")public Result updatePwd(@RequestBody Map<String,String> params){//校验参数String OldPwd = params.get("old_pwd");String NewPwd = params.get("new_pwd");String RePwd = params.get("re_pwd");//校验三个参数是否都被传入if (!StringUtils.hasLength(OldPwd) || !StringUtils.hasLength(NewPwd) || !StringUtils.hasLength(RePwd)){return Result.error("参数不能为空");}//校验原密码是否正确Map<String, Object> claims = ThreadUtil.get();String username = (String)claims.get("username");User user = userService.findByUsername(username);System.out.println(user.getPassword());System.out.println(OldPwd);System.out.println(MD5Util.MD5Lower("123456"));System.out.println(MD5Util.MD5Lower(OldPwd));if (!MD5Util.MD5Lower(OldPwd).equals(user.getPassword())){return Result.error("原密码错误");}if (!NewPwd.equals(RePwd)){return Result.error("新密码不一致");}userService.updatePwd(NewPwd);//调用service层修改密码return Result.success();}
UserServiceImpl:
@Overridepublic void updatePwd(String pwd) {Map<String,Object> map = ThreadUtil.get();String username = (String) map.get("username");System.out.println("username = " + username);Integer id = userMapper.findByUsername(username).getId();userMapper.updatePwd(MD5Util.MD5Lower(pwd),id);}
测试结果:
查看数据库:
注意事项:由于不可知原因,在测试接口时,参数前面被添加了逗号,因此在校验原密码时记得加上逗号。
至此关于用户模块的接口开发全部完成。