大事件板块二
一、文章分类模块 Category
1. 业务五大功能
- 新增文章分类
- 查询当前用户自己创建的分类列表
- 根据 ID 查询分类详情
- 修改分类信息
- 删除分类
2. 实体类标准代码
java
运行
import lombok.Data; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; 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; // 定义分组接口 public interface Add extends Default {} public interface Update extends Default {} }3. 实体字段分组校验配置
java
运行
// 只有修改需要ID非空,新增不需要 @NotNull(message = "分类ID不能为空", groups = Category.Update.class) private Integer id; // 新增、修改都要校验,不单独写分组,默认Default @NotEmpty(message = "分类名称不能为空") @Pattern(regexp = "^\\S{1,10}$",message = "名称1-10位,不能有空格") private String categoryName; @NotEmpty(message = "分类别名不能为空") @Pattern(regexp = "^\\S{1,10}$",message = "别名1-10位,不能有空格") private String categoryAlias;4. 分组继承核心作用
Add、Update全部继承Default
- 所有没写 groups的注解,默认属于
Default - 使用
@Validated(Add.class)既校验 Add 专属规则,又自动校验所有 Default 公共规则 - 使用
@Validated(Update.class)既校验 Update 专属 id 非空,又自动校验名称、别名公共规则
5. Controller 控制层
java
运行
@RestController @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; // 新增 - 指定新增分组 @PostMapping public Result add(@RequestBody @Validated(Category.Add.class) Category category){ categoryService.add(category); return Result.success(); } // 查询列表 @GetMapping public Result<List<Category>> list(){ return Result.success(categoryService.list()); } // 详情查询 @GetMapping("/detail") public Result<Category> detail(Integer id){ return Result.success(categoryService.findById(id)); } // 修改 - 指定修改分组 @PutMapping public Result update(@RequestBody @Validated(Category.Update.class) Category category){ categoryService.update(category); return Result.success(); } // 删除 @DeleteMapping public Result delete(Integer id){ categoryService.delete(id); return Result.success(); } }6. Service 业务层
java
运行
@Service public class CategoryServiceImpl implements CategoryService { @Autowired private CategoryMapper categoryMapper; @Override public void add(Category category) { // 获取当前登录用户ID category.setCreateUser(ThreadLocalUtil.getUserId()); category.setCreateTime(LocalDateTime.now()); category.setUpdateTime(LocalDateTime.now()); categoryMapper.add(category); } @Override public List<Category> list() { Integer userId = ThreadLocalUtil.getUserId(); return categoryMapper.list(userId); } @Override public Category findById(Integer id) { return categoryMapper.findById(id); } @Override public void update(Category category) { category.setUpdateTime(LocalDateTime.now()); categoryMapper.update(category); } @Override public void delete(Integer id) { categoryMapper.delete(id); } }7. Mapper 与 XML
java
运行
public interface CategoryMapper { void add(Category category); List<Category> list(Integer userId); Category findById(Integer id); void update(Category category); void delete(Integer id); }xml
<!--新增--> <insert id="add"> insert into category(category_name,category_alias,create_user,create_time,update_time) values(#{categoryName},#{categoryAlias},#{createUser},#{createTime},#{updateTime}) </insert> <!--查询个人列表--> <select id="list" resultType="Category"> select * from category where create_user=#{userId} </select> <!--根据ID查询--> <select id="findById" resultType="Category"> select * from category where id=#{id} </select> <!--修改--> <update id="update"> update category set category_name=#{categoryName},category_alias=#{categoryAlias},update_time=#{updateTime} where id=#{id} </update> <!--删除--> <delete id="delete"> delete from category where id=#{id} </delete>8. ThreadLocal 作用
- 存储当前登录用户 ID
- 同一线程共享数据,不用前端反复传用户 ID
- 新增、查询个人数据自动绑定用户
- 请求结束清空,防止内存泄漏
二、分组校验全套核心规则(所有答疑全部整理)
1. 基础规则
- 校验注解不加 groups→ 默认归属Default 默认分组
- 校验注解加 groups=xxx.class→ 只属于指定分组,脱离 Default
2. @Validated 三种写法区别
- 只写 @Validated 不带任何分组只校验 Default 分组,所有自定义分组字段全部不校验
- @Validated(Add.class)Add 继承 Default → 新增专属校验 + 所有公共 Default 校验全部生效
- @Validated(Update.class)Update 继承 Default → 修改专属 id 校验 + 公共校验全部生效
3. 你最疑惑的核心矛盾点(重点背诵)
场景需求
某一个字段:绝大多数场景必须校验,只有唯一一种场景不需要校验 **
矛盾根源
- 如果你给这个字段单独绑定一个专属分组这个字段只属于这个组,其他所有 Add、Update、Default 全部校验不到它直接导致正常业务全部失效,完全不能用
- 如果你不给字段分组,默认归 Default所有继承 Default 的接口都会强制校验它,普通固定分组没办法单独跳过这一个字段
结论
- 普通固定分组做不到:只跳过单个字段,其他字段正常校验
- 普通分组只能实现两种效果
- 全接口全部校验
- 全接口全部不校验
- 想要单字段局部免校验方案 1:业务层手写 if 判断手动控制(最简单)方案 2:使用动态分组
DefaultGroupSequenceProvider(注解实现)
反向需求
一个字段只在一种情况校验,其余全部不校验只能直接给字段绑定唯一指定分组弊端:除了这个场景,其余所有接口都无法校验该字段,业务局限性极大,尽量少用
4. 分组开发规范
- 新增接口统一用
@Validated(Add.class) - 修改接口统一用
@Validated(Update.class) - 公共通用校验全部不写 groups,依靠 Default 统一管理
- 只有新增 / 修改差异化字段,才单独指定 groups
三、自定义校验注解 @State(文章状态校验)
需求
文章状态只能为:已发布 / 草稿
1. 自定义注解
java
运行
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = StateValidator.class) public @interface State { String message() default "状态只能为已发布或草稿"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }2. 校验规则类
java
运行
public class StateValidator implements ConstraintValidator<State,String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if(value == null) return false; return value.equals("已发布") || value.equals("草稿"); } }3. 实体使用方式(最优写法)
java
运行
// 不写groups,默认Default // 因为Add、Update都继承Default,新增修改自动全部校验状态 @State private String state;4. 重点
不需要手动给@State加分组,依靠 Default 自动全局生效,代码最简洁
四、文件上传两大方式完整版
方式一:本地上传
1. 上传接口
java
运行
@PostMapping("/upload") public Result<String> upload(MultipartFile file) throws Exception { // 获取原始文件名 String originalName = file.getOriginalFilename(); // 截取后缀 String suffix = originalName.substring(originalName.lastIndexOf(".")); // UUID生成唯一文件名,防止重名覆盖 String newName = UUID.randomUUID() + suffix; // 保存到本地路径 file.transferTo(new File("D:/upload/" + newName)); return Result.success(newName); }2. 配置文件限制大小
yaml
spring: servlet: multipart: max-file-size: 100MB max-request-size: 100MB3. 缺点
路径固定、无法外网访问、服务器占用大、分布式项目不适用
方式二:阿里云 OSS 云端上传(课堂最终版)
1. 全套依赖
xml
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.1</version> </dependency>2. 标准工具类(传文件名 + 输入流,正确 URL 拼接)
java
运行
public class AliOssUtil { private static final String ENDPOINT = "https://oss-cn-beijing.aliyuncs.com"; private static final String ACCESS_KEY_ID = "填写自己"; private static final String ACCESS_KEY_SECRET = "填写自己"; private static final String BUCKET_NAME = "填写自己"; public static String upload(String originalFileName, InputStream inputStream){ // 截取后缀 String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")); // 唯一文件名 String fileName = UUID.randomUUID() + suffix; // 创建客户端 OSS ossClient = new OSSClientBuilder().build(ENDPOINT,ACCESS_KEY_ID,ACCESS_KEY_SECRET); // 上传 ossClient.putObject(BUCKET_NAME,fileName,inputStream); // 课堂标准正确拼接URL String url = "https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.indexOf("/")+1)+"/"+fileName; ossClient.shutdown(); return url; } }3. Controller 调用
java
运行
@PostMapping("/oss/upload") public Result<String> ossUpload(MultipartFile file) throws Exception{ String url = AliOssUtil.upload(file.getOriginalFilename(),file.getInputStream()); return Result.success(url); }4. OSS 重点
- Bucket 名称全网唯一
- AK/SK 严禁泄露
- 返回外网 URL 直接存入数据库
- 工具类不依赖 MultipartFile,通用性极强
