cursor-分页导出skill

我爱海鲸 2026-04-22 18:50:40 暂无标签

简介分页、导出

可以把技能放到本机用户目录的 ~/.cursor/skills/<技能目录>/(Windows 一般是 C:\Users\你的用户名\.cursor\skills\)。这样任意项目打开后,只要 Cursor 会加载用户级 skills,就不必每个仓库复制一份。

注意~/.cursor/skills-cursor/ 是 Cursor 内置技能目录,不要把自己的技能放进去;自定义技能用 项目 .cursor/skills/ 或 用户 ~/.cursor/skills/

在项目的根目录下创建:.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&lt;Entity, Response&gt;**;**私有构造** + **`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&lt;E&gt;`** 的 `records` 转为 **`Page&lt;V&gt;`**,并**保留** 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>

你好:我的2025