ai-engineering-init 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.claude/skills/leniu-java-export/SKILL.md +389 -95
  2. package/.codex/skills/leniu-java-export/SKILL.md +389 -95
  3. package/.cursor/skills/bug-detective/SKILL.md +19 -19
  4. package/.cursor/skills/leniu-java-export/SKILL.md +389 -95
  5. package/.cursor/skills/project-navigator/SKILL.md +164 -258
  6. package/package.json +7 -1
  7. package/scripts/build-skills.js +180 -0
  8. package/src/platform-map.json +56 -0
  9. package/src/skills/add-skill/SKILL.md +488 -0
  10. package/src/skills/add-todo/SKILL.md +269 -0
  11. package/src/skills/api-development/SKILL.md +266 -0
  12. package/src/skills/architecture-design/SKILL.md +262 -0
  13. package/src/skills/backend-annotations/SKILL.md +302 -0
  14. package/src/skills/banana-image/CHANGELOG.md +37 -0
  15. package/src/skills/banana-image/README.md +146 -0
  16. package/src/skills/banana-image/SKILL.md +171 -0
  17. package/src/skills/banana-image/assets/logo.png +0 -0
  18. package/src/skills/banana-image/references/advanced-usage.md +189 -0
  19. package/src/skills/banana-image/scripts/apply_template.py +125 -0
  20. package/src/skills/banana-image/scripts/banana_image_exec.ts +412 -0
  21. package/src/skills/banana-image/scripts/batch_prep.py +82 -0
  22. package/src/skills/banana-image/scripts/package-lock.json +1437 -0
  23. package/src/skills/banana-image/scripts/package.json +18 -0
  24. package/src/skills/banana-image/scripts/requirements.txt +10 -0
  25. package/src/skills/banana-image/templates/poster.json +22 -0
  26. package/src/skills/banana-image/templates/product.json +17 -0
  27. package/src/skills/banana-image/templates/social.json +22 -0
  28. package/src/skills/banana-image/templates/thumbnail.json +17 -0
  29. package/src/skills/brainstorm/SKILL.md +216 -0
  30. package/src/skills/bug-detective/SKILL.md +256 -0
  31. package/src/skills/bug-detective/references/error-patterns.md +242 -0
  32. package/src/skills/check/SKILL.md +367 -0
  33. package/src/skills/code-patterns/SKILL.md +280 -0
  34. package/src/skills/code-patterns/references/leniu-code-patterns.md +87 -0
  35. package/src/skills/codex-code-review/SKILL.md +135 -0
  36. package/src/skills/collaborating-with-codex/SKILL.md +174 -0
  37. package/src/skills/collaborating-with-codex/scripts/codex_bridge.py +275 -0
  38. package/src/skills/collaborating-with-gemini/SKILL.md +194 -0
  39. package/src/skills/collaborating-with-gemini/scripts/gemini_bridge.py +275 -0
  40. package/src/skills/crud/SKILL.md +265 -0
  41. package/src/skills/crud-development/SKILL.md +409 -0
  42. package/src/skills/data-permission/SKILL.md +292 -0
  43. package/src/skills/data-permission/references/custom-data-scope.md +90 -0
  44. package/src/skills/database-ops/SKILL.md +407 -0
  45. package/src/skills/dev/SKILL.md +187 -0
  46. package/src/skills/error-handler/SKILL.md +371 -0
  47. package/src/skills/file-oss-management/SKILL.md +255 -0
  48. package/src/skills/file-oss-management/references/entities.md +105 -0
  49. package/src/skills/file-oss-management/references/service-impl.md +104 -0
  50. package/src/skills/git-workflow/SKILL.md +397 -0
  51. package/src/skills/init-docs/SKILL.md +194 -0
  52. package/src/skills/json-serialization/SKILL.md +357 -0
  53. package/src/skills/leniu-api-development/SKILL.md +319 -0
  54. package/src/skills/leniu-api-development/references/real-examples.md +273 -0
  55. package/src/skills/leniu-architecture-design/SKILL.md +383 -0
  56. package/src/skills/leniu-backend-annotations/SKILL.md +277 -0
  57. package/src/skills/leniu-brainstorm/SKILL.md +242 -0
  58. package/src/skills/leniu-brainstorm/references/business-scenarios.md +162 -0
  59. package/src/skills/leniu-code-patterns/SKILL.md +411 -0
  60. package/src/skills/leniu-crud-development/SKILL.md +404 -0
  61. package/src/skills/leniu-crud-development/references/templates.md +597 -0
  62. package/src/skills/leniu-customization-location/SKILL.md +410 -0
  63. package/src/skills/leniu-data-permission/SKILL.md +341 -0
  64. package/src/skills/leniu-database-ops/SKILL.md +426 -0
  65. package/src/skills/leniu-error-handler/SKILL.md +462 -0
  66. package/src/skills/leniu-java-amount-handling/SKILL.md +461 -0
  67. package/src/skills/leniu-java-code-style/SKILL.md +510 -0
  68. package/src/skills/leniu-java-concurrent/SKILL.md +400 -0
  69. package/src/skills/leniu-java-entity/SKILL.md +237 -0
  70. package/src/skills/leniu-java-entity/references/templates.md +237 -0
  71. package/src/skills/leniu-java-export/SKILL.md +570 -0
  72. package/src/skills/leniu-java-logging/SKILL.md +229 -0
  73. package/src/skills/leniu-java-logging/references/data-mask.md +46 -0
  74. package/src/skills/leniu-java-logging/references/logging-scenarios.md +113 -0
  75. package/src/skills/leniu-java-mq/SKILL.md +338 -0
  76. package/src/skills/leniu-java-mybatis/SKILL.md +267 -0
  77. package/src/skills/leniu-java-mybatis/references/report-mapper.md +88 -0
  78. package/src/skills/leniu-java-report-query-param/SKILL.md +291 -0
  79. package/src/skills/leniu-java-task/SKILL.md +367 -0
  80. package/src/skills/leniu-java-total-line/SKILL.md +196 -0
  81. package/src/skills/leniu-marketing-price-rule-customizer/SKILL.md +301 -0
  82. package/src/skills/leniu-marketing-recharge-rule-customizer/SKILL.md +285 -0
  83. package/src/skills/leniu-mealtime/SKILL.md +215 -0
  84. package/src/skills/leniu-redis-cache/SKILL.md +331 -0
  85. package/src/skills/leniu-report-customization/SKILL.md +335 -0
  86. package/src/skills/leniu-report-customization/references/table-fields.md +93 -0
  87. package/src/skills/leniu-report-standard-customization/SKILL.md +328 -0
  88. package/src/skills/leniu-report-standard-customization/references/analysis-module.md +64 -0
  89. package/src/skills/leniu-report-standard-customization/references/table-fields.md +113 -0
  90. package/src/skills/leniu-security-guard/SKILL.md +306 -0
  91. package/src/skills/leniu-utils-toolkit/SKILL.md +380 -0
  92. package/src/skills/mysql-debug/SKILL.md +364 -0
  93. package/src/skills/next/SKILL.md +137 -0
  94. package/src/skills/openspec-apply-change/SKILL.md +165 -0
  95. package/src/skills/openspec-archive-change/SKILL.md +122 -0
  96. package/src/skills/openspec-bulk-archive-change/SKILL.md +254 -0
  97. package/src/skills/openspec-continue-change/SKILL.md +126 -0
  98. package/src/skills/openspec-explore/SKILL.md +299 -0
  99. package/src/skills/openspec-ff-change/SKILL.md +109 -0
  100. package/src/skills/openspec-new-change/SKILL.md +82 -0
  101. package/src/skills/openspec-onboard/SKILL.md +414 -0
  102. package/src/skills/openspec-sync-specs/SKILL.md +146 -0
  103. package/src/skills/openspec-verify-change/SKILL.md +176 -0
  104. package/src/skills/performance-doctor/SKILL.md +303 -0
  105. package/src/skills/progress/SKILL.md +193 -0
  106. package/src/skills/project-navigator/SKILL.md +211 -0
  107. package/src/skills/redis-cache/SKILL.md +333 -0
  108. package/src/skills/redis-cache/references/listeners.md +23 -0
  109. package/src/skills/scheduled-jobs/SKILL.md +314 -0
  110. package/src/skills/security-guard/SKILL.md +353 -0
  111. package/src/skills/security-guard/references/encrypt-config.md +103 -0
  112. package/src/skills/security-guard/references/sensitive-strategies.md +42 -0
  113. package/src/skills/sms-mail/SKILL.md +308 -0
  114. package/src/skills/sms-mail/references/mail-config.md +88 -0
  115. package/src/skills/sms-mail/references/sms-config.md +74 -0
  116. package/src/skills/social-login/SKILL.md +266 -0
  117. package/src/skills/social-login/references/provider-configs.md +118 -0
  118. package/src/skills/start/SKILL.md +154 -0
  119. package/src/skills/store-pc/SKILL.md +366 -0
  120. package/src/skills/sync/SKILL.md +149 -0
  121. package/src/skills/task-tracker/SKILL.md +307 -0
  122. package/src/skills/tech-decision/SKILL.md +393 -0
  123. package/src/skills/tenant-management/SKILL.md +288 -0
  124. package/src/skills/tenant-management/references/tenant-scenarios.md +91 -0
  125. package/src/skills/test-development/SKILL.md +301 -0
  126. package/src/skills/test-development/references/parameterized-examples.md +119 -0
  127. package/src/skills/ui-pc/SKILL.md +438 -0
  128. package/src/skills/update-status/SKILL.md +159 -0
  129. package/src/skills/utils-toolkit/SKILL.md +362 -0
  130. package/src/skills/utils-toolkit/references/redis-utils-api.md +56 -0
  131. package/src/skills/websocket-sse/SKILL.md +271 -0
  132. package/src/skills/workflow-engine/SKILL.md +321 -0
