SpringBoot 项目开发
SpringBoot 项目开发
0.环境搭建
执行资料中的
big_event.sql脚本,准备数据库表。-- 创建数据库 create database javaDB; -- 使用数据库 use javaDB; -- 用户表 create table user ( id int unsigned primary key auto_increment comment 'ID', username varchar(20) not null unique comment '用户名', password varchar(32) comment '密码', nickname varchar(10) default '' comment '昵称', email varchar(128) default '' comment '邮箱', user_pic varchar(128) default '' comment '头像', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间' ) comment '用户表'; -- 分类表 create table category( id int unsigned primary key auto_increment comment 'ID', category_name varchar(32) not null comment '分类名称', category_alias varchar(32) not null comment '分类别名', create_user int unsigned not null comment '创建人ID', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间', constraint fk_category_user foreign key (create_user) references user(id) -- 外键约束 ); -- 文章表 create table article( id int unsigned primary key auto_increment comment 'ID', title varchar(30) not null comment '文章标题', content varchar(10000) not null comment '文章内容', cover_img varchar(128) not null comment '文章封面', state varchar(3) default '草稿' comment '文章状态: 只能是[已发布] 或者 [草稿]', category_id int unsigned comment '文章分类ID', create_user int unsigned not null comment '创建人ID', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间', constraint fk_article_category foreign key (category_id) references category(id),-- 外键约束 constraint fk_article_user foreign key (create_user) references user(id) -- 外键约束 )创建
springboot工程,引入对应的依赖(web、mybatis、mysql驱动)(1)使用
IDEA进行springboot工程的创建,如下图所示。
(2)继承
Spring Boot的默认配置。<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.3</version> </parent>(3)引入 依赖信息。
<dependencies> <!-- web 依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.3.4</version> </dependency> <!-- mybatis 依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!-- mysql 驱动依赖--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>9.0.0</version> </dependency> <!-- Lombok(用于简化代码) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>(4)刷新
pom文件,让Maven重新进行依赖加载。配置文件
application.yml引入mybatis的配置信息。这里可能需要进行
resources目录的创建。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/javaDB username: root password: 1234创建包结构,并准备实体类。
依次创建
controller、mapper、pojo、service、service.impl、utils包,并将User、Category和Article实体类导入到pojo包下方。User实体类:import lombok.Data; import java.time.LocalDateTime; @Data public class User { private Integer id; private String username; private String password; private String nickname; private String email; private String userPic; private LocalDateTime createTime; private LocalDateTime updateTime; }Category实体类:import lombok.Data; import java.time.LocalDateTime; @Data public class Category { private Integer id; private String categoryName; private String categoryAlias; private Integer createUser; private LocalDateTime createTime; private LocalDateTime updateTime; }Article实体类:# Article.java import lombok.Data; import java.time.LocalDateTime; @Data public class Article { private Integer id; private String title; private String content; private String coverImg; private String state; // 应该只允许 "已发布" 或 "草稿" private Integer categoryId; private Integer createUser; private LocalDateTime createTime; private LocalDateTime updateTime; }启动类需要进行修改,一般默认的类名为
App,修改为项目 +Application的形式,如项目名称是big-event则对应的启动类名称为BigEventApplication,修改启动类的内容,如下所示:package com.euansu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; // SpringBootApplication注解 @SpringBootApplication public class BigEventApplication { public static void main(String[] args) { // 这里是固定的组合,传入类和参数 SpringApplication.run(BigEventApplication.class, args); } }
创建结束后,项目的目录结构如下:
big-event
│ pom.xml # Maven 项目配置文件
├─src
│ └─main
│ ├─java # Java 源代码目录
│ │ └─com
│ │ └─euansu # 包名 com.euansu
│ │ │ BigEventApplication.java # Spring Boot 启动类
│ │ ├─controller # 控制器类(处理 HTTP 请求)
│ │ ├─mapper # Mapper 接口(数据库操作)
│ │ ├─pojo # 实体类(POJO)
│ │ │ Article.java # 实体类(Article)
│ │ │ Category.java # 实体类(Category)
│ │ │ User.java # 实体类(User)
│ │ │
│ │ ├─service
│ │ │ └─impl # 服务实现类(业务逻辑)
│ │ └─utils # 工具类(通用方法)
│ └─resources # 资源目录
│ application.yml # Spring Boot 主配置文件使用启动类运行项目,如下图所示:

1.接口开发
1.1 用户模块接口开发
用户模块有六个接口需要开发:注册、登录、获取用户详细信息、更新用户基本信息、更新用户头像、更新用户密码
1.1.1 注册接口
在开发注册接口前,先对 SpringBoot 项目的返回进行规范,项目的响应为三个公共字段:code、message 和 data,这里增加一个实体类进行返回信息的处理。
package com.euansu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// 统一响应结果
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code; // 业务状态码,0-成功 1-失败
private String message; // 提示信息
private T data; // 响应数据
// 快速返回操作成功响应结果(带响应数据)
public static <E> Result<E> success(E data) {
return new Result<>(0, "操作成功", data);
}
// 快速返回操作成功响应结果
public static Result success() {
return new Result(0, "操作成功", null);
}
public static Result error(String message) {
return new Result(1, message, null);
}
}注册接口的开发流程图如下所示,先进行 Controller 的开发,接下来是 Service 和 Mapper。

