--- name: open-admin description: 在业务项目中创建 CRUD 业务模块,遵循 open-admin 框架的 Entity→Repository→Service→Controller→前端页面→菜单配置的完整模式。适用于以 Maven JAR + npm 包方式引入 open-admin 的业务项目。 --- # open-admin — 业务模块创建指南 ## 适用范围 当业务项目已通过 Maven JAR(`io.github.jiangood:open-admin`)和 npm 包(`@jiangood/open-admin`)方式集成了 open-admin 框架,需要创建新的业务 CRUD 模块时使用。 ## 前提条件检查 开始之前,Claude 必须读取业务项目的以下文件确认集成状态: ### Maven `pom.xml` 中必须有: ```xml io.github.jiangood open-admin ${open-admin.version} ``` ### Spring Boot 配置 `application.yml` 已导入框架默认配置(必选): ```yaml spring: config: import: classpath:application-lib.yml ``` 同时检查 `@SpringBootApplication` 或 `@ComponentScan` 是否覆盖了业务项目自身的包扫描范围。业务项目需要确保自己的包(如 `com.mycompany.myproject`)被扫描到,框架的 `OpenAdminConfiguration` 只扫描 `io.github.jiangood.openadmin` 包。 ### npm `package.json` 中包含: ```json "dependencies": { "@jiangood/open-admin": "^2.0.0", "umi": "^4.0.0", "antd": "^6.0.0", "react": "^19.0.0", ... } ``` 并确认 `config/config.js` 通过 `getPluginDir()` 机制使用了框架插件(即引用了 `@jiangood/open-admin/config`)。 ### 目录结构检查 ``` 业务项目 src/ 下应包含: main/java/com/xxx/ # Java 源码 main/resources/ # 配置资源 config/ # 菜单/字典 YAML(可选,无则使用框架默认) main/resources/application.yml ``` ## 第一步:需求确认 向开发者确认以下信息: 1. **业务实体名称**:中英文名(如"客户 / Customer") 2. **字段列表**:每个字段的名称、类型(String / Integer / BigDecimal / Boolean / LocalDateTime / 枚举)、是否必填、是否作为查询条件、是否为字典项 3. **权限规划**:增删改查分别用什么权限 code(如 `biz-customer:read`、`biz-customer:create`、`biz-customer:update`、`biz-customer:delete` 等) 4. **菜单位置**:顶级菜单还是挂在现有菜单下 ## 第二步:后端模块创建 ### 命名约定 | 元素 | 命名规则 | 示例 | |------|---------|------| | 基础包 | `{groupId}.{project}` | `com.mycompany.myproject` | | 模块包 | `{base}.modules.{module}` | `com.mycompany.myproject.modules.customer` | | 实体类 | `{Entity}` | `Customer` | | 数据库表 | 小写、下划线分隔、`biz_` 前缀 | `biz_customer` | | 请求路径 | `admin/{kebab-module}` | `admin/customer` | | 权限前缀 | `{kebab-module}:{action}` | `customer:read`, `customer:create`, `customer:update`, `customer:delete` | ### Entity - 包:`{base}.modules.{module}.entity.{Entity}` - 继承 `io.github.jiangood.openadmin.framework.data.BaseEntity`(自带 id/UUIDv7、createTime、updateTime、createBy、updateBy、delFlag、remark) - `@Table(name = "biz_xxx")` 指定物理表名 - `@Getter` `@Setter` `@FieldNameConstants`(Lombok) - Java 21 的 `String` 类型字段不需要 `@Column`(Hibernate 自动驼峰转下划线),除非需要指定 `length` 或 `nullable` - 校验用 `jakarta.validation` 注解(`@NotBlank`、`@NotNull`、`@Size`) - 枚举字段使用框架 `BaseEnum` 接口 + `EnumConverter`(参考 `io.github.jiangood.openadmin.framework.enums`) - 可选:`@Remark("字段说明")` 来自 `io.github.jiangood.openadmin.util.annotation.Remark` ```java package com.mycompany.myproject.modules.customer.entity; import io.github.jiangood.openadmin.framework.data.BaseEntity; import io.github.jiangood.openadmin.util.annotation.Remark; import jakarta.persistence.Entity; import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.Setter; import lombok.experimental.FieldNameConstants; @Remark("客户") @Entity @Getter @Setter @FieldNameConstants @Table(name = "biz_customer") public class Customer extends BaseEntity { private static final long serialVersionUID = 1L; @NotBlank @Remark("客户名称") @Size(max = 100) private String name; @Remark("联系人") @Size(max = 50) private String contact; @Remark("联系电话") @Size(max = 20) private String phone; @NotNull @Remark("状态") private Boolean enabled; } ``` ### Repository - 包:`{base}.modules.{module}.repository.{Entity}Repository` - 继承 `io.github.jiangood.openadmin.framework.data.BaseRepository` - 无需额外方法,通用 CRUD + Spec 动态查询 + 分页由 BaseRepository 提供 - 复杂查询用 `Spec` 构建(`io.github.jiangood.openadmin.framework.data.specification.Spec`) ```java package com.mycompany.myproject.modules.customer.repository; import com.mycompany.myproject.modules.customer.entity.Customer; import io.github.jiangood.openadmin.framework.data.BaseRepository; import org.springframework.stereotype.Repository; @Repository public interface CustomerRepository extends BaseRepository { } ``` ### Service - 包:`{base}.modules.{module}.service.{Entity}Service` - 继承 `io.github.jiangood.openadmin.framework.data.BaseService` - 构造器注入 Repository(`super(repository)`) - 通用方法由 BaseService 提供:`page()`、`list()`、`get()`、`save()`、`update()`、`deleteById()` 等 - 自定义业务逻辑在此层添加 ```java package com.mycompany.myproject.modules.customer.service; import com.mycompany.myproject.modules.customer.entity.Customer; import com.mycompany.myproject.modules.customer.repository.CustomerRepository; import io.github.jiangood.openadmin.framework.data.BaseService; import org.springframework.stereotype.Service; @Service public class CustomerService extends BaseService { public CustomerService(CustomerRepository customerRepository) { super(customerRepository); } } ``` ### Controller - 包:`{base}.modules.{module}.controller.{Entity}Controller` - `@RestController` + `@RequestMapping("admin/{kebab-module}")` - `@RequiredArgsConstructor` 构造器注入 Service - `@HasPermission("{module}:{action}")` 权限控制 - 统一返回 `io.github.jiangood.openadmin.util.dto.AjaxResult` 标准 5 个端点: | 端点 | 方法 | URL | 权限 | 说明 | |------|------|-----|------|------| | 分页查询 | `@RequestMapping("page")` | `admin/{module}/page` | `{module}:read` | 支持 searchText 模糊搜索 + Pageable | | 详情 | `@GetMapping("info/{id}")` | `admin/{module}/info/{id}` | `{module}:read` | 返回单条记录 | | 创建 | `@PostMapping("create")` | `admin/{module}/create` | `{module}:create` | @RequestBody @Valid | | 更新 | `@PostMapping("update")` | `admin/{module}/update` | `{module}:update` | @RequestBody @Valid | | 删除 | `@PostMapping("delete")` | `admin/{module}/delete` | `{module}:delete` | @RequestBody IdReq | | 选项列表 | `@GetMapping("options")` | `admin/{module}/options` | `{module}:read` | 下拉框数据源(非必选) | ```java package com.mycompany.myproject.modules.customer.controller; import com.mycompany.myproject.modules.customer.entity.Customer; import com.mycompany.myproject.modules.customer.service.CustomerService; import io.github.jiangood.openadmin.framework.data.specification.Spec; import io.github.jiangood.openadmin.framework.perm.HasPermission; import io.github.jiangood.openadmin.util.dto.AjaxResult; import io.github.jiangood.openadmin.util.dto.IdReq; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; import java.util.List; import io.github.jiangood.openadmin.util.dto.antd.Option; @RestController @RequestMapping("admin/customer") @RequiredArgsConstructor public class CustomerController { private final CustomerService service; @HasPermission("customer:read") @RequestMapping("page") public AjaxResult page(String searchText, @PageableDefault(direction = Sort.Direction.DESC, sort = "updateTime") Pageable pageable) { Spec q = Spec.of().orLike(searchText, "name", "contact"); Page page = service.findAll(q, pageable); return AjaxResult.ok().data(page); } @HasPermission("customer:read") @GetMapping("info/{id}") public AjaxResult info(@PathVariable String id) { return service.findById(id) .map(c -> AjaxResult.ok().data(c)) .orElse(AjaxResult.fail().msg("记录不存在")); } @HasPermission("customer:create") @PostMapping("create") public AjaxResult create(@Valid @RequestBody Customer input) { service.save(input, null); return AjaxResult.ok().msg("创建成功"); } @HasPermission("customer:update") @PostMapping("update") public AjaxResult update(@Valid @RequestBody Customer input) { service.save(input, null); return AjaxResult.ok().msg("更新成功"); } @HasPermission("customer:delete") @PostMapping("delete") public AjaxResult delete(@Valid @RequestBody IdReq req) { service.deleteById(req.getId()); return AjaxResult.ok().msg("删除成功"); } @HasPermission("customer:read") @GetMapping("options") public AjaxResult options(String searchText) { Spec q = Spec.of().orLike(searchText, "name"); List list = service.findAll(q, Sort.by("name")); List