# JeecgBoot 后端框架全局开发规范与最佳实践 > 分析日期:2026-06-03 > 框架版本:JeecgBoot 3.9.2 > 分析范围:JeecgBoot 原框架(排除 `jeecg-module-zjrs` 业务子模块) --- ## 一、框架概述 ### 1.1 项目结构 ``` jeecg-boot/ ├── jeecg-boot-base-core/ # 框架核心基础库 │ └── src/main/java/org/jeecg/ │ ├── common/ # 通用工具类、切面、异常、常量、VO │ ├── config/ # 全局配置(Shiro/MyBatis/WebMVC/Swagger等) │ └── modules/base/ # 基础公共服务(日志、消息) ├── jeecg-boot-module/ │ ├── jeecg-module-system/ # 系统管理模块(用户/角色/部门/字典/消息/定时任务等) │ ├── jeecg-module-demo/ # 示例模块(单表CRUD/主子表/动态数据源/MCP等) │ └── jeecg-module-zjrs/ # 湛江人社业务模块(本次分析排除) └── jeecg-server-cloud/ # 微服务Cloud版本相关 ``` ### 1.2 技术栈 | 层级 | 技术 | |--------|--------------------------------------| | 基础框架 | Spring Boot 2.x + Spring MVC | | ORM | MyBatis-Plus 3.x | | 安全认证 | Apache Shiro + JWT | | 缓存 | Redis(Spring Cache + RedisTemplate) | | 数据库连接池 | Druid | | Excel | AutoPoi(基于 Apache POI 封装) | | 模板引擎 | Freemarker | | 定时任务 | Quartz | | API文档 | Swagger3 / OpenAPI 3.0 | | 对象存储 | MinIO / 阿里云 OSS / 本地存储 | | 消息推送 | WebSocket + 邮件 + 短信 + 微信 + 钉钉 + 企业微信 | --- ## 二、框架分层架构与开发规约 ### 2.1 标准分层 ``` Controller 层 → 接收请求、参数校验、调用Service、返回Result │ 继承 JeecgController ▼ Service 层 → 业务逻辑、事务管理、调用Mapper │ 接口 extends IService,实现 extends ServiceImpl ▼ Mapper 层 → 数据访问、自定义SQL │ 继承 BaseMapper,XML 可选 ▼ Entity 层 → 数据实体、数据库表映射 继承 JeecgEntity(推荐),加 @TableName、@Excel 等 ``` ### 2.2 各层最优写法 #### Entity 层(实体类) ```java @Data @TableName("table_name") // 数据库表名 @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) // 支持链式调用 public class XxxEntity extends JeecgEntity { // 继承基类,自动获得 id/createBy/createTime/updateBy/updateTime @Excel(name = "字段名", width = 15) // 需要导出/导入的字段加 @Excel private String fieldName; @Excel(name = "字典字段", width = 15, dicCode = "dict_code") // 字典翻译 private String dictField; @Excel(name = "日期字段", width = 20, format = "yyyy-MM-dd HH:mm:ss") // 日期格式 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date dateField; // 不需要导出/导入的字段不加 @Excel 注解 private String internalField; } ``` **JeecgEntity 基类提供的能力**: - 自动填充公共字段(`id`、`createBy`、`createTime`、`updateBy`、`updateTime`) - 通过 `MybatisInterceptor` 拦截器自动注入 `createBy`/`createTime`/`sysOrgCode`(插入时)和 `updateBy`/`updateTime`(更新时) #### Mapper 层 ```java // 标准 Mapper(无自定义 SQL 场景) public interface XxxMapper extends BaseMapper { // MyBatis-Plus 内置方法即可满足常规 CRUD } ``` ```java // 带自定义 SQL 的 Mapper(需要配合 XML) public interface XxxMapper extends BaseMapper { @Select("SELECT * FROM table WHERE field = #{value}") // 简单 SQL 可用注解 List selectByField(String value); List selectByCondition(@Param("param") XxxEntity param); // 复杂 SQL 用 XML } ``` #### Service 层 ```java // 接口 public interface IXxxService extends IService { // 自定义业务方法 void customBusinessMethod(XxxEntity entity); } // 实现 @Service public class XxxServiceImpl extends ServiceImpl implements IXxxService { @Autowired private XxxMapper xxxMapper; @Override @Transactional(rollbackFor = Exception.class) // 涉及多表操作必须加事务 public void customBusinessMethod(XxxEntity entity) { // 业务逻辑 } } ``` #### Controller 层 ```java @Slf4j @RestController @RequestMapping("/模块/实体") public class XxxController extends JeecgController { // ========== 分页列表查询 ========== @GetMapping(value = "/list") @PermissionData(pageComponent = "system/模块/index") // 数据权限 public Result list(XxxEntity entity, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, HttpServletRequest req) { QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(entity, req.getParameterMap()); Page page = new Page<>(pageNo, pageSize); IPage pageList = service.page(page, queryWrapper); return Result.OK(pageList); } // ========== 新增 ========== @PostMapping(value = "/add") @AutoLog(value = "新增XXX") public Result add(@RequestBody XxxEntity entity) { service.save(entity); return Result.OK("添加成功!"); } // ========== 修改 ========== @PutMapping(value = "/edit") @AutoLog(value = "编辑XXX", operateType = CommonConstant.OPERATE_TYPE_3) public Result edit(@RequestBody XxxEntity entity) { service.updateById(entity); return Result.OK("更新成功!"); } // ========== 删除 ========== @DeleteMapping(value = "/delete") @AutoLog(value = "删除XXX") public Result delete(@RequestParam(name="id", required=true) String id) { service.removeById(id); return Result.OK("删除成功!"); } // ========== 批量删除 ========== @DeleteMapping(value = "/deleteBatch") public Result deleteBatch(@RequestParam(name="ids", required=true) String ids) { service.removeByIds(Arrays.asList(ids.split(","))); return Result.OK("批量删除成功!"); } // ========== 通过ID查询 ========== @GetMapping(value = "/queryById") public Result queryById(@RequestParam(name="id", required=true) String id) { XxxEntity obj = service.getById(id); return Result.OK(obj); } // ========== Excel导出 ========== @RequestMapping(value = "/exportXls") public ModelAndView exportXls(HttpServletRequest request, XxxEntity entity) { return super.exportXls(request, entity, XxxEntity.class, "文件名"); } // ========== Excel导入 ========== @RequestMapping(value = "/importExcel", method = RequestMethod.POST) public Result importExcel(HttpServletRequest request, HttpServletResponse response) { return super.importExcel(request, response, XxxEntity.class); } } ``` --- ## 三、QueryGenerator —— 查询条件自动生成引擎 ### 3.1 核心用法 ```java QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(entity, request.getParameterMap()); ``` 这是整个 CRUD 体系的核心引擎,实现了**前端零配置即可完成复杂查询条件自动构建**。 ### 3.2 自动推断查询规则 `QueryGenerator` 通过分析请求参数的值特征,自动推断查询规则: | 值特征 | 推断规则 | 示例 | |---------|----------|------------------------| | `>= 值` | 大于等于(GE) | `>= 100` | | `<= 值` | 小于等于(LE) | `<= 100` | | `> 值` | 大于(GT) | `> 100` | | `< 值` | 小于(LT) | `< 100` | | `*值*` | 全模糊 LIKE | `*张*` → `%张%` | | `*值` | 左模糊 LIKE | `*张` → `%张` | | `值*` | 右模糊 LIKE | `张*` → `张%` | | `值1,值2` | IN 查询 | `1,2,3` → `IN (1,2,3)` | | `!值` | 不等于(NE) | `!admin` | | 普通值 | 等于(EQ) | `admin` | ### 3.3 区间查询 通过 `字段_begin` 和 `字段_end` 参数实现: ``` ?createTime_begin=2026-01-01&createTime_end=2026-12-31 ``` ### 3.4 高级查询 通过 `superQueryParams` 参数传入 JSON 格式的复杂查询条件,支持 AND/OR 组合。 ### 3.5 排序支持 优先级从高到低: 1. `sortInfoString` — 多字段排序(最高优先级) 2. `defSortString` — 默认排序 3. `column` + `order` — 单字段排序 ### 3.6 数据权限注入 `QueryGenerator` 在构建查询条件时,会自动从 `Request` 的 Attribute 中读取由 `PermissionDataAspect` 注入的数据权限规则,拼接到 SQL 条件中,实现行级数据过滤。 --- ## 四、Excel 导入导出体系 ### 4.1 @Excel 注解速查 | 属性 | 说明 | 示例 | |-------------|------------|------------------------------------------| | `name` | 列标题(必填) | `@Excel(name = "姓名")` | | `width` | 列宽 | `@Excel(width = 15)` | | `format` | 日期格式 | `@Excel(format = "yyyy-MM-dd HH:mm:ss")` | | `dicCode` | 字典编码(自动翻译) | `@Excel(dicCode = "sex")` | | `dictTable` | 字典表名(表字典) | `@Excel(dictTable = "sys_depart")` | | `dicText` | 字典文本字段 | `@Excel(dicText = "depart_name")` | | `dicCode` | 字典编码字段 | `@Excel(dicCode = "id")` | | `type` | 字段类型 | 1=文本(默认), 2=图片, 4=数值 | ### 4.2 导出(最优写法) **单表导出**(直接调用父类方法): ```java @RequestMapping(value = "/exportXls") public ModelAndView exportXls(HttpServletRequest request, XxxEntity entity) { return super.exportXls(request, entity, XxxEntity.class, "文件名"); } ``` **父类 `JeecgController.exportXls` 执行流程**: ``` Step 1: QueryGenerator.initQueryWrapper() 组装查询条件 Step 2: selections 参数过滤选中数据 Step 3: service.list(queryWrapper) 获取导出数据 Step 4: 构建 ModelAndView(JeecgEntityExcelView) ├── FILE_NAME → 文件名 ├── CLASS → 实体类(含 @Excel 注解定义列结构) ├── PARAMS → ExportParams(标题、Sheet名、格式) ├── DATA_LIST → 导出数据列表 └── EXPORT_FIELDS → 可选,指定导出列 ``` **导出变体**: - 分 Sheet 导出:`super.exportXlsSheet(request, entity, Class, "文件名", exportFields, sheetSize)` - 大数据量导出:`super.exportXlsForBigData(request, entity, Class, "文件名", pageSize)` **主子表导出**(手动实现): ```java @RequestMapping(value = "/exportXls") public ModelAndView exportXls(HttpServletRequest request, XxxEntity entity) { QueryWrapper qw = QueryGenerator.initQueryWrapper(entity, request.getParameterMap()); List pageList = new ArrayList<>(); List mainList = service.list(qw); for (XxxEntity main : mainList) { XxxVo vo = new XxxVo(); BeanUtils.copyProperties(main, vo); vo.setSubList(subService.selectByMainId(main.getId())); // 查询子表 pageList.add(vo); } ModelAndView mv = new ModelAndView(new JeecgEntityExcelView()); mv.addObject(NormalExcelConstants.FILE_NAME, "文件名"); mv.addObject(NormalExcelConstants.CLASS, XxxVo.class); // 使用 VO(含 @ExcelCollection) mv.addObject(NormalExcelConstants.PARAMS, new ExportParams("标题", "导出人:xxx", "Sheet名")); mv.addObject(NormalExcelConstants.DATA_LIST, pageList); return mv; } ``` ### 4.3 导入(最优写法) **单表导入**(直接调用父类方法): ```java @RequestMapping(value = "/importExcel", method = RequestMethod.POST) public Result importExcel(HttpServletRequest request, HttpServletResponse response) { return super.importExcel(request, response, XxxEntity.class); } ``` **带校验和错误汇总的导入**: ```java @RequestMapping(value = "/importExcel", method = RequestMethod.POST) public Result importExcel(HttpServletRequest request, HttpServletResponse response) { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; Map fileMap = multipartRequest.getFileMap(); for (Map.Entry entity : fileMap.entrySet()) { MultipartFile file = entity.getValue(); ImportParams params = new ImportParams(); params.setTitleRows(2); params.setHeadRows(1); params.setNeedSave(true); try { List list = ExcelImportUtil.importExcel(file.getInputStream(), XxxEntity.class, params); List errorMessage = new ArrayList<>(); int successLines = 0, errorLines = 0; for (int i = 0; i < list.size(); i++) { try { service.save(list.get(i)); successLines++; } catch (Exception e) { errorLines++; errorMessage.add("第 " + (i + 1) + " 行:" + e.getMessage()); } } return ImportExcelUtil.imporReturnRes(errorLines, successLines, errorMessage); } catch (Exception e) { return Result.error("文件导入失败:" + e.getMessage()); } } return Result.error("文件导入失败!"); } ``` ### 4.4 下载导入模板 = 复用导出接口 JeecgBoot **没有独立的"下载导入模板"接口**。当前端请求 `exportXls` 接口但不带查询条件时,查询结果为**空列表**,AutoPoi 仍生成包含标题行和表头行的 Excel 文件——这就是"导入模板"。 ``` ┌──────────────────────────────────────────────────┐ │ 第1行: 大标题(ExportParams.title) │ ← titleRows=2 ├──────────────────────────────────────────────────┤ │ 第2行: 副标题(ExportParams.secondTitle) │ ├──────┬──────┬──────┬──────┬──────────────────────┤ │ 列1 │ 列2 │ 列3 │ 列4 │ ... │ ← headRows=1 ├──────┼──────┼──────┼──────┼──────────────────────┤ │ (空) │ (空) │ (空) │ (空) │ ... │ ← 数据行为空 └──────┴──────┴──────┴──────┴──────────────────────┘ ``` --- ## 五、主子表(一对多)CRUD 模式 ### 5.1 架构 - **主表实体** 和 **子表实体** 独立定义,子表通过 **外键字段** 关联主表 - **VO/Page类** 将主表字段与子表列表组合,用于前后端数据传输(含 `@ExcelCollection` 注解) - **Service层** 负责事务管理,统一处理主表和子表的增删改 - **Mapper层** 提供通过外键查询/删除子表数据的专用方法 ### 5.2 子表实体(外键是关键) ```java @Data @TableName("sub_table") public class SubEntity implements Serializable { @TableId(type = IdType.ASSIGN_ID) private String id; private String name; /** 外键 — 关联主表ID */ private String mainId; // 命名规范: {主表实体名小写}Id } ``` ### 5.3 VO 类(聚合主子表 + @ExcelCollection) ```java @Data public class MainEntityPage { private String id; @Excel(name = "主表字段", width = 15) private String field; @ExcelCollection(name = "子表") // AutoPoi 注解,Excel 导入导出时识别 private List subEntityList; } ``` ### 5.4 子表 Mapper(增加外键查询/删除方法) ```java public interface SubEntityMapper extends BaseMapper { /** 通过主表外键批量删除 */ @Delete("DELETE FROM sub_table WHERE main_id = #{mainId}") boolean deleteByMainId(String mainId); /** 通过主表外键查询 */ @Select("SELECT * FROM sub_table WHERE main_id = #{mainId}") List selectByMainId(String mainId); } ``` ### 5.5 Service 层(事务管理) ```java // 新增:先插主表,再循环插子表,设置外键 @Override @Transactional(rollbackFor = Exception.class) public void saveMain(MainEntity main, List subList) { mainMapper.insert(main); if (subList != null) { for (SubEntity sub : subList) { sub.setMainId(main.getId()); // 设置外键 subMapper.insert(sub); } } } // 修改策略一(先删后插) @Override @Transactional(rollbackFor = Exception.class) public void updateMain(MainEntity main, List subList) { mainMapper.updateById(main); subMapper.deleteByMainId(main.getId()); // 删除旧子表数据 if (subList != null) { for (SubEntity sub : subList) { sub.setMainId(main.getId()); subMapper.insert(sub); // 重新插入 } } } // 修改策略二(差异对比——推荐):前端传完整子表列表,后端对比新增/更新/删除 @Override @Transactional(rollbackFor = Exception.class) public void updateCopyMain(MainEntity main, List subList) { mainMapper.updateById(main); for (SubEntity sub : subList) { SubEntity dbItem = subMapper.selectById(sub.getId()); if (dbItem == null) { sub.setMainId(main.getId()); subMapper.insert(sub); // 数据库不存在 → 新增 } else { subMapper.updateById(sub); // 数据库存在 → 更新 } } // 取差集:数据库有但前端没传的 → 删除 List dbList = subMapper.selectByMainId(main.getId()); List submittedIds = subList.stream().map(SubEntity::getId).collect(Collectors.toList()); dbList.stream() .filter(item -> !submittedIds.contains(item.getId())) .forEach(item -> subMapper.deleteById(item.getId())); } // 删除:先删子表,再删主表 @Override @Transactional(rollbackFor = Exception.class) public void delMain(String id) { subMapper.deleteByMainId(id); mainMapper.deleteById(id); } ``` ### 5.6 Controller 层 ```java // 新增(接收 VO,拆分为主表实体和子表列表) @PostMapping(value = "/add") public Result add(@RequestBody MainEntityPage vo) { MainEntity main = new MainEntity(); BeanUtils.copyProperties(vo, main); service.saveMain(main, vo.getSubEntityList()); return Result.ok("添加成功!"); } // 查询子表(每个子表一个独立接口) @GetMapping(value = "/querySubListByMainId") public Result querySubListByMainId(@RequestParam(name="id", required=true) String id) { List list = subService.selectByMainId(id); return Result.ok(list); } ``` ### 5.7 主子表 vs 单表差异对比 | 对比维度 | 单表 CRUD | 主子表 CRUD | |------------------|------------------------------|-----------------------------------------------------| | **Controller入参** | `@RequestBody Entity` | `@RequestBody EntityPage`(VO对象) | | **新增方法** | `service.save(entity)` | `service.saveMain(main, subList)` | | **修改方法** | `service.updateById(entity)` | `service.updateMain()` 或 `service.updateCopyMain()` | | **删除方法** | `service.removeById(id)` | `service.delMain(id)` | | **查询子表** | 无 | 每个子表一个独立查询接口 | | **Service事务** | 框架默认 | 显式 `@Transactional(rollbackFor = Exception.class)` | | **导入 headRows** | 1 | 2(主子表表头占2行) | ### 5.8 代码生成器模板风格 JeecgBoot 提供 6 种主子表生成模板风格(位于 `jeecg-system-biz/src/main/resources/jeecg/code-template-online/`): | 风格 | 路径 | 说明 | |-----------------|--------------------------|-----------| | **default** | `default/onetomany/` | 默认经典风格 | | **tab** | `tab/onetomany/` | Tab页签风格 | | **jvxe** | `jvxe/onetomany/` | JVxe行编辑风格 | | **inner-table** | `inner-table/onetomany/` | 内嵌表格风格 | | **erp** | `erp/onetomany/` | ERP风格 | --- ## 六、全局异常处理体系 ### 6.1 异常类继承体系 ``` RuntimeException ├── JeecgBootException (核心业务异常, errCode=500) │ └── JeecgBootAssertException (断言异常, 由 AssertUtils 使用) ├── JeecgBootBizTipException (业务提醒异常, errCode=500, 不记系统日志) ├── JeecgBoot401Exception (认证/鉴权异常) └── JeecgSqlInjectionException (SQL注入防护异常) ``` ### 6.2 JeecgBootException vs JeecgBootBizTipException | 维度 | JeecgBootException | JeecgBootBizTipException | |------------|--------------------------------|--------------------------| | **语义** | 系统级错误、不可恢复的业务错误 | 业务操作提醒、可预期的用户提示 | | **记录系统日志** | 是(写入 `sys_log` 表) | 否(仅 `log.error` 打印) | | **适用场景** | 数据不存在、管理员保护、验证码失效 | 密码错误、权限不足提醒、操作冲突 | | **典型示例** | `"admin用户,不允许删除!"`、`"token非法"` | `"旧密码输入错误"`、`"您已是该租户成员"` | ### 6.3 异常抛出方式 **方式一:直接抛出 JeecgBootException(最常用,用于系统级错误)** ```java throw new JeecgBootException("操作数据不存在!"); throw new JeecgBootException("admin用户,不允许删除!"); throw new JeecgBootException("短信接口请求太多,请稍后再试!", CommonConstant.PHONE_SMS_FAIL_CODE); throw new JeecgBootException("获取钉钉用户信息失败", cause); ``` **方式二:抛出 JeecgBootBizTipException(业务提醒,不记系统日志)** ```java throw new JeecgBootBizTipException("旧密码输入错误!"); throw new JeecgBootBizTipException("您已是该租户成员"); ``` **方式三:使用 AssertUtils 断言(声明式校验)** ```java AssertUtils.assertNotEmpty("姓名不能为空", name); AssertUtils.assertNotEmpty("用户ID不能为空", userId); AssertUtils.assertTrue("操作数据不存在", data != null); AssertUtils.assertEmpty("该记录已存在", existingRecord); AssertUtils.assertEquals("验证码不匹配", expectedCode, actualCode); AssertUtils.assertGt("数量必须大于0", quantity, 0); AssertUtils.assertIn("非法状态", status, "ACTIVE", "PENDING"); ``` **方式四:直接返回 Result.error()(仅 Controller 层使用)** ```java if (obj == null) { return Result.error("未找到对应数据"); } ``` ### 6.4 全局异常处理器 (JeecgBootExceptionHandler) 使用 `@RestControllerAdvice` 统一拦截所有 Controller 层异常,转换为 `Result` 返回: | 异常类型 | 返回code | 记录系统日志 | |-----------------------------------|-------------------|--------| | `JeecgBootException` | 自定义errCode(默认500) | 是 | | `JeecgBootBizTipException` | 自定义errCode(默认500) | 否 | | `JeecgBoot401Exception` | 401 | 是 | | `JeecgSqlInjectionException` | 500 | 视情况 | | `MethodArgumentNotValidException` | 500 | 是 | | `DuplicateKeyException` | 500 | 是 | | `DataIntegrityViolationException` | 500 | 是 | | `UnauthorizedException` | 510 | 否 | | `MaxUploadSizeExceededException` | 500 | 是 | | `Exception`(兜底) | 500 | 是 | ### 6.5 异常选择原则 - 如果异常**需要被运维关注排查**(数据不一致、关键操作失败)→ `JeecgBootException` - 如果异常**只是给用户的友好提示**(输入校验、操作冲突)→ `JeecgBootBizTipException` - **字段非空/格式校验** → `AssertUtils` 或 `JeecgBootBizTipException` - **不允许直接抛出 `RuntimeException`**,必须使用框架异常类 --- ## 七、数据校验体系 ### 7.1 推荐方案:分层组合校验 最优实践是**三种方式混合使用**: - **字段级校验**(非空、长度、格式)→ JSR303 注解 + `@Validated` - **业务规则校验**(唯一性、关联性、状态约束)→ `AssertUtils` 或直接抛异常 - **安全校验** → 框架内置的 `SqlInjectionUtil` + `SsrfFileTypeFilter` ### 7.2 JSR303 声明式校验 ```java // 实体类 @Data @TableName("xxx") public class XxxEntity extends JeecgEntity { @NotBlank(message = "姓名不能为空") @Size(max = 50, message = "姓名长度不能超过50") @Excel(name = "姓名", width = 15) private String name; @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") @Excel(name = "手机号", width = 15) private String phone; } // Controller @PostMapping(value = "/add") public Result add(@Validated @RequestBody XxxEntity entity) { service.save(entity); return Result.OK("添加成功!"); } ``` ### 7.3 AssertUtils 业务规则校验 ```java @Override public void saveEntity(XxxEntity entity) { AssertUtils.assertNotEmpty("姓名不能为空", entity.getName()); // 业务规则校验 long count = this.count(new LambdaQueryWrapper() .eq(XxxEntity::getCode, entity.getCode())); AssertUtils.assertTrue("编码已存在", count == 0); this.save(entity); } ``` ### 7.4 SQL注入防护 框架内置了三层防护: | 工具类 | 功能 | |---------------------------------|---------------------------------------| | `SqlInjectionUtil` | SQL关键词/正则检测、表名/字段名/排序字段合法性校验 | | `AbstractQueryBlackListHandler` | 查询表/字段黑名单(如 `sys_user.password` 禁止查询) | | `SsrfFileTypeFilter` | 上传文件类型白名单 + 文件头黑名单检测 | --- ## 八、全局工具类体系 所有工具类位于 `jeecg-boot-base-core/src/main/java/org/jeecg/common/util/`。 ### 8.1 常用工具类速查 | 工具类 | 功能 | 常用方法 | |----------------------|-----------|---------------------------------------------------------------------------------------------| | `oConvertUtils` | 通用转换工具 | `getString()`、`getInt()`、`isEmpty()`、`randomGen()`、`copyNonNullFields()`、`listIsNotEmpty()` | | `SpringContextUtils` | Spring上下文 | `getApplicationContext()`、`getBean()`、`getHttpServletRequest()` | | `TokenUtils` | Token工具 | `getTokenByRequest()`、`verifyToken()` | | `PasswordUtil` | 密码加密 | `encrypt(username, password, salt)` | | `SqlInjectionUtil` | SQL注入防护 | `filterContent()`、`checkSqlInject()` | | `QueryGenerator` | 查询条件生成 | `initQueryWrapper(entity, paramMap)` | | `ImportExcelUtil` | 导入结果封装 | `imporReturnRes(errorLines, successLines, errorMessages)` | | `IPUtils` | IP获取 | `getIpAddr()` | | `DateUtils` | 日期处理 | `parseDate()`、`formatDate()` | | `FillRuleUtil` | 编码规则生成 | 自动生成订单号、部门编码等 | | `YouBianCodeUtil` | 流水号生成 | `getNextYouBianCode()` | | `UUIDGenerator` | UUID生成 | `generate()` | | `RestUtil` | HTTP调用 | `get()`、`post()`、`put()`、`delete()` | | `CommonUtils` | 文件上传路由 | `upload(file, bizPath, uploadType)` | ### 8.2 加密/安全工具 | 工具类 | 位置 | 功能 | |----------------------|--------------------|--------------------------| | `AesEncryptUtil` | `util/encryption/` | AES加解密(CBC+PKCS5Padding) | | `SecurityTools` | `util/security/` | RSA/AES加解密、签名验证 | | `Md5Util` | `util/` | MD5加密 | | `SsrfFileTypeFilter` | `util/filter/` | 文件上传安全校验 | ### 8.3 存储工具 | 工具类 | 功能 | |---------------|-----------------------------------| | `MinioUtil` | MinIO 对象存储(上传/下载/删除/预签名URL) | | `OssBootUtil` | 阿里云 OSS(上传/下载/删除/预签名URL) | | `CommonUtils` | 上传路由(根据配置自动选择 local/minio/alioss) | ### 8.4 动态数据源工具 | 工具类 | 功能 | |--------------------------|---------------------------------------| | `DynamicDBUtil` | 动态数据源下通过 Spring JDBC Template 直接操作数据库 | | `DataSourceCachePool` | 动态数据源缓存池 | | `FreemarkerParseFactory` | Freemarker模板解析工厂(用于代码生成和SQL模板渲染) | --- ## 九、AOP 切面体系 所有切面位于 `jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/`。 ### 9.1 四大核心切面 | 切面类 | 拦截注解 | 功能 | |------------------------|-----------------------|------------------------------------------------------------------| | `AutoLogAspect` | `@AutoLog` | 自动记录操作日志到 `sys_log` 表(含方法名、参数、耗时、IP) | | `DictAspect` | `@Dict` / `@AutoDict` | 在 Controller 返回前自动将字典code翻译为文本(如 `sex: 1` → `sex_dictText: "男"`) | | `PermissionDataAspect` | `@PermissionData` | 查询数据权限规则并注入 Request Attribute,供 MyBatis 拦截器组装 SQL 条件 | | `DynamicTableAspect` | `@DynamicTable` | 运行时动态切换表名 | ### 9.2 @Dict 字典翻译注解 **最佳实践**:在实体类字段上标注 `@Dict`,Controller 方法上标注 `@AutoDict`,框架会自动在下发 JSON 时追加 `_dictText` 后缀的翻译字段。 ```java // 实体类 @Dict(dicCode = "sex") @Excel(name = "性别", width = 15, dicCode = "sex") private Integer sex; @Dict(dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") @Excel(name = "部门", dictTable = "sys_depart", dicText = "depart_name", dicCode = "id") private String departId; // Controller @AutoDict // 方法级注解,启用自动字典翻译 @GetMapping(value = "/list") public Result list(...) { ... } ``` 返回的 JSON 中会自动追加: ```json { "sex": 1, "sex_dictText": "男", "departId": "xxx", "departId_dictText": "技术部" } ``` ### 9.3 @PermissionData 数据权限 ```java @PermissionData(pageComponent = "system/user/index") @GetMapping(value = "/list") public Result list(...) { ... } ``` **执行流程**: ``` 1. @PermissionData 注解 → PermissionDataAspect 切面拦截 2. 查询当前用户在此菜单组件的 SysPermissionDataRule 3. 将规则写入 Request Attribute 4. QueryGenerator 从 Request 读取规则 → 拼接到 SQL WHERE 条件 ``` --- ## 十、缓存体系 ### 10.1 缓存架构 | 组件 | 说明 | |------------------------------------------|----------------------------------------| | Redis | 底层缓存存储 | | `RedisUtil` | Redis 操作工具类(来自 `jeecg-boot-common` 依赖) | | Spring Cache(`@Cacheable`/`@CacheEvict`) | 声明式缓存 | | `CacheConstant` | 缓存Key常量(`jeecg-boot-common` 依赖) | ### 10.2 缓存使用最佳实践 **字典缓存**(框架已内置): ```java // 字典查询自动缓存 @Cacheable(value = CacheConstant.SYS_DICT_TABLE_CACHE) public List queryDictItemsByCode(String code) { ... } // 字典变更时清除缓存 @CacheEvict(value = {CacheConstant.SYS_DICT_CACHE, CacheConstant.SYS_ENABLE_DICT_CACHE}, allEntries = true) public Result delete(@RequestParam String id) { ... } ``` **用户缓存**(框架已内置): ```java // 用户变更时清除用户缓存 @CacheEvict(value = {CacheConstant.SYS_USERS_CACHE}, allEntries = true) public void updateUser(...) { ... } ``` **自定义业务缓存示例**: ```java @Cacheable(value = "myBusinessCache", key = "#id") public XxxEntity getFromDb(String id) { return mapper.selectById(id); } @CacheEvict(value = "myBusinessCache", key = "#entity.id") public void updateEntity(XxxEntity entity) { mapper.updateById(entity); } ``` ### 10.3 DictAspect 的 Redis 缓存 `DictAspect` 在字典翻译时直接使用 `RedisTemplate`: - 普通字典 Key:`sys:cache:dict::{dictCode}:{value}` - 表字典 Key:`sys:cache:dictTable::SimpleKey [{table},{text},{code},{value}]` --- ## 十一、安全认证与权限体系 ### 11.1 Shiro + JWT 架构 ``` 请求到达 → JwtFilter(校验Token)→ ShiroRealm(认证+授权)→ Controller ``` 核心文件清单: | 文件 | 功能 | |--------------------|------------------------------------------| | `ShiroConfig.java` | Shiro核心配置,注册过滤器链、SecurityManager、Redis缓存 | | `ShiroRealm.java` | 认证授权Realm(JWT解析、角色/权限查询) | | `JwtFilter.java` | JWT过滤器(Token校验、跨域处理、租户上下文注入) | | `JwtToken.java` | JWT Token封装 | | `@IgnoreAuth` | 免认证注解(标注在方法上跳过Token校验) | ### 11.2 权限注解使用 ```java // 功能权限 @RequiresPermissions("system:user:add") @PostMapping("/add") public Result add(@RequestBody SysUser user) { ... } // 角色权限 @RequiresRoles("admin") @GetMapping("/adminOnly") public Result adminOnly() { ... } // 数据权限 @PermissionData(pageComponent = "system/user/index") @GetMapping("/list") public Result list(...) { ... } ``` ### 11.3 免认证配置 ```java // 方式一:@IgnoreAuth 注解(方法级) @IgnoreAuth @GetMapping("/publicApi") public Result publicApi() { ... } // 方式二:jeecg.shiro.excludeUrls 配置(路径级) // 在 application.yml 中配置 jeecg: shiro: excludeUrls: /sys/common/static/**, /sys/common/upload, ... ``` --- ## 十二、消息推送体系 ### 12.1 消息发送架构 ``` 业务代码调用 PushMsgUtil.sendMessage(templateCode, params) ↓ Freemarker 渲染模板内容 → 写入 sys_sms 表 ↓ Quartz 定时任务 SendMsgJob 扫描未发送消息 ↓ 根据消息类型分发到不同实现: ├── EmailSendMsgHandle → 邮件 ├── SmsSendMsgHandle → 短信(支持阿里云/腾讯云) ├── WxSendMsgHandle → 微信公众号 ├── DdSendMsgHandle → 钉钉 ├── QywxSendMsgHandle → 企业微信 └── SystemSendMsgHandle → 站内消息(WebSocket推送) ``` ### 12.2 消息模板使用 ```java @Autowired private PushMsgUtil pushMsgUtil; // 发送消息(模板编码 + 参数) pushMsgUtil.sendMessage("C2010006", JSON.toJSONString(params)); ``` ### 12.3 系统通告 通过 `SysAnnouncementController` 管理通告的发布、撤回,`SysAnnouncementSend` 跟踪用户阅读状态。 --- ## 十三、定时任务体系 ### 13.1 Quartz 定时任务 ```java // 1. 创建 Job 类,实现 org.quartz.Job 接口 public class MyJob implements Job { @Override public void execute(JobExecutionContext context) { // 定时任务逻辑 } } // 2. 通过系统管理界面或 API 配置 // 实体: QuartzJob(jobClassName + cronExpression + parameter + status) // 管理界面: /sys/quartzJob ``` ### 13.2 示例 Job - `SampleJob` — 无参示例 - `SampleParamJob` — 带参示例 - `AsyncJob` — 异步执行示例 - `SendMsgJob` — 消息发送定时扫描 --- ## 十四、文件上传体系 ### 14.1 上传架构 ``` 前端 POST /sys/common/upload (file + biz) ↓ CommonController.upload() ↓ SsrfFileTypeFilter.checkUploadFileType() // 安全校验 ↓ uploadType == "local" ? YES → uploadLocal() → /opt/upFiles NO → CommonUtils.upload() → MinioUtil / OssBootUtil ↓ 返回 Result(success=true, message=相对路径) ``` ### 14.2 配置 ```yaml jeecg: uploadType: local # local / minio / alioss path: upload: /opt/upFiles # 本地上传根目录 webapp: /opt/webapp # webapp路径 spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB ``` ### 14.3 安全校验 | 检测项 | 方式 | |---------|--------------------------------------------------------------------| | 文件后缀白名单 | jpg, jpeg, png, gif, bmp, pdf, doc, docx, xls, xlsx, zip, rar 等30+ | | 文件头黑名单 | jsp, php, class, sql(检测文件魔数) | | 路径遍历防护 | 过滤 `..` 字符、限制深度5层 | | 下载类型校验 | `checkDownloadFileType()` | ### 14.4 静态文件访问 - 接口:`GET /sys/common/static/{相对路径}` - 认证:Shiro 配置为 `anon`(匿名访问) - 实现:流式读取本地文件输出 --- ## 十五、代码生成器与在线表单 ### 15.1 代码生成器模板 模板位于 `jeecg-system-biz/src/main/resources/jeecg/code-template-online/`: | 类型 | 风格 | 模板路径 | 支持前端 | |-----|------|--------------------------|--------------------------------| | 单表 | 经典 | `default/one/` | vue2, vue3, vue3Native, uniapp | | 一对多 | 经典 | `default/onetomany/` | vue2 | | 一对多 | JVxe | `jvxe/onetomany/` | vue3, vue3Native | | 一对多 | ERP | `erp/onetomany/` | vue3, vue3Native | | 一对多 | 内嵌子表 | `inner-table/onetomany/` | vue3 | | 一对多 | Tab | `tab/onetomany/` | vue3 | | 树形 | 默认 | `default/tree/` | vue3, vue3Native | 每个模板生成的文件: - **后端**:Controller、Entity、Mapper、MapperXML、Service接口、Service实现 - **前端**:列表页、表单组件、弹窗组件、API接口定义、列/字段配置 - **SQL**:菜单插入脚本 ### 15.2 在线表单(Online Form) 通过 `/online/cgform/**` 接口提供零代码开发能力,核心路由: | URL | 功能 | |-----------------------------------------|----------| | `/online/cgform/api/getData/{code}` | 获取表单数据 | | `/online/cgform/api/getTreeData/{code}` | 获取树形数据 | | `/online/cgform/api/addAll` | 新增配置 | | `/online/cgform/api/editAll` | 编辑配置 | | `/online/cgform/head/enhanceJs/**` | JS增强 | | `/online/cgform/head/enhanceJava/**` | Java增强回调 | | `/online/cgreport/**` | Online报表 | | `/online/graphreport/**` | Online图表 | | `/bigscreen/**` | 大屏配置 | | `/drag/**` | 仪表盘配置 | --- ## 十六、开发一个新模块的最小步骤 基于以上分析,在 JeecgBoot 框架中开发一个带完整 **CRUD + 导入导出** 的新业务模块,所需步骤: ### 16.1 单表模块(4个步骤) 1. **创建实体类**:继承 `JeecgEntity`,加 `@TableName`、`@Excel`、`@Dict` 注解 2. **创建 Mapper**:继承 `BaseMapper` 3. **创建 Service**:接口继承 `IService`,实现继承 `ServiceImpl` 4. **创建 Controller**:继承 `JeecgController>`,实现6个CRUD方法 + 2个导入导出方法 **无需额外开发的**: - 查询条件自动构建 → `QueryGenerator` 已实现 - 公共字段自动填充 → `MybatisInterceptor` 已实现 - 字典值自动翻译 → `DictAspect` + `@Dict` 已实现 - 操作日志自动记录 → `AutoLogAspect` + `@AutoLog` 已实现 - Excel 导出渲染 → `JeecgEntityExcelView` 已实现 - Excel 导入解析 → `ExcelImportUtil` 已实现 - 导入模板下载 → 复用 `exportXls` 接口 ### 16.2 主子表模块(10个步骤) 1. **创建主表实体**:继承 `JeecgEntity`,加 `@TableName`、`@TableId` 2. **创建子表实体**:同上,必须包含外键字段 3. **创建VO类**:包含主表字段 + `@ExcelCollection` 标注的子表列表字段 4. **创建主表Mapper**:继承 `BaseMapper` 5. **创建子表Mapper**:继承 `BaseMapper`,增加 `deleteByMainId` + `selectByMainId` 6. **创建子表Service**:接口 `IService` + 实现 `ServiceImpl` 7. **创建主表Service接口**:增加 `saveMain`、`updateMain`、`delMain`、`delBatchMain` 8. **创建主表Service实现**:注入所有子表Mapper,实现事务方法 9. **创建Controller**:继承 `JeecgController`,实现CRUD + 子表查询 + 导入导出 10. **前端页面配置**:添加菜单SQL、配置路由、开发页面组件 --- ## 十七、关键源码索引 ### 17.1 框架核心库 (jeecg-boot-base-core) | 组件 | 文件 | |--------------|--------------------------------------------------------------------------------------------------------------| | Controller基类 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java` | | Entity基类 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/entity/JeecgEntity.java` | | Service基类接口 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/service/JeecgService.java` | | 查询生成器 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/query/QueryGenerator.java` | | 统一返回结果 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/vo/Result.java` | | 全局异常处理器 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java` | | 核心业务异常 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootException.java` | | 业务提醒异常 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootBizTipException.java` | | 断言工具类 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/AssertUtils.java` | | 导入工具类 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/ImportExcelUtil.java` | | 通用工具类 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/oConvertUtils.java` | | SQL注入防护 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/SqlInjectionUtil.java` | | 文件安全过滤器 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/filter/SsrfFileTypeFilter.java` | | 全局常量 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/constant/CommonConstant.java` | ### 17.2 切面与注解 (jeecg-boot-base-core) | 组件 | 文件 | |-------------------|--------------------------------------------------------------------------------------------------------| | 日志切面 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java` | | 字典翻译切面 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/DictAspect.java` | | 数据权限切面 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/PermissionDataAspect.java` | | @AutoLog注解 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/AutoLog.java` | | @Dict注解 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/Dict.java` | | @AutoDict注解 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/AutoDict.java` | | @PermissionData注解 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/annotation/PermissionData.java` | | @IgnoreAuth注解 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/IgnoreAuth.java` | ### 17.3 全局配置 (jeecg-boot-base-core) | 组件 | 文件 | |------------|-----------------------------------------------------------------------------------------------------| | Shiro配置 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java` | | ShiroRealm | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java` | | JWT过滤器 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/filters/JwtFilter.java` | | MVC配置 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/WebMvcConfiguration.java` | | MyBatis拦截器 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisInterceptor.java` | | 核心配置加载 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/JeecgBaseConfig.java` | | AutoPoi配置 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/AutoPoiConfig.java` | | Swagger3配置 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/Swagger3Config.java` | | 多租户配置 | `jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/mybatis/MybatisPlusSaasConfig.java` | ### 17.4 系统模块示例 (jeecg-module-system) | 组件 | 文件 | |----------------|------------------------------------------------------------------------------------------------------------------------------------------------| | 用户管理Controller | `jeecg-boot/jeecg-boot-module/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserController.java` | | 字典管理Controller | `jeecg-boot/jeecg-boot-module/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysDictController.java` | | 角色管理Controller | `jeecg-boot/jeecg-boot-module/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysRoleController.java` | | 通用上传Controller | `jeecg-boot/jeecg-boot-module/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/CommonController.java` | | 定时任务Controller | `jeecg-boot/jeecg-boot-module/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/controller/QuartzJobController.java` | ### 17.5 Demo模块示例 (jeecg-module-demo) | 组件 | 文件 | |-------------------|----------------------------------------------------------------------------------------------------------------------------------------| | 单表CRUD Controller | `jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/controller/JeecgDemoController.java` | | 主子表Controller | `jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/controller/JeecgOrderMainController.java` | | 主子表Service实现 | `jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/service/impl/JeecgOrderMainServiceImpl.java` | | 主子表VO | `jeecg-boot/jeecg-boot-module/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/test/vo/JeecgOrderMainPage.java` | --- ## 十八、涉及的文件清单 | 文件 | 操作 | 原因 | |--------------------------------------------|----|-------------------| | `.docs/260603-JeecgBoot后端框架全局开发规范与最佳实践.md` | 新增 | 重新分析框架,撰写全面开发规范文档 |