创建所要用到的
Java类,UserController.java、UserService.java、UserServiceImpl.java、UserMapper.javaUserController.javapackage com.euansu.controller; public class UserController { }UserService.javapackage com.euansu.service; public interface UserService { }UserServiceImpl.javapackage com.euansu.service.impl; import com.euansu.service.UserService; public class UserServiceImpl implements UserService { }UserMapper.javapackage com.euansu.mapper; public interface UserMapper { }
UserController.javapackage com.euansu.controller; import com.euansu.pojo.Result; import com.euansu.pojo.User; import com.euansu.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/register") public Result register(String username, String password) { // 查询用户名是否存在 User u = userService.findByUserName(username); if(u != null) { // 被占用了 return Result.error("用户名已被占用"); } else{ // 注册新用户 userService.register(username,password); return Result.success(); } } }UserService.javapackage com.euansu.service; import com.euansu.pojo.User; public interface UserService { // 根据用户名查询用户 User findByUserName(String username); // 注册 void register(String username, String password); }UserServiceImpl.java,这里需要引入MD5算法对用户名进行加密处理。# UserServiceImpl.java package com.euansu.service.impl; import com.euansu.mapper.UserMapper; import com.euansu.pojo.User; import com.euansu.service.UserService; import com.euansu.utils.Md5Util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public User findByUserName(String username) { User u = userMapper.findByUserName(username); return u; } @Override public void register(String username, String password) { // 密码做加密处理 String md5String = Md5Util.getMD5String(password); // 用户注册 userMapper.add(username, md5String); } }UserMapper.javapackage com.euansu.mapper; import com.euansu.pojo.User; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @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}, #{md5String}, now(), now())") void add(String username, String md5String); }实现如上代码后,使用
postman进行测试,能够正常对用户的注册请求进行响应处理。
用户注册接口的调用流程总结如下:
客户端(浏览器或前端)发送 HTTP 请求 ↓
Controller 层:
UserController接收请求 ↓Service 层:通过
UserService接口调用UserServiceImpl↓Mapper/DAO 层:
UserServiceImpl调用UserMapper直接与数据库交互 ↓数据库:执行 SQL 向下访问数据
1.1.2 注册接口的参数校验
参数校验的要求如下:
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
| username | 用户名 | string | 是 | 5-16位非空字符 |
| password | 密码 | string | 是 | 5-16位非空字符 |
使用 Spring Validation 来实现接口的参数校验,操作步骤如下:
导入
Spring Validation依赖信息,刷新pom文件。<!-- Spring Validation 起步依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>在参数上添加
@Pattern注解,指定校验规则。@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/register") // Pattern 注解 public Result register(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password) { // 查询用户名是否存在 User u = userService.findByUserName(username); if(u != null) { // 被占用了 return Result.error("用户名已被占用"); } else{ // 注册新用户 userService.register(username,password); return Result.success(); } } }在
Controller类上添加Validated注解。@RestController @RequestMapping("/user") // Validated注解 @Validated public class UserController { @Autowired private UserService userService; @PostMapping("/register") public Result register(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password) { // 查询用户名是否存在 User u = userService.findByUserName(username); if(u != null) { // 被占用了 return Result.error("用户名已被占用"); } else{ // 注册新用户 userService.register(username,password); return Result.success(); } } }添加完成后,使用
Postman接口测试工具进行测试,出现如下报错。
查看控制台,会发现这是参数校验不通过的报错,但是没有对报错进行处理导致直接返回了
500的异常报错,接下来还需要增加异常的全局处理。
在全局异常处理器中处理参数校验失败的内容,在项目中增加一个
exception的包,添加GlobalExceptionHandler.java,全局异常处理器的内容如下所示:import com.euansu.pojo.Result; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result handlerException(Exception e) { e.printStackTrace(); return Result.error(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "操作失败"); } }再次请求,能够正常进行错误的响应。

1.1.3 登录主逻辑
登录接口的开发流程如下图所示:

在Controller中添加登录接口,主逻辑实现代码如下:
@PostMapping("/login")
public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password){
// 根据用户名查询用户
User loginUser = userService.findByUserName(username);
// 判断用户是否存在
if(loginUser == null) {
return Result.error("用户不存在");
}
// 判断密码是否正确
if(Md5Util.getMD5String(password).equals(loginUser.getPassword())) {
// 登录成功
return Result.success("jwt token令牌");
}else{
return Result.error("登录密码错误");
}
}1.1.4 登录令牌(JWT)
还有一个 ArticleController下的接口,在未登录的时候需要进行检查,确认用户只有登录后才允许访问该接口。
package com.euansu.controller;
import com.euansu.pojo.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/article")
public class ArticleController {
@GetMapping("/list")
public Result<String> list(){
return Result.success("文章列表信息。。。");
}
}如下图所示,用户未进行登录也访问到了接口信息,这里需要对接口添加认证,其中的一种形式是登录令牌。

JWT,全称 Json Web Token,定义了一种简洁的、自包含的格式,用于通信双方以 json 数据格式安全的传输信息。
组成:
- 第一部分:
Header(头),记录令牌类型、签名算法等,例如:{"alg":"HS256","type":"JWT"} - 第二部分:
Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"euansu"} - 第三部分:
Signature(签名),防止Token被篡改、确保安全性。将header、payload加入制定秘钥,通过签名算法计算而来。

