Data // Lombok注解自动生成get/set/toString等方法不用手写 EqualsAndHashCode(callSuper false) // 重写equals/hashCode不继承父类属性 Accessors(chain true) // 链式调用比如 vo.setOrderId(123).setPayPrice(100); ApiModel(valueOrderExcelVo, description 产品导出) // Swagger注解接口文档说明 public class OrderExcelVo implements Serializable { // 序列化跨网络/文件传输必备 ApiModelProperty(value 订单号) // Swagger注解字段说明 private String orderId; ApiModelProperty(value 实际支付金额) private String payPrice; // 其他字段... }EqualsAndHashCode(callSuper false)这个注解就是在实体类上加的他是lombok的注解它的作用就是省略了比较hashcode和equals比较的代码。这个注解是 Lombok 提供的核心作用是自动帮你生成 equals () 和 hashCode () 方法而callSuper false是控制生成逻辑的关键参数默认行为如果不写callSuperLombok 对非继承Object的类会直接报错强制你指定对继承Object的类默认callSuper false。callSuper false生成的 equals/hashCode 只考虑当前类的属性完全忽略父类的属性。比如你有一个子类Student继承Person父类有age属性子类有name属性用这个注解后判断两个Student对象是否相等时只会比较name不会比较父类的age。callSuper true生成的 equals/hashCode 会先调用父类的 equals/hashCode再结合当前类的属性一起判断。核心就是为了让代码更安全、更严谨同时还能保证符合业务逻辑因为// 两个内存地址不同但业务上是同一个用户id1 User user1 new User(1L, 张三); User user2 new User(1L, 张三); // 坑1HashSet 去重失效认为是两个不同对象存了重复数据 SetUser userSet new HashSet(); userSet.add(user1); userSet.add(user2); System.out.println(userSet.size()); // 输出2预期是1BUG // 坑2HashMap 取值失败找不到对应的Value MapUser, String userRoleMap new HashMap(); userRoleMap.put(user1, 管理员); System.out.println(userRoleMap.get(user2)); // 输出null预期是“管理员”BUG // 坑3业务判断出错明明是同一个用户却判定为不同 if (!user1.equals(user2)) { System.out.println(不是同一个用户); // 错误执行这段逻辑BUG }只要需要把实体类对象传递给各类中间件或跨进程 / 跨服务器传输就必须让实体类实现Serializable接口。// 固定写法直接复制用 private static final long serialVersionUID 1L;值设置成1L。PreAuthorizeSpring Security 的权限注解控制接口访问权限比如只有管理员能调Slf4j // Lombok注解日志工具不用手动new Logger RestController // Controller ResponseBody返回JSON而非页面 RequestMapping(api/public/jsconfig) // 接口统一前缀所有该类接口都以这个开头 Api(tags 公共JS配置) // Swagger注解接口分组标签 public class GetJSConfig { Autowired // 依赖注入自动创建SystemConfigService实例不用new private SystemConfigService systemConfigService; // 权限控制只有拥有public:jsconfig:getcrmebchatconfig权限的用户能访问 PreAuthorize(hasAuthority(public:jsconfig:getcrmebchatconfig)) ApiOperation(value CRMEB-chat客服统计) // Swagger注解接口说明 RequestMapping(value /getcrmebchatconfig, method RequestMethod.GET) // 接口路径请求方式 public String set(){ // 调用Service层方法获取配置值 return systemConfigService.getValueByKey(Constants.JS_CONFIG_CRMEB_CHAT_TONGJI); } // 移动端域名获取接口 ApiOperation(value 获取移动端域名) RequestMapping(value /get/front/domain, method RequestMethod.GET) public CommonResultString getFrontDomain() { // CommonResult统一返回格式CRMEB自定义包含code/message/data return CommonResult.success(systemConfigService.getFrontDomain()); } }1. 隔离性核心原理权限数组存在SecurityContextHolderTHREAD_LOCAL 模式每个用户请求对应独立线程数据仅当前线程可见天然隔离优势无锁竞争、性能优、无需额外判断比加锁更适配权限场景。2. 存储规则触发存储用户首次登录 / 退出后重新登录 / 权限修改后重新登录仅此时查数据库加载复用规则同一用户未退出、会话未过期时调接口复用内存数组无需重复查库 / 存储。3. 核心链路数据库权限码path 字段→ 登录加载至用户线程数组 → 注解拿权限码比对数组仅内存操作→ 匹配则放行。代码如下import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; import java.util.stream.Collectors; /** * 核心登录时查数据库权限 → 存入线程隔离的权限数组 */ Service public class PermissionLoadService { // 1. 注入权限Mapper操作数据库 Resource private SystemPermissionsMapper permissionsMapper; // 2. 注入用户Service获取用户所属角色ID Resource private SystemUserService systemUserService; /** * 核心方法加载权限并存入线程隔离数组 * param username 登录用户名 */ public void loadPermissionToIsolatedArray(String username) { // 第一步查数据库 // 1. 根据用户名获取用户信息核心取角色ID SystemUser loginUser systemUserService.getByUsername(username); Integer roleId loginUser.getRoleId(); // 2. 根据角色ID查数据库获取该角色的所有权限记录 ListSystemPermissions permissionsList permissionsMapper.selectByRoleId(roleId); // 3. 提取权限码仅取path字段即注解里的权限码 ListString permissionCodes permissionsList.stream() .map(SystemPermissions::getPath) // 数据库path字段 → 权限码字符串 .filter(code - code ! null !code.isEmpty()) // 过滤空值 .collect(Collectors.toList()); // 第二步存入线程隔离数组 // 1. 转换为Spring Security能识别的权限对象 ListSimpleGrantedAuthority authorities permissionCodes.stream() .map(SimpleGrantedAuthority::new) // 权限码字符串 → 权限对象 .collect(Collectors.toList()); // 2. 封装用户认证信息绑定权限数组 Authentication authentication new UsernamePasswordAuthenticationToken( username, // 当前登录用户名标识用户 null, // 密码无需存入仅权限校验用 authorities // 核心线程隔离的权限数组 ); // 3. 存入SecurityContextHolderTHREAD_LOCAL模式天然线程隔离 // 此操作后该数组仅当前用户的线程可见其他用户线程无法访问 SecurityContextHolder.getContext().setAuthentication(authentication); } } // 配套依赖简化版 /** * 用户实体仅保留核心字段 */ class SystemUser { private Integer roleId; // 用户所属角色ID // get/set省略 public Integer getRoleId() { return roleId; } } /** * 权限实体仅保留核心字段 */ class SystemPermissions { private String path; // 权限码核心字段 // get/set省略 public String getPath() { return path; } } /** * 权限Mapper仅保留核心查询方法 */ interface SystemPermissionsMapper { // 根据角色ID查询权限列表 ListSystemPermissions selectByRoleId(Integer roleId); } /** * 用户Service仅保留核心方法 */ interface SystemUserService { // 根据用户名查询用户 SystemUser getByUsername(String username); }就是一开始登录的时候执行这个然后存入数组中每个用户的数组是隔离的然后跟·那个注解里面的值进行校验就是数组里面有没有那个注解括号里面的那个值如果有则放行如果没有则不放行此时就是校验 admin:combination:list这个值看数组里面有没有他们在数据库中就是这样的存的查出来放入数组中然后这个注解就是进行判断校验数组里面有没有他这个括号里面的值如果有的话则通过。Mypuls还可以加后缀比如说你需要加悲观锁但是还想要用mypuls所以就得用后缀具体代码如下MP 没有直接封装加锁的方法但可以通过last()方法拼接 SQL 后缀实现加锁import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.springframework.stereotype.Service; import com.example.demo.entity.User; import com.example.demo.mapper.UserMapper; import javax.annotation.Resource; Service public class UserService { Resource private UserMapper userMapper; // 用MP的LambdaQueryWrapper last()拼接FOR UPDATE public User getUserWithLock(Long id) { LambdaQueryWrapperUser wrapper new LambdaQueryWrapperUser() .eq(User::getId, id) // 条件id ? .last(FOR UPDATE); // 拼接加锁语法 return userMapper.selectOne(wrapper); } }注意last()会直接拼接 SQL要避免 SQL 注入务必用 MP 的参数绑定比如eq(User::getId, id)不要直接拼接字符串参数。异常完善的四个类这样前端能更好的识别package com.yupi.yuaicodemother.exception; import lombok.Getter; Getter public class BusinessException extends RuntimeException{ /** * 错误码 */ private final int code; public BusinessException(int code, String message) { super(message); this.code code; } public BusinessException(ErrorCode errorCode) { super(errorCode.getMessage()); this.code errorCode.getCode(); } public BusinessException(ErrorCode errorCode, String message) { super(message); this.code errorCode.getCode(); } }package com.yupi.yuaicodemother.exception; import lombok.Getter; Getter public enum ErrorCode { SUCCESS(0, ok), PARAMS_ERROR(40000, 请求参数错误), NOT_LOGIN_ERROR(40100, 未登录), NO_AUTH_ERROR(40101, 无权限), TOO_MANY_REQUEST(42900, 请求过于频繁), NOT_FOUND_ERROR(40400, 请求数据不存在), FORBIDDEN_ERROR(40300, 禁止访问), SYSTEM_ERROR(50000, 系统内部异常), OPERATION_ERROR(50001, 操作失败); /** * 状态码 */ private final int code; /** * 信息 */ private final String message; ErrorCode(int code, String message) { this.code code; this.message message; } }package com.yupi.yuaicodemother.exception; import cn.hutool.json.JSONUtil; import com.yupi.yuaicodemother.common.BaseResponse; import com.yupi.yuaicodemother.common.ResultUtils; import com.yupi.yuaicodemother.exception.BusinessException; import com.yupi.yuaicodemother.exception.ErrorCode; import io.swagger.v3.oas.annotations.Hidden; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.io.IOException; import java.util.Map; Hidden RestControllerAdvice Slf4j public class GlobalExceptionHandler { ExceptionHandler(BusinessException.class) public BaseResponse? businessExceptionHandler(BusinessException e) { log.error(BusinessException, e); // 尝试处理 SSE 请求 if (handleSseError(e.getCode(), e.getMessage())) { return null; } // 对于普通请求返回标准 JSON 响应 return ResultUtils.error(e.getCode(), e.getMessage()); } ExceptionHandler(RuntimeException.class) public BaseResponse? runtimeExceptionHandler(RuntimeException e) { log.error(RuntimeException, e); // 尝试处理 SSE 请求 if (handleSseError(ErrorCode.SYSTEM_ERROR.getCode(), 系统错误)) { return null; } return ResultUtils.error(ErrorCode.SYSTEM_ERROR, 系统错误); } /** * 处理SSE请求的错误响应 * * param errorCode 错误码 * param errorMessage 错误信息 * return true表示是SSE请求并已处理false表示不是SSE请求 */ private boolean handleSseError(int errorCode, String errorMessage) { ServletRequestAttributes attributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes null) { return false; } HttpServletRequest request attributes.getRequest(); HttpServletResponse response attributes.getResponse(); // 判断是否是SSE请求通过Accept头或URL路径 String accept request.getHeader(Accept); String uri request.getRequestURI(); if ((accept ! null accept.contains(text/event-stream)) || uri.contains(/chat/gen/code)) { try { // 设置SSE响应头 response.setContentType(text/event-stream); response.setCharacterEncoding(UTF-8); response.setHeader(Cache-Control, no-cache); response.setHeader(Connection, keep-alive); // 构造错误消息的SSE格式 MapString, Object errorData Map.of( error, true, code, errorCode, message, errorMessage ); String errorJson JSONUtil.toJsonStr(errorData); // 发送业务错误事件避免与标准error事件冲突 String sseData event: business-error\ndata: errorJson \n\n; response.getWriter().write(sseData); response.getWriter().flush(); // 发送结束事件 response.getWriter().write(event: done\ndata: {}\n\n); response.getWriter().flush(); // 表示已处理SSE请求 return true; } catch (IOException ioException) { log.error(Failed to write SSE error response, ioException); // 即使写入失败也表示这是SSE请求 return true; } } return false; } }package com.yupi.yuaicodemother.exception; public class ThrowUtils { /** * 条件成立则抛出异常 * * param condition * param runtimeException */ public static void throwIf(boolean condition, RuntimeException runtimeException) { if (condition) { throw runtimeException; } } /** * 条件成立则抛异常 * * param condition 条件 * param errorCode 错误码 */ public static void throwIf(boolean condition, ErrorCode errorCode) { throwIf(condition, new BusinessException(errorCode)); } /** * 条件成立则抛异常 * * param condition 条件 * param errorCode 错误码 * param message 错误信息 */ public static void throwIf(boolean condition, ErrorCode errorCode, String message) { throwIf(condition, new BusinessException(errorCode, message)); } }给字段加上雪花算法防止被破解Id(keyType KeyType.Generator, value KeyGenerators.snowFlakeId) private Long id;就这个value就是前面那个keytype就是指定是主键。这是mybits Flex的专属用法生成的是Long型的。