---
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