这里在 SpringBoot 自带的测试方法中测试 JWT 令牌的相关功能,测试步骤如下:
引入
jwt依赖和springboot-test依赖,添加后刷新pom依赖。<!-- Java JWT依赖 --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency> <!-- SpringBoot test依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>在
test包下添加JwtTest.java测试类,代码如下:import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import org.junit.jupiter.api.Test; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtTest { @Test public void testGen(){ // 生成JWT对象 Map<String, Object> claims = new HashMap<>(); claims.put("id",1); claims.put("username", "euansu"); String token = JWT.create() .withClaim("user",claims) .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) .sign(Algorithm.HMAC256("euansu")); System.out.println(token); } @Test public void testParse(){ // 解析JWT对象 String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6ImV1YW5zdSJ9LCJleHAiOjE3NDk4MTkzMjd9.5q-XBmgY6XDPS_f_F-OVW1Gn6jMC7VvooAfvphaSPeI"; JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("euansu")).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token); Map<String, Claim> claims = decodedJWT.getClaims(); System.out.println(claims.get("user")); } }测试一下正常的
JWT令牌的生成和解析。如下图所示,正常生成一个
JWT令牌。
将生成的
JWT令牌进行解析,能够正常获取到载荷信息,如下图所示。
测试
JWT令牌被篡改后,是否能够正常解析。// 修改header String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6ImV1YW5zdSJ9LCJleHAiOjE3NDk4MTkzMjd9.5q-XBmgY6XDPS_f_F-OVW1Gn6jMC7VvooAfvphaSPeI";
// 修改载荷 String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6ImV1YW5zdSJ9LCJleHAiOjE3NDk4MTkzMjd0.5q-XBmgY6XDPS_f_F-OVW1Gn6jMC7VvooAfvphaSPeI";
1.1.5 登录认证
登录认证这里的流程是:
- 使用拦截器统一验证令牌。
- 登录和注册接口需要放行。
新建
JwtUtil.java工具类,写入创建和解析JWT会话令牌的方法,详细代码如下所示:import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import java.util.Date; import java.util.Map; public class JwtUtil { private static final String SECRET = "euansu"; // 接收业务数据,生成token并返回 public static String generateToken(Map<String, Object> claims) { return JWT.create() .withClaim("claims",claims) .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) .sign(Algorithm.HMAC256(SECRET)); } // 接收token,验证token,返回业务数据 public static Map<String, Object> parseToken(String token) { return JWT.require(Algorithm.HMAC256(SECRET)) .build() .verify(token) .getClaim("claims") .asMap(); } }修改
UserController.java中登录的方法,当用户登录成功后,返回一个JWT的tokne串,修改后的代码如下所示:@PostMapping("/login") public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username,@Pattern(regexp = "^\\S{5,16}$") String password){ // 根据用户名查询用户 User loginUser = userService.findByUserName(username); // 判断用户是否存在 if(loginUser == null) { return Result.error("用户不存在"); } // 判断密码是否正确 if(Md5Util.getMD5String(password).equals(loginUser.getPassword())) { // 登录成功 Map<String, Object> claims = new HashMap<>(); claims.put("id",loginUser.getId()); claims.put("username", loginUser.getUsername()); JwtUtil.generateToken(claims); return Result.success(JwtUtil.generateToken(claims)); }else{ return Result.error("登录密码错误"); } }添加一个登录拦截器,用于校验请求头中是否携带
Authorization串,具体代码如下所示:# 这里新建了一个 interceptors 的包 # 创建了LoginInterceptor类 package com.euansu.interceptors; import com.euansu.utils.JwtUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import java.util.Map; @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 令牌验证 String token = request.getHeader("Authorization"); // 验证token try{ Map<String, Object> claims = JwtUtil.parseToken(token); // 不报错,放行 return true; } catch (Exception e){ // http 响应状态码为401 response.setStatus(401); // 不放行 return false; } } }增加一个
SpringBoot MVC配置类,主要用于配置拦截器,详细代码如下所示:package com.euansu.config; import com.euansu.interceptors.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { System.out.println("LoginInterceptor"); // 登录和注册接口不做拦截 registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login", "/user/register"); } }修改完成后,重启项目进行测试。
当请求头中不含有
Authorization时,返回401状态的错误。
当请求头中包含
Authorization时,能够正常返回请求的响应信息。
1.1.6 获取用户的详细信息
获取用户详细信息的接口流程

首先在 UserController 中添加 userInfo 接口,由于根据用户名查询的方法在 Service 和 Mapper 中已有,这里就不在做开发了,代码如下:
@GetMapping("/userInfo")
public Result<User> UserInfo(@RequestHeader(name="Authorization") String token){
// 这个接口从token令牌中解析用户信息,无参数输入
// 根据用户名查询用户
Map<String, Object> map = JwtUtil.parseToken(token);
String username = (String) map.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}使用接口工具测试,能够正常返回响应信息,测试截图如下:

每增加一个接口,需要手动添加 Authorization 请求头,否则会报用户认证的错误,这里过于繁琐,Postman 提供了集合统一增加请求头的方式,在 Script中,选择 pre-request,增加如下内容,就完成了 Authorization 请求头的增加。
pm.request.addHeader("Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjMsInVzZXJuYW1lIjoibmFuZ2UifSwiZXhwIjoxNzUwMDQzMTY4fQ.sk7dnq82ZPaVxmNRdfuGgLhccKUxWOjmAMfLRqKezw0")
在单个接口的请求中,移除 Authorization,能够正常得到响应。

再查看用户信息接口的返回信息,返回的数据字段中将 password 字段也进行了返回,这是不对的,需要在用户实体类中给 password 字段增加 JsonIgnore 注解,这样在 SpringMVC 将当前对象转化为 JSON 字符串的时候忽略掉 password 这个属性,如下:
import com.fasterxml.jackson.annotation.JsonIgnore;
// 这里特别注意引入JsonIgnore是com.fasterxml.jackson.annotation.JsonIgnore,否则会导致注解不生效
@Data
public class User {
...
@JsonIgnore // 让SpringMVC把当前对象转换成JSON字符串的时候,忽略password,最终的JSON字符串中就没有password这个属性了
private String password;
}检查数据库中 User 用户表,其中的 create_time 和 update_time 均不为空,但是返回为空,这里是因为 User 实体类中使用了驼峰命名的方式,与表字段对象不上。

@Data
public class User {
...
private LocalDateTime createTime;
private LocalDateTime updateTime;
}这里需要在 SpringBoot 的项目配置文件中增加 map-underscore-to-camel-case 配置,开启驼峰命名方式和下划线的自动转换。
mybatis:
configuration:
map-underscore-to-camel-case: true # 开启驼峰命名和下划线命名的转换修改完成后,重启项目再次进行请求,请求结果中的 password 字段消失,createTime 和 updateTime 字段值不为空。

1.1.7 获取用户详细信息优化(ThreadLocal)
ThreadLocal :提供线程局部变量
- 用来存取数据:
set()/get()。 - 使用
ThreadLocal存储的数据,线程安全。

使用 ThreadLocal 能够实现线程间的共享,可以在拦截器中进行设置,在 Controller、Service或者 Dao层实现变量的获取。

这里对获取用户详细信息的接口进行改造,在拦截中进行 ThreadLocal 的设置,在 UserService中进行获取,拦截器中增加 ThreadLocal 变量的设置和清理,代码如下:
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 令牌验证
String token = request.getHeader("Authorization");
// 验证token
try{
Map<String, Object> claims = JwtUtil.parseToken(token);
// 不报错,放行
// 设置 ThreadLocal 变量
ThreadLocalUtil.set(claims);
return true;
} catch (Exception e){
// http 响应状态码为401
response.setStatus(401);
// 不放行
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 请求完成,清空 ThreadLocal中的数据
ThreadLocalUtil.remove();
}
}在 UserController 中,直接获取 ThreadLocal 设置的变量信息,代码如下:
@GetMapping("/userInfo")
public Result<User> UserInfo(@RequestHeader(name="Authorization") String token){
// 根据用户名查询用户
// Map<String, Object> map = JwtUtil.parseToken(token);
// String username = (String) map.get("username");
// 通过拦截器后的请求,直接获取ThreadLocal中的变量信息
Map<String,Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}修改完成后,重启 SpringBoot 项目工程,能够正常获取到用户信息的接口响应。

1.1.8 更新用户基本信息
更新用户信息的开发流程图如下所示,需要依次在 UserController 、UserService和 UserMapper 进行开发。

UserController 进行 update 接口的开发,新增代码如下所示:
@PutMapping("/update")
public Result update(@RequestBody User user){
userService.update(user);
return Result.success();
}UserService 中进行 update 方法的签名(即方法名称、参数和返回值类型),这里不提供具体的实现。
public interface UserService {
// 更新
void update(User user);
}UserServiceImpl 实现类中进行 UserService 中方法的实现,具体代码如下。
@Override
public void update(User user) {
user.setUpdateTime(LocalDateTime.now());
userMapper.update(user);
}UserMapper 数据交互层做数据的更新操作,具体代码如下所示。
@Mapper
public interface UserMapper {
// 更新
@Update("update user set nickname=#{nickname},email=#{email},update_time=#{updateTime} where id=#{id}")
void update(User user);
}编写完如上代码后,重启 SpringBoot项目,做用户更新请求前,检查数据库中记录信息,如下所示。

使用 postman 接口测试工具进行请求,如下所示,显示请求成功。

再去检查数据库中的记录信息,nickname、email、update_time 字段均被修改,更新操作成功。

1.1.9 更新用户基本信息_参数校验
用户基本信息参数校验规则如下:
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
| id | 主键ID | number | 是 | |
| username | 用户名称 | string | 否 | 5~16位非空字符 |
| nickname | 昵称 | string | 是 | 1~10位非空字符 |
| 邮箱 | string | 是 | 满足邮箱格式 |
参数校验的视线流程只有两步:1.修改User实体类,对要进行校验的字段进行相关规则的注解。2.在请求参数前添加 Validated 注解。
修改 User 实体类,对要校验的参数进行相关注解,如下所示。
@Data
public class User {
@NotNull
private Integer id;
private String username;
@JsonIgnore // 让SpringMVC把当前对象转换成JSON字符串的时候,忽略password,最终的JSON字符串中就没有password这个属性了
private String password;
@NotEmpty
@Pattern(regexp = "^\\S{1,10}$")
private String nickname;
@NotEmpty
@Email
private String email;
private String userPic;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}注解的相关说明如下表格中所示。
| 注解 | 作用 |
|---|---|
| NotNull | 值不能为null |
| NotEmpty | 值不能为null,并且内容不为空 |
| 满足邮箱格式 |
修改 UserController 中的 update 的请求参数,添加 @Validated 注解,如下所示。
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
userService.update(user);
return Result.success();
}修改完成后使用 postman 接口测试工具进行请求,能够正常返回参数校验不通过的信息。

1.1.10 更新用户头像
请求路径:
/user/updateAvatar请求方式:
PATCH
更新用户头像的开发流程如下所示:

UserController 中增加 updateAvatar 接口的代码,调用 userService 下的 updateAvatar 来实现具体的逻辑。
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam String avatarUrl){
userService.updateAvatar(avatarUrl);
return Result.success();
}在 userService 中增加 updateAvatar 的方法签名,如下所示:
public interface UserService {
// 更新头像
void updateAvatar(String avatarUrl);
}在 UserServiceImpl 添加 updateAvatar 的具体代码,调用 UserMapper 中的 updateAvatar 来负责具体的数据交互操作,如下所示:
@Override
public void updateAvatar(String avatarUrl) {
Map<String,Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updateAvatar(avatarUrl, id);
}在 UserMapper 中增加 updateAvatar 的数据交互操作,如下所示:
@Mapper
public interface UserMapper {
// 更新头像
@Update("update user set user_pic=#{avatarUrl},update_time=now() where id=#{id}")
void updateAvatar(String avatarUrl,Integer id);
}如上代码编写完成后,重启 SpringBoot 项目程序,使用 postman 测试工具进行测试。

操作成功后,这里检查数据库,user_pic 字段出现对应的记录信息。

如上所示,这里传入的字段只是一个字符串,并不是实际的头像地址,这样是不对的,需要增加头像地址的参数校验,修改 UserController 中的 updateAvatar 方法,在方法的参数中增加 URL 的校验注解,如下所示。
import org.hibernate.validator.constraints.URL;
public Result updateAvatar(@RequestParam @URL String avatarUrl)再次使用 postman 接口测试工具请求服务时,就提示接口校验不通过了,如下图所示。

修改传入的 avatarUrl 为一个实际的图片地址,再次请求就能够得到正常的响应,如下所示。

检查数据库中的记录,user_pic 存储了一个正常的图片地址。

1.1.10 更新用户密码
请求路径:
/user/upatePwd请求方式:
PATCH请求参数格式:
application/json
更新用户密码的开发流程图如下所示,在 UserController 中的参数指定类型为 Map,这里是因为传入的参数没有与实体类相对应,因此声明为 Map 对象。

首先在 userController 中增加 updatePwd 的接口代码,生成参数类型为 Map 类型,在接口中队参数进行校验后,调用 userService 的 updatePwd 方法。
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String,String> parmas){
// 1.参数校验
String oldPwd = parmas.get("old_pwd");
String newPwd = parmas.get("new_pwd");
String rePwd = parmas.get("re_pwd");
if (!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd) || !StringUtils.hasLength(rePwd)) {
return Result.error("缺少必要的参数");
}
// 原密码是否正确
Map<String,Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = userService.findByUserName(username);
System.out.println(oldPwd);
if(!loginUser.getPassword().equals(Md5Util.getMD5String(oldPwd))) {
return Result.error("原密码填写不正确");
}
if(!rePwd.equals(newPwd)){
return Result.error("两次填写的新密码不一致");
}
// 2.调用service完成密码更新
userService.updatePwd(newPwd);
return Result.success();
}在 UserService 中增加 updatePwd 方法的签名,如下所示
public interface UserService {
// 更新密码
void updatePwd(String newPwd);
}在 UserServiceImpl 实体类中增加 updatePwd 的方法操作,调用userMapper中的 updatePwd 做数据交互的操作,如下所示
@Override
public void updatePwd(String newPwd) {
Map<String,Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updatePwd(Md5Util.getMD5String(newPwd), id);
}在 userMapper 中增加 updatePwd 方法的实现代码,如下所示:
@Mapper
public interface UserMapper {
// 更新密码
@Update("update user set password=#{md5String},update_time=now() where id=#{id}")
void updatePwd(String md5String, Integer id);
}代码编写完成后,重启 SpringBoot 工程,使用 postman 调用接口进行测试,能够正常修改密码,如下图所示。

