springboot--实战--大事件--用户接口开发

开发模式&环境搭建

开发模式:

前后端分离开发

前端程序员写前端页面,后端程序员写后端的接口,前端工程发送请求来访问后台,后台处理完请求后要给前端相应对应的数据。

还需要一套标准来约束即接口文档,在接口文档中会对每一个接口的访问路径,请求方式以及请求参数还有响应数据进行明确的说明。

一般这样的接口文档就需要提供好,有了接口文档就有了开发的标准。前端程序员参照接口文档进行开发,后端程序员也参考同一份接口文档开发,最终开发好的项目就不会出现接口出错的情况了。

环境搭建

准备数据库表

创建springboot工程,引入对应的依赖(web,MyBatis,MySQL驱动)

在配置文件中引入MyBatis的配置信息

创建包结构,并准备实体类

注册接口

用户

  • 注册

查看用户表及其实体类:

image-20250518180858771

在数据库表中user_pic应该为图片,但为什么数据类型为varchar,因为将来将图片放在第三方服务器上,这里存放的是一个URL地址。

开发接口流程:

首先我们要明确一下需求,需要知道该功能将来用户是如何使用的,

接下来需要去阅读接口文档明确该接口的输入以及输出

接着要进行思路分析,分析将来如何写代码逻辑,再接着就是开发写代码,代码写完之后需要测试接口的正确性

image-20250518182141970

在写注册接口时,依然是按照三层架构的方式书写:

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,导入之后可正常启动。

接口测试:

image-20250526141928823

检查数据库数据

image-20250526141950512

测试成功。

注册接口参数校验

我们在接口文档中对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();}}​}}

接口测试得:

image-20250526163823949

证明代码执行成功。

但是该业务代码太过繁琐,且在只有两个参数的情况下,就已经如此,在访问量高的情况下,更不用想。

解决方案:

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();}}​}}

测试接口结果:

image-20250526170051144

发现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() : "服务器异常");}}
 ​

效果展示:

image-20250526171306285

小结:

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("密码错误");​}}

测试成功

image-20250526175754823

登录认证

在实际项目中,会有许多接口,在提供服务之前需要对用户的登录状态进行检查,检查的过程被称为登录认证。

而在当前项目中并没有登录认证,因此我们要为项目添加登录验证。

如果要实现登录认证,要借助于令牌的技术,即我们在登录页面登录成功之后,需要生成一个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);}
}

image-20250526185457526

验证令牌:

@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());});}

image-20250526190115278

  • 如果篡改了头部与载荷,就会抛出异常

  • 如果修改了密钥,就会抛出异常

  • 如果过期了,就会抛出异常

image-20250526190206145

注意事项:

  • 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

image-20250526192252523

在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("未登录");}}
}

测试结果

image-20250526195028811

在我们实际项目中会有很多的接口需要验证用户的登录状态,我们不能在每一个接口中书写业务代码,所以我们应该使用拦截器来进行验证令牌,让所有请求都经过拦截器,在拦截器里统一完成令牌验证,如果生效则通行,失效则拦截。

代码如下:

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

image-20250526213824116

携带token

image-20250526213834283

注意事项:

  • 使用拦截器统一验证令牌

  • 登录与注册接口需要放行

获取用户详细信息

明确需求:当用户登陆成功后需要跳转到首页,在首页顶部导航栏需要展示用户的昵称,头像等,以及个人中心得基本资料,更换头像,重置密码等都需要先查询数据库来获取用户的详细信息,再去更新。

根据接口文档分析实现思路

我们还是需要在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);
}

效果展示:

image-20250526221629715

发现问题:在返回的数据中将用户的密码也返回,在接口文档中并没有返回密码。

解决方案:在实体类上的密码属性添加@JsonIgnore,让springmvc把当前对象转换成json字符串时,忽略password,最终的json字符串中就没有password了。

注意事项:一定要检查导入的依赖为com.fasterxml.jackson.annotation.JsonIgnore

测试效果:

image-20250526222930683

发现问题:在创建数据库表示规定createTime以及updateTime非空,而在获取的信息中却为空。

原因:因为在实体类中的createTime与updateTime命名为驼峰命名,而数据库中的命名为下划线命名,这样就导致MyBatis无法自动识别,需要在配置文件中设置

mybatis:configuration:map-underscore-to-camel-case: true # 开启驼峰映射

这时开启驼峰命名与下划线命名的相互转换。

测试结果:

image-20250526223517347

优化获取用户信息接口

问题引入:

在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中。

image-20250527165446398

最后在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);}
}

测试结果:

image-20250527165220206

注意事项: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

再次测试:

image-20250527175611376

检查数据库:

image-20250527175658023

