JeecgBoot 项目理解与使用心得

JeecgBoot 项目理解与使用心得(内容增强版)

给第一次上手的同学一条清晰路线,也给已经在用的团队一套"更稳、更快、更可维护"的实践清单。本文聚焦 项目定位、模块地图、上手路线、生成器最佳实践、工程化与安全基线、性能优化、前端协作、DevOps 与可观测性、测试策略、常见坑排雷,并附带可直接落地的代码片段与检查清单。


1. 我对 JeecgBoot 的定位

一句话:JeecgBoot = 低代码平台(在线建模) + 代码生成器(前后端同生) + 企业级脚手架(Spring Boot + MyBatis-Plus + Ant Design Vue)。

  • 优势

    • 交付效率:模型 → 生成 → 小改小调,即可交付 CRUD 模块。
    • 上手成本:分层清晰、生态主流、社区活跃。
    • 可维护:生成的是真实工程代码,适合二次开发与长期运维。
  • 边界

    • 模板即规范:模板与公共库的质量,决定批量复制的质量。
    • 复杂业务仍需良好领域建模和工程治理,低代码不是银弹。

2. 模块地图与协作关系(后端/前端)

  • 后端

    • jeecg-boot-module-system:系统基础能力(用户、角色、字典、文件、日志等)
    • jeecg-boot-base-core:公共库(工具、异常、统一返回、查询生成器、拦截器)
    • 技术栈:Spring Boot + MyBatis-Plus(Wrapper/分页/乐观锁/代码生成器)
  • 前端

    • Ant Design Vue + 路由/权限指令 + 代码生成页面(列表/表单/导入导出)
    • 表单、表格、字典组件完备,适合中后台场景

协作:模型与字段 → 生成前后端骨架 → 后端补业务逻辑/查询 → 前端补交互/校验/样式。


3. 快速上手路线图(从"能跑"到"跑得稳")

  1. 准备与启动

    • 初始化数据库(官方脚本),配置数据源与文件存储;
    • 启动后端、前端(或使用打包版);
    • 登录系统,熟悉系统模块(用户/角色/字典)。
  2. 模型→生成

    • 使用在线建模/代码生成器,生成单表/主从表页面;
    • 生成后在 IDE 中补业务逻辑(Service/Mapper);
    • 在前端完善校验与交互(必要时自定义组件)。
  3. 工程化加固(务必做)

    • 安全基线/性能限幅/统一异常/日志脱敏 固化到公共库与模板;
    • 接入 CI / 质量门禁 / 可观测性
    • 规范 DTO/VO 分离、包结构、单元/集成测试

4. 代码生成器最佳实践(决定"复制出来"的质量)

4.1 数据建模与命名

  • 字段统一命名风格(snake_casecamelCase),尽量一致;
  • 通用字段提前规划:idtenant_idorg_idcreate_bycreate_timeupdate_byupdate_timedel_flag
  • 选择合适主键策略(雪花或数据库自增),大表建议雪花,避免热点自增锁。

4.2 主从表与关联

  • 生成主子表结构时,外键字段与索引要就位;
  • 读多写少的从表可考虑懒加载;写频繁则在 Service 里控制事务边界。

4.3 校验与字典

  • 后端 DTO 加 JSR-380 校验注解(@NotBlank @Size @Pattern ...),Controller 上加 @Validated
  • 字典/枚举建议双轨:数据库字典 + 枚举常量(关键枚举落到代码可读)。

4.4 模板"开箱即工程化"(强烈建议)

  • Controller 模板固化:分页限幅、排序白名单、LIKE 转义、统一异常与状态码
  • 导出模板固化:Excel 公式注入防护
  • 上传模板固化:魔数/大小/后缀校验
  • 前端模板固化:表单校验 与后端 @Validated 对齐;新 API (如 v-model:open)。

5. 工程化与安全基线(可直接复制的片段)

5.1 LIKE 转义(防止"全表扫描"与越权匹配)

java 复制代码
public final class SqlLike {
  private SqlLike(){}
  public static String esc(String s){
    if (s==null) return null;
    return s.replace("\\","\\\\").replace("%","\\%").replace("_","\\_");
  }
  public static void like(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<?> w,
                          String col, String kw){
    String v = "%" + esc(kw) + "%";
    w.apply("`"+col+"` LIKE {0} ESCAPE '\\\\'", v);
  }
}

5.2 排序白名单(禁止任意列/片段传入)

java 复制代码
public final class SortGuard {
  private SortGuard(){}
  public static void assertSortable(String col, java.util.Set<String> whitelist){
    if (col==null || !whitelist.contains(col))
      throw new org.springframework.web.server.ResponseStatusException(
          org.springframework.http.HttpStatus.BAD_REQUEST, "非法排序字段");
  }
  public static boolean isAsc(String dir){ return !"desc".equalsIgnoreCase(dir); }
}