使用修改后的密码,能够正常进行登录,如下所示。

1.2 文章分类模块接口开发
1.2.1 文章分类接口新增
请求路径:
/category请求方式:
POST请求参数格式:
applictaion/json
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
categoryName | 分类名称 | String | 是 | |
categoryAlias | 分类别名 | String | 是 |
新增文章分类接口开发流程图

这里是第一次新增 Category 模块的接口,因此首先需要新建 CategoryController、CategoryService、CategoryServiceImpl和 CategoryMapper,对应的实体类 Category 已经提前创建完成。
首先在 CategoryController 中新增 RestController 等相关注解,并增加 add 接口。
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping
public Result addCategory(@RequestBody Category category) {
categoryService.add(category);
return Result.success();
}
}在 CategoryService 中增加 add 接口的声明,如下所示
public interface CategoryService {
// 新增文章分类
void add(Category category);
}在 CategoryServiceImpl 中增加 add 接口的实现方法以及 Service 等注解
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public void add(Category category) {
// 补充属性值
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
Map<String,Object> map = ThreadLocalUtil.get();
Integer userId = (Integer) map.get("id");
category.setId(userId);
categoryMapper.add(category);
}
}在 CategoryMapper 中增加 add 的数据交互操作以及 Mapper 的注解,如下所示
@Mapper
public interface CategoryMapper {
@Insert("insert into category (category_name,category_alias,create_user,create_time,update_time) values (#{categoryName},#{categoryAlias},#{id},#{createTime},#{updateTime})" )
void add(Category category);
}重启 SpringBoot 工程,使用 postman 工具进行测试,如下所示,能够成功进行文章分类的添加操作