优化:参数校验

在前面只是将user属性更新,但是并没有对属性进行校验,

首先ID必须传递,不能为null,nickname必须传递,不能为null,且必须是1-10位非空字符,以及email也是必须传递,不能为null,需要满足邮箱的格式。

可以继续使用Spring Validation,如果要对实体参数进行校验,首先要在实体类的成员变量上添加validation提供的注解,对指定的属性值完成校验。

使用注解详情:

注解作用
@NotNull值不能为null
@NotEmpty值不能为null,且内容不为空
@Email满足邮箱格式

代码展示:

 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();}}

测试结果:

image-20250527181103740

查看数据库:

image-20250527181142127

当不添加ID时:

image-20250527181350337

小结:实体参数校验:

  • 实体类的成员变量上添加注解(@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。

测试结果:

image-20250527185949066

查看数据库:

image-20250527190026979

优化:参数校验

此时无论前端传入任何值都会直接传入,但是用户头像要求为URL格式,因此我们可以调用validation提供的参数校验注解@URL,直接放在参数上面即可。

代码展示:

 @PatchMapping("/updateAvatar")public Result updateAvatar(@RequestParam("avatar") @URL String avatarUrl){userService.updateAvatar(avatarUrl);return Result.success();}

测试结果:

image-20250527190434115

更新用户密码

明确需求:当用户在个人中心点击重置密码时,会在刷新一个表单,在该表单用户需要填写三项内容,原密码、新密码、确认新密码,如果没问题,点击修改密码,最终需要访问后台的接口完成密码的更新。

根据接口文档分析实现思路

请求方式依旧为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);}

测试结果:

image-20250527195813001

查看数据库:

image-20250527195958574

注意事项:由于不可知原因,在测试接口时,参数前面被添加了逗号,因此在校验原密码时记得加上逗号。

至此关于用户模块的接口开发全部完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/82636.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html使用JS实现账号密码登录的简单案例

目录 案例需求 思路 错误案例及问题 修改思路 案例提供 所需要的组件 <input>标签&#xff0c;<button>标签&#xff0c;<script>标签 详情使用参考&#xff1a;HTML 教程 | 菜鸟教程 案例需求 编写一个程序&#xff0c;最多允许用户尝试登录 3 次。…

小米玄戒O1架构深度解析(一):十核异构设计与缓存层次详解

前言 这两天&#xff0c;小米的全新SOC玄戒O1横空出世&#xff0c;引发了科技数码圈的一次小地震&#xff0c;那么小米的这颗所谓的自研SOC&#xff0c;内部究竟有着什么不为人知的秘密呢&#xff1f;我们一起一探究竟。 目录 前言1 架构总览1.1 基本构成1.2 SLC缺席的原因探…

VSCode如何像Pycharm一样“““回车快速生成函数注释文档?如何设置文档的样式?autoDocstring如何设置自定义模板?

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 让VSCode拥有PyCharm级注释生成能力 📒🚀 实现方案🛠️ 备用方案📒 自定义注释文档格式样式 📒🔄 切换主流注释风格✨ 深度自定义模板🛠️ 类型提示与注释联动优化⚓️ 相关链接 ⚓️📖 介绍 📖 用PyCharm写P…

数据库的事务(Transaction)

在数据库中&#xff0c;事务&#xff08;Transaction&#xff09; 是保证数据操作一致性和完整性的核心机制。它通过一组原子性的操作单元&#xff0c;确保所有操作要么全部成功&#xff08;提交&#xff09;&#xff0c;要么全部失败&#xff08;回滚&#xff09;。以下是数据…

2025-05-27 Python深度学习7——损失函数和反向传播

文章目录 1 损失函数1.1 L1Loss1.2 MSELoss1.3 CrossEntropyLoss 2 反向传播 本文环境&#xff1a; Pycharm 2025.1Python 3.12.9Pytorch 2.6.0cu124 1 损失函数 ​ 损失函数 (loss function) 是将随机事件或其有关随机变量的取值映射为非负实数以表示该随机事件的"风险&…

python+tkinter实现GUI界面调用即梦AI文生图片API接口

背景 目前字节跳动公司提供了即梦AI的接口免费试用&#xff0c;但是并发量只有1&#xff0c;不过足够我们使用了。我这里想做个使用pythontkinter实现的GUI可视化界面客户端&#xff0c;这样就不用每次都登录官方网站去进行文生图片&#xff0c;当然文生视频&#xff0c;或者图…

#git 储藏库意外被清空 Error: bad index – Fatal: index file corrupt

