在项目的根目录下创建:.cursor\skills\spring-admin-page-export
examples.md:
# 分页 + 导出:分步模板(**不写**环境包名)
**约定**:下列代码块**不出现**`package` 行,**不出现**项目内包路径的 `import`;生成/落地代码时,在仓库里**打开任意同层类**,复制其 `package` 与对 `PageQuery` / `ResultData` 等的 `import`。**类名、URL、常量**仍可用占位符(如 `Example*`)方便对照结构。
以下模板中类名、路径(如 `"/example-admin"`)仅示意;`Example*` 落地时换为业务名。字段**驼峰**;无魔法值。
---
## 0. 包名与模块(先读后写)
1. **Controller / ServiceImpl / *Wrapper* / Excel 行**:在**目标 Spring 模块**的 `src/main/java` 中任选一个现有 `*Controller` / `*Service` / `*Wrapper` / `*ExcelRow`,与其**同包**或按**同模块已有分层**建类;`*Wrapper` 与 `BaseEntityWrapper` 的**继承、static build** 须与**已有** `extends BaseEntityWrapper` 的类一致。
2. **PageQuery / Response**:在**实际放 DTO 的模块**中,与已有 `*PageQuery` **同级包习惯**;若 DTO 在另一模块,以该模块 `pom` 与现有一致为准。
3. **分页基类、统一返回、Wrapper**:从已有 `*PageQuery` / Controller **原样复制** `import` 与继承关系,或使用 IDE「转到定义」,**不手写**环境包名。
---
## 1. 查询 DTO
```java
// 在文件头声明: package(与同模块其他 *PageQuery 一致,技能中不预写)
// import: 分页基类、lombok、java.time 等,从**已有** *PageQuery* 原样复制 import 行
import java.time.LocalDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* 入参:某业务记录分页/导出查询。分页字段见父类;导出时忽略分页字段。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ExampleRecordPageQuery extends /* 简名须与下方 import 一致,从已有 *PageQuery* 复制整段 */ YourProjectPageQueryBase {
// 说明:把 YourProjectPageQueryBase 换成你仓库里真实父类简名;技能不提供该名的包路径
private String keyword;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAtBegin;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAtEnd;
}
```
**说明**:`YourProjectPageQueryBase` 仅为占位,**无**其 `import` 行;**须整段**从**当前仓库**已有 `*PageQuery` 复制父类简名、继承行与全部项目侧 `import`。
---
## 2. Controller
```java
// package 与同模块 *Controller 一致
// import: PageQuery、Response、ResultData、*Service 等,从同目录或兄弟 Controller 复制
// 以下为框架与 JDK;项目侧类型未列出
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping("/example-admin")
public class ExampleRecordAdminController {
private static final String XLSX_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private static final String EXPORT_FILENAME_TIME_PATTERN = "yyyyMMddHHmmss";
private static final String EXPORT_FILENAME_PREFIX = "example-records-";
private static final String XLSX_EXTENSION = ".xlsx";
private static final DateTimeFormatter EXPORT_FILENAME_TIME =
DateTimeFormatter.ofPattern(EXPORT_FILENAME_TIME_PATTERN);
// @Resource private *YourService* ...
// public ResultData<Page<*Response*>> pageRecords(*PageQuery* query) { ... }
// public void export(*PageQuery* query, HttpServletResponse response) throws IOException { ... }
}
```
在方法体中补全**你们项目**的 `ResultData`、`*PageQuery`、`*Service` 类型,并从已有 Controller **复制**响应头、日志文案风格。`throws IOException` 与 try-catch 策略同团队约定。
---
## 3. Service 接口
```java
// package: 与当前模块 *Service 接口* 同包
// import: Page, *PageQuery*, *Response*, OutputStream, IOException 等,从兄弟接口复制
// Page<...> 与 *PageQuery* / *Response* 为占位,替换为实际类型
public interface ExampleRecordAdminService {
// Page<ExampleRecordResponse> pageRecords(ExampleRecordPageQuery query);
// void exportRecordsExcel(ExampleRecordPageQuery query, OutputStream outputStream) throws IOException;
}
```
---
## 4. 业务 *Wrapper* 与 `BaseEntityWrapper`
与分页、导出**共用**的「实体 → 出参」映射,应落在**业务包装器**上,并继承工作区中已有的**抽象** `BaseEntityWrapper`(全限定名、模块依赖从「转到定义」与兄弟 `*Wrapper` 复制,技能不写包名)。
**基类能力(以你仓库中实际方法为准)**:`entityVO(E entity)`;`listVO(List<E>)`;`pageVO(Page<E>)` 将 `Page` 的 `records` 转为出参 `Page` 并保留 total/current/size。
**子类模式(与现有 *Wrapper* 同构;下列为结构示意)**:
```java
// package 与同模块其它 *Wrapper 一致
// import: BaseEntityWrapper、*Entity*、*Response*,从已有 Wrapper 复制
// public class ExampleEntityWrapper extends BaseEntityWrapper<YourEntity, YourResponse> {
//
// private ExampleEntityWrapper() { }
//
// public static ExampleEntityWrapper build() {
// return new ExampleEntityWrapper();
// }
//
// @Override
// public YourResponse entityVO(YourEntity entity) {
// YourResponse r = new YourResponse();
// // 字段一一 set,与 *Response* 一致;可附 JavaDoc
// return r;
// }
// }
```
**在 Service 分页方法中**(与已有实现一致):先 `Page<YourEntity> raw = yourMapper.selectPage(page, buildWrapper(query));`,再 `return ExampleEntityWrapper.build().pageVO(raw);`,**不要**在 Service 里再手写 `for` 把 `List<Entity>` 转成 `List<Response>` 若本仓库已统一由 `pageVO` 完成。
**在导出**中:对查到的每条 `YourEntity` 可 `YourWrapper.build().entityVO(entity)` 得 `YourResponse`(或与 Excel 行转换共用一套中间步骤),与分页字段含义保持一致。
---
## 5. Service 实现(要点)
- 分页:Mapper `selectPage` 得到 `Page<Entity>` 后,**`某EntityWrapper.build().pageVO(该 Page)`** 返回给接口层,与「第 4 节」一致。
- `EXPORT_MAX_ROWS`、sheet 名、时间格式**常量化**。
- `buildWrapper(query)` 与分页**共用**;导出 `selectList` 后 `LIMIT` 或等价限制。
- `FesodSheet.write`、Mapper、`@DS` 与同模块**已有**实现类对齐;在仓库中**按类名**搜一个最相近的 `*ServiceImpl` 作参照(**不要**从技能中抄路径或包名)。
---
## 6. Excel 行模型
```java
// package: 与当前模块 *ExcelRow* 同包
// import: Fesod 注解、lombok,从兄弟 Excel 行复制
import org.apache.fesod.sheet.annotation.ExcelProperty;
import lombok.Data;
@Data
public class ExampleRecordExcelRow {
@ExcelProperty("ID")
private String id;
@ExcelProperty("创建时间")
private String createdAt;
}
```
---
## 7. 编译检查
1. 目标模块的 `pom` 中依赖与**同功能已有**分页/导出类所在模块**同级**拉齐(MP、Fesod、Lombok、DTO 模块依赖等)。
2. 在父工程下对**当前改动的模块**执行 `compile`;**模块坐标**以父 `pom` 或 IDE 模块名为准,技能中不指定具体 artifact 名。
---
**禁止**:从本技能、旧对话中复制任何形如 `a.b.c.d` 的**项目包名**到代码;**一律**以打开文件时的 `package` 与 `import` 为准。
SKILL.md:
---
name: spring-admin-page-export
description: Implements Spring Boot admin list pagination and Excel (xlsx) export using MyBatis-Plus Page, BasePageRequest query DTO, Apache Fesod Sheet, ResultData, HttpServletResponse, and entity-to-Response conversion via BaseEntityWrapper and per-domain *Wrapper classes. Resolves all project-specific packages from the workspace. Use when adding GET /page and /export APIs, 后台列表分页, Excel 导出, or VO/Wrapper 转换.
---
# 后台「分页 + Excel 导出」双接口(Spring,对齐已有实现时)
在**目标 Web 模块**中新增**与现有同构**的一对接口时,按下列约定生成**可编译、可运行**的代码;**任何 `package`、项目内全限定名、多模块子工程名**均须在**打开当前仓库后**从已有类与 `pom` 解析,**技能不内置当前环境的包名、路径或类名**。
## 0. 包与模块:随当前项目环境
| 要确定的内容 | 做法 |
| --------------------------------- | ------------------------------------------------------------ |
| **Web 根包** | 在目标模块 `src/main/java` 下打开任意一个现有 `@RestController`;`package` 与同模块其它 Controller **同一前缀**;子包名(如 `controller`)依团队习惯。 |
| **DTO / Response 包** | 在**实际存放共享 DTO 的模块**的源码树中,对照已有 `*PageQuery`、`*Response` 的**包层级**与命名。 |
| **分页基类** | 打开现有 `*PageQuery`,复制其对分页父类的 `import` 与 `extends`;或在工作区搜索「分页查询基类」的常见命名模式,**仅用当前检到的全限定名**。 |
| **ResultData 等** | 从**同业务、同模块**已有类复制 `import`。 |
| **BaseEntityWrapper 与 *Wrapper** | 在工作区搜索**抽象** `BaseEntityWrapper` 与 `extends BaseEntityWrapper` 的**具体**包装类;`import` 以转到定义为准;与分页/导出**共用**同一条 `Entity → Response` 映射。 |
**找模板**:在**当前工作区**用符号搜索 / 全文搜索,定位**已存在**的「管理端列表分页 + 同条件 Excel 导出」或最接近的一组类(Controller、`*PageQuery`、**`*EntityWrapper`、BaseEntityWrapper**、Service 实现、Excel 行 DTO),按**同一分层与依赖**复刻;不要从技能中抄写任何**包名、模块路径、类全名**。
更完整的分步模板见 [examples.md](examples.md)。
---
## 1. 分层与职责
| 层次 | 职责 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| `*PageQuery` | 放在**与现有 DTO 相同模块与包习惯**中;继承**当前工程**的分页基类(全限定名以实参为准);`pageNum`/`pageSize` 与**驼峰**筛选;`LocalDateTime` 加 `@DateTimeFormat(pattern = "...")` |
| `*Response` | 列表/分页出参,**驼峰** + JavaDoc |
| `*EntityWrapper`(业务包装器,常放在 Web 或业务模块的 `wrapper` 等包) | 继承项目中的 **BaseEntityWrapper<Entity, Response>**;**私有构造** + **`public static build()`**;实现 `Response entityVO(Entity entity)` 做字段一一映射;**禁止**在多处复制粘贴相同映射逻辑。 |
| `*ExcelRow` | 放在与团队已有 Excel 行类**相同模块**;Fesod `@ExcelProperty` 列名 |
| `*Service` / `*ServiceImpl` | 分页:先 `Page<Entity> raw = mapper.selectPage(...)`,再 **` return YourEntityWrapper.build().pageVO(raw); `** 得到 `Page<Response>`;与导出**共用** `buildWrapper(query)`;导出 `selectList` + 行数上限 + 逐条或列表经 Wrapper/`entityVO` 再转 `*ExcelRow` + `FesodSheet.write(...).doWrite(...)` |
| `*Controller` | `GET .../page` 返回 `ResultData<Page<Response>>`;`GET .../export` 设响应头后 `export*(query, response.getOutputStream())` |
### 1.1 BaseEntityWrapper(工具约定)
在工作区搜索 **`class BaseEntityWrapper`** 或 **`abstract class BaseEntityWrapper`**,用 IDE「转到定义」确认其**所在模块与包**(本技能不预写路径)。该抽象类通常提供:
- `abstract V entityVO(E entity);` — 单条实体转出参。
- `listVO(List<E> list)` — 内部 `stream` 调用 `entityVO`。
- `pageVO(Page<E> pages)` — 把 MyBatis-Plus **`Page<E>`** 的 `records` 转为 **`Page<V>`**,并**保留** current、size、total 等分页元数据。
**分页接口** 的 Service 在 `selectPage` 之后,应优先 **`return 对应业务 Wrapper.build().pageVO(原始 Page)`**,使 Controller 拿到的 `data.records` 已是 `Response` 类型,与现有习惯一致。导出若需 `Response` 也优先 **`Wrapper.build().entityVO(entity)`**,再视需要折成 Excel 行,避免与分页两套映射不一致。
---
## 2. 分页接口
- 路径风格与**同 Controller** 中已有 `GetMapping` 一致(如 kebab-case + `/page` 后缀等)。
- 入参:单个 `*PageQuery`;返回:`ResultData.success(service.pageXxx(query))`;泛型为 MyBatis-Plus 的 `Page<...Response>`(框架以依赖为准)。
- 服务层:`new Page<>(query.getPageNum(), query.getPageSize())` + `selectPage` + 与导出共享的 `buildWrapper(query)`,**出参**经**业务 *Wrapper* 的 `pageVO`** 转为 `Page<Response>`。
---
## 3. 导出接口
- 同资源族 + `/export`;筛选与分页一致,文档中说明导出**忽略**分页字段(若 `PageQuery` 含分页父类)。
- 响应头:`Content-Type`(xlsx)、字符集、附件文件名(时间戳、前缀均**常量化**)。
- 服务层:与分页**同一** `buildWrapper`;**必须**有最大导出行数常量 + `LIMIT`(或安全等价实现)。
---
## 4. 无魔法值
导出行上限、时间格式、文件名前缀、sheet 名、业务码展示值等,一律 **private static final** 或枚举。
---
## 5. 异常与可运行性
- 导出处:日志带 `query`,禁止空 `catch`;与项目统一异常/响应策略一致。
- 依赖仅使用**各模块 `pom` 已声明**的库;多数据源时 `@DS` 等与原 Service 实现一致。
---
## 6. 风格检查清单
- [ ] 注释完整
- [ ] 驼峰命名
- [ ] 无业务魔法值
- [ ] 分页与导出共用 `buildWrapper`(`LambdaQueryWrapper` 等),导出有行数上限
- [ ] 分页 `Page` 的出参经 **`BaseEntityWrapper` 子类的 `pageVO`**,不手工拼 `List<Response>`(除非本仓库有明确例外)
- [ ] 已新增或复用与 `Entity` / `*Response` 成对的 `*EntityWrapper`(`entityVO` 与现有风格一致)
- [ ] `package` 与项目内 `import` **全部**来自当前环境,未从技能中抄写占位包名
---
## 7. 与责任链/其它技能的边界
本技能**仅**覆盖管理端**分页 + 同条件 Excel 导出**定型;不替代 [`chain-of-responsibility-expert`](../chain-of-responsibility-expert/SKILL.md) 等。
---
## 8. 附加资源
- 分层与常量化: [examples.md](examples.md)
- Fesod 与 Spring: <https://fesod.apache.org/zh-cn/docs/sheet/write/spring>