该接口还缺少相关的数据校验操作,如下所示,当设置 categoryAlias 时,应该提示字段为空而不是操作成功

在 Category 实体类中的 categoryName 和 categoryAlias 增加相关校验注解,如下所示
@Data
public class Category {
private Integer id; // 主键ID
@NotEmpty
private String categoryName; // 分类名称
@NotEmpty
private String categoryAlias; // 分类别名
private Integer createUser; // 创建人ID
private LocalDateTime createTime; // 创建时间
private LocalDateTime updateTime; // 更新时间
}修改 CateoryController 中 add 接口的参数,增加校验的注解,如下所示
@PostMapping
// 增加 Validated 校验注解
public Result addCategory(@RequestBody @Validated Category category) {
categoryService.add(category);
return Result.success();
}使用 postman 测试工具进行接口的调用,提示 categoryAlias 参数为空,如下所示

1.2.2 文章分类列表接口
请求路径:
/category/请求方式:
GET请求参数:无
文章分类列表接口的开发流程图如下所示,这里需要注意最后的 Mapper 层只需要查询用户相关的分类,不能查询他人新增的分类

文章分类新增接口和文章分类列表接口路由一致,但对应的请求方法存在差异,这里的开发流程也一样,首先编写 Controller 层的代码,然后是 Service 层,最后则是 Mapper层。
在 CategoryController 层新增文章列表接口的代码,调用 CategoryService 层的相关代码。
@GetMapping
public Result<List<Category>> list() {
List<Category> cs = categoryService.list();
return Result.success(cs);
}在CategroyService 层增加列表方法的声明,如下所示
public interface CategoryService {
// 新增文章分类
void add(Category category);
// 文章分类列表
List<Category> list();
}在 CategoryServiceImpl 中增加列表方法的具体实现,如下所示
@Override
public List<Category> list() {
Map<String,Object> map = ThreadLocalUtil.get();
Integer userId = (Integer) map.get(("id"));
return categoryMapper.list(userId);
}最后则是在 CategoryMapper 中编写数据交互的具体操作,如下所示
@Select("select * from category where create_user=#{userId}")
List<Category> list(Integer userId);重启 SpringBoot 项目后,使用 postman 工具进行测试,能够正常获取到文章分类的列表,如下所示

这里获取到的时间格式存在问题,还需要在实体类的时间字段上增加注解,设置返回数据中的时间格式,如下所示
@Data
public class Category {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime; // 创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime; // 更新时间
}重启 SpringBoot 工程项目,再次使用 postman 接口工具进行测试,能够返回调整时间格式后的字段信息

1.2.3 文章分类详情
请求路径:
/category/detail请求方式:
GET请求参数格式:
queryString
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
id | 主键ID | Number | 是 |
获取文章分类详情的开发流程图如下

首先,在 CategoryController 在编写分类详情接口,调用 Service 层的方法实现具体逻辑,代码如下所示
@GetMapping("/detail")
public Result<Category> detail(@RequestParam Integer id) {
Category category = categoryService.findById(id);
return Result.success(category);
}在 CategoryService 声明 findById 的方法,代码如下所示
public interface CategoryService {
// 新增文章分类
void add(Category category);
// 文章分类列表
List<Category> list();
// 文章详情
Category findById(Integer id);
}在 CategoryServiceImpl 中编写 findById 的具体代码实现,如下所示
@Override
public Category findById(Integer id) {
return categoryMapper.findById(id);
}在 CategoryMapper 中编写数据交互的操作方法,代码如下所示
@Select("select * from category where id=#{id}")
Category findById(Integer id);重启 SpringBoot 项目,使用 postman 测试工具请求 detai 接口,如下所示能够正常返回文章分类的详情信息

1.2.4 文章分类更新
请求路径:
/category请求方式:
PUT请求参数格式:
application/json
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
id | 主键ID | Number | 是 | |
categoryName | 分类名称 | String | 是 | |
categoryAlias | 分类别名 | String | 是 |
更新文章分类的接口开发流程图

首先在CategoryCrontoller 中添加文章分类接口的代码实现,如下所示
@PutMapping
public Result updateCategory(@RequestBody @Validated Category category) {
categoryService.update(category);
return Result.success();
}在 CategoryService 中编写对应方法的声明,如下所示
public interface CategoryService {
// 新增文章分类
void add(Category category);
// 文章分类列表
List<Category> list();
// 文章分类详情
Category findById(Integer id);
// 文章分类更新
void update(Category category);
}在 CategoryServiceImpl 编写对应的实现方法,如下所示
@Override
public void update(Category category) {
category.setUpdateTime(LocalDateTime.now());
categoryMapper.update(category);
}在 Categroy 中编写对应数据交互的方法,如下所示
@Update("update category set category_name=#{categoryName},category_alias=#{categoryAlias} where id=#{id}")
void update(Category category);更新操作的时候,要求 id 、categoryName 和 categoryAlias 字段不能为空,需要修改实体类,在对应的字段上添加注解
@Data
public class Category {
@NotNull
private Integer id; // 主键ID
@NotEmpty(groups = {Add.class, Update.class})
private String categoryName; // 分类名称
@NotEmpty(groups = {Add.class, Update.class})
private String categoryAlias; // 分类别名
private Integer createUser; // 创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime; // 创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime; // 更新时间
}重启 SpringBoot 项目后,使用 postman 接口测试工具请求测试,能够正常更新文章分类的记录信息,如下所示

使用postman 调用详情接口,对应的文章分类详情被修改,如下所示