5.3 下载/预览的路径规范化与软链接防护(Path Traversal)

java 复制代码
public final class PathSafe {
  private PathSafe(){}
  public static java.nio.file.Path safeResolve(java.nio.file.Path base, String userInput) throws IOException {
    String decoded = java.net.URLDecoder.decode(userInput, java.nio.charset.StandardCharsets.UTF_8);
    decoded = decoded.replace('\\','/'); // 统一分隔符
    java.nio.file.Path baseReal = base.toRealPath(java.nio.file.LinkOption.NOFOLLOW_LINKS);
    java.nio.file.Path normalized = baseReal.resolve(decoded).normalize();
    if (!normalized.startsWith(baseReal)) throw new org.springframework.web.server.ResponseStatusException(
        org.springframework.http.HttpStatus.FORBIDDEN,"非法路径");
    java.nio.file.Path real = normalized.toRealPath(); // 解析软链接
    if (!real.startsWith(baseReal) || !java.nio.file.Files.isRegularFile(real))
      throw new org.springframework.web.server.ResponseStatusException(
          org.springframework.http.HttpStatus.NOT_FOUND,"目标不存在或非法");
    return real;
  }
}

5.4 SSRF 防护(URL 转发/外链中转)

java 复制代码
public final class UrlSafe {
  private UrlSafe(){}
  public static void assertSafe(java.net.URI u){
    String s = u.getScheme();
    if(!"http".equalsIgnoreCase(s) && !"https".equalsIgnoreCase(s))
      throw new org.springframework.web.server.ResponseStatusException(
          org.springframework.http.HttpStatus.BAD_REQUEST,"非法协议");
    int p = u.getPort();
    if(p!=-1 && p!=80 && p!=443)
      throw new org.springframework.web.server.ResponseStatusException(
          org.springframework.http.HttpStatus.BAD_REQUEST,"非法端口");
    try {
      for (java.net.InetAddress a : java.net.InetAddress.getAllByName(u.getHost())) {
        if (a.isAnyLocalAddress() || a.isLoopbackAddress()
         || a.isLinkLocalAddress() || a.isSiteLocalAddress())
          throw new org.springframework.web.server.ResponseStatusException(
              org.springframework.http.HttpStatus.FORBIDDEN,"禁止访问内网地址");
      }
    } catch (java.net.UnknownHostException e) {
      throw new org.springframework.web.server.ResponseStatusException(
          org.springframework.http.HttpStatus.BAD_REQUEST,"域名解析失败");
    }
  }
}

5.5 Excel 公式注入(CSV/Excel Injection)

java 复制代码
public static String sanitizeExcel(String v){
  return v!=null && v.matches("^[=+\\-@].*") ? "'"+v : v;
}
// 写单元格前:
cell.setCellValue(sanitizeExcel(value));

5.6 文件上传:统一入口校验 + 流式写入(避免 OOM)

java 复制代码
// Controller 入口(无论 local/oss/minio,一律先校验)
if (!(request instanceof org.springframework.web.multipart.MultipartHttpServletRequest)) {
  return Result.error("需要 multipart/form-data");
}
MultipartFile file = ((org.springframework.web.multipart.MultipartHttpServletRequest)request).getFile("file");
if (file==null || file.isEmpty()) return Result.error("文件为空");

// 你们已有 SsrfFileTypeFilter,务必统一调用
org.jeecg.common.util.filter.SsrfFileTypeFilter.checkUploadFileType(file);

// 流式落盘(或 file.transferTo(target))
try (java.io.InputStream in = file.getInputStream();
     java.io.OutputStream out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(target))) {
  byte[] buf = new byte[8192];
  for (int n; (n=in.read(buf))!=-1; ) out.write(buf,0,n);
}

5.7 统一异常与状态码(简版)

java 复制代码
@RestControllerAdvice
public class GlobalEx {
  @ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Result<?> badReq(org.springframework.web.bind.MethodArgumentNotValidException e){
    java.util.List<String> msgs = new java.util.ArrayList<>();
    e.getBindingResult().getFieldErrors().forEach(er -> msgs.add(er.getField()+": "+er.getDefaultMessage()));
    return Result.error("参数校验失败: " + String.join("; ", msgs));
  }

  @ExceptionHandler(org.springframework.web.multipart.MaxUploadSizeExceededException.class)
  @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
  public Result<?> tooLarge(){ return Result.error("上传文件过大"); }
}

6. 性能与可维护性(从"单点优化"到"系统性治理")

  • 分页限幅 :后端统一限制 pageSize<=1000,异常值回落默认。
  • 索引与 SQL:每个模糊查询列评估索引;多条件组合下关注执行计划(MySQL 8 慢 SQL 日志务必开启)。
  • 缓存策略:字典/常量/配置放 Redis,有效期与更新策略明确;热点 key 做本地二级缓存。
  • 异步化:大导出、消息通知、耗时计算放入队列;导出按分页流式写入,避免卡接口。
  • 连接池与线程池:压测后调优;给线程池"命名 + 指标暴露"。
  • 读写分离/分库分表 :增长到一定量级后再考虑;先做好 观察 → 评估 → 演进