@@ -8,65 +8,81 @@ description: |
8
8
  - 实现异步导出(数据量大时)
9
9
  - 实现分页导出(防内存溢出)
10
10
  - 导出API接口设计(@PostMapping("/export"))
11
- - 实现同步导出(EasyExcelUtil)
12
11
 
13
12
  适用项目:
14
13
  - leniu-tengyun-core:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun-core
15
14
  - leniu-yunshitang:/Users/xujiajun/Developer/gongsi_proj/leniu-api/leniu-tengyun/leniu-yunshitang
16
15
 
17
- 触发词:导出、Excel导出、异步导出、分页导出、@ExcelProperty、exportApi、数据导出、EasyExcelUtil、导出列
16
+ 触发词:导出、Excel导出、异步导出、分页导出、@ExcelProperty、exportApi、数据导出
18
17
  ---
19
18
 
20
- # leniu 数据导出规范
19
+ # leniu-tengyun-core 数据导出规范
21
20
 
22
- ## 核心组件
21
+ ## 项目特征
23
22
 
24
- | 组件 | 用途 |
23
+ | 特征 | 说明 |
25
24
  |------|------|
26
- | `ExportApi` | 异步分页导出 |
27
- | `EasyExcelUtil` | 同步导出(直接返回文件流) |
28
- | `I18n.getMessage()` | 文件名国际化 |
29
- | `ReportBaseParam` | 基类(含 exportCols、page) |
30
- | `ReportConstant` | 报表常量(工作表名称等) |
25
+ | 包名 | `net.xnzn.*` |
26
+ | 异常类 | `LeException` |
27
+ | 导出工具 | `ExportApi.startExcelExportTaskByPage()` |
28
+ | 国际化 | `I18n.getMessage()` |
29
+ | 工具类 | Hutool(CollUtil、BeanUtil 等) |
30
+ | 请求包装 | `LeRequest<T>` |
31
+
32
+ ## 核心组件
33
+
34
+ - **ExportApi**: 导出API接口
35
+ - **EasyExcelUtil**: 同步导出工具(报表 Controller 直接使用)
36
+ - **I18n**: 国际化工具
37
+ - **PageDTO**: 分页参数
38
+ - **ReportConstant**: 报表常量(工作表名称等)
31
39
 