1.2.5 分组校验
上一章节添加了文章分类更新的接口,对 id 字段添加了 NotNull 的注解,需要注意到的一点是,文章分类更新和文章分类新增的参数都用了Category实体类,但是新增文章分类的接口并不需要校验 id 字段,再次请求新增文章分类的时候,提示 id 字段要求不为空,这里就需要用到分组校验,也即不同的分组下的接口校验参数不一样。

把校验项进行归类分组,在完成不同的功能的时候,校验指定组中的校验项:
定义分组(在实体类的内部定义接口)
public interface Add{} public interface Update{}定义校验项时指定归属的分组(通过groups属性指定)
@Data public class Category { @NotNull(groups = Update.class) private Integer id; // 主键ID @NotEmpty(groups = {Add.class, Update.class}) private String categoryName; // 分类名称 @NotEmpty(groups = {Add.class, Update.class}) private String categoryAlias; // 分类别名 }校验时指定要校验的分组(给@Validated注解的value属性赋值)
@RestController @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; @PostMapping public Result addCategory(@RequestBody @Validated(Category.Add.class) Category category) { categoryService.add(category); return Result.success(); } @PutMapping public Result updateCategory(@RequestBody @Validated(Category.Update.class) Category category) { categoryService.update(category); return Result.success(); } }

按照如上依次进行修改后,重启 SpringBoot 项目,使用 postman 测试文章分类新增接口,能够正常新增分类。

使用如上方式能够实现接口分组参数校验的需求,但是如果要校验的分组过多,在字段添加 groups = {Add.class, Update.class,...}就会显得很臃肿,这里有一个默认分组的概念:
参考分组集成的的概念修改实体类中分组校验的定义,修改代码为如下样式
@Data
public class Category {
@NotNull(groups = Update.class)
private Integer id; // 主键ID
@NotEmpty
private String categoryName; // 分类名称
@NotEmpty
private String categoryAlias; // 分类别名
private Integer createUser; // 创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime; // 创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime; // 更新时间
public interface Add extends Default {}
public interface Update extends Default {}
}重启 SpringBoot 进行测试,能够正常新增文章分类。

去掉 id 字段,更新时能够校验出缺少 id 字段,如下所示。

1.2.6 文章分类删除
请求路径:
/category/{id}请求方式:
DELETE请求参数格式:路由参数
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
id | 主键ID | Number | 是 |
删除接口的开发流程依旧是 Controller 、 Service 和 Mapper,首先在 Category 中新增文章分类删除的接口代码,如下所示
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
System.out.println("id");
categoryService.deleteById(id);
return Result.success();
}在 CategoryService 中编写 deleteById 方法的声明,如下所示
public interface CategoryService {
// 新增文章分类
void add(Category category);
// 文章分类列表
List<Category> list();
// 文章分类详情
Category findById(Integer id);
// 文章分类更新
void update(Category category);
// 文章分类删除
void deleteById(Integer id);
}在 CategoryServiceImpl中编写deleteById方法的具体实现,如下所示
@Override
public void deleteById(Integer id) {
categoryMapper.deleteById(id);
}在 CategroyMapper 中编写与数据的交互操作,如下所示
@Delete("delete from category where id=#{id}")
void deleteById(Integer id);重启 SpringBoot 项目,使用 postman 接口测试工具请求文章分类删除的接口,能够正常进行对应文章分类的删除

调用文章分类列表接口,返回的数据中不存在已删除的文章分类信息

1.3 文章模块接口开发
1.3.1 新增文章接口
请求路径:
/article请求方式:
POST请求参数格式:
application/json
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
title | 文章标题 | String | 是 | 1~10个非空字符 |
content | 文章正文 | String | 是 | |
coverImg | 封面图像地址 | String | 是 | 必须是 url 地址 |
state | 发布状态 | String | 是 | 已发布、草稿 |
categoryId | 文章分类ID | Number | 是 |
新增文章接口的开发流程图

文章模块这里是首次开发,因此需要先创建 ArticleController、ArticleService、ArticleServiceImpl和ArticleMapper。
在 ArticleController 中增加新增文章的接口代码,这里调用 Service层的代码来实现,如下所示
@PostMapping
public Result addArticle(@RequestBody Article article) {
articleService.addArticle(article);
return Result.success();
}在 ArticleService中添加文章新增代码的声明,如下所示
public interface ArticleService {
// 新增文章
void addArticle(Article article);
}在 ArticleServiceImpe 实现类中编写新增文章的具体实现,如下所示
@Override
public void addArticle(Article article) {
article.setCreateTime(LocalDateTime.now());
article.setUpdateTime(LocalDateTime.now());
Map<String,Object> map = ThreadLocalUtil.get();
Integer userId = (Integer) map.get("id");
article.setCreateUser(userId);
articleMapper.addArticle(article);
}在 ArticleMapper 中编写与数据的交互操作,如下所示
@Insert("INSERT INTO article (title, content, cover_img, category_id, create_user, state, create_time, update_time) " +
"VALUES (#{title},#{content},#{coverImg},#{categoryId},#{createUser},#{state},#{createTime},#{updateTime})")
void addArticle(Article article);如上代码编写完成后,重启 SpringBoot 工程项目,使用 postman 工具进行测试,请求文章新增接口,能够正常进行文章的新增操作

