Crmeb.java项目理解(一)
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型的。

相关新闻

EMUI8 开机执行sh脚本

EMUI8 开机执行sh脚本

1.解包system.img在system/etc/目录创建adb.sh,设置755权限①adb.sh内容:#!/system/bin/sh wm size 1080x1920 && wm density 480②设置755权限:chmod 755 "system/etc/adb.sh"2.解包ramdisk.img在init.rc,添加…

2026/7/3 12:00:08 阅读更多 →
面试---英语口语题目总结

面试---英语口语题目总结

1. 你的名字、专业、学校、家乡。进行一下描述---Please describe your name, major, school, and hometown.My name is [你的名字], a senior from Sichuan University of Science & Engineering majoring in Software Engineering, and my hometown is Yibin, known for …

2026/5/17 12:21:25 阅读更多 →
OpenClaude skills

OpenClaude skills

claude skills Claude 是由 Anthropic 公司开发的大型语言模型(LLM)。 Claude Skills 是 Anthropic 推出的一种模块化能力包,通过封装标准化的流程知识(SOP)实现任务自动化。它不是一次性提示词,而是一个可保存、复用、共享的“技能包”。 Claude Skills 是让 AI 拥有…

2026/7/4 16:03:00 阅读更多 →

最新新闻

手机号找回QQ号码的完整指南:3步解决账号遗忘难题

手机号找回QQ号码的完整指南:3步解决账号遗忘难题

手机号找回QQ号码的完整指南:3步解决账号遗忘难题 【免费下载链接】phone2qq 项目地址: https://gitcode.com/gh_mirrors/ph/phone2qq 你是否曾经因为忘记QQ号码而无法登录微信、QQ邮箱或其他重要应用?或者需要验证某个手机号是否关联了QQ账号&a…

2026/7/4 23:47:25 阅读更多 →
博士生AI工具选择:稳定性与学术工作流才是核心

博士生AI工具选择:稳定性与学术工作流才是核心

1. 博士生AI工具选择的本质:不是选模型,而是选工作流稳定性与学术生产力杠杆理工科博士生在2026年3月这个时间点,面对Claude Pro和GPT Plus的二选一,真正要回答的问题从来不是“哪个模型参数更强”,而是“哪个工具能让…

2026/7/4 23:47:25 阅读更多 →
前端应用的离线暂停更新策略:从原理到实践

前端应用的离线暂停更新策略:从原理到实践

一、 引言:为什么需要离线暂停更新策略?在当今追求极致用户体验的前端开发中,应用的更新与部署方式直接影响用户感知。传统的强制刷新或静默更新策略,在用户进行关键操作时(如填写长表单、观看视频、进行交易&#xff…

2026/7/4 23:45:23 阅读更多 →
Python实现自动驾驶后视镜折叠图像增强技术

Python实现自动驾驶后视镜折叠图像增强技术

1. 后视镜折叠增强功能解析这个Python脚本实现了一个名为"后视镜折叠"的图像增强功能,主要用于自动驾驶或辅助驾驶系统中的视觉数据处理。核心功能是通过在车辆两侧添加粉色色块来模拟后视镜折叠的效果,从而增强模型对后视镜折叠场景的识别能力…

2026/7/4 23:45:23 阅读更多 →
LSTM与GRU门控机制实战选型指南:时序建模的工业权衡

LSTM与GRU门控机制实战选型指南:时序建模的工业权衡

1. 为什么今天还要掰开揉碎讲LSTM和GRU?——一个干了十年时序建模的老兵的真心话你有没有过这种体验:模型跑通了,指标也还行,但一上线就掉链子?训练时验证集AUC 0.92,生产环境里预测结果飘得像没系绳的气球…

2026/7/4 23:45:23 阅读更多 →
基于YOLOv11的果树害虫智能识别系统开发与优化

基于YOLOv11的果树害虫智能识别系统开发与优化

1. 项目概述:基于YOLOv11的果树害虫智能识别系统去年在果园实地调研时,我发现果农们仍在用最原始的方法识别害虫——拿着放大镜一片叶子一片叶子地检查。这种低效的识别方式直接导致虫害防治的滞后性,往往发现时已经造成不可逆的损失。这正是…

2026/7/4 23:43:22 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