32
40
  ## 两种导出模式
33
41
 
34
42
  | 模式 | 工具 | 适用场景 |
35
43
  |------|------|---------|
36
- | **同步导出** | `EasyExcelUtil.writeExcelByDownLoadIncludeWrite()` | 数据量不大,直接返回文件流 |
44
+ | **同步导出** | `EasyExcelUtil.writeExcelByDownLoadIncludeWrite()` | 报表 Controller 直接返回文件流,数据量不大 |
37
45
  | **异步分页导出** | `exportApi.startExcelExportTaskByPage()` | 大数据量,任务队列方式 |
38
- | **异步(Feign)** | `orderClients.export().startExcelExportTaskByPage()` | 跨模块导出 |
46
+ | **异步分页导出(Feign)** | `orderClients.export().startExcelExportTaskByPage()` | 跨模块导出,通过 Feign 客户端 |
39
47
 
40
- ---
41
-
42
- ## 同步导出模板
48
+ ## 同步导出(EasyExcelUtil)
43
49
 
44
50
  ```java
45
51
  @ApiOperation(value = "流水汇总-同步导出")
46
52
  @PostMapping("/export")
47
- @SneakyThrows // 必须加,EasyExcelUtil 抛受检异常
53
+ @SneakyThrows
48
54
  public void export(@RequestBody LeRequest<ReportAnalysisTurnoverParam> request,
49
55
  HttpServletResponse response) {
50
56
  ReportAnalysisTurnoverParam param = request.getContent();
57
+
58
+ // 1. 查询数据
51
59
  ReportBaseTotalVO<TurnoverVO> result = reportService.pageSummary(param);
52
60
 
61
+ // 2. 将列表 + 合计行合并(合计行追加到列表末尾)
53
62
  List<TurnoverVO> records = result.getResultPage().getRecords();
54
- CollUtil.addAll(records, result.getTotalLine()); // 合计行追加到末尾
63
+ CollUtil.addAll(records, result.getTotalLine()); // 合计行是单个 VO 或 List
55
64
 
65
+ // 3. 直接写出文件流
56
66
  EasyExcelUtil.writeExcelByDownLoadIncludeWrite(
57
67
  response,
58
- I18n.getMessage("report.turnover.title"),
59
- TurnoverVO.class,
60
- I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS),
61
- records,
62
- param.getExportCols()
68
+ I18n.getMessage("report.turnover.title"), // 文件名(国际化)
69
+ TurnoverVO.class, // VO 类型
70
+ I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS), // 工作表名
71
+ records, // 数据列表(含合计行)
72
+ param.getExportCols() // 导出列
63
73
  );
64
74
  }
65
75
  ```