1.3.2 新增文章参数校验(自定义校验)
自定义校验:已有的注解不能满足所有的校验需求,特殊的情况需要自定义校验(自定义校验注解)
以新增文章为例,其中有一个参数要求是 state 参数的值只能是已发布或者草稿,这样的特殊需求无法使用 @NotEmpty、@URL、@Pattern等来实现,这里就需要我们手动实现一个自定义校验方法。
自定义注解
State这里新增一个
anno包,将State注解类放到包下,这里的内容可以通过@NotEmpty来参考修改。package com.euansu.anno; import com.euansu.validation.StateValidation; import jakarta.validation.Constraint; import jakarta.validation.Payload; import java.lang.annotation.*; @Documented // 元注解,标识State是可以抽取到帮助文档 @Target({ElementType.FIELD}) // 元注解,标识State是可以作用到字段上 @Retention(RetentionPolicy.RUNTIME) // 元注解,标识State在那个阶段会被保留 @Constraint(validatedBy = {StateValidation.class}) // 指定谁来给State提供校验规则 public @interface State { // 提供校验失败后的提供信息 String message() default "state参数的值只能是已发布或者草稿"; // 指定分组 Class<?>[] groups() default {}; // 负载,可以获取State注解的附加信息,一般不用 Class<? extends Payload>[] payload() default {}; }自定义校验数据的类
StateValidation实现ConstraubrValidator接口这里同样是新增一个包
validation,将StateValidation放到包下,重写isValid提供对应的校验规则package com.euansu.validation; import com.euansu.anno.State; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; // <给那个注解提供校验规则,校验的数据类型> public class StateValidation implements ConstraintValidator<State, String> { @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { // 提供校验规则 /** * @parma value 将来要校验的数据 * @parma constraintValidatorContext * @return 如果返回false,校验不通过 * */ if (s == null) { return false; } if(s.equals("已发布")||s.equals("草稿")){ return true; } return false; } }在需要校验的地方使用自定义注解
package com.euansu.pojo; import com.euansu.anno.State; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.Data; import org.hibernate.validator.constraints.URL; import java.time.LocalDateTime; @Data public class Article { private Integer id; @NotEmpty @Pattern(regexp = "^\\S{1,10}$") private String title; @NotEmpty private String content; @NotEmpty @URL private String coverImg; // 注解在字段的上方 @State private String state; // 应该只允许 "已发布" 或 "草稿" @NotNull // 涉及数据使用NotNull,字符串使用NotEmpty private Integer categoryId; private Integer createUser; private LocalDateTime createTime; private LocalDateTime updateTime; }
使用 postman 测试工具测试,返回的信息中提示 state参数的值只能是已发布或者草稿。

1.3.3 文章列表接口(条件分页)
请求路径:
/article请求方式:
GET请求参数格式:
queryString
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
pageNum | 当前页码 | Number | 是 | |
pageSize | 每页条数 | Number | 是 | |
categoryId | 文章分类ID | Number | 否 | |
state | 发布状态 | String | 否 | 已发布 | 草稿 |
文章列表(条件分页)的实现流程图

首先需要新增一个 PageBean 的实体类,用来处理分页返回的结果数据,代码实现如下
package com.euansu.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
// 分页返回结果对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean<T> {
private Long total; // 总条数
private List<T> items; // 当前页数据集合
}在Controller中添加文章列表的接口,代码实现如下所示,需要注意这个接口并不一定需要 categoryId 和 state 两个字段
@GetMapping("/list")
public Result<PageBean<Article>> list(
Integer pageNum,
Integer pageSize,
@RequestParam(required = false) Integer categoryId,
@RequestParam(required = false) String state
){
PageBean<Article> pb = articleService.list(pageNum,pageSize,categoryId,state);
return Result.success(pb);
}在 ArticleService 中增加条件分页列表查询的方法声明,如下所示
public interface ArticleService {
// 新增文章
void addArticle(Article article);
// 条件分页列表查询
PageBean<Article> list(Integer pageNum, Integer pageSize, Integer categoryId, String state);
}在 ArticleServiceImpl实现类中编写声明的方法,这里用到了 PageHelper 插件,需要先在 pom 文件中引入这个坐标,引入完成后刷新 pom 文件
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>在ArticleServiceImpl中编写使用 PageHelper插件实现的条件分页查询方法
@Override
public PageBean<Article> list(Integer pageNum, Integer pageSize, Integer categoryId, String state) {
// 1.创建PageBean对象,用于封装查询完成的对象
PageBean<Article> pb = new PageBean<>();
// 2.开启分页查询,需要用到 PageHelper 插件
PageHelper.startPage(pageNum,pageSize);
// 3.调用Mapper
Map<String,Object> map = ThreadLocalUtil.get();
Integer userId = (Integer) map.get("id");
List<Article> as = articleMapper.list(userId, categoryId, state);
// Page中提供了方法,可以获取PageHelper分页查询后,得到的总记录条数和当前页数据
Page<Article> p = (Page<Article>) as;
// 把数据填充到PageBean中
pb.setTotal(p.getTotal());
pb.setItems(p.getResult());
return pb;
}在 ArticleMapper 中编写条件查询的方法,这里不要直接写查询方法,因为传入的参数是动态的
@Mapper
public interface ArticleMapper {
List<Article> list(Integer userId, Integer categoryId, String state);
}这里使用映射文件,在和 mapper 对应路径的 resources 下方编写映射文件,路径的关系如下图所示

映射文件的内容如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.euansu.mapper.ArticleMapper">
<!--动态SQL-->
<select id="list" resultType="com.euansu.pojo.Article">
select * from article
<where>
<if test="categoryId!=null">
category_id=#{categoryId}
</if>
<if test="state!=null">
and state=#{state}
</if>
and create_user=#{userId}
</where>
</select>
</mapper>编写完成后,重启 SpringBoot 工程,使用 postman 接口测试工具测试,能够正常实现条件查询分页效果

1.3.4 文章更新接口
请求路径:
/article请求方式:
UPDATE请求参数格式:
application/json
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
title | 文章标题 | String | 是 | |
content | 文章内容 | String | 是 | |
categoryId | 文章分类ID | Number | 是 | |
state | 发布状态 | String | 是 | 已发布 | 草稿 |
coverImg | 文章封面 | String | 是 |
文章更新接口的实现流程:
ArticleController中增加文章更新的接口,调用ArticleService中的方法来实现。ArticleService声明文章更新的方法,在ArticleServiceImpl中实现。ArticleServiceImpl实现文章更新的方法,调用ArticleMapper来实现与数据库的交互操作。ArticleMapper实现文章与数据库的交互操作。
ArticleController 中增加文章更新的接口,代码如下所示
@PutMapping
public Result updateArticle(@RequestBody @Validated Article article) {
articleService.updateArticle(article);
return Result.success();
}ArticleService 声明文章更新的方法,代码如下所示
void updateArticle(Article article);ArticleServiceImpl 实现文章更新的方法,代码如下所示
@Override
public void updateArticle(Article article) {
article.setUpdateTime(LocalDateTime.now());
articleMapper.update(article);
}ArticleMapper 实现文章与数据库的交互操作,代码如下所示
@Update("UPDATE article SET title=#{title}, content=#{content}, cover_img=#{coverImg}, category_id=#{categoryId}, state=#{state}, update_time=#{updateTime} WHERE id=#{id}")
void update(Article article);重启 SpringBoot 项目后,使用 postman 接口测试工具进行测试,能够正常进行信息的修改。

使用 Postman 测试工具调用接口查询文章的存储信息,能够看到存储的文章发生了变化。