7. 前端协作心得(Ant Design Vue)

  • 表单校验 :与后端 @Validated 一致;对手机号、邮箱、金额等用统一校验器。
  • 表格:大列表开启虚拟滚动/列宽控制;分页与后端同步。
  • 字典组件:约定 value/label/disabled 字段格式;前后端一个口径。
  • 弹窗组件 :注意新 API(如 v-model:open);生成器模板与组件版本保持同步。
  • 跨端:如需 H5/小程序,尽早抽离"服务接口层"与"UI 层",减少耦合。

8. DevOps 与可观测性

  • CI/质量门禁:SonarQube(Bug/Vuln/Code Smell 阈值)、SpotBugs、Checkstyle/Spotless;
  • 依赖安全:OWASP Dependency-Check/Snyk;
  • 数据库迁移:Flyway/Liquibase 管理脚本版本;
  • 日志:Logback JSON + TraceId/SpanId;
  • 指标:Micrometer + Prometheus + Grafana(QPS、P95、错误率、线程池、JVM、连接池);
  • 链路:OpenTelemetry 分析慢链路与异常。

9. 测试策略(最低配也要有)

  • 单元测试:Service/Utils;
  • 集成测试:Testcontainers 起 MySQL/Redis,覆盖核心查询/事务;
  • E2E:关键生成页面用 Playwright/Cypress 跑"查询/新增/编辑/导出"主链路;
  • 回归用例:LIKE 转义、排序白名单、导出公式、路径/SSRF 等安全基线用例常驻。

10. 常见坑排雷

  • Long 精度丢失(前端):ID 用字符串传输;前端 json 解析用 bigInt 方案或统一转 string。
  • 时间与时区 :后端存 UTC,前端按时区展示;统一 yyyy-MM-dd HH:mm:ss
  • 逻辑删除 :MyBatis-Plus @TableLogic 与数据库唯一键配合(避免"软删后唯一冲突")。
  • 事务边界:主从表写入建议一事务;跨服务用消息保证最终一致性。
  • 乐观锁 :大并发更新场景使用 @Version,冲突重试或提示。
  • 导出大文件:务必异步化 + 流式;避免一次性读入内存。

11. 生产上线检查清单

  • 代码生成模板已固化:分页限幅 / 排序白名单 / LIKE 转义 / 导出防注入 / 上传魔数
  • 转发/下载口:SSRF 防护 / 路径规范化 + 软链接防护 / Content-Disposition 安全编码
  • CAS/外部调用:系统信任库 + 主机名校验 + 连接/读取超时
  • 日志脱敏:password/token/Authorization/手机号 等统一过滤
  • 大导出:异步任务 + 分页流式;前端可查看任务状态
  • 质量门禁:Sonar/依赖安全扫描通过;Checkstyle/Spotless 已启用
  • 测试:关键路径单测/集成/E2E 覆盖
  • 可观测性:QPS、P95、错误率、线程池、JVM、连接池指标可见;Trace 打通
  • 数据权限/多租户:拦截器注入范围过滤,导出/下载同样受控

12. 总结

JeecgBoot 的价值在于:把低代码效率与工程化可维护结合起来 。想要"跑得稳、跑得久",关键是把 安全、性能、规范、可观测性 这四条"地基",沉到模板与公共库里,让每个新模块 开箱即工程化 。 按照本文的清单逐条落地,你的 JeecgBoot 项目会更好地满足企业级场景对 稳定性、安全性与迭代效率 的双重要求。


相关推荐
cyforkk12 小时前
Spring Boot @RestController 注解详解
java·spring boot·后端
canonical_entropy13 小时前
可逆计算:一场软件构造的世界观革命
后端·aigc·ai编程
重庆穿山甲13 小时前
从0到1:用 Akka 持久化 Actor + Outbox + RocketMQ 做到“订单-库存最终一致”
后端
我不只是切图仔14 小时前
我只是想给网站加个注册验证码,咋就那么难!
前端·后端
专注VB编程开发20年14 小时前
CSS 的命名方式像是 PowerShell 的动词-名词结构,缺乏面向对象的层级关系
开发语言·后端·rust
野犬寒鸦14 小时前
力扣hot100:相交链表与反转链表详细思路讲解(160,206)
java·数据结构·后端·算法·leetcode
爱吃烤鸡翅的酸菜鱼15 小时前
【Spring】原理:Bean的作用域与生命周期
后端·spring
JohnYan15 小时前
工作笔记 - 微信消息发送和处理
javascript·后端·微信