66
76
 
67
- ---
77
+ **注意事项**:
78
+ - 方法签名必须加 `@SneakyThrows`(`EasyExcelUtil` 抛出受检异常)
79
+ - `HttpServletResponse response` 作为方法参数接收
80
+ - 合计行用 `CollUtil.addAll(records, totalLine)` 追加到列表末尾
81
+ - 若合计行是单个 VO:`records.add(totalLine)` 即可
68
82
 
69
- ## 异步分页导出模板
83
+ ## 异步分页导出
84
+
85
+ ### 基础导出模板
70
86
 
71
87
  ```java
72
88
  @ApiOperation(value = "xxx导出")
@@ -74,62 +90,105 @@ public void export(@RequestBody LeRequest<ReportAnalysisTurnoverParam> request,
74
90
  public void export(@RequestBody LeRequest<XxxPageParam> request) {
75
91
  XxxPageParam param = request.getContent();
76
92
 
77
- // 合计行(可选)
93
+ // 获取合计行(可选)
78
94
  XxxVO totalLine = xxxService.getSummaryTotal(param);
79
95
 
96
+ // 启动异步导出任务
80
97
  exportApi.startExcelExportTaskByPage(
81
- I18n.getMessage("report.xxx.title"), // 文件名(国际化)
82
- I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS), // 工作表名
83
- XxxVO.class, // 数据类型
84
- param.getExportCols(), // 导出列
85
- param.getPage(), // 分页参数
86
- totalLine, // 合计行(可为null)
87
- () -> xxxService.pageList(param).getResultPage() // 数据提供者
98
+ I18n.getMessage("report.xxx.title"), // 文件名(国际化)
99
+ I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS), // 工作表名
100
+ XxxVO.class, // 数据类型
101
+ param.getExportCols(), // 导出列
102
+ param.getPage(), // 分页参数
103
+ totalLine, // 合计行(可为null)
104
+ () -> xxxService.pageList(param).getResultPage() // 数据提供者
88
105
  );
89
106
  }
90
107
  ```
91
108
 
92
- ---
109
+ ### 实际项目示例
110
+
111
+ ```java
112
+ @PostMapping("/purchase/order/export")
113
+ @ApiOperation(value = "采购-采购订单汇总-导出")
114
+ public void exportPurchaseOrder(@RequestBody LeRequest<MonitorPageParam> param) {
115
+ MonitorPageParam content = param.getContent();
116
+
117
+ // 1. 获取合计行
118
+ PurchaseOrderSummaryVO totalLine = monitorSafetyPurchaseService.getPurchaseOrderSummaryTotal(content);
119
+
120
+ // 2. 启动导出任务
121
+ exportApi.startExcelExportTaskByPage(
122
+ I18n.getMessage("school.purchase-order-summary"), // 文件名(国际化)
123
+ I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS), // 工作表名
124
+ PurchaseOrderSummaryVO.class, // VO类型
125
+ content.getExportCols(), // 导出列
126
+ content.getPage(), // 分页参数
127
+ totalLine, // 合计行
128
+ () -> monitorSafetyPurchaseService.getPurchaseOrderSummary(content).getResultPage()
129
+ );
130
+ }
131
+ ```
93
132
 
94
- ## 跨模块导出(Feign 客户端)
133
+ ## 异步分页导出(Feign 客户端模式)
134
+
135
+ 跨模块导出时,通过 Feign 客户端调用目标模块的导出接口。订单模块导出是典型示例:
95
136
 
96
137
  ```java
138
+ /**
139
+ * 订单导出 Controller(独立拆分,避免与主 Controller 耦合)
140
+ */
97
141
  @Slf4j
98
142
  @Api(tags = "订单导出")
99
143
  @RestController
100
144
  @RequestMapping("/web/order/export")
101
145
  public class OrderInfoExportWebController {
102
146
 
103
- @Autowired @Lazy // @Lazy 防循环依赖
147
+ // ✅ 跨模块依赖:通过 Feign 客户端调用,@Lazy 避免循环依赖
148
+ @Autowired
149
+ @Lazy
104
150
  private OrderClients orderClients;
105
151
 
106
152
  @ApiOperation(value = "订单导出")
107
153
  @PostMapping("/start")
108
154
  public void export(@RequestBody LeRequest<OrderDetailWebDTO> request) {
109
155
  OrderDetailWebDTO dto = request.getContent();
156
+
157
+ // 转换为内部查询参数
110
158
  OrderSearchParam searchParam = dto.convertToOrderSearchParam();
111
159
 
160
+ // 通过 Feign 客户端启动异步导出任务
112
161
  orderClients.export().startExcelExportTaskByPage(
113
- I18n.getMessage("order.export.title"),
114
- I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS),
115
- OrderDetailVO.class,
116
- dto.getExportCols(),
117
- dto.getPage(),
118
- null,
119
- () -> orderClients.order().pageOrder(searchParam)
162
+ I18n.getMessage("order.export.title"), // 文件名
163
+ I18n.getMessage(ReportConstant.REPORT_TITLE_DETAILS), // 工作表名
164
+ OrderDetailVO.class, // VO 类型
165
+ dto.getExportCols(), // 导出列
166
+ dto.getPage(), // 分页参数
167
+ null, // 无合计行
168
+ () -> orderClients.order().pageOrder(searchParam) // Lambda 提供数据
120
169
  );
121
170
  }
122
171
  }
123
172
  ```