1.3.5 文章删除接口
请求路径:
/article请求方式:
DELETE请求参数格式:
multipart/form-data
文章删除接口的实现流程:
ArticleController中增加文章删除的接口,调用ArticleService中的方法来实现。ArticleService声明文章删除的方法,在ArticleServiceImpl中实现。ArticleServiceImpl实现文章删除的方法,调用ArticleMapper来实现与数据库的交互操作。ArticleMapper实现文章与数据库的交互操作。
ArticleController 中增加文章删除的接口,接口的代码如下所示
@DeleteMapping
public Result delete(@RequestParam Integer id) {
articleService.delete(id);
return Result.success();
}ArticleService 声明文章删除的方法,声明代码如下所示
void delete(Integer id);ArticleServiceImpl 实现文章删除的方法,代码如下所示
@Override
public void delete(Integer id) {
articleMapper.delete(id);
}ArticleMapper 实现文章与数据库的交互操作,代码如下所示
@Delete("DELETE FROM article WHERE id=#{id}")
void delete(Integer id);重启 SpringBoot 项目后,使用 postman 接口测试工具进行测试,能够正常进行文章的删除。

使用 postman 接口测试工具,无法查询到 id 为 2 的文章记录信息。

1.4 其他接口
1.4.1 文件上传(本地存储)
请求路径:
/upload请求方式:
POST请求参数格式:
multipart/form-data
请求参数说明
| 参数名称 | 说明 | 类型 | 是否必须 | 备注 |
|---|---|---|---|---|
file | 表单中文件请求参数的名称 | file | 是 |
文件上传接口的实现思路

新增 FileUploadController 编写文件上传接口的相关实现,代码如下:
package com.euansu.controller;
import com.euansu.pojo.Result;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RestController
public class FileUploadController {
@PostMapping("/upload")
public Result<String> upload(MultipartFile file) throws IOException {
// 获取文件原始名称
String originalFilename = file.getOriginalFilename();
// 拼接存储名称
String filename = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
// 把文件的内容存储到本地磁盘
file.transferTo(new File("C:\\Code\\Java\\JavaStudy\\SpringBootStudy\\upload\\" + filename));
return Result.success("文件url地址是...");
}
}重启 SpringBoot工程后,使用 Postman 进行接口测试,能够正常存储文件。

检查本地磁盘中对应位置,能够查看到存储的文件,如下图所示

1.4.2 文件上传(七牛云)
七牛云接口文档:https://developer.qiniu.com/kodo/1239/java
首先在 pom.xml 中引入七牛云的相关坐标,如下所示
<!--七牛云-->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.19.0, 7.19.99]</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.2</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>happy-dns-java</artifactId>
<version>0.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>拷贝七牛云 https://developer.qiniu.com/kodo/1239/java#upload-file 文件上传的代码到本地进行测试,如下所示
package com.euansu;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.UploadManager;
import com.qiniu.common.Zone;
import com.qiniu.storage.Configuration;
import com.qiniu.util.Auth;
import java.io.IOException;
public class UploadDemo {
//设置好账号的ACCESS_KEY和SECRET_KEY
String ACCESS_KEY = "ACCESS_KEY";
String SECRET_KEY = "SECRET_KEYT";
//要上传的空间
String bucketname = "bucketname";
//上传到七牛后保存的文件名
String key = "f03fce72-18d1-4e69-b260-88878122ac7b.png";
//上传文件的路径
String FilePath = "C:\\Code\\Java\\JavaStudy\\SpringBootStudy\\upload\\f03fce72-18d1-4e69-b260-88878122ac7b.png";
//密钥配置
Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
///////////////////////指定上传的Zone的信息//////////////////
//第一种方式: 指定具体的要上传的zone
//注:该具体指定的方式和以下自动识别的方式选择其一即可
//要上传的空间(bucket)的存储区域为华东时
// Zone z = Zone.zone0();
//要上传的空间(bucket)的存储区域为华北时
// Zone z = Zone.zone1();
//要上传的空间(bucket)的存储区域为华南时
// Zone z = Zone.zone2();
//第二种方式: 自动识别要上传的空间(bucket)的存储区域是华东、华北、华南。
Zone z = Zone.autoZone();
Configuration c = new Configuration(z);
//创建上传对象
UploadManager uploadManager = new UploadManager(c);
public static void main(String args[]) throws IOException {
new UploadDemo().upload();
}
//简单上传,使用默认策略,只需要设置上传的空间名就可以了
public String getUpToken() {
return auth.uploadToken(bucketname);
}
public void upload() throws IOException {
try {
//调用put方法上传
Response res = uploadManager.put(FilePath, key, getUpToken());
//打印返回的信息
System.out.println(res.bodyString());
} catch (QiniuException e) {
Response r = e.response;
// 请求失败时打印的异常的信息
System.out.println(r.toString());
try {
//响应的文本信息
System.out.println(r.bodyString());
} catch (QiniuException e1) {
//ignore
}
}
}
}这里进行本地文件的上传,使用工具进行测试如下图所示

在七牛云后台能够查看到上传的图片文件

1.4.3 文件上传(七牛云程序集成)
封装七牛云的工具类为 QiniuUtil,增加文件上传的方法
package com.euansu.utils;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
import com.qiniu.http.Response;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import java.io.IOException;
import java.io.InputStream;
public class QiniuUtil {
//设置好账号的ACCESS_KEY和SECRET_KEY
private static final String ACCESS_KEY = "ACCESS_KEY";
private static final String SECRET_KEY = "SECRET_KEY";
//要上传的空间
private static final String BUCKET_NAME = "BUCKET_NAME";
public static String uploadFiles(InputStream file, String fileName) {
//密钥配置
Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
// 自动识别要上传的区域
Zone z = Zone.autoZone();
Configuration c = new Configuration(z);
// 创建上传对象
UploadManager uploadManager = new UploadManager(c);
// 获取上传的Token
String uploadToken = auth.uploadToken(BUCKET_NAME);
try {
//调用put方法上传
Response res = uploadManager.put(file.readAllBytes(), fileName, uploadToken);
//打印返回的信息
System.out.println(res.bodyString());
// 构造返回的地址
String url = "http://img.euansu.cn/" + fileName;
return url;
} catch (QiniuException e) {
Response r = e.response;
// 请求失败时打印的异常的信息
System.out.println(r.toString());
try {
//响应的文本信息
System.out.println(r.bodyString());
} catch (QiniuException e1) {
//ignore
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
}在 Controller 层调用七牛云的 uploadFiles 方法,如下所示
String url = QiniuUtil.uploadFiles(file.getInputStream(),filename);重启 SpringBoot 项目,使用 Postman 进行接口测试,显示能够正常上传图片文件。

在浏览器中能够使用返回的地址正常访问。