问题&#xff1a;通常是由于 Git 的索引文件损坏导致 原因&#xff1a;系统崩溃或断电、硬盘故障、Git 操作错误等 方案&#xff1a;重建索引文件&#xff1a;将当前的索引文件重命名为其他名称或删除&#xff0c;比如 index.m&#xff0c;然后命令行重建索引&#xff0c;git…

GitLab 18.0 正式发布,15.0 将不再受技术支持,须升级【二】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

车载网关策略 --- 车载网关通信故障处理机制深度解析

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

Unity数字人开发笔记

开源工程地址&#xff1a;https://github.com/zhangliwei7758/unity-AI-Chat-Toolkit 先致敬zhangliwei7758&#xff0c;开放这个源码 一、建立工程 建立Unity工程&#xff08;UnityAiChat&#xff09;拖入Unity-AI-Chat-Toolkit.unitypackage打开chatSample工程&#xff0c;可…

Cherry Studio连接配置MCP服务器

之前写了一篇关于Cherry Studio的文章&#xff0c;不了解的可以先看一下 AI工具——Cherry Studio&#xff0c;搭建满血DeepSeek R1的AI对话客户端【硅基流动DeepSeek API】-CSDN博客 最近Cherry Studio更新了一个新功能&#xff1a;MCP服务器 在 v1.2.9 版本中&#xff0c;…

OpenSSH 服务配置与会话保活完全指南

一、/etc/ssh/sshd_config 配置机制 1. 配置文件基础 文件作用 OpenSSH 服务器 (sshd) 的主配置文件&#xff0c;控制连接、认证、端口转发等行为。 加载与生效 修改后需重启服务&#xff1a;sudo systemctl restart sshd # Systemd 系统 sudo service ssh restart # S…

阿里云国际版注册邮箱格式详解

“为什么我的阿里云国际版注册总提示邮箱无效&#xff1f;” 这是许多初次接触阿里云国际版&#xff08;Alibaba Cloud International&#xff09;的用户常遇到的困惑。随着全球化进程加速&#xff0c;越来越多的企业选择阿里云国际版部署海外业务&#xff0c;而注册环节中邮箱…

【IDEA问题】springboot本地启动应用报错:程序包不存在;找不到符号

问题&#xff1a; springboot本地启动应用报错&#xff1a; 程序包xxx不存在&#xff1b;找不到符号 解决方案&#xff1a; 1.确保用maven重新导入依赖 2.删除.idea文件夹 3.invalidate caches里&#xff0c;把能选择的都勾选上&#xff0c;然后清除缓存重启 4.再在上方工具栏…

FFmpeg 时间戳回绕处理:保障流媒体时间连续性的核心机制

FFmpeg 时间戳回绕处理&#xff1a;保障流媒体时间连续性的核心机制 一、回绕处理函数 /** * Wrap a given time stamp, if there is an indication for an overflow * * param st stream // 传入一个指向AVStream结构体的指针&#xff0c;代表流信息 * pa…

【b站计算机拓荒者】【2025】微信小程序开发教程 - chapter3 项目实践 -1 项目功能描述

1 项目功能描述 # 智慧社区-小程序-1 欢迎页-加载后端&#xff1a;动态变化-2 首页-轮播图&#xff1a;动态-公共栏&#xff1a;动态-信息采集&#xff0c;社区活动&#xff0c;人脸检测&#xff0c;语音识别&#xff0c;心率检测&#xff0c;积分商城-3 信息采集页面-采集人数…

5.27 day 30

知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 作业&#xff1a;自己新建几个不同路径文件尝试下如何导入 一、导入官方库 我们复盘下学习py…

【GitHub Pages】部署指南

vue项目 编辑你的 vite.config.ts 文件&#xff0c;加上 base 路径&#xff0c;设置为你的 GitHub 仓库名 import { defineConfig } from vite import vue from vitejs/plugin-vue// 假设你的仓库是 https://github.com/your-username/my-vue-app export default defineConfi…

远程控制技术全面解析:找到适合你的最佳方案

背景&#xff1a;远程控制为何成为企业核心需求&#xff1f; 随着企业数字化转型的推进&#xff0c;远程控制技术已成为异地办公和运维的关键工具。无论是跨国企业需要高效管理全球设备&#xff0c;还是中小型企业追求经济高效的解决方案&#xff0c;选择合适的远程控制技术&a…

触觉智能RK3506星闪开发板规格书 型号IDO-EVB3506-V1

产品概述 触觉智能RK3506星闪开发板&#xff0c;型号IDO-EVB3506-V1采用 Rockchip RK3506&#xff08;三核 Cortex-A7单核Cortex-M0, 主频最高1.5GHz&#xff09;设计的评估开发板&#xff0c;专为家电显控、显示HMI、手持终端、工业IOT网关、工业控制、PLC等领域而设计。内置…