124
173
 
125
- ---
174
+ **独立 Export Controller 的优点**:
175
+ - 将导出逻辑与查询逻辑解耦
176
+ - 避免单个 Controller 过于庞大
177
+ - `@Autowired @Lazy` 防止 Spring 循环依赖
178
+
179
+ ## VO类导出注解
126
180
 
127
- ## VO 类导出注解
181
+ ### 使用@ExcelProperty
182
+
183
+ > ⚠️ **金额字段必须使用 `converter = CustomNumberConverter.class`,禁止用 `@NumberFormat`!**
128
184
 
129
185
  ```java
186
+ import net.xnzn.core.common.export.converter.CustomNumberConverter;
187
+
130
188
  @Data
131
189
  @ApiModel("xxx导出VO")
132
190
  public class XxxVO {
191
+
133
192
  @ExcelProperty(value = "ID", index = 0)
134
193
  @ApiModelProperty("ID")
135
194
  private Long id;
@@ -138,139 +197,374 @@ public class XxxVO {
138
197
  @ApiModelProperty("名称")
139
198
  private String name;
140
199
 
141
- @ExcelProperty(value = "金额(元)", index = 2)
142
- @NumberFormat("#,##0.00")
200
+ @ExcelProperty(value = "状态", index = 2)
201
+ @ApiModelProperty("状态")
202
+ private String statusDesc;
203
+
204
+ // ✅ 金额字段:必须用 CustomNumberConverter,框架自动完成分→元转换
205
+ @ExcelProperty(value = "金额(元)", index = 3, converter = CustomNumberConverter.class)
206
+ @ApiModelProperty("金额(分)")
143
207
  private BigDecimal amount;
144
208
 
145
- @ExcelProperty(value = "创建时间", index = 3)
209
+ @ExcelProperty(value = "创建时间", index = 4)
210
+ @ApiModelProperty("创建时间")
146
211
  @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
147
212
  private LocalDateTime createTime;
148
213
  }
149
214
  ```
150
215
 
151
- ---
216
+ ## 导出参数类
217
+
218
+ ### PageParam包含导出列
219
+
220
+ ```java
221
+ @Data
222
+ @ApiModel("xxx分页查询参数")
223
+ public class XxxPageParam extends ReportBaseParam {
224
+
225
+ @ApiModelProperty(value = "查询条件")
226
+ private String keyword;
152
227
 
153
- ## Service 层:导出时不分页
228
+ // 其他查询条件...
229
+ // 导出列 exportCols 和分页 page 已在 ReportBaseParam 基类中定义
230
+ }
231
+ ```
232
+
233
+ ## Service层导出逻辑
234
+
235
+ ### 导出时不分页
154
236
 
155
237
  ```java
156
238
  public PageVO<XxxVO> pageList(XxxPageParam param) {
239
+ // 导出时不分页,查询全部数据
157
240
  if (CollUtil.isNotEmpty(param.getExportCols())) {
158
- // 导出时不分页,不调用 PageMethod.startPage()
241
+ // 不调用 PageMethod.startPage()
159
242
  List<XxxEntity> records = mapper.selectList(param);
160
- return PageVO.of(BeanUtil.copyToList(records, XxxVO.class));
243
+ List<XxxVO> voList = BeanUtil.copyToList(records, XxxVO.class);
244
+ return PageVO.of(voList);
161
245
  }
162
- // 正常分页
246
+
247
+ // 正常分页查询
163
248
  PageMethod.startPage(param);
164
249
  List<XxxEntity> records = mapper.pageList(param);
165
- return PageVO.of(BeanUtil.copyToList(records, XxxVO.class));
250
+ List<XxxVO> voList = BeanUtil.copyToList(records, XxxVO.class);
251
+ return PageVO.of(voList);
166
252
  }
167
253
  ```
168
254
 
169
- ## 带合计行的导出 Service
255
+ ### 带合计行的导出
170
256
 
