SpringBlade/.claude/skills/blade-design/references/backend.md
2026-04-17 18:50:28 +08:00

1005 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 后端代码生成参考
本文档包含 SpringBlade 开源版后端各层的完整代码模板。所有模板均来源于 SpringBlade 开源工程真实业务代码(参考 `modules/system`、`modules/desk` 及 Cloud 的 `blade-service/blade-system` 等)。
## 目录
- [一、Entity 实体类](#一entity-实体类)
- [二、DTO 数据传输对象](#二dto-数据传输对象)
- [三、VO 视图对象](#三vo-视图对象)
- [四、Excel 导出类](#四excel-导出类)
- [五、Mapper 接口](#五mapper-接口)
- [六、Mapper XML](#六mapper-xml)
- [七、Service 接口](#七service-接口)
- [八、ServiceImpl 实现](#八serviceimpl-实现)
- [九、Controller 控制器](#九controller-控制器)
- [十、Wrapper 数据转换器](#十wrapper-数据转换器)
- [十一、Feign Client 接口Cloud](#十一feign-client-接口cloud)
- [十二、Feign Client 实现Cloud](#十二feign-client-实现cloud)
---
## 一、Entity 实体类
> 开源版 Entity 命名**不带 `Entity` 后缀**(如 `User.java`、`Post.java`、`Notice.java`)。
### 模式ATenantEntity多租户业务表默认
```java
package org.springblade.modules.{module}.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.mp.base.TenantEntity;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* {中文名}实体类
*
* @author {author}
*/
@Data
@TableName("blade_{table_name}")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "{中文名}")
public class {Name} extends TenantEntity {
/**
* {字段注释}
*/
@Schema(description = "{字段注释}")
private {Type} {fieldName};
// --- 字段类型注解模式 ---
// Long 类型字段(防止前端精度丢失)
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "关联ID")
private Long refId;
// Date 类型字段
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "发布时间")
private Date releaseTime;
// Integer 字典字段(配合 Wrapper 翻译)
@Schema(description = "分类")
private Integer category;
// String 普通文本
@Schema(description = "标题")
private String title;
// String 大文本
@Schema(description = "内容")
private String content;
}
```
**TenantEntity 自动继承的字段**(不需要在子类中声明):
- `id` (Long) - 主键,@TableId(type = IdType.ASSIGN_ID),雪花 ID
- `tenantId` (String) - 租户 ID
- `createUser` (Long) - 创建人
- `createDept` (Long) - 创建部门
- `createTime` (Date) - 创建时间
- `updateUser` (Long) - 更新人
- `updateTime` (Date) - 更新时间
- `status` (Integer) - 业务状态,默认 1
- `isDeleted` (Integer) - 逻辑删除标记,@TableLogic默认 0
### 模式BBaseEntity跨租户业务表
```java
package org.springblade.modules.{module}.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.mp.base.BaseEntity;
/**
* {中文名}实体类
*
* @author {author}
*/
@Data
@TableName("blade_{table_name}")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "{中文名}")
public class {Name} extends BaseEntity {
@Schema(description = "{字段注释}")
private {Type} {fieldName};
}
```
**BaseEntity 继承字段**:与 TenantEntity 相同但不含 `tenantId`
### 模式CRaw Serializable原生模式
```java
package org.springblade.modules.{module}.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* {中文名}实体类
*
* @author {author}
*/
@Data
@TableName("blade_{table_name}")
@Schema(description = "{中文名}")
public class {Name} implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
// --- 业务字段 ---
@Schema(description = "{字段注释}")
private {Type} {fieldName};
// --- 审计字段(手动定义) ---
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建人", hidden = true)
private Long createUser;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "创建部门", hidden = true)
private Long createDept;
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "创建时间", hidden = true)
private Date createTime;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "更新人", hidden = true)
private Long updateUser;
@DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
@JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
@Schema(description = "更新时间", hidden = true)
private Date updateTime;
@Schema(description = "业务状态", hidden = true)
private Integer status;
@TableLogic
@Schema(description = "是否已删除", hidden = true)
private Integer isDeleted;
}
```
### Cloud 版本 Entity 差异
Cloud 中 Entity 放在 API 模块,包名不同:
```java
// Boot
package org.springblade.modules.{module}.entity;
// Cloud
package org.springblade.{module}.entity;
```
其余代码完全相同。
---
## 二、DTO 数据传输对象
### 标准 DTO继承 Entity
```java
package org.springblade.modules.{module}.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.modules.{module}.entity.{Name};
/**
* {中文名}数据传输对象
*
* @author {author}
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class {Name}DTO extends {Name} {
}
```
大多数情况下 DTO 为空类,仅作为扩展点。如有特定传输需求可添加字段。
### 精简 DTO仅特定字段
```java
package org.springblade.modules.{module}.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* {中文名}数据传输对象
*
* @author {author}
*/
@Data
public class {Name}DTO implements Serializable {
@Schema(description = "特定字段")
private String specificField;
}
```
---
## 三、VO 视图对象
### 标准 VO简单扩展
```java
package org.springblade.modules.{module}.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.modules.{module}.entity.{Name};
/**
* {中文名}视图对象
*
* @author {author}
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class {Name}VO extends {Name} {
/**
* 分类名称(由 Wrapper 通过字典翻译填充)
*/
@Schema(description = "分类名称")
private String categoryName;
}
```
对于每个需要字典翻译的字段,在 VO 中添加对应的 `{字段名}Name` 字段。
### 树形 VO实现 INode 接口)
用于 Menu、Dict、Dept 等树形结构:
```java
package org.springblade.modules.{module}.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tool.node.INode;
import org.springblade.modules.{module}.entity.{Name};
import java.util.ArrayList;
import java.util.List;
/**
* {中文名}视图对象(树形)
*
* @author {author}
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class {Name}VO extends {Name} implements INode<{Name}VO> {
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "主键ID")
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
@Schema(description = "父节点ID")
private Long parentId;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Schema(description = "子节点")
private List<{Name}VO> children;
@Schema(description = "是否有子节点")
private Boolean hasChildren;
@Schema(description = "上级名称")
private String parentName;
@Override
public List<{Name}VO> getChildren() {
if (this.children == null) {
this.children = new ArrayList<>();
}
return this.children;
}
}
```
---
## 四、Excel 导出类
```java
package org.springblade.modules.{module}.excel;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
import java.io.Serializable;
/**
* {中文名}导出对象
*
* @author {author}
*/
@Data
@ColumnWidth(25)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class {Name}Excel implements Serializable {
@ExcelProperty("{字段中文名}")
private {Type} {fieldName};
// 不导出审计字段createUser、createTime 等)
}
```
> 开源版使用 FastExcel`cn.idev.excel.*`,由 `blade-starter-excel` 引入)。
---
## 五、Mapper 接口
```java
package org.springblade.modules.{module}.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import org.springblade.modules.{module}.entity.{Name};
import org.springblade.modules.{module}.excel.{Name}Excel;
import org.springblade.modules.{module}.vo.{Name}VO;
import java.util.List;
/**
* {中文名} Mapper 接口
*
* @author {author}
*/
public interface {Name}Mapper extends BaseMapper<{Name}> {
/**
* 自定义分页查询
*/
List<{Name}VO> select{Name}Page(IPage page, {Name}VO {modelCode});
/**
* 导出数据查询
*/
List<{Name}Excel> export{Name}(@Param("ew") Wrapper<{Name}> queryWrapper);
}
```
**树形模板追加方法**
```java
/**
* 树形结构查询
*/
List<{Name}VO> tree(String tenantId);
```
---
## 六、Mapper XML
### 标准 CRUD
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.modules.{module}.mapper.{Name}Mapper">
<!-- 通用查询映射结果 -->
<resultMap id="{modelCode}ResultMap" type="org.springblade.modules.{module}.entity.{Name}">
<result column="id" property="id"/>
<!-- 每个业务字段一行映射 -->
<result column="{column_name}" property="{fieldName}"/>
<!-- 审计字段映射TenantEntity 模式需要) -->
<result column="create_user" property="createUser"/>
<result column="create_dept" property="createDept"/>
<result column="create_time" property="createTime"/>
<result column="update_user" property="updateUser"/>
<result column="update_time" property="updateTime"/>
<result column="status" property="status"/>
<result column="is_deleted" property="isDeleted"/>
</resultMap>
<!-- 自定义分页查询 -->
<select id="select{Name}Page" resultMap="{modelCode}ResultMap">
SELECT
t.*,
d.dict_value AS categoryName
FROM blade_{table_name} t
LEFT JOIN (SELECT * FROM blade_dict WHERE code = '{dictCode}') d
ON t.category = d.dict_key
WHERE t.is_deleted = 0
<if test="{modelCode}.tenantId != null and {modelCode}.tenantId != ''">
AND t.tenant_id = #{{{modelCode}.tenantId}}
</if>
<!-- 动态查询条件 -->
<if test="{modelCode}.{queryField} != null and {modelCode}.{queryField} != ''">
AND t.{query_column} LIKE CONCAT('%', #{{{modelCode}.{queryField}}}, '%')
</if>
<if test="{modelCode}.{eqField} != null">
AND t.{eq_column} = #{{{modelCode}.{eqField}}}
</if>
</select>
<!-- 导出查询 -->
<select id="export{Name}" resultType="org.springblade.modules.{module}.excel.{Name}Excel">
SELECT * FROM blade_{table_name}
${ew.customSqlSegment}
</select>
</mapper>
```
**注意事项**
- 如果有字典关联查询,在 select 中 LEFT JOIN blade_dict 表
- 动态条件中 `LIKE` 用于模糊查询字段,`=` 用于精确查询字段
- TenantEntity 模式需要 `tenant_id` 过滤条件
- resultMap 中每个数据库列名snake_case映射到 Java 属性camelCase
---
## 七、Service 接口
### TenantEntity / BaseEntity 模式
```java
package org.springblade.modules.{module}.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import org.springblade.modules.{module}.entity.{Name};
import org.springblade.modules.{module}.vo.{Name}VO;
/**
* {中文名} 服务接口
*
* @author {author}
*/
public interface I{Name}Service extends BaseService<{Name}> {
/**
* 自定义分页
*/
IPage<{Name}VO> select{Name}Page(IPage<{Name}VO> page, {Name}VO {modelCode});
}
```
### Raw Serializable 模式
```java
package org.springblade.modules.{module}.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.{module}.entity.{Name};
/**
* {中文名} 服务接口
*
* @author {author}
*/
public interface I{Name}Service extends IService<{Name}> {
}
```
---
## 八、ServiceImpl 实现
### TenantEntity / BaseEntity 模式
```java
package org.springblade.modules.{module}.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.modules.{module}.entity.{Name};
import org.springblade.modules.{module}.mapper.{Name}Mapper;
import org.springblade.modules.{module}.service.I{Name}Service;
import org.springblade.modules.{module}.vo.{Name}VO;
import org.springframework.stereotype.Service;
/**
* {中文名} 服务实现
*
* @author {author}
*/
@Service
public class {Name}ServiceImpl extends BaseServiceImpl<{Name}Mapper, {Name}> implements I{Name}Service {
@Override
public IPage<{Name}VO> select{Name}Page(IPage<{Name}VO> page, {Name}VO {modelCode}) {
{modelCode}.setTenantId(AuthUtil.getTenantId());
return page.setRecords(baseMapper.select{Name}Page(page, {modelCode}));
}
}
```
**注意**BaseEntity 模式下不需要 `setTenantId()` 那行。
### Raw Serializable 模式
```java
package org.springblade.modules.{module}.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.modules.{module}.entity.{Name};
import org.springblade.modules.{module}.mapper.{Name}Mapper;
import org.springblade.modules.{module}.service.I{Name}Service;
import org.springframework.stereotype.Service;
/**
* {中文名} 服务实现
*
* @author {author}
*/
@Service
public class {Name}ServiceImpl extends ServiceImpl<{Name}Mapper, {Name}> implements I{Name}Service {
}
```
---
## 九、Controller 控制器
### Boot 版本
```java
package org.springblade.modules.{module}.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.{module}.entity.{Name};
import org.springblade.modules.{module}.service.I{Name}Service;
import org.springblade.modules.{module}.vo.{Name}VO;
import org.springblade.modules.{module}.wrapper.{Name}Wrapper;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* {中文名} 控制器
*
* @author {author}
*/
@RestController
@AllArgsConstructor
@RequestMapping(AppConstant.APPLICATION_{MODULE}_NAME + "/{modelCode}")
@Tag(name = "{中文名}", description = "{中文名}接口")
public class {Name}Controller extends BladeController {
private final I{Name}Service {modelCode}Service;
/**
* 详情
*/
@GetMapping("/detail")
@ApiOperationSupport(order = 1)
@Operation(summary = "详情", description = "传入{modelCode}")
public R<{Name}VO> detail({Name} {modelCode}) {
{Name} detail = {modelCode}Service.getOne(Condition.getQueryWrapper({modelCode}));
return R.data({Name}Wrapper.build().entityVO(detail));
}
/**
* 分页列表
*/
@GetMapping("/list")
@ApiOperationSupport(order = 2)
@Operation(summary = "分页", description = "传入{modelCode}")
@PreAuth(RoleConstant.HAS_ROLE_ADMIN)
public R<IPage<{Name}VO>> list(@RequestParam Map<String, Object> {modelCode}, Query query) {
IPage<{Name}> pages = {modelCode}Service.page(
Condition.getPage(query),
Condition.getQueryWrapper({modelCode}, {Name}.class)
);
return R.data({Name}Wrapper.build().pageVO(pages));
}
/**
* 自定义分页
*/
@GetMapping("/page")
@ApiOperationSupport(order = 3)
@Operation(summary = "自定义分页", description = "传入{modelCode}")
public R<IPage<{Name}VO>> page({Name}VO {modelCode}, Query query) {
IPage<{Name}VO> pages = {modelCode}Service.select{Name}Page(Condition.getPage(query), {modelCode});
return R.data(pages);
}
/**
* 新增
*/
@PostMapping("/save")
@ApiOperationSupport(order = 4)
@Operation(summary = "新增", description = "传入{modelCode}")
public R save(@Valid @RequestBody {Name} {modelCode}) {
return R.status({modelCode}Service.save({modelCode}));
}
/**
* 修改
*/
@PostMapping("/update")
@ApiOperationSupport(order = 5)
@Operation(summary = "修改", description = "传入{modelCode}")
public R update(@Valid @RequestBody {Name} {modelCode}) {
return R.status({modelCode}Service.updateById({modelCode}));
}
/**
* 新增或修改
*/
@PostMapping("/submit")
@ApiOperationSupport(order = 6)
@Operation(summary = "新增或修改", description = "传入{modelCode}")
public R submit(@Valid @RequestBody {Name} {modelCode}) {
return R.status({modelCode}Service.saveOrUpdate({modelCode}));
}
/**
* 删除
*/
@PostMapping("/remove")
@ApiOperationSupport(order = 7)
@Operation(summary = "逻辑删除", description = "传入ids")
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.status({modelCode}Service.deleteLogic(Func.toLongList(ids)));
}
}
```
### Cloud 版本差异
Cloud 版本仅以下几处不同:
```java
// 1. 包名不同
package org.springblade.{module}.controller;
// 2. import 路径不同Entity/VO/Service 都在 org.springblade.{module} 下)
import org.springblade.{module}.entity.{Name};
import org.springblade.{module}.vo.{Name}VO;
import org.springblade.{module}.service.I{Name}Service;
// 3. RequestMapping 不需要服务名前缀
@RequestMapping("/{modelCode}")
```
### 无 Wrapper 版本
如不使用 Wrapperdetail 和 list 方法简化为:
```java
@GetMapping("/detail")
public R<{Name}> detail({Name} {modelCode}) {
{Name} detail = {modelCode}Service.getOne(Condition.getQueryWrapper({modelCode}));
return R.data(detail);
}
@GetMapping("/list")
public R<IPage<{Name}>> list(@RequestParam Map<String, Object> {modelCode}, Query query) {
IPage<{Name}> pages = {modelCode}Service.page(
Condition.getPage(query),
Condition.getQueryWrapper({modelCode}, {Name}.class)
);
return R.data(pages);
}
```
### Raw Serializable 模式的 remove 方法
```java
@PostMapping("/remove")
@ApiOperationSupport(order = 7)
@Operation(summary = "删除", description = "传入ids")
public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.status({modelCode}Service.removeBatchByIds(Func.toLongList(ids)));
}
```
### 权限注解使用规则
SpringBlade 开源版仅支持角色级权限:
| 场景 | 写法 |
|------|------|
| 仅管理员可访问(类级) | `@PreAuth(RoleConstant.HAS_ROLE_ADMIN)` 加在类上 |
| 仅管理员可访问(方法级) | `@PreAuth(RoleConstant.HAS_ROLE_ADMIN)` 加在方法上 |
| 隐藏 Swagger 文档 | `@Hidden`(来自 OpenAPI 3 |
| 全公开 | 不加任何权限注解 |
### 导出接口
```java
@GetMapping("/export-{modelCode}")
@ApiOperationSupport(order = 8)
@Operation(summary = "导出数据", description = "传入{modelCode}")
@PreAuth(RoleConstant.HAS_ROLE_ADMIN)
public void export{Name}(@RequestParam Map<String, Object> {modelCode}, HttpServletResponse response) {
QueryWrapper<{Name}> queryWrapper = Condition.getQueryWrapper({modelCode}, {Name}.class);
List<{Name}Excel> list = {modelCode}Service.export{Name}(queryWrapper);
ExcelUtil.export(response, "{中文名}数据", "{中文名}数据表", list, {Name}Excel.class);
}
```
---
## 十、Wrapper 数据转换器
```java
package org.springblade.modules.{module}.wrapper;
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.modules.system.cache.DictCache;
import org.springblade.modules.{module}.entity.{Name};
import org.springblade.modules.{module}.vo.{Name}VO;
/**
* {中文名}包装类,返回视图层所需的字段
*
* @author {author}
*/
public class {Name}Wrapper extends BaseEntityWrapper<{Name}, {Name}VO> {
public static {Name}Wrapper build() {
return new {Name}Wrapper();
}
@Override
public {Name}VO entityVO({Name} {modelCode}) {
{Name}VO {modelCode}VO = BeanUtil.copyProperties({modelCode}, {Name}VO.class);
// 字典翻译:将字典键值转换为可读名称
String categoryName = DictCache.getValue("{dictCode}", {modelCode}.getCategory());
{modelCode}VO.setCategoryName(categoryName);
return {modelCode}VO;
}
}
```
**常见翻译模式**
```java
// 1. 字典翻译
String categoryName = DictCache.getValue("{dictCode}", entity.getCategory());
// 2. 关联实体名称(通过缓存)
User user = UserCache.getUser(entity.getUserId());
if (Func.isNotEmpty(user)) {
vo.setUserName(user.getAccount());
}
// 3. 父节点名称(树形结构)
if (Func.equals(entity.getParentId(), BladeConstant.TOP_PARENT_ID)) {
vo.setParentName(BladeConstant.TOP_PARENT_NAME);
} else {
{Name} parent = {modelCode}Service.getById(entity.getParentId());
vo.setParentName(parent.getName());
}
```
**树形 Wrapper 追加方法**
```java
public List<{Name}VO> listNodeVO(List<{Name}> list) {
List<{Name}VO> collect = list.stream()
.map(entity -> BeanUtil.copyProperties(entity, {Name}VO.class))
.collect(Collectors.toList());
return ForestNodeMerger.merge(collect);
}
```
---
## 十一、Feign Client 接口Cloud
放在 API 模块中(`blade-service-api/blade-{module}-api`
```java
package org.springblade.{module}.feign;
import org.springblade.core.launch.constant.AppConstant;
import org.springblade.core.mp.support.BladePage;
import org.springblade.core.tool.api.R;
import org.springblade.{module}.entity.{Name};
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* {中文名} Feign 接口
*
* @author {author}
*/
@FeignClient(value = AppConstant.APPLICATION_{MODULE}_NAME)
public interface I{Name}Client {
String API_PREFIX = "/feign/client/{modelCode}";
String TOP = API_PREFIX + "/top";
/**
* 获取分页数据
*/
@GetMapping(TOP)
BladePage<{Name}> top(@RequestParam("current") Integer current, @RequestParam("size") Integer size);
}
```
---
## 十二、Feign Client 实现Cloud
放在 Service 模块中(`blade-service/blade-{module}`
```java
package org.springblade.{module}.feign;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.support.BladePage;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.{module}.entity.{Name};
import org.springblade.{module}.service.I{Name}Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* {中文名} Feign 实现
*
* @author {author}
*/
@Hidden
@RestController
@AllArgsConstructor
public class {Name}Client implements I{Name}Client {
private final I{Name}Service service;
@Override
@GetMapping(TOP)
public BladePage<{Name}> top(Integer current, Integer size) {
Query query = new Query();
query.setCurrent(current);
query.setSize(size);
IPage<{Name}> page = service.page(Condition.getPage(query));
return BladePage.of(page);
}
}
```
**Feign 实现要点**
- `@Hidden`:对 Swagger 文档隐藏(内部接口)
- `@RestController`:作为 HTTP 端点暴露
- 路径常量复用接口中定义的常量
---
## 附:完整 import 参考
### Entity 常用 import
```java
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.mp.base.BaseEntity; // BaseEntity
import org.springblade.core.mp.base.TenantEntity; // TenantEntity
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.format.annotation.DateTimeFormat;
```
### Controller 常用 import
```java
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.launch.constant.AppConstant; // Boot 路由前缀使用
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.RoleConstant;
import org.springblade.core.tool.utils.Func;
import org.springframework.web.bind.annotation.*;
```
### Service 常用 import
```java
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.secure.utils.AuthUtil;
import org.springframework.stereotype.Service;
```
### Wrapper 常用 import
```java
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.node.ForestNodeMerger;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.system.cache.DictCache; // 字典缓存Boot 路径)
```