171
257
  ```java
258
+ // ⚠️ 系统默认在商户库执行,业务查询无需 Executors.readInSystem()
259
+ // Executors.readInSystem() 仅用于需要访问系统库的场景(如全局配置、商户管理)
260
+
172
261
  public ReportBaseTotalVO<XxxVO> pageWithTotal(XxxPageParam param) {
173
262
  ReportBaseTotalVO<XxxVO> result = new ReportBaseTotalVO<>();
174
263
 
175
- // 导出时不查合计行(避免性能开销)
264
+ // 1. 导出时不查询合计行(避免不必要的性能开销)
176
265
  if (CollUtil.isEmpty(param.getExportCols())) {
177
- result.setTotalLine(mapper.getSummaryTotal(param));
266
+ XxxVO totalLine = mapper.getSummaryTotal(param);
267
+ result.setTotalLine(totalLine);
178
268
  }
179
269
 
270
+ // 2. 导出时不分页
180
271
  if (CollUtil.isNotEmpty(param.getExportCols())) {
181
- result.setResultPage(PageVO.of(mapper.getSummaryList(param)));
272
+ List<XxxVO> list = mapper.getSummaryList(param);
273
+ result.setResultPage(PageVO.of(list));
182
274
  } else {
275
+ // 正常分页查询
183
276
  PageMethod.startPage(param);
184
- result.setResultPage(PageVO.of(mapper.getSummaryList(param)));
277
+ List<XxxVO> list = mapper.getSummaryList(param);
278
+ result.setResultPage(PageVO.of(list));
185
279
  }
280
+
186
281
  return result;
187
282
  }
188
283
  ```
189
284
 
190
- ---
191
-
192
285
  ## 导出文件名国际化
193
286
 
194
- ```properties
195
- # resources/message_zh.properties
287
+ ### 使用I18n
288
+
289
+ ```java
290
+ // 在 resources/message_zh.properties 中定义
196
291
  report.order.title=订单报表
197
- # resources/message_en.properties
292
+ report.order.sheet=订单明细
293
+
294
+ // 在 resources/message_en.properties 中定义
198
295
  report.order.title=Order Report
296
+ report.order.sheet=Order Details
297
+
298
+ // 在代码中使用
299
+ exportApi.startExcelExportTaskByPage(
300
+ I18n.getMessage("report.order.title"), // 订单报表
301
+ I18n.getMessage("report.order.sheet"), // 订单明细
302
+ OrderVO.class,
303
+ param.getExportCols(),
304
+ param.getPage(),
305
+ totalLine,
306
+ () -> orderService.pageList(param).getResultPage()
307
+ );
308
+ ```
309
+
310
+ ## 导出列控制
311
+
312
+ ### 前端传递导出列
313
+
314
+ ```json
315
+ {
316
+ "page": {
317
+ "current": 1,
318
+ "size": 10
319
+ },
320
+ "exportCols": ["id", "name", "status", "amount", "createTime"],
321
+ "keyword": "test"
322
+ }
199
323
  ```
200
324
 
325
+ ### 后端处理导出列
326
+
201
327
  ```java
202
- I18n.getMessage("report.order.title") // 根据当前语言自动返回
328
+ @PostMapping("/export")
329
+ public void export(@RequestBody LeRequest<XxxPageParam> request) {
330
+ XxxPageParam param = request.getContent();
331
+
332
+ // 校验导出列
333
+ if (CollUtil.isEmpty(param.getExportCols())) {
334
+ throw new LeException("导出列不能为空");
335
+ }
336
+
337
+ // 启动导出任务
338
+ exportApi.startExcelExportTaskByPage(
339
+ I18n.getMessage("report.xxx.title"),
340
+ I18n.getMessage("report.xxx.sheet"),
341
+ XxxVO.class,
342
+ param.getExportCols(), // 传递导出列
343
+ param.getPage(),
344
+ null,
345
+ () -> xxxService.pageList(param).getResultPage()
346
+ );
347
+ }
203
348
  ```
204
349
 
205
- ---
350
+ ## 导出数据转换
206
351
 
207
- ## 导出列控制
352
+ ### 状态码转换为描述(使用BeanUtil)
208
353
 
209
- 前端传递:
210
- ```json
211
- { "exportCols": ["id", "name", "status", "amount"], "page": { "current": 1, "size": 10 } }
354
+ ```java
355
+ public PageVO<XxxVO> pageList(XxxPageParam param) {
356
+ PageMethod.startPage(param);
357
+ List<XxxEntity> records = mapper.pageList(param);
358
+
359
+ // 转换为VO并处理状态描述(leniu 使用 BeanUtil,不用 MapstructUtils)
360
+ List<XxxVO> voList = records.stream()
361
+ .map(entity -> {
362
+ XxxVO vo = new XxxVO();
363
+ BeanUtil.copyProperties(entity, vo);
364
+
365
+ // 状态码转换为描述
366
+ vo.setStatusDesc(StatusEnum.getByCode(entity.getStatus()).getDesc());
367
+
368
+ return vo;
369
+ })
370
+ .collect(Collectors.toList());
371
+
372
+ return PageVO.of(voList);
373
+ }
212
374
  ```
213
375
 
214
- 后端校验:
376
+ ## 常见场景
377
+
378
+ ### 场景1:订单导出
379
+
215
380
  ```java
216
- if (CollUtil.isEmpty(param.getExportCols())) {
217
- throw new LeException("导出列不能为空");
381
+ @ApiOperation(value = "订单导出")
382
+ @PostMapping("/export")
383
+ public void export(@RequestBody LeRequest<OrderPageParam> request) {
384
+ OrderPageParam param = request.getContent();
385
+
386
+ log.info("【导出】订单导出,条件:{}", param);
387
+
388
+ // 获取合计行
389
+ OrderVO totalLine = orderService.getSummaryTotal(param);
390
+
391
+ // 启动导出任务
392
+ exportApi.startExcelExportTaskByPage(
393
+ I18n.getMessage("report.order.title"),
394
+ I18n.getMessage("report.order.sheet"),
395
+ OrderVO.class,
396
+ param.getExportCols(),
397
+ param.getPage(),
398
+ totalLine,
399
+ () -> orderService.pageList(param).getResultPage()
400
+ );
218
401
  }
219
402
  ```
220
403
 
221
- ---
404
+ ### 场景2:报表导出
222
405
 
223
- ## 带权限过滤的导出
406
+ ```java
407
+ @ApiOperation(value = "销售报表导出")
408
+ @PostMapping("/export")
409
+ public void export(@RequestBody LeRequest<SalesReportParam> request) {
410
+ SalesReportParam param = request.getContent();
411
+
412
+ log.info("【导出】销售报表导出,日期范围:{} - {}",
413
+ param.getStartDate(), param.getEndDate());
414
+
415
+ // 获取合计行
416
+ SalesReportVO totalLine = reportService.getSummaryTotal(param);
417
+
418
+ // 启动导出任务
419
+ exportApi.startExcelExportTaskByPage(
420
+ I18n.getMessage("report.sales.title"),
421
+ I18n.getMessage("report.sales.sheet"),
422
+ SalesReportVO.class,
423
+ param.getExportCols(),
424
+ param.getPage(),
425
+ totalLine,
426
+ () -> reportService.getSummary(param).getResultPage()
427
+ );
428
+ }
429
+ ```
430
+
431
+ ### 场景3:带权限过滤的导出
224
432
 
225
433
  ```java
434
+ @ApiOperation(value = "数据导出")
226
435
  @PostMapping("/export")
227
436
  public void export(@RequestBody LeRequest<DataPageParam> request) {
228
437
  DataPageParam param = request.getContent();
438
+
439
+ // 获取用户权限
229
440
  MgrUserAuthPO authPO = mgrAuthApi.getUserAuthPO();
230
441
  ReportDataPermissionParam dataPermission =
231
442
  reportDataPermissionService.getDataPermission(authPO);
232
443
 
444
+ log.info("【导出】数据导出,用户:{}, 权限范围:{}",
445
+ authPO.getUserId(), dataPermission.getCanteenIds());
446
+
447
+ // 启动导出任务(权限过滤在Service层处理)
233
448
  exportApi.startExcelExportTaskByPage(
234
449
  I18n.getMessage("report.data.title"),
235
450
  I18n.getMessage("report.data.sheet"),
236
- DataVO.class, param.getExportCols(), param.getPage(), null,
451
+ DataVO.class,
452
+ param.getExportCols(),
453
+ param.getPage(),
454
+ null,
237
455
  () -> dataService.pageList(param).getResultPage()
238
456
  );
239
457
  }
240
458
  ```
241
459
 
242
- ---
460
+ ## 导出性能优化
243
461
 
244
- ## 导出数据脱敏
462
+ ### 1. 限制导出数量
245
463
 
246
464
  ```java
247
- // 在 VO 转换时处理
248
- if (CollUtil.isNotEmpty(param.getExportCols())) {
249
- vo.setMobile(maskMobile(user.getMobile()));
250
- vo.setIdCard(maskIdCard(user.getIdCard()));
465
+ @PostMapping("/export")
466
+ public void export(@RequestBody LeRequest<XxxPageParam> request) {
467
+ XxxPageParam param = request.getContent();
468
+
469
+ // 查询总数
470
+ long total = xxxService.count(param);
471
+
472
+ // 限制导出数量
473
+ if (total > 100000) {
474
+ throw new LeException("导出数据量过大,请缩小查询范围");
475
+ }
476
+
477
+ // 启动导出任务
478
+ exportApi.startExcelExportTaskByPage(
479
+ I18n.getMessage("report.xxx.title"),
480
+ I18n.getMessage("report.xxx.sheet"),
481
+ XxxVO.class,
482
+ param.getExportCols(),
483
+ param.getPage(),
484
+ null,
485
+ () -> xxxService.pageList(param).getResultPage()
486
+ );
251
487
  }
252
488
  ```
253
489
 
254
- ---
490
+ ### 2. 导出数据脱敏
491
+
492
+ ```java
493
+ public PageVO<UserVO> pageList(UserPageParam param) {
494
+ PageMethod.startPage(param);
495
+ List<User> records = mapper.pageList(param);
255
496
 
256
- ## 导出数量限制
497
+ List<UserVO> voList = records.stream()
498
+ .map(user -> {
499
+ UserVO vo = new UserVO();
500
+ BeanUtil.copyProperties(user, vo);
501
+
502
+ // 导出时脱敏
503
+ if (CollUtil.isNotEmpty(param.getExportCols())) {
504
+ vo.setMobile(maskMobile(user.getMobile()));
505
+ vo.setIdCard(maskIdCard(user.getIdCard()));
506
+ }
507
+
508
+ return vo;
509
+ })
510
+ .collect(Collectors.toList());
511
+
512
+ return PageVO.of(voList);
513
+ }
514
+ ```
515
+
516
+ ## 最佳实践
517
+
518
+ ### 1. 导出日志
257
519
 
258
520
  ```java
259
- long total = xxxService.count(param);
260
- if (total > 100000) {
261
- throw new LeException("导出数据量过大,请缩小查询范围");
521
+ @PostMapping("/export")
522
+ public void export(@RequestBody LeRequest<XxxPageParam> request) {
523
+ XxxPageParam param = request.getContent();
524
+
525
+ log.info("【导出】开始导出,文件名:{}, 条件:{}",
526
+ I18n.getMessage("report.xxx.title"), param);
527
+
528
+ exportApi.startExcelExportTaskByPage(
529
+ I18n.getMessage("report.xxx.title"),
530
+ I18n.getMessage("report.xxx.sheet"),
531
+ XxxVO.class,
532
+ param.getExportCols(),
533
+ param.getPage(),
534
+ null,
535
+ () -> xxxService.pageList(param).getResultPage()
536
+ );
537
+
538
+ log.info("【导出】导出任务已启动");
262
539
  }
263
540
  ```
264
541
 
265
- ---
542
+ ### 2. 导出权限校验
543
+
544
+ ```java
545
+ @PostMapping("/export")
546
+ public void export(@RequestBody LeRequest<XxxPageParam> request) {
547
+ // 校验导出权限
548
+ if (!hasExportPermission()) {
549
+ throw new LeException("无导出权限");
550
+ }
551
+
552
+ // 启动导出任务
553
+ exportApi.startExcelExportTaskByPage(...);
554
+ }
555
+ ```
266
556
 
267
557
  ## 常见错误
268
558
 
269
- | 错误写法 | 正确写法 |
270
- |---------|---------|
271
- | `throw new ServiceException("msg")` | `throw new LeException("msg")` |
272
- | `MapstructUtils.convert(a, B.class)` | `BeanUtil.copyProperties(a, b)` |
273
- | `@RequestParam` 接收请求 | `@RequestBody LeRequest<T>` |
274
- | `import javax.validation.*` | `import jakarta.validation.*` |
275
- | 不写导出日志 | `log.info("【导出】开始导出...")` |
276
- | 同步导出不加 `@SneakyThrows` | 必须加(受检异常) |
559
+ | 错误写法 | 正确写法 | 说明 |
560
+ |---------|---------|------|
561
+ | `throw new ServiceException("msg")` | `throw new LeException("msg")` | leniu 项目异常类 |
562
+ | `MapstructUtils.convert(a, B.class)` | `BeanUtil.copyProperties(a, b)` | leniu 使用 Hutool |
563
+ | `@RequestParam` 接收请求 | `@RequestBody LeRequest<T>` | leniu 接口使用 LeRequest 包装 |
564
+ | `import javax.validation.*` | `import jakarta.validation.*` | JDK 21 + Spring Boot 3.x |
565
+ | 不写导出日志 | log.info 记录导出参数 | 便于排查导出问题 |
566
+ | `@NumberFormat("#,##0.00")` 用于金额字段 | `@ExcelProperty(converter = CustomNumberConverter.class)` | `@NumberFormat` 无法完成分→元转换,框架的 `CustomNumberConverter` 才能自动处理 |
567
+ | ⛔ `mapFunc` 参数手动做分→元转换 | ✅ VO 字段加 `converter = CustomNumberConverter.class` | 金额转换在 VO 注解层处理,不在 `startExcelExportTaskByPage` 的 mapFunc 里做 |
568
+ | ⛔ 手写 `EasyExcel.write()` + `List<List<Object>>` | ✅ 使用 `exportApi.startExcelExportTaskByPage()` | 禁止手动拼接行列数据 |
569
+ | ⛔ Service 里手动创建临时 File + `exportApi.createRecord()` | ✅ 使用 `exportApi.startExcelExportTaskByPage()` | 统一使用标准异步分页导出接口 |
570
+ | ⛔ VO 无 `@ExcelProperty` 注解 | ✅ 每个导出字段必须加 `@ExcelProperty("列头")` | 无注解字段不会被导出 |