Merge pull request #400 from mfish-qf/develop

feat(workflow): 工作流会签逻辑完善
This commit is contained in:
mfish
2026-05-08 21:09:07 +08:00
committed by GitHub
218 changed files with 4557 additions and 99 deletions
+59
View File
@@ -0,0 +1,59 @@
---
name: comment-checker
description: 检查并补充Java代码中的缺失注释。扫描项目中的Java文件,识别缺失JavaDoc注释的类、方法和字段,并根据代码逻辑自动生成规范的中文注释。
tools: Read, Grep, Glob, Edit
---
# 角色定义
你是一个专业的Java代码注释补充专家,专注于为缺失注释的代码添加规范的JavaDoc注释。
## 工作流程
1. 扫描指定目录下的所有Java文件
2. 识别缺失注释的位置(类、方法、字段)
3. 分析代码逻辑,生成合适的注释内容
4. 使用search_replace工具补充注释
5. 验证修改后的代码
## 注释规范
### 类注释
```java
/**
* @description: 类的功能描述
* @author: mfish
* @date: YYYY/MM/dd
*/
```
### 方法注释
```java
/**
* 方法功能描述
*
* @param param1 参数1说明
* @param param2 参数2说明
* @return 返回值说明
* @throws ExceptionType 异常说明
*/
```
### 字段注释
```java
/** 字段用途说明 */
```
## 处理优先级
1. 优先处理public类和方法
2. 优先处理Service接口和Controller类
3. 优先处理API模块中的Feign接口
4. 复杂业务逻辑方法必须添加注释
## 注意事项
- 使用中文编写注释
- 保持与现有代码风格一致
- 不要修改已有注释的内容
- 确保注释准确描述代码功能
+622
View File
@@ -0,0 +1,622 @@
---
name: crud-generator
description: 为 mfish-nocode-pro 项目生成标准增删改查(CRUD)代码,包括 Entity、Req、Mapper、Service、ServiceImpl、Controller 六层结构。当用户说"帮我生成增删改查"、"新增一个模块"、"生成CRUD代码"时使用此 skill。
---
# CRUD 代码生成器
## 项目架构概览
```
mf-api/mf-xxx-api/
└── src/main/java/cn/com/mfish/xxx/api/entity/ # API 层实体(跨服务共享)
mf-business/mf-xxx/
└── src/main/java/cn/com/mfish/xxx/
├── controller/ # Controller 层
├── entity/ # 业务实体
├── mapper/ # Mapper 接口
├── req/ # 请求参数类
└── service/
├── XxxService.java
└── impl/XxxServiceImpl.java
```
## 技术栈约定
- ORM: **MyBatis-Plus**`BaseMapper<T>``ServiceImpl<M,T>``IService<T>`
- 分页: **PageHelper**`PageHelper.startPage(pageNum, pageSize)`
- 权限: `@RequiresPermissions("模块:功能:操作")`insert/update/delete/query/export
- 日志: `@Log(title = "xxx-操作", operateType = OperateType.INSERT/UPDATE/DELETE)`
- 返回值: `Result<T>``Result<PageResult<T>>``Result<Boolean>`
- 文档: SpringDoc `@Tag``@Operation``@Parameter`
- ID类型: String UUID → `@TableId(type = IdType.ASSIGN_UUID)`;数值自增 → `@TableId(type = IdType.AUTO)`
- 基类: `BaseEntity<T>`(含 id、createBy、createTime、updateBy、updateTime),T 与 ID 类型一致
- 导出: `ExcelUtils.write(fileName, list)`(来自 `cn.com.mfish.common.core.utils.excel.ExcelUtils`
- 字符串判空:`StringUtils.isEmpty()`(来自 `cn.com.mfish.common.core.utils.StringUtils`
- **Swagger 注解**: 所有 Entity、Req 类必须添加 `@Schema` 注解(类级和字段级)
---
## 生成步骤
### 第一步:收集信息
询问用户(如未提供):
1. **模块名**(如:order、product
2. **中文名称**(如:订单、商品)
3. **数据表名**(如:sys_order
4. **字段列表**:字段名、类型、中文描述、是否必填
5. **权限前缀**(如:`sys:order`
6. **包路径**(如:`cn.com.mfish.sys`
7. **数据权限**(可选):是否需要按租户/用户/角色/组织过滤数据,表中需有对应字段
---
### 第二步:生成各层代码
按以下顺序生成,文件路径基于 `mf-business/mf-xxx/src/main/java/` 目录。
#### 1. Entity 实体类
```java
package {包路径}.entity;
import cn.com.mfish.common.core.entity.BaseEntity;
import cn.idev.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
// 有 Date 类型字段时引入:
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
// 有 BigDecimal 类型字段时引入:
import java.math.BigDecimal;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
@Data
@TableName("{表名}")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "{表名}对象 {中文名称}")
public class {类名} extends BaseEntity<String> {
// String UUID 主键:
@ExcelProperty("唯一ID")
@Schema(description = "唯一ID")
@TableId(type = IdType.ASSIGN_UUID)
@Accessors(chain = true)
private String id;
// 数值自增主键(替换上面的 id 声明):
// @TableId(type = IdType.AUTO)
// @Accessors(chain = true)
// private Integer id;
// 普通字段:
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private {类型} {字段名};
// 日期字段(DATE 类型):
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private Date {字段名};
// 日期时间字段(DATETIME 类型):
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty("{字段注释}")
@Schema(description = "{字段注释}")
private Date {字段名};
}
```
#### 2. Req 请求参数类
```java
package {包路径}.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
// 有 Date 类型搜索字段时引入:
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
@Data
@Accessors(chain = true)
@Schema(description = "{中文名称}请求参数")
public class Req{类名} {
// 普通搜索字段:
@Schema(description = "{字段注释}")
private {类型} {字段名};
// DATE 类型搜索字段:
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Schema(description = "{字段注释}")
private Date {字段名};
// DATETIME 类型搜索字段:
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "{字段注释}")
private Date {字段名};
}
```
#### 3. Mapper 接口 + XML
```java
package {包路径}.mapper;
import {包路径}.entity.{类名};
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
public interface {类名}Mapper extends BaseMapper<{类名}> {
// 如有复杂查询,声明自定义方法,并对应 XML
}
```
对应 XML 文件(`resources/mapper/{类名}Mapper.xml`):
```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="{包路径}.mapper.{类名}Mapper">
</mapper>
```
#### 4. Service 接口
```java
package {包路径}.service;
import cn.com.mfish.common.core.web.PageResult;
import cn.com.mfish.common.core.web.ReqPage;
import cn.com.mfish.common.core.web.Result;
import {包路径}.entity.{类名};
import {包路径}.req.Req{类名};
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.IOException;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
public interface {类名}Service extends IService<{类名}> {
/** 分页列表查询 */
Result<PageResult<{类名}>> queryPageList(Req{类名} req{类名}, ReqPage reqPage);
/** 添加 */
Result<{类名}> add({类名} {变量名});
/** 编辑 */
Result<{类名}> edit({类名} {变量名});
/** 通过id删除 */
Result<Boolean> delete(String id);
/** 批量删除(ids 逗号分隔) */
Result<Boolean> deleteBatch(String ids);
/** 通过id查询 */
Result<{类名}> queryById(String id);
/** 导出 */
void export(Req{类名} req{类名}, ReqPage reqPage) throws IOException;
}
```
> 若 ID 为数值型(如 `Integer`),将 `String id` 改为对应类型。
#### 5. ServiceImpl 实现类
```java
package {包路径}.service.impl;
import cn.com.mfish.common.core.utils.StringUtils;
import cn.com.mfish.common.core.utils.excel.ExcelUtils;
import cn.com.mfish.common.core.web.PageResult;
import cn.com.mfish.common.core.web.ReqPage;
import cn.com.mfish.common.core.web.Result;
import {包路径}.entity.{类名};
import {包路径}.mapper.{类名}Mapper;
import {包路径}.req.Req{类名};
import {包路径}.service.{类名}Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
@Service
public class {类名}ServiceImpl extends ServiceImpl<{类名}Mapper, {类名}> implements {类名}Service {
@Override
public Result<PageResult<{类名}>> queryPageList(Req{类名} req{类名}, ReqPage reqPage) {
return Result.ok(new PageResult<>(queryList(req{类名}, reqPage)), "{中文名称}-查询成功!");
}
private List<{类名}> queryList(Req{类名} req{类名}, ReqPage reqPage) {
PageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize());
LambdaQueryWrapper<{类名}> lambdaQueryWrapper = new LambdaQueryWrapper<{类名}>()
// String 类型字段用 StringUtils.isEmpty 判断:
.like(!StringUtils.isEmpty(req{类名}.get{字段}()), {类名}::get{字段}, req{类名}.get{字段}())
// 非 String 类型字段用 null != xxx 判断:
.eq(null != req{类名}.get{字段}(), {类名}::get{字段}, req{类名}.get{字段}());
return list(lambdaQueryWrapper);
}
@Override
public Result<{类名}> add({类名} {变量名}) {
if (save({变量名})) {
return Result.ok({变量名}, "{中文名称}-添加成功!");
}
return Result.fail({变量名}, "错误:{中文名称}-添加失败!");
}
@Override
public Result<{类名}> edit({类名} {变量名}) {
if (updateById({变量名})) {
return Result.ok({变量名}, "{中文名称}-编辑成功!");
}
return Result.fail({变量名}, "错误:{中文名称}-编辑失败!");
}
@Override
public Result<Boolean> delete(String id) {
if (removeById(id)) {
return Result.ok(true, "{中文名称}-删除成功!");
}
return Result.fail(false, "错误:{中文名称}-删除失败!");
}
@Override
public Result<Boolean> deleteBatch(String ids) {
if (removeByIds(Arrays.asList(ids.split(",")))) {
return Result.ok(true, "{中文名称}-批量删除成功!");
}
return Result.fail(false, "错误:{中文名称}-批量删除失败!");
}
@Override
public Result<{类名}> queryById(String id) {
{类名} {变量名} = getById(id);
return Result.ok({变量名}, "{中文名称}-查询成功!");
}
@Override
public void export(Req{类名} req{类名}, ReqPage reqPage) throws IOException {
// swagger 调用有问题,使用 postman 测试
ExcelUtils.write("{中文名称}_" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()), queryList(req{类名}, reqPage));
}
}
```
**搜索条件 LambdaQueryWrapper 规则:**
- `String` 类型字段 → `.like(!StringUtils.isEmpty(req.getXxx()), Entity::getXxx, req.getXxx())`(模糊) 或 `.eq(!StringUtils.isEmpty(req.getXxx()), Entity::getXxx, req.getXxx())`(精确)
-`String` 类型字段 → `.eq(null != req.getXxx(), Entity::getXxx, req.getXxx())`
#### 6. Controller 控制器
```java
package {包路径}.controller;
import cn.com.mfish.common.core.enums.OperateType;
import cn.com.mfish.common.core.web.PageResult;
import cn.com.mfish.common.core.web.ReqPage;
import cn.com.mfish.common.core.web.Result;
import cn.com.mfish.common.log.annotation.Log;
import cn.com.mfish.common.oauth.annotation.RequiresPermissions;
import {包路径}.entity.{类名};
import {包路径}.req.Req{类名};
import {包路径}.service.{类名}Service;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
@Slf4j
@Tag(name = "{中文名称}")
@RestController
@RequestMapping("/{变量名}")
public class {类名}Controller {
@Resource
private {类名}Service {变量名}Service;
/**
* 分页列表查询
*/
@Operation(summary = "{中文名称}-分页列表查询", description = "{中文名称}-分页列表查询")
@GetMapping
@RequiresPermissions("{权限前缀}:query")
// 需要数据权限时加:@DataScope(table = "{表名}", type = DataScopeType.Tenant)
public Result<PageResult<{类名}>> queryPageList(Req{类名} req{类名}, ReqPage reqPage) {
return {变量名}Service.queryPageList(req{类名}, reqPage);
}
/**
* 添加
*/
@Log(title = "{中文名称}-添加", operateType = OperateType.INSERT)
@Operation(summary = "{中文名称}-添加")
@PostMapping
@RequiresPermissions("{权限前缀}:insert")
public Result<{类名}> add(@RequestBody {类名} {变量名}) {
return {变量名}Service.add({变量名});
}
/**
* 编辑
*/
@Log(title = "{中文名称}-编辑", operateType = OperateType.UPDATE)
@Operation(summary = "{中文名称}-编辑")
@PutMapping
@RequiresPermissions("{权限前缀}:update")
public Result<{类名}> edit(@RequestBody {类名} {变量名}) {
return {变量名}Service.edit({变量名});
}
/**
* 通过id删除
*/
@Log(title = "{中文名称}-通过id删除", operateType = OperateType.DELETE)
@Operation(summary = "{中文名称}-通过id删除")
@DeleteMapping("/{id}")
@RequiresPermissions("{权限前缀}:delete")
public Result<Boolean> delete(@Parameter(name = "id", description = "唯一性ID") @PathVariable String id) {
return {变量名}Service.delete(id);
}
/**
* 批量删除
*/
@Log(title = "{中文名称}-批量删除", operateType = OperateType.DELETE)
@Operation(summary = "{中文名称}-批量删除")
@DeleteMapping("/batch/{ids}")
@RequiresPermissions("{权限前缀}:delete")
public Result<Boolean> deleteBatch(@Parameter(name = "ids", description = "唯一性ID") @PathVariable String ids) {
return {变量名}Service.deleteBatch(ids);
}
/**
* 通过id查询
*/
@Operation(summary = "{中文名称}-通过id查询")
@GetMapping("/{id}")
@RequiresPermissions("{权限前缀}:query")
// 需要数据权限时加:@DataScope(table = "{表名}", type = DataScopeType.Tenant)
public Result<{类名}> queryById(@Parameter(name = "id", description = "唯一性ID") @PathVariable String id) {
return {变量名}Service.queryById(id);
}
/**
* 导出
*/
@Operation(summary = "导出{中文名称}", description = "导出{中文名称}")
@GetMapping("/export")
@RequiresPermissions("{权限前缀}:export")
public void export(Req{类名} req{类名}, ReqPage reqPage) throws IOException {
{变量名}Service.export(req{类名}, reqPage);
}
}
```
> **Controller 核心原则**Controller 只做路由转发,**所有业务逻辑在 Service 中实现**Controller 方法体直接 `return xxxService.方法()`。
---
## 命名约定
| 占位符 | 说明 | 示例 |
|--------|------|------|
| `{类名}` | PascalCase 类名 | `SysOrder` |
| `{变量名}` | camelCase 变量名(也用作 @RequestMapping 路径) | `sysOrder` |
| `{表名}` | 数据库表名(下划线) | `sys_order` |
| `{权限前缀}` | 权限标识符(`{apiPrefix}:{变量名}` | `sys:sysOrder` |
| `{包路径}` | Java 包名 | `cn.com.mfish.sys` |
---
## 注意事项
1. **业务逻辑分层**Controller 只做路由转发,`return xxxService.方法()` 即可,业务实现全在 ServiceImpl
2. **有业务校验逻辑时**:在 ServiceImpl 中提取 `verifyXxx()` 私有方法,并在 add/edit 中调用
3. **有关联删除时**:在 ServiceImpl 中用 `@Transactional` 处理级联操作
4. **复杂查询**:在 Mapper 中声明方法,在 `resources/mapper/` 下创建对应 XML 文件
5. **模块位置**:根据功能归属放到对应的 `mf-business/mf-xxx` 子模块
6. **API 层实体**:如需跨服务调用,将实体放到 `mf-api/mf-xxx-api` 模块
7. **软删除场景**:实体增加 `delFlag` 字段,删除时 `updateById(new Xxx().setId(id).setDelFlag(1))`,查询时 `.eq(Xxx::getDelFlag, 0)`
8. **租户隔离场景**:实体增加 `tenantId` 字段,Controller 的写操作加 `@DataScope(table="表名", type=DataScopeType.Tenant)` 注解,Service 中用 `AuthInfoUtils.getCurrentTenantId()` 写入,修改时 `setTenantId(null)` 避免覆盖
9. **数值型主键**`BaseEntity<Integer>``@TableId(type = IdType.AUTO)`Service/Controller 中 `id` 参数类型相应改为 `Integer`
10. **Swagger 注解强制要求**:所有 Entity、Req 类必须添加 `@Schema` 注解,包括:
- **类级别**`@Schema(description = "描述信息", name = "类名")`
- **字段级别**:每个字段都需添加 `@Schema(description = "字段描述")`
- 便于生成完整的 API 文档和 Swagger UI 展示
11. **异常处理规范**:人为抛出的业务异常统一采用 `MyRuntimeException` 处理
- **引入包**`import cn.com.mfish.common.core.exception.MyRuntimeException;`
- **使用场景**:业务校验失败、数据不存在、权限不足等业务异常情况
- **示例代码**
```java
// 数据不存在校验
if (entity == null) {
throw new MyRuntimeException("错误:记录不存在!");
}
// 重复性校验
if (baseMapper.exists(new LambdaQueryWrapper<Entity>()
.eq(Entity::getField, value))) {
throw new MyRuntimeException("错误:已存在,不能重复提交!");
}
// 状态校验
if (!"active".equals(entity.getStatus())) {
throw new MyRuntimeException("错误:记录状态不正确!");
}
```
- **消息格式**:建议以 `"错误:"` 开头,便于前端统一处理和识别
- **不要使用**:避免直接使用 `RuntimeException` 或其他自定义异常
12. **安全规范 - 异常信息不暴露给前端**:
- **核心原则**:返回给前端的错误消息必须是友好的、通用的提示,不能包含具体的异常堆栈或技术细节
- **错误示例**`return Result.fail(false, "错误:配置不正确," + e.getMessage());` ❌
- **正确示例**`return Result.fail(false, "错误:配置不正确,请检查配置是否完整且符合规范");` ✅
- **日志记录**:详细的异常信息应通过 `log.error()` 记录到日志文件中,便于开发人员排查
- **实现模式**
```java
try {
// 业务逻辑
someOperation();
} catch (Exception e) {
// 详细异常信息记录到日志
log.error("操作失败:{}", e.getMessage(), e);
// 返回给前端的是友好的提示信息
return Result.fail(false, "错误:操作失败,请检查配置是否正确");
}
```
- **适用范围**:所有 catch 块中返回给前端的错误消息都必须遵循此规范
---
## 数据权限控制
数据权限通过 `@DataScope` / `@DataScopes` 注解在 **Controller 查询方法** 上声明,框架自动在 SQL 中追加过滤条件。
> **重要约束**
> - 注解只能用于**查询方法**,不能用于新增/修改/删除
> - 注解加在 **Controller 层**,不在 Service 层
> - 表中须有对应的权限字段:租户 `tenant_id`、用户 `user_id`、角色 `role_id`、组织 `org_id`
### DataScopeType 权限类型
| 类型 | 说明 | 表中需要字段 |
|------|------|-----------|
| `DataScopeType.Tenant` | 按当前租户过滤 | `tenant_id` |
| `DataScopeType.User` | 按当前用户过滤 | `user_id` |
| `DataScopeType.Role` | 按当前角色过滤 | `role_id` |
| `DataScopeType.Org` | 按当前组织及下级过滤 | `org_id` |
| `DataScopeType.None` | 不过滤(默认) | — |
### 常用场景示例
**1. 单表租户过滤(最常用)**
```java
@GetMapping
@RequiresPermissions("{权限前缀}:query")
@DataScope(table = "{表名}", type = DataScopeType.Tenant)
public Result<PageResult<{类名}>> queryPageList(Req{类名} req, ReqPage reqPage) { ... }
```
**2. 单表组织过滤**
```java
@DataScope(table = "{表名}", type = DataScopeType.Org)
```
**3. 固定角色值过滤(指定具体角色编码)**
```java
@DataScope(table = "{表名}", type = DataScopeType.Role, values = {"manage", "superAdmin"})
```
**4. 排除公开数据(满足排除条件的记录不被过滤,始终可查)**
```java
@DataScope(table = "{表名}", type = DataScopeType.Tenant, excludes = "is_public=1")
```
**5. 忽略条件(优先级最高,满足时其他权限条件全部失效)**
```java
// 变量值从 ServletRequest.getParameter 中取,为空时不使用忽略条件
@DataScope(table = "{表名}", ignores = "share_token=#{_shareToken} and share_end_time>=now()")
```
**6. 多表组合权限(使用 @DataScopes**
```java
import cn.com.mfish.common.oauth.annotation.DataScopes;
@DataScopes({
@DataScope(table = "{主表名}", type = DataScopeType.Tenant),
@DataScope(table = "{关联表名}", type = DataScopeType.Tenant, excludes = "is_public=1")
})
```
**7. 租户 + 角色组合过滤**
```java
@DataScopes({
@DataScope(table = "{表名}", type = DataScopeType.Tenant),
@DataScope(table = "{表名}", type = DataScopeType.Role, values = {"manage", "superAdmin"})
})
```
### 所需 Import
```java
import cn.com.mfish.common.oauth.annotation.DataScope;
import cn.com.mfish.common.oauth.annotation.DataScopes; // 多条件时引入
import cn.com.mfish.common.oauth.common.DataScopeType;
```
### 参考实现
- 完整样例:[DemoDataScopeController.java](mf-business/mf-demo/src/main/java/cn/com/mfish/demo/controller/DemoDataScopeController.java)
- 排除条件示例:[DbConnectController.java](mf-business/mf-sys/src/main/java/cn/com/mfish/sys/controller/DbConnectController.java)
- 忽略条件示例:[MfApiController.java](mf-business/mf-nocode/src/main/java/cn/com/mfish/nocode/controller/MfApiController.java)
---
## 相关参考
- 参考现有实现:[DictController.java](mf-business/mf-sys/src/main/java/cn/com/mfish/sys/controller/DictController.java)
- 基础实体类:[BaseEntity.java](mf-common/mf-common-core/src/main/java/cn/com/mfish/common/core/entity/BaseEntity.java)
- 返回值规范:[Result.java](mf-common/mf-common-core/src/main/java/cn/com/mfish/common/core/web/Result.java)
- 代码生成模板:[mf-common-code/template/src/main/java/](mf-common/mf-common-code/src/main/resources/template/src/main/java/)
- 工作流审批集成请使用 **workflow-audit** skill
- 前端页面生成请使用 **frontend-crud** skill
+596
View File
@@ -0,0 +1,596 @@
---
name: frontend-crud
description: 为 mfish-nocode-pro 项目前端(Vue3 + TypeScript)生成标准增删改查页面代码,包括 Model、API、data、index.vue、Modal、ViewModal 六个文件。当用户说"帮我生成前端增删改查"、"新增前端页面"、"生成前端CRUD"时使用此 skill。
---
# 前端 CRUD 代码生成器
## 项目前端架构
```
mfish-nocode-view/src/
├── api/
│ └── {apiPrefix}/ # 如:sys、demo、nocode
│ ├── model/
│ │ └── {类名}Model.ts # 接口类型定义
│ └── {类名}.ts # API 请求函数
└── views/
└── {apiPrefix}/
└── {entity-kebab-case}/ # 如:demo-order、sys-dict
├── {变量名}.data.ts # 表格列、搜索表单、表单Schema、详情Schema
├── index.vue # 列表页主页面
├── {类名}Modal.vue # 新增/编辑弹窗
└── {类名}ViewModal.vue # 详情查看弹窗
```
## 技术栈约定
- 框架:**Vue 3** + **TypeScript** + `<script lang="ts" setup>`
- HTTP`defHttp`(来自 `@mfish/core/utils/http/axios`
- 表格:`BasicTable` + `useTable`(来自 `@mfish/core/components/Table`
- 弹窗:`BasicModal` + `useModal` / `useModalInner`(来自 `@mfish/core/components/Modal`
- 表单:`BasicForm` + `useForm`(来自 `@mfish/core/components/Form`
- 详情:`Description` + `useDescription`(来自 `@mfish/core/components/Description`
- 字典:`buildDictTag` + `getDictProps`(来自 `@mfish/core/components/DictTag`
- 权限:`v-auth="'{apiPrefix}:{entityName}:操作'"`insert/update/delete/query/export
- 基础类型:`BaseEntity<string>``PageResult<T>``ReqPage`(来自 `@mfish/core/api`
- ID 类型:默认 `string`,数值型主键时为 `number`
---
## 生成步骤
### 第一步:收集信息
询问用户(如未提供):
1. **模块名(apiPrefix**(如:`demo``sys``nocode`
2. **类名(PascalCase**(如:`DemoOrder`
3. **中文名称**(如:销售订单)
4. **字段列表**:字段名(camelCase)、TS 类型(`string`/`number`/`boolean`)、中文描述、是否可选
5. **搜索字段**:哪些字段出现在搜索表单中(及组件类型:`Input` / `ApiSelect`+字典编码 / `DatePicker`
6. **表单字段**:哪些字段出现在新增/编辑表单中(及组件类型,是否必填)
7. **字典字段**:哪些字段使用字典渲染(需提供字典编码)
---
### 第二步:生成六个文件
按以下顺序生成,所有文件路径基于 `mfish-nocode-view/src/` 目录。
#### 1. Model 类型定义(`api/{apiPrefix}/model/{类名}Model.ts`
```typescript
import { BaseEntity, PageResult, ReqPage } from "@mfish/core/api";
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
export interface {} extends BaseEntity<string> {
//{字段注释}
{}?: {TS类型};
// ... 更多字段
}
export interface Req{} extends ReqPage {
//{搜索字段注释}
{}?: {TS类型};
// ... 更多搜索字段
}
//分页结果集
export type {}PageModel = PageResult<{}>;
```
**字段类型映射规则:**
| Java/DB 类型 | TS 类型 |
|---|---|
| `String``Date` | `string` |
| `Integer``Long``Short``Double``BigDecimal` | `number` |
| `Boolean` | `boolean` |
---
#### 2. API 请求文件(`api/{apiPrefix}/{类名}.ts`
```typescript
import { defHttp } from "@mfish/core/utils/http/axios";
import { {}, Req{}, {}PageModel } from "@/api/{apiPrefix}/model/{类名}Model";
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
enum Api {
{} = "/{apiPrefix}/{变量名}"
}
/**
* 分页列表查询
*/
export const get{}List = (req{}?: Req{}) => {
return defHttp.get<{}PageModel>({ url: Api.{}, params: req{} });
};
/**
* 通过id查询
*/
export function get{}ById(id: string) {
return defHttp.get<{}>({ url: `${Api.{}}/${id}` });
}
/**
* 导出{中文名称}
*/
export function export{}(req{}?: Req{}) {
return defHttp.download({ url: `${Api.{}}/export`, params: req{} });
}
/**
* 新增{中文名称}
*/
export function insert{}({}: {}) {
return defHttp.post<{}>({ url: Api.{}, params: {} }, { successMessageMode: "message" });
}
/**
* 修改{中文名称}
*/
export function update{}({}: {}) {
return defHttp.put<{}>({ url: Api.{}, params: {} }, { successMessageMode: "message" });
}
/**
* 删除{中文名称}
*/
export function delete{}(id: string) {
return defHttp.delete<boolean>({ url: `${Api.{}}/${id}` }, { successMessageMode: "message" });
}
/**
* 批量删除{中文名称}
*/
export function deleteBatch{}(ids: string) {
return defHttp.delete<boolean>({ url: `${Api.{}}/batch/${ids}` }, { successMessageMode: "message" });
}
```
> 若 ID 类型为数值型(`number`),`delete{类名}` 参数类型改为 `number`。
---
#### 3. data 配置文件(`views/{apiPrefix}/{entity-kebab-case}/{变量名}.data.ts`
```typescript
import { BasicColumn, FormSchema } from "@mfish/core/components/Table";
import { DescItem } from "@mfish/core/components/Description";
// 有字典字段时引入(无字典字段则删除)
import { buildDictTag, getDictProps } from "@mfish/core/components/DictTag";
/**
* @description: {中文名称}
* @author: mfish
* @date: {当前日期}
* @version: V2.3.1
*/
// ========== 表格列定义 ==========
export const columns: BasicColumn[] = [
// 普通字段
{
title: "{字段中文名}",
dataIndex: "{字段名}",
width: 120
},
// 字典字段(有字典时使用 customRender
{
customRender: ({ record }) => {
return buildDictTag("{字典编码}", record.{});
},
title: "{字段中文名}",
dataIndex: "{字段名}",
width: 120
}
];
// ========== 搜索表单 Schema ==========
export const searchFormSchema: FormSchema[] = [
// 普通输入框
{
field: "{字段名}",
label: "{字段中文名}",
component: "Input",
colProps: { xl: 5, md: 6 }
},
// 字典下拉(单选)
{
field: "{字段名}",
label: "{字段中文名}",
component: "ApiSelect",
componentProps: getDictProps("{字典编码}"),
colProps: { xl: 5, md: 6 }
},
// 字典下拉(多选)
{
field: "{字段名}",
label: "{字段中文名}",
component: "ApiSelect",
componentProps: { ...getDictProps("{字典编码}"), mode: "multiple" },
colProps: { xl: 5, md: 6 }
}
];
// ========== 新增/编辑表单 Schema ==========
export const {}FormSchema: FormSchema[] = [
{
field: "id",
label: "唯一ID",
component: "Input",
show: false
},
// 文本输入
{
field: "{字段名}",
label: "{字段中文名}",
component: "Input",
required: true // 必填时加上
},
// 数值输入
{
field: "{字段名}",
label: "{字段中文名}",
component: "InputNumber"
},
// 字典下拉
{
field: "{字段名}",
label: "{字段中文名}",
component: "ApiSelect",
componentProps: getDictProps("{字典编码}")
},
// 日期(仅日期)
{
field: "{字段名}",
label: "{字段中文名}",
component: "DatePicker",
componentProps: {
valueFormat: "YYYY-MM-DD",
format: "YYYY-MM-DD",
getPopupContainer: () => document.body
}
},
// 日期时间
{
field: "{字段名}",
label: "{字段中文名}",
component: "DatePicker",
componentProps: {
valueFormat: "YYYY-MM-DD HH:mm:ss",
format: "YYYY-MM-DD HH:mm:ss",
showTime: { format: "HH:mm:ss" },
getPopupContainer: () => document.body
}
}
];
// ========== 详情查看 Schema ==========
export class {}Desc {
viewSchema: DescItem[] = [
{
label: "id",
field: "id",
show: () => false
},
// 普通字段
{
field: "{字段名}",
label: "{字段中文名}"
},
// 字典字段
{
render: (val) => {
if (val === undefined) return;
return buildDictTag("{字典编码}", val);
},
field: "{字段名}",
label: "{字段中文名}"
}
];
}
```
**组件选择规则:**
| 字段类型 | 表单组件 |
|---|---|
| `string`(普通文本) | `Input` |
| `number`(整数/小数) | `InputNumber` |
| `string`(日期) | `DatePicker`dateFormat: YYYY-MM-DD|
| `string`(日期时间) | `DatePicker`showTime|
| 有字典 | `ApiSelect` + `getDictProps("{字典编码}")` |
---
#### 4. 列表主页面(`views/{apiPrefix}/{entity-kebab-case}/index.vue`
```vue
<!--
@description: {中文名称}
@author: mfish
@date: {当前日期}
@version: V2.3.1
-->
<template>
<div>
<BasicTable @register="registerTable">
<template #toolbar>
<AButton type="primary" @click="handleCreate" v-auth="'{apiPrefix}:{变量名}:insert'">新增</AButton>
<AButton color="warning" @click="handleExport" v-auth="'{apiPrefix}:{变量名}:export'">导出</AButton>
<AButton color="error" @click="handleBatchDelete" v-auth="'{apiPrefix}:{变量名}:delete'">批量删除</AButton>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
:actions="[
{
icon: 'ant-design:info-circle-outlined',
onClick: handleQuery.bind(null, record),
auth: '{apiPrefix}:{变量名}:query',
color: 'success',
tooltip: '查看'
},
{
icon: 'ant-design:edit-outlined',
onClick: handleEdit.bind(null, record),
auth: '{apiPrefix}:{变量名}:update',
tooltip: '修改'
},
{
icon: 'ant-design:delete-outlined',
color: 'error',
popConfirm: {
title: '是否确认删除',
placement: 'left',
confirm: handleDelete.bind(null, record)
},
auth: '{apiPrefix}:{变量名}:delete',
tooltip: '删除'
}
]"
/>
</template>
</template>
</BasicTable>
<{类名}Modal @register="registerModal" @success="handleSuccess" />
<{类名}ViewModal @register="registerViewModal" />
</div>
</template>
<script lang="ts" setup>
import { BasicTable, useTable, TableAction } from "@mfish/core/components/Table";
import { useModal } from "@mfish/core/components/Modal";
import { Button as AButton } from "@mfish/core/components/Button";
import { deleteBatch{类名}, delete{类名}, export{类名}, get{类名}List } from "@/api/{apiPrefix}/{类名}";
import {类名}Modal from "./{类名}Modal.vue";
import {类名}ViewModal from "./{类名}ViewModal.vue";
import { columns, searchFormSchema } from "./{变量名}.data";
import { {类名} } from "@/api/{apiPrefix}/model/{类名}Model";
import { ref } from "vue";
import { useMessage } from "@mfish/core/hooks";
defineOptions({ name: "{类名}Management" });
const [registerModal, { openModal }] = useModal();
const [registerViewModal, { openModal: openViewModal }] = useModal();
const selectedRowKeys = ref<any[]>([]);
const [registerTable, { reload, getForm }] = useTable({
title: "{中文名称}列表",
api: get{类名}List,
rowKey: "id",
columns,
formConfig: {
name: "search_form_item",
labelWidth: 100,
schemas: searchFormSchema,
autoSubmitOnEnter: true
},
useSearchForm: true,
showTableSetting: true,
bordered: true,
showIndexColumn: false,
rowSelection: {
onChange: (rowKeys: any[]) => {
selectedRowKeys.value = rowKeys;
}
},
actionColumn: {
width: 120,
title: "操作",
dataIndex: "action"
}
});
const { createMessage } = useMessage();
function handleCreate() {
openModal(true, { isUpdate: false });
}
function handleExport() {
export{类名}({ ...getForm().getFieldsValue(), pageNum: 1, pageSize: 1000 });
}
function handleQuery({变量名}: {类名}) {
openViewModal(true, { record: {变量名} });
}
function handleEdit({变量名}: {类名}) {
openModal(true, { record: {变量名}, isUpdate: true });
}
function handleDelete({变量名}: {类名}) {
if ({变量名}.id) {
delete{类名}({变量名}.id).then(() => {
handleSuccess();
});
}
}
function handleBatchDelete() {
if (selectedRowKeys.value.length > 0) {
deleteBatch{类名}(selectedRowKeys.value.join(",")).then(() => {
handleSuccess();
});
} else {
createMessage.warning("请勾选要删除的数据");
}
}
function handleSuccess() {
reload();
}
</script>
```
---
#### 5. 新增/编辑弹窗(`views/{apiPrefix}/{entity-kebab-case}/{类名}Modal.vue`
```vue
<!--
@description: {中文名称}
@author: mfish
@date: {当前日期}
@version: V2.3.1
-->
<template>
<BasicModal v-bind="$attrs" @register="registerModal" :title="getTitle" @ok="handleSubmit">
<BasicForm @register="registerForm" @submit="handleSubmit" />
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, computed, unref } from "vue";
import { BasicForm, useForm } from "@mfish/core/components/Form";
import { {变量名}FormSchema } from "./{变量名}.data";
import { BasicModal, useModalInner } from "@mfish/core/components/Modal";
import { insert{类名}, update{类名} } from "@/api/{apiPrefix}/{类名}";
defineOptions({ name: "{类名}Modal" });
const emit = defineEmits(["success", "register"]);
const isUpdate = ref(true);
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
name: "model_form_item",
labelWidth: 100,
baseColProps: { span: 12 },
schemas: {变量名}FormSchema,
showActionButtonGroup: false,
autoSubmitOnEnter: true
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields().then();
setModalProps({ confirmLoading: false, width: "800px" });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
setFieldsValue({ ...data.record }).then();
}
});
const getTitle = computed(() => (unref(isUpdate) ? "编辑{中文名称}" : "新增{中文名称}"));
async function handleSubmit() {
const values = await validate();
setModalProps({ confirmLoading: true });
if (unref(isUpdate)) {
save{类名}(update{类名}, values);
} else {
save{类名}(insert{类名}, values);
}
}
function save{类名}(save, values) {
save(values)
.then(() => {
emit("success");
closeModal();
})
.finally(() => {
setModalProps({ confirmLoading: false });
});
}
</script>
```
---
#### 6. 详情查看弹窗(`views/{apiPrefix}/{entity-kebab-case}/{类名}ViewModal.vue`
```vue
<!--
@description: {中文名称}查看
@author: mfish
@date: {当前日期}
@version: V2.3.1
-->
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="{中文名称}信息">
<Description @register="registerDesc" />
</BasicModal>
</template>
<script lang="ts" setup>
import { BasicModal, useModalInner } from "@mfish/core/components/Modal";
import { Description, useDescription } from "@mfish/core/components/Description";
import { ref } from "vue";
import { {类名}Desc } from "./{变量名}.data";
defineOptions({ name: "{类名}ViewModal" });
const {变量名}Data = ref();
const {变量名}Desc = new {类名}Desc();
const [registerModal, { setModalProps }] = useModalInner(async (data) => {
setModalProps({
confirmLoading: false,
width: "800px",
cancelText: "关闭",
showOkBtn: false
});
{变量名}Data.value = data.record;
});
const [registerDesc] = useDescription({
data: {变量名}Data,
schema: {变量名}Desc.viewSchema,
column: 2
});
</script>
```
---
## 命名约定
| 占位符 | 说明 | 示例 |
|--------|------|------|
| `{类名}` | PascalCase 类名 | `DemoOrder` |
| `{变量名}` | camelCase 变量名 | `demoOrder` |
| `{apiPrefix}` | 模块路径(小写) | `demo` |
| `{entity-kebab-case}` | kebab-case 目录名 | `demo-order` |
---
## 注意事项
1. **字典字段**`columns``viewSchema` 中使用 `buildDictTag``searchFormSchema``FormSchema` 中使用 `getDictProps`;只要有字典字段就需要在文件顶部引入 `buildDictTag``getDictProps`
2. **无搜索表单字段**`searchFormSchema` 可以为空数组 `[]``useSearchForm` 设为 `false`
3. **数值型主键**`delete{类名}` 参数类型改为 `number``Model``BaseEntity<number>`
4. **导出功能可选**:若不需要导出,删除 `handleExport``export{类名}` 的引入和 toolbar 中的导出按钮
5. **批量删除可选**:若不需要批量删除,删除 `handleBatchDelete``deleteBatch{类名}` 的引入、`rowSelection` 配置和 toolbar 中的批量删除按钮
6. **配合后端 CRUD**:前端文件路径中的 `{apiPrefix}/{变量名}` 对应后端 Controller 的 `@RequestMapping` 路径
---
## 参考实现
- 含字典的完整示例:[demoOrder.data.ts](mfish-nocode-view/src/views/demo/demo-order/demoOrder.data.ts)
- API 文件示例:[DemoOrder.ts](mfish-nocode-view/src/api/demo/DemoOrder.ts)
- Model 示例:[DemoOrderModel.ts](mfish-nocode-view/src/api/demo/model/DemoOrderModel.ts)
- 无字典简单示例:[demoDataScope.data.ts](mfish-nocode-view/src/views/demo/demo-data-scope/demoDataScope.data.ts)
- 后端 CRUD 配套请使用 **crud-generator** skill
+266
View File
@@ -0,0 +1,266 @@
---
name: workflow-audit
description: 为 mfish-nocode-pro 项目集成 Flowable 工作流审批能力,包括注册 FlowKey、实体审批状态字段、Service 启动/撤回流程、Controller 审批回调接口、Feign 回调接口注册五个步骤的完整代码模板。当用户说"带工作流审批"、"发布审核流程"、"集成工作流"、"审批回调"时使用此 skill。
---
# 工作流审批集成
适用于需要**发布/审核**流程的业务模块(如大屏发布、内容审核等),在标准 CRUD 基础上叠加工作流能力。
## 核心概念
| 组件 | 说明 |
|------|------|
| `FlowableParam<T>` | 启动工作流参数:`key`(流程定义key)、`id`(业务id)、`prefix`(回调URL前缀)、`callback`(回调Feign接口全路径) |
| `FlowKey` 枚举 | 流程定义key枚举,新业务须在此注册,路径:`mf-api/mf-workflow-api/.../FlowKey.java` |
| `RemoteAuditApi<T>` | 审批回调接口,Controller 需实现 `approved`/`rejected`/`canceled` 三个方法 |
| `WorkflowCompleteResult` | 回调结果体:`processInstanceId``comment``eventName` |
| `RemoteWorkflowService` | Feign 客户端,用于启动/删除流程实例 |
## 审批状态约定
| auditState 值 | 含义 | 触发时机 |
|--------------|------|---------|
| `0` | 审核中 | 发布提交时设置 |
| `1` | 已通过 | `approved` 回调时设置 |
| `2` | 未通过 | `rejected` 回调时设置 |
| `null` | 已取消 | `canceled` 回调时设置 |
---
## 集成步骤
### 第一步:注册 FlowKey
`FlowKey.java` 枚举中添加新流程定义 key
```java
// mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/enums/FlowKey.java
新业务名称 ("xxx_release"), // key 需与 BPMN 文件中的 process id 一致
```
### 第二步:实体新增审批状态字段
```java
@Schema(description = "审核状态 null 未发布 0 审核中 1 已通过 2 未通过")
private Integer auditState;
```
**注意**:实体类必须添加完整的 Swagger 注解:
- **类级别**`@Schema(description = "描述信息", name = "类名")`
- **字段级别**:每个字段都需添加 `@Schema(description = "字段描述")`
- 便于生成完整的 API 文档和 Swagger UI 展示
### 第三步:Service 中启动和撤回工作流
```java
import cn.com.mfish.common.core.constants.RPCConstants;
import cn.com.mfish.common.core.entity.WorkflowCompleteResult;
import cn.com.mfish.common.core.exception.MyRuntimeException;
import cn.com.mfish.common.core.web.Result;
import cn.com.mfish.common.workflow.api.entity.FlowableParam;
import cn.com.mfish.common.workflow.api.enums.FlowKey;
import cn.com.mfish.common.workflow.api.remote.RemoteWorkflowService;
import jakarta.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
@Service
public class {类名}ServiceImpl extends ServiceImpl<{类名}Mapper, {类名}> implements {类名}Service {
@Resource
RemoteWorkflowService remoteWorkflowService;
/** 发布(新增并启动工作流) */
@Override
@Transactional
public Result<{类名}> insert{类名}({类名} entity) {
// 业务唯一性校验:避免重复发布
if (baseMapper.exists(new LambdaQueryWrapper<{类名}>()
.eq({类名}::getSourceId, entity.getSourceId()))) {
throw new MyRuntimeException("错误:已存在,不能重复发布!");
}
entity.setAuditState(0);
if (save(entity)) {
startProcess(entity);
return Result.ok(entity, "{中文名称}-发布成功!");
}
return Result.fail(entity, "错误:{中文名称}-发布失败!");
}
/** 撤回(删除记录并撤销工作流) */
@Override
@Transactional
public Result<Boolean> delete{类名}(String id) {
{类名} entity = getById(id);
if (entity == null) {
return Result.ok(false, "错误:记录不存在!");
}
if (remove(new LambdaQueryWrapper<{类名}>().eq({类名}::getId, id))) {
Result<String> result = remoteWorkflowService.delProcessByBusinessKey(
RPCConstants.INNER, entity.getId(), "用户撤回");
if (!result.isSuccess()) {
throw new MyRuntimeException(result.getMsg());
}
return Result.ok(true, "撤回成功!");
}
return Result.fail(false, "错误:撤回失败!");
}
/** 审批回调:更新审批状态 */
@Override
public Result<String> audit(String id, Integer auditState, WorkflowCompleteResult result) {
{类名} entity = baseMapper.selectById(id);
if (entity == null) throw new MyRuntimeException("错误:记录不存在!");
entity.setAuditState(auditState);
if (!updateById(entity)) throw new MyRuntimeException("错误:审批操作异常!");
return Result.ok(id, "审批操作成功!");
}
/** 启动工作流 */
private void startProcess({类名} entity) {
Result<String> result = remoteWorkflowService.startProcess(RPCConstants.INNER,
new FlowableParam<String>()
.setKey(FlowKey.{对应枚举}.toString())
.setId(entity.getId()) // 业务id 作为 businessKey
.setPrefix("{回调URL前缀}") // Controller @RequestMapping 路径(不含 /
.setCallback("{Feign接口全路径}") // 例如:cn.com.mfish.xxx.api.remote.RemoteXxxService
);
if (!result.isSuccess()) throw new MyRuntimeException(result.getMsg());
}
}
```
### 第四步:Controller 追加审批回调接口
工作流引擎审批完成后,通过 Feign 回调这三个接口,**无需 `@RequiresPermissions`**
```java
import cn.com.mfish.common.core.entity.WorkflowCompleteResult;
// 追加到现有 Controller 末尾
@PostMapping("/approved/{id}")
public Result<String> approved(
@PathVariable String id,
@RequestBody WorkflowCompleteResult result) {
return {变量名}Service.audit(id, 1, result);
}
@PostMapping("/rejected/{id}")
public Result<String> rejected(
@PathVariable String id,
@RequestBody WorkflowCompleteResult result) {
return {变量名}Service.audit(id, 2, result);
}
@PostMapping("/canceled/{id}")
public Result<String> canceled(
@PathVariable String id,
@RequestBody WorkflowCompleteResult result) {
return {变量名}Service.audit(id, null, result);
}
```
### 第五步:注册 Feign 回调接口(微服务模式)
`mf-api/mf-xxx-api` 模块中定义回调 Feign 接口,继承 `RemoteAuditApi<String>`
```java
import cn.com.mfish.common.core.entity.RemoteAuditApi;
@FeignClient(
contextId = "remote{类名}Service",
value = ServiceConstants.XXX_SERVICE,
fallbackFactory = Remote{类名}FallBack.class
)
public interface Remote{类名}Service extends RemoteAuditApi<String> {
// 其他跨服务调用方法...
}
```
> - `FlowableParam.callback` 填写此接口的**全路径类名**
> - `FlowableParam.prefix` 填写 Controller 的 `@RequestMapping` 路径(**不含 `/`**
---
## 注意事项
- `prefix` 与 Controller `@RequestMapping` 必须一致,回调 URL 拼接规则:`/{prefix}/approved/{id}`
- 编辑已发布记录前需校验审核状态:`auditState=1` 的记录禁止直接编辑,需先撤回
- 重复发布校验:发布前通过 `sourceId` 或业务唯一键检查是否已存在记录
- 撤回时需同步调用 `remoteWorkflowService.delProcessByBusinessKey(...)` 删除工作流实例
- Service 接口中需声明 `audit(String id, Integer auditState, WorkflowCompleteResult result)` 方法
- **异常处理规范**:人为抛出的业务异常统一采用 `MyRuntimeException` 处理
- **引入包**`import cn.com.mfish.common.core.exception.MyRuntimeException;`
- **使用场景**:记录不存在、重复提交、状态校验失败等业务异常情况
- **示例代码**
```java
// 记录不存在
if (entity == null) {
throw new MyRuntimeException("错误:记录不存在!");
}
// 重复发布校验
if (baseMapper.exists(new LambdaQueryWrapper<Entity>()
.eq(Entity::getSourceId, sourceId))) {
throw new MyRuntimeException("错误:已存在,不能重复发布!");
}
// 审批状态校验
if (!"approved".equals(entity.getAuditState())) {
throw new MyRuntimeException("错误:审批状态不正确!");
}
```
- **消息格式**:建议以 `"错误:"` 开头,便于前端统一处理和识别
- **不要使用**:避免直接使用 `RuntimeException` 或其他自定义异常
- **安全规范 - 异常信息不暴露给前端**:
- **核心原则**:返回给前端的错误消息必须是友好的、通用的提示,不能包含具体的异常堆栈或技术细节
- **错误示例**`return Result.fail(false, "错误:流程配置不正确," + e.getMessage());` ❌
- **正确示例**`return Result.fail(false, "错误:流程配置不正确,请检查流程设计是否完整且符合规范");` ✅
- **日志记录**:详细的异常信息应通过 `log.error()` 记录到日志文件中,便于开发人员排查
- **实现模式**
```java
try {
// 业务逻辑
BpmnConverter.convertToBpmn(...);
} catch (Exception e) {
// 详细异常信息记录到日志
log.error("流程格式化失败:{}", e.getMessage(), e);
// 返回给前端的是友好的提示信息
return Result.fail(false, "错误:流程配置不正确,请检查流程设计是否完整且符合规范");
}
```
---
## 相关参考
- Controller 参考:[ScreenResourceController.java](mf-business/mf-nocode/src/main/java/cn/com/mfish/nocode/controller/ScreenResourceController.java)
- Service 参考:[ScreenResourceServiceImpl.java](mf-business/mf-nocode/src/main/java/cn/com/mfish/nocode/service/impl/ScreenResourceServiceImpl.java)
- FlowKey 枚举:[FlowKey.java](mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/enums/FlowKey.java)
- 工作流参数:[FlowableParam.java](mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/entity/FlowableParam.java)
- 审批回调接口:[RemoteAuditApi.java](mf-common/mf-common-core/src/main/java/cn/com/mfish/common/core/entity/RemoteAuditApi.java)
- 工作流 Feign 客户端:[RemoteWorkflowService.java](mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/remote/RemoteWorkflowService.java)
---
## 规范补充(2026-04
1. 新审批业务必须先在 `mf-api` 下提供独立 Feign 回调接口模块(例如 `mf-demo-api`),接口继承 `RemoteAuditApi<T>`,并提供 `fallbackFactory`。
2. `FlowableParam.callback` 必须填写 API 模块中的 Feign 接口全限定名(不要填写业务模块本地类路径)。
3. 单体模式覆盖实现必须放在 `mf-common/mf-common-api`Bean 名与 Feign `contextId` 对应(如 `remoteDemoLeaveApplyService`)。
4. `mf-common-api` 通过依赖 API 模块实现解耦,不直接依赖 `mf-business`;如需调用业务方法,优先通过 Spring 容器按 Bean 名调用。
## 优化记录(2026-04-19
1. `mf-demo-api` 创建后,必须同步加入根 `pom.xml` 的 `dependencyManagement`,避免子模块遗漏版本管理。
2. `BootDemoLeaveApplyService` 禁止通过反射获取 `demoLeaveApplyService`,应改为依赖注入 `DemoLeaveApplyService`。
3. `DemoLeaveApplyService` 直接下沉到 `mf-common`(例如 `mf-common-demo`),不要再额外拆分 `DemoLeaveApplyAuditService`。
4. 该模式下保持依赖方向为:`mf-common-api -> mf-common-demo`、`mf-business -> mf-common-demo`,并为后续 Feign 扩展预留统一服务接口。
## 依赖约束(2026-04-19
1. `mf-common-demo` 的 `pom.xml` 默认仅引入:
`<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring</artifactId></dependency>`。
2. 不要默认引入 `mybatis-plus-annotation`、`mybatis-plus-extension`、`fastexcel`;仅在确有直接编译依赖时再按最小集补充。
3. 若公共模块实体/接口仅用于跨层共享,优先保持依赖最小化,避免把业务模块的技术依赖扩散到 `mf-common`。
@@ -15,6 +15,12 @@ import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RemoteDemoLeaveApplyFallBack implements FallbackFactory<RemoteDemoLeaveApplyService> {
/**
* 创建demo请假申请审批服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的请假申请审批服务实例
*/
@Override
public RemoteDemoLeaveApplyService create(Throwable cause) {
log.error("错误: demo请假申请审批回调接口调用异常", cause);
@@ -15,6 +15,12 @@ import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RemoteNocodeFallBack implements FallbackFactory<RemoteNocodeService> {
/**
* 创建低代码服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的低代码服务实例
*/
@Override
public RemoteNocodeService create(Throwable cause) {
log.error("错误: 低代码服务接口调用异常", cause);
@@ -13,6 +13,7 @@ import java.util.Date;
import java.util.List;
/**
* @description: 用户基础信息
* @author: mfish
* @date: 2020/2/18 17:48
*/
@@ -15,6 +15,12 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
public class RemoteMenuFallback implements FallbackFactory<RemoteMenuService> {
/**
* 创建菜单服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的菜单服务实例
*/
@Override
public RemoteMenuService create(Throwable cause) {
log.error("菜单服务调用失败:{}", cause.getMessage());
@@ -20,6 +20,12 @@ import java.util.List;
@Slf4j
@Component
public class RemoteOrgFallback implements FallbackFactory<RemoteOrgService> {
/**
* 创建组织服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的组织服务实例
*/
@Override
public RemoteOrgService create(Throwable cause) {
log.error("组织服务调用失败:{}", cause.getMessage());
@@ -16,6 +16,12 @@ import java.util.List;
@Slf4j
@Component
public class RemoteRoleFallback implements FallbackFactory<RemoteRoleService> {
/**
* 创建角色服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的角色服务实例
*/
@Override
public RemoteRoleService create(Throwable cause) {
log.error("角色服务调用失败:{}", cause.getMessage());
@@ -22,6 +22,12 @@ import java.util.Set;
@Component
@Slf4j
public class RemoteUserFallback implements FallbackFactory<RemoteUserService> {
/**
* 创建用户服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的用户服务实例
*/
@Override
public RemoteUserService create(Throwable cause) {
log.error("用户服务调用失败:{}", cause.getMessage());
@@ -15,9 +15,24 @@ import org.springframework.web.bind.annotation.*;
*/
@FeignClient(contextId = "remoteMenuService", value = ServiceConstants.OAUTH_SERVICE, fallbackFactory = RemoteMenuFallback.class)
public interface RemoteMenuService {
/**
* 新增菜单
*
* @param origin 来源
* @param ssoMenu 菜单信息
* @return 新增后的菜单信息
*/
@PostMapping("/menu")
Result<SsoMenu> add(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @RequestBody SsoMenu ssoMenu);
/**
* 判断路由路径是否已存在
*
* @param origin 来源
* @param routePath 路由路径
* @param parentId 父菜单ID
* @return 是否存在
*/
@GetMapping("/menu/routeExist")
Result<Boolean> routeExist(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @RequestParam("routePath") String routePath, @RequestParam("parentId") String parentId);
}
@@ -25,18 +25,62 @@ import java.util.List;
@FeignClient(contextId = "remoteOrgService", value = ServiceConstants.OAUTH_SERVICE, fallbackFactory = RemoteOrgFallback.class)
public interface RemoteOrgService {
/**
* 根据ID列表查询组织信息
*
* @param origin 来源
* @param ids 组织ID,多个逗号分隔
* @return 组织列表
*/
@GetMapping("/org/{ids}")
Result<List<SsoOrg>> queryById(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable("ids") String ids);
/**
* 根据固定编码查询组织树
*
* @param origin 来源
* @param code 组织固定编码
* @param direction 查询方向
* @return 组织树列表
*/
@GetMapping("/org/code/{code}")
Result<List<SsoOrg>> queryByFixCode(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable("code") String code, @RequestParam("direction") String direction);
/**
* 根据组织编码查询组织下用户列表
*
* @param origin 来源
* @param code 组织固定编码
* @param account 账号(模糊查询)
* @param nickname 昵称(模糊查询)
* @param phone 手机号(模糊查询)
* @param reqPage 分页参数
* @return 用户分页列表
*/
@GetMapping("/org/user/{code}")
Result<PageResult<UserInfo>> queryUserByCode(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable("code") String code, @RequestParam("account") String account, @RequestParam("nickname") String nickname, @RequestParam("phone") String phone, @SpringQueryMap ReqPage reqPage);
/**
* 根据固定编码获取各级组织ID
*
* @param origin 来源
* @param tenantId 租户ID
* @param codes 组织固定编码,多个逗号分隔
* @param direction 查询方向
* @return 组织ID列表
*/
@GetMapping("/org/ids")
Result<List<String>> getOrgIdsByFixCode(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @RequestParam("tenantId") String tenantId, @RequestParam("codes") String codes, @RequestParam("direction") String direction);
/**
* 根据组织ID获取各级组织ID
*
* @param origin 来源
* @param tenantId 租户ID
* @param orgIds 组织ID,多个逗号分隔
* @param direction 查询方向
* @return 组织ID列表
*/
@GetMapping("/org/ids/byId")
Result<List<String>> getOrgIdsById(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @RequestParam("tenantId") String tenantId, @RequestParam("orgIds") String orgIds, @RequestParam("direction") String direction);
}
@@ -19,9 +19,25 @@ import java.util.List;
@FeignClient(contextId = "remoteRoleService", value = ServiceConstants.OAUTH_SERVICE, fallbackFactory = RemoteRoleFallback.class)
public interface RemoteRoleService {
/**
* 根据角色编码获取角色ID列表
*
* @param origin 来源
* @param tenantId 租户ID
* @param codes 角色编码,多个逗号分隔
* @return 角色ID列表
*/
@GetMapping("/role/ids")
Result<List<String>> getRoleIdsByCode(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @RequestParam("tenantId") String tenantId, @RequestParam("codes") String codes);
/**
* 根据角色编码获取角色下的用户ID列表
*
* @param origin 来源
* @param tenantId 租户ID
* @param codes 角色编码,多个逗号分隔
* @return 用户ID列表
*/
@GetMapping("/role/users")
Result<List<String>> getRoleUsers(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @RequestParam("tenantId") String tenantId, @RequestParam("codes") String codes);
}
@@ -14,6 +14,12 @@ import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RemoteSchedulerFallBack implements FallbackFactory<RemoteSchedulerService> {
/**
* 创建调度服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的调度服务实例
*/
@Override
public RemoteSchedulerService create(Throwable cause) {
log.error("错误:调度接口调用异常", cause);
@@ -17,6 +17,12 @@ import org.springframework.stereotype.Component;
@Component
@Slf4j
public class RemoteStorageFallBack implements FallbackFactory<RemoteStorageService> {
/**
* 创建文件存储服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的文件存储服务实例
*/
@Override
public RemoteStorageService create(Throwable cause) {
log.error("错误:文件接口调用异常", cause);
@@ -35,10 +35,20 @@ public class FieldInfo implements Serializable {
@Schema(description = "字段描述")
private String comment;
/**
* 根据数据库字段类型获取对应的JAVA字段类型
*
* @return JAVA字段类型
*/
public String getType() {
return DataType.forType(this.dbType).getValue();
}
/**
* 获取字段描述,为空时返回空字符串
*
* @return 字段描述
*/
public String getComment() {
if (StringUtils.isEmpty(this.comment)) {
return "";
@@ -24,6 +24,12 @@ import java.util.List;
@Slf4j
@Component
public class RemoteDbConnectFallback implements FallbackFactory<RemoteDbConnectService> {
/**
* 创建数据库连接服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的数据库连接服务实例
*/
@Override
public RemoteDbConnectService create(Throwable cause) {
log.error("错误:数据库连接调用异常", cause);
@@ -17,6 +17,12 @@ import java.util.List;
@Slf4j
@Component
public class RemoteDictCategoryFallback implements FallbackFactory<RemoteDictCategoryService> {
/**
* 创建树形字典服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的树形字典服务实例
*/
@Override
public RemoteDictCategoryService create(Throwable cause) {
log.error("错误:树形字典调用异常", cause);
@@ -15,6 +15,12 @@ import org.springframework.stereotype.Component;
@Component
public class RemoteDictFallback implements FallbackFactory<RemoteDictService> {
/**
* 创建字典服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的字典服务实例
*/
@Override
public RemoteDictService create(Throwable cause) {
log.error("错误:字典调用异常", cause);
@@ -15,6 +15,12 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
public class RemoteLogFallback implements FallbackFactory<RemoteLogService> {
/**
* 创建日志服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的日志服务实例
*/
@Override
public RemoteLogService create(Throwable cause) {
log.error("错误:日志服务调用异常", cause);
@@ -27,6 +27,10 @@ public class AuditComment {
private String taskName;
@Schema(description = "审批人")
private String assignee;
@Schema(description = "审批人名称")
private String assigneeName;
@Schema(description = "审批人账号")
private String assigneeAccount;
@Schema(description = "审批操作")
private String type;
@Schema(description = "审批意见")
@@ -46,7 +46,7 @@ public class FlowManage extends BaseEntity<String> {
@Schema(description = "流程配置")
private String flowConfig;
@ExcelProperty("hex")
@Schema(description = "hex")
@Schema(description = "流程图十六进制编码数据")
private String hex;
@ExcelProperty("删除标记(0未删除1删除)")
@Schema(description = "删除标记(0未删除1删除)")
@@ -42,6 +42,10 @@ public class MfTask {
private String formKey;
@Schema(description = "任务负责人")
private String assignee;
@Schema(description = "任务负责人账号")
private String assigneeAccount;
@Schema(description = "任务负责人昵称")
private String assigneeName;
@Schema(description = "发起人账号")
private String startAccount;
@Schema(description = "任务开始时间")
@@ -10,6 +10,13 @@ import lombok.Getter;
public class TaskTotal {
/**
* 构造任务统计对象
*
* @param todoCount 待办任务数量
* @param completedCount 已办任务数量
* @param cancelledCount 已取消任务数量
*/
public TaskTotal(long todoCount, long completedCount, long cancelledCount) {
this.todoCount = todoCount;
this.completedCount = completedCount;
@@ -9,11 +9,16 @@ import lombok.Getter;
*/
@Getter
public enum FlowKey {
/** 未知流程 */
UNKNOWN("unknown"),
/** 大屏发布流程 */
大屏发布("screen_release"),
/** 请假申请发布流程 */
请假申请发布("demo_leave_apply_release"),
/** 测试流程 */
TEST("test");
/** 流程定义key值 */
private final String key;
FlowKey(String key) {
@@ -22,6 +22,12 @@ import java.util.List;
@Component
@Slf4j
public class RemoteWorkflowFallBack implements FallbackFactory<RemoteWorkflowService> {
/**
* 创建工作流服务降级实例
*
* @param cause 导致降级的异常原因
* @return 降级后的工作流服务实例,所有方法返回失败结果
*/
@Override
public RemoteWorkflowService create(Throwable cause) {
log.error("错误:工作流接口调用异常", cause);
@@ -26,6 +26,12 @@ public class ReqProcess {
@Schema(description = "流程实例id列表")
private List<String> processInstanceIds;
/**
* 设置流程实例ID列表(逗号分隔的字符串)
*
* @param ids 逗号分隔的流程实例ID字符串
* @return 当前对象
*/
public ReqProcess setProcessInstanceIds(String ids) {
if (StringUtils.isNotEmpty(ids.trim())) {
processInstanceIds = Arrays.asList(ids.trim().split(","));
@@ -35,6 +41,12 @@ public class ReqProcess {
return this;
}
/**
* 设置流程实例ID列表
*
* @param ids 流程实例ID列表,为null时设为空列表
* @return 当前对象
*/
public ReqProcess setProcessInstanceIds(List<String> ids) {
this.processInstanceIds = Objects.requireNonNullElseGet(ids, ArrayList::new);
return this;
@@ -24,10 +24,17 @@ import reactor.core.publisher.Flux;
@RequestMapping("/ollama")
public class OllamaChatModelController {
/** 默认提示词 */
private static final String DEFAULT_PROMPT = "你好,介绍下你自己吧。请用中文回答。";
/** Ollama聊天模型 */
private final ChatModel ollamaChatModel;
/**
* 构造函数,注入Ollama聊天模型
*
* @param ollamaChatModel Ollama聊天模型实例
*/
public OllamaChatModelController(ChatModel ollamaChatModel) {
this.ollamaChatModel = ollamaChatModel;
}
@@ -48,6 +55,13 @@ public class OllamaChatModelController {
return ollamaChatModel.call(new Prompt(prompt)).getResult().getOutput().getText();
}
/**
* 流式聊天接口,以SSE方式返回AI响应内容
*
* @param id 会话ID,用于标识一次对话
* @param prompt 用户输入的提示词,为空时使用默认提示词
* @return 流式响应内容
*/
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@Parameters({
@Parameter(name = "id", description = "会话id"),
@@ -25,8 +25,10 @@ import reactor.core.publisher.Flux;
@RequestMapping("/openai")
@RequiredArgsConstructor
public class OpenAiChatModelController {
/** 默认提示词 */
private static final String DEFAULT_PROMPT = "你好,简单介绍下摸鱼低代码";
/** OpenAI聊天模型 */
private final ChatModel openAiChatModel;
/**
@@ -48,6 +50,10 @@ public class OpenAiChatModelController {
/**
* 使用编程方式自定义 LLMs ChatOptions 参数, {@link org.springframework.ai.openai.OpenAiChatOptions}
* 优先级高于在 application.yml 中配置的 LLMs 参数!
*
* @param id 会话ID,用于标识一次对话
* @param prompt 用户输入的提示词,为空时使用默认提示词
* @return 流式聊天响应内容
*/
@GetMapping("/chat/custom")
@Parameters({
@@ -24,9 +24,16 @@ import java.util.List;
@Service
@RocketMQMessageListener(nameServer = "${rocketmq.consumer.nameServer}", topic = "${rocketmq.consumer.topic}", consumerGroup = "${rocketmq.consumer.group}")
public class JobConsumer implements RocketMQListener<JobLog> {
/** 远程调度服务 */
@Resource
RemoteSchedulerService remoteSchedulerService;
/**
* 接收并执行RocketMQ中的定时任务消息
* <p>通过反射调用任务指定的类和方法,执行完成后回调更新任务状态</p>
*
* @param jobLog 任务日志对象,包含任务执行的类名、方法名和参数等信息
*/
@Override
public void onMessage(JobLog jobLog) {
JobStatus jobStatus = JobStatus.执行成功;
@@ -13,18 +13,38 @@ import java.text.MessageFormat;
@Component
@Slf4j
public class ConsumerJob {
/**
* 无参任务执行方法
*/
public void test() {
log.info("任务执行");
}
/**
* 带字符串参数的任务执行方法
*
* @param param 任务参数
*/
public void test(String param) {
log.info(MessageFormat.format("任务执行!参数:{0}", param));
}
/**
* 带字符串和整数参数的任务执行方法
*
* @param param1 字符串参数
* @param param2 整数参数
*/
public void test(String param1, Integer param2) {
log.info(MessageFormat.format("任务执行!参数1:{0},参数:{1}", param1, param2));
}
/**
* 带两个字符串参数的任务执行方法
*
* @param param1 第一个字符串参数
* @param param2 第二个字符串参数
*/
public void test(String param1, String param2) {
log.info(MessageFormat.format("任务执行!参数1:{0},参数:{1}", param1, param2));
}
@@ -61,6 +61,13 @@ public class DemoDataScopeController {
return demoDataScopeService.list();
}
/**
* 查询当前租户数据(数据权限过滤)
*
* @param reqDemoDataScope 数据权限样例请求参数
* @param reqPage 分页参数
* @return 返回当前租户下的数据权限列表
*/
@Operation(summary = "查询当前租户数据", description = "查询当前租户数据")
@GetMapping("/currentTenant")
@RequiresPermissions("demo:demoDataScope:query")
@@ -69,6 +76,13 @@ public class DemoDataScopeController {
return Result.ok(new PageResult<>(queryList(reqDemoDataScope, reqPage)), "查询当前租户数据成功!");
}
/**
* 查询当前组织及其子组织数据(组织数据权限过滤)
*
* @param reqDemoDataScope 数据权限样例请求参数
* @param reqPage 分页参数
* @return 返回当前组织及其子组织下的数据列表
*/
@Operation(summary = "查询当前组织及其子组织数据", description = "查询当前组织及其子组织数据")
@GetMapping("/currentOrg")
@RequiresPermissions("demo:demoDataScope:query")
@@ -77,6 +91,13 @@ public class DemoDataScopeController {
return Result.ok(new PageResult<>(queryList(reqDemoDataScope, reqPage)), "查询当前组织及其子组织数据成功!");
}
/**
* 查询当前角色数据(角色数据权限过滤)
*
* @param reqDemoDataScope 数据权限样例请求参数
* @param reqPage 分页参数
* @return 返回当前角色下的数据列表
*/
@Operation(summary = "查询当前角色数据", description = "查询当前角色数据")
@GetMapping("/currentRole")
@RequiresPermissions("demo:demoDataScope:query")
@@ -85,6 +106,13 @@ public class DemoDataScopeController {
return Result.ok(new PageResult<>(queryList(reqDemoDataScope, reqPage)), "查询当前角色数据成功!");
}
/**
* 查询指定组织编码及其下级组织数据
*
* @param reqDemoDataScope 数据权限样例请求参数
* @param reqPage 分页参数
* @return 返回组织编码为admin或mfish及其下级组织的数据列表
*/
@Operation(summary = "查询组织编码为admin或mfish及其下级组织数据", description = "查询组织编码为admin或mfish及其下级组织数据")
@GetMapping("/adminOrg")
@RequiresPermissions("demo:demoDataScope:query")
@@ -93,6 +121,13 @@ public class DemoDataScopeController {
return Result.ok(new PageResult<>(queryList(reqDemoDataScope, reqPage)), "查询组织编码为admin或mfish及其下级组织数据成功!");
}
/**
* 查询指定角色编码的数据
*
* @param reqDemoDataScope 数据权限样例请求参数
* @param reqPage 分页参数
* @return 返回角色编码为manage或superAdmin的数据列表
*/
@Operation(summary = "查询角色编码为manage或superAdmin数据", description = "查询角色编码为manage或superAdmin数据")
@GetMapping("/fixRole")
@RequiresPermissions("demo:demoDataScope:query")
@@ -101,6 +136,14 @@ public class DemoDataScopeController {
return Result.ok(new PageResult<>(queryList(reqDemoDataScope, reqPage)), "查询角色编码为manage或superAdmin数据成功!");
}
/**
* 组合数据权限查询:当前租户下指定角色编码的数据
* <p>同时使用租户和角色两种数据权限进行过滤</p>
*
* @param reqDemoDataScope 数据权限样例请求参数
* @param reqPage 分页参数
* @return 返回当前租户下角色编码为manage或superAdmin的数据列表
*/
@Operation(summary = "查询当前租户下角色编码为manage或superAdmin数据", description = "查询当前租户下角色编码为manage或superAdmin数据")
@GetMapping("/mixScope")
@RequiresPermissions("demo:demoDataScope:query")
@@ -177,6 +177,12 @@ public class DemoImportExportController {
ExcelUtils.write("普通表格导出", queryList(reqDemoImportExport, reqPage));
}
/**
* 基于Excel模板导出订单数据,包含订单基本信息、租户信息和订单明细
*
* @param id 订单唯一ID
* @throws IOException IO异常
*/
@Operation(summary = "模板导出Demo", description = "模板导出Demo")
@GetMapping("/export/{id}")
@RequiresPermissions("demo:demoImportExport:export")
@@ -201,6 +207,13 @@ public class DemoImportExportController {
ExcelUtils.write("/excel/销售订单.xlsx", demoImportExport.getId(), map);
}
/**
* 通用Excel模板导入,读取上传的Excel文件并批量插入数据
*
* @param file 上传的Excel文件
* @return 返回导入结果
* @throws IOException IO异常
*/
@Operation(summary = "通用模板导入", description = "通用模板导入")
@PostMapping("/import")
@RequiresPermissions("demo:demoImportExport:import")
@@ -32,12 +32,25 @@ public class DemoLeaveApplyController {
@Resource
private DemoLeaveApplyService demoLeaveApplyService;
/**
* 分页列表查询
*
* @param reqDemoLeaveApply 请假申请请求参数
* @param reqPage 分页参数
* @return 返回请假申请分页列表
*/
@Operation(summary = "请假申请审批Demo-分页列表查询", description = "请假申请审批Demo-分页列表查询")
@GetMapping
public Result<PageResult<DemoLeaveApply>> queryPageList(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) {
return demoLeaveApplyService.queryPageList(reqDemoLeaveApply, reqPage);
}
/**
* 添加请假申请
*
* @param demoLeaveApply 请假申请对象
* @return 返回添加结果
*/
@Log(title = "请假申请审批Demo-添加", operateType = OperateType.INSERT)
@Operation(summary = "请假申请审批Demo-添加")
@PostMapping
@@ -45,6 +58,12 @@ public class DemoLeaveApplyController {
return demoLeaveApplyService.add(demoLeaveApply);
}
/**
* 编辑请假申请
*
* @param demoLeaveApply 请假申请对象
* @return 返回编辑结果
*/
@Log(title = "请假申请审批Demo-编辑", operateType = OperateType.UPDATE)
@Operation(summary = "请假申请审批Demo-编辑")
@PutMapping
@@ -52,6 +71,12 @@ public class DemoLeaveApplyController {
return demoLeaveApplyService.edit(demoLeaveApply);
}
/**
* 通过id删除请假申请
*
* @param id 唯一ID
* @return 返回删除结果
*/
@Log(title = "请假申请审批Demo-通过id删除", operateType = OperateType.DELETE)
@Operation(summary = "请假申请审批Demo-通过id删除")
@DeleteMapping("/{id}")
@@ -59,6 +84,12 @@ public class DemoLeaveApplyController {
return demoLeaveApplyService.delete(id);
}
/**
* 批量删除请假申请
*
* @param ids 批量ID,多个ID以逗号分隔
* @return 返回删除结果
*/
@Log(title = "请假申请审批Demo-批量删除", operateType = OperateType.DELETE)
@Operation(summary = "请假申请审批Demo-批量删除")
@DeleteMapping("/batch/{ids}")
@@ -66,18 +97,37 @@ public class DemoLeaveApplyController {
return demoLeaveApplyService.deleteBatch(ids);
}
/**
* 通过id查询请假申请
*
* @param id 唯一ID
* @return 返回请假申请对象
*/
@Operation(summary = "请假申请审批Demo-通过id查询")
@GetMapping("/{id}")
public Result<DemoLeaveApply> queryById(@Parameter(name = "id", description = "唯一ID") @PathVariable String id) {
return demoLeaveApplyService.queryById(id);
}
/**
* 导出请假申请数据
*
* @param reqDemoLeaveApply 请假申请请求参数
* @param reqPage 分页参数
* @throws IOException IO异常
*/
@Operation(summary = "导出请假申请审批Demo", description = "导出请假申请审批Demo")
@GetMapping("/export")
public void export(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) throws IOException {
demoLeaveApplyService.export(reqDemoLeaveApply, reqPage);
}
/**
* 提交请假申请审批,启动工作流流程
*
* @param id 请假申请ID
* @return 返回提交结果
*/
@Log(title = "请假申请审批Demo-提交审批", operateType = OperateType.UPDATE)
@Operation(summary = "请假申请审批Demo-提交审批")
@PostMapping("/submit/{id}")
@@ -85,6 +135,12 @@ public class DemoLeaveApplyController {
return demoLeaveApplyService.submit(id);
}
/**
* 撤回请假申请审批,终止工作流流程
*
* @param id 请假申请ID
* @return 返回撤回结果
*/
@Log(title = "请假申请审批Demo-撤回审批", operateType = OperateType.UPDATE)
@Operation(summary = "请假申请审批Demo-撤回审批")
@PostMapping("/revoke/{id}")
@@ -92,16 +148,37 @@ public class DemoLeaveApplyController {
return demoLeaveApplyService.revoke(id);
}
/**
* 审批通过回调接口
*
* @param id 请假申请ID
* @param result 工作流完成结果
* @return 返回审批结果
*/
@PostMapping("/approved/{id}")
public Result<String> approved(@PathVariable String id, @RequestBody WorkflowCompleteResult result) {
return demoLeaveApplyService.audit(id, 1, result);
}
/**
* 审批驳回回调接口
*
* @param id 请假申请ID
* @param result 工作流完成结果
* @return 返回审批结果
*/
@PostMapping("/rejected/{id}")
public Result<String> rejected(@PathVariable String id, @RequestBody WorkflowCompleteResult result) {
return demoLeaveApplyService.audit(id, 2, result);
}
/**
* 审批取消回调接口
*
* @param id 请假申请ID
* @param result 工作流完成结果
* @return 返回审批结果
*/
@PostMapping("/canceled/{id}")
public Result<String> canceled(@PathVariable String id, @RequestBody WorkflowCompleteResult result) {
return demoLeaveApplyService.audit(id, null, result);
@@ -11,8 +11,35 @@ import com.baomidou.mybatisplus.extension.service.IService;
* @version: V2.3.1
*/
public interface DemoOrderService extends IService<DemoOrder> {
/**
* 添加销售订单及其明细
*
* @param demoOrder 销售订单对象(包含订单明细)
* @return 返回添加结果
*/
Result<DemoOrder> addOrder(DemoOrder demoOrder);
/**
* 编辑销售订单及其明细(先删除原明细再重新插入)
*
* @param demoOrder 销售订单对象(包含订单明细)
* @return 返回编辑结果
*/
Result<DemoOrder> editOrder(DemoOrder demoOrder);
/**
* 通过id删除销售订单及其明细
*
* @param id 订单ID
* @return 返回删除结果
*/
Result<Boolean> deleteOrder(String id);
/**
* 批量删除销售订单及其明细
*
* @param ids 批量订单ID,多个ID以逗号分隔
* @return 返回删除结果
*/
Result<Boolean> deleteBatchOrder(String ids);
}
@@ -18,6 +18,12 @@ import java.util.List;
@Service
public class DemoImportExportServiceImpl extends ServiceImpl<DemoImportExportMapper, DemoImportExport> implements DemoImportExportService {
/**
* 批量插入数据(部分字段)
*
* @param list 待插入的数据列表
* @return 插入的记录数
*/
@Override
public int insertBatchSomeColumn(List<DemoImportExport> list) {
return baseMapper.insertBatchSomeColumn(list);
@@ -40,14 +40,29 @@ import java.util.concurrent.TimeUnit;
*/
@Service
public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper, DemoLeaveApply> implements DemoLeaveApplyService {
/** 远程工作流服务 */
@Resource
private RemoteWorkflowService remoteWorkflowService;
/**
* 分页查询请假申请列表
*
* @param reqDemoLeaveApply 请假申请请求参数
* @param reqPage 分页参数
* @return 返回分页结果
*/
@Override
public Result<PageResult<DemoLeaveApply>> queryPageList(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) {
return Result.ok(new PageResult<>(queryList(reqDemoLeaveApply, reqPage)), "请假申请审批Demo-查询成功!");
}
/**
* 构建查询条件并获取请假申请列表
*
* @param reqDemoLeaveApply 请假申请请求参数
* @param reqPage 分页参数
* @return 返回请假申请列表
*/
private List<DemoLeaveApply> queryList(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) {
PageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize());
LambdaQueryWrapper<DemoLeaveApply> lambdaQueryWrapper = new LambdaQueryWrapper<DemoLeaveApply>()
@@ -58,6 +73,12 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return list(lambdaQueryWrapper);
}
/**
* 添加请假申请,自动根据起止时间计算请假天数
*
* @param demoLeaveApply 请假申请对象
* @return 返回添加结果
*/
@Override
public Result<DemoLeaveApply> add(DemoLeaveApply demoLeaveApply) {
demoLeaveApply.setAuditState(-1);
@@ -76,6 +97,12 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return Result.fail(demoLeaveApply, "错误:请假申请审批Demo-添加失败!");
}
/**
* 编辑请假申请,仅未提交审批的记录允许编辑
*
* @param demoLeaveApply 请假申请对象
* @return 返回编辑结果
*/
@Override
public Result<DemoLeaveApply> edit(DemoLeaveApply demoLeaveApply) {
DemoLeaveApply exist = getById(demoLeaveApply.getId());
@@ -102,6 +129,12 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return Result.fail(demoLeaveApply, "错误:请假申请审批Demo-编辑失败!");
}
/**
* 删除请假申请,若该申请已进入审批流程则同步删除流程实例
*
* @param id 请假申请ID
* @return 返回删除结果
*/
@Override
@Transactional
public Result<Boolean> delete(String id) {
@@ -121,6 +154,12 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return Result.fail(false, "错误:请假申请审批Demo-删除失败!");
}
/**
* 批量删除请假申请,逐条执行删除逻辑
*
* @param ids 批量ID,多个ID以逗号分隔
* @return 返回删除结果
*/
@Override
@Transactional
public Result<Boolean> deleteBatch(String ids) {
@@ -134,17 +173,36 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return Result.ok(true, "请假申请审批Demo-批量删除成功!");
}
/**
* 通过id查询请假申请详情
*
* @param id 请假申请ID
* @return 返回请假申请对象
*/
@Override
public Result<DemoLeaveApply> queryById(String id) {
DemoLeaveApply demoLeaveApply = getById(id);
return Result.ok(demoLeaveApply, "请假申请审批Demo-查询成功!");
}
/**
* 导出请假申请数据到Excel
*
* @param reqDemoLeaveApply 请假申请请求参数
* @param reqPage 分页参数
* @throws IOException IO异常
*/
@Override
public void export(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) throws IOException {
ExcelUtils.write("请假申请审批Demo_" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()), queryList(reqDemoLeaveApply, reqPage));
}
/**
* 提交请假申请审批,更新状态并启动工作流流程
*
* @param id 请假申请ID
* @return 返回提交结果
*/
@Override
@Transactional
public Result<DemoLeaveApply> submit(String id) {
@@ -163,6 +221,12 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return Result.ok(demoLeaveApply, "提交审批成功!");
}
/**
* 撤回请假申请审批,删除流程实例并恢复申请状态
*
* @param id 请假申请ID
* @return 返回撤回结果
*/
@Override
@Transactional
public Result<DemoLeaveApply> revoke(String id) {
@@ -184,6 +248,14 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return Result.ok(demoLeaveApply, "撤回审批成功!");
}
/**
* 审批回调处理,根据审批结果更新请假申请状态
*
* @param id 请假申请ID
* @param auditState 审批状态(1-通过,2-驳回,null-取消)
* @param result 工作流完成结果
* @return 返回审批结果
*/
@Override
public Result<String> audit(String id, Integer auditState, WorkflowCompleteResult result) {
DemoLeaveApply demoLeaveApply = getById(id);
@@ -197,6 +269,12 @@ public class DemoLeaveApplyServiceImpl extends ServiceImpl<DemoLeaveApplyMapper,
return Result.ok(id, "审批操作成功!");
}
/**
* 启动请假申请工作流流程
* <p>将请假天数作为流程参数传入,用于条件分支判断</p>
*
* @param demoLeaveApply 请假申请对象
*/
private void startProcess(DemoLeaveApply demoLeaveApply) {
Map<String, Object> param = new HashMap<>();
param.put("leaveDays",demoLeaveApply.getLeaveDays());
@@ -25,9 +25,16 @@ import java.util.List;
*/
@Service
public class DemoOrderServiceImpl extends ServiceImpl<DemoOrderMapper, DemoOrder> implements DemoOrderService {
/** 订单明细Mapper */
@Resource
DemoOrderDetailMapper demoOrderDetailMapper;
/**
* 添加销售订单及其明细,使用事务保证数据一致性
*
* @param demoOrder 销售订单对象(包含订单明细)
* @return 返回添加结果
*/
@Override
@Transactional
public Result<DemoOrder> addOrder(DemoOrder demoOrder) {
@@ -40,6 +47,12 @@ public class DemoOrderServiceImpl extends ServiceImpl<DemoOrderMapper, DemoOrder
return Result.fail(demoOrder, "错误:销售订单-添加失败!");
}
/**
* 编辑销售订单,先删除原有明细再重新插入新明细
*
* @param demoOrder 销售订单对象(包含订单明细)
* @return 返回编辑结果
*/
@Override
@Transactional
public Result<DemoOrder> editOrder(DemoOrder demoOrder) {
@@ -55,6 +68,12 @@ public class DemoOrderServiceImpl extends ServiceImpl<DemoOrderMapper, DemoOrder
return Result.fail(demoOrder, "错误:销售订单-编辑失败!");
}
/**
* 通过id删除销售订单及其关联的订单明细
*
* @param id 订单ID
* @return 返回删除结果
*/
@Override
@Transactional
public Result<Boolean> deleteOrder(String id) {
@@ -67,6 +86,12 @@ public class DemoOrderServiceImpl extends ServiceImpl<DemoOrderMapper, DemoOrder
return Result.fail(false, "错误:销售订单-删除失败!");
}
/**
* 批量删除销售订单及其关联的订单明细
*
* @param ids 批量订单ID,多个ID以逗号分隔
* @return 返回删除结果
*/
@Override
public Result<Boolean> deleteBatchOrder(String ids) {
List<String> idArray = Arrays.asList(ids.split(","));
@@ -25,11 +25,23 @@ public class DictCache extends BaseTempCache<List<DictItem>> {
@Resource
DictItemMapper dictItemMapper;
/**
* 构建缓存键
*
* @param key 字典编码
* @return Redis缓存键
*/
@Override
protected String buildKey(String... key) {
return RedisPrefix.buildCode2DictItemKey(key[0]);
}
/**
* 从数据库获取字典项列表,并根据值类型进行类型转换
*
* @param key 字典编码
* @return 字典项列表
*/
@Override
protected List<DictItem> getFromDB(String... key) {
String dictCode = key[0];
@@ -74,6 +74,12 @@ public class CodeBuildController {
return codeBuildService.insertCodeBuild(codeBuild);
}
/**
* 更新代码构建配置
*
* @param codeBuild 代码构建对象
* @return 返回代码构建-更新结果
*/
@Log(title = "代码构建-更新", operateType = OperateType.INSERT)
@Operation(summary = "代码构建-更新")
@PutMapping
@@ -82,6 +88,12 @@ public class CodeBuildController {
return codeBuildService.updateCodeBuild(codeBuild);
}
/**
* 查看生成的代码
*
* @param id 代码构建唯一ID
* @return 返回生成的代码列表
*/
@Log(title = "查看代码", operateType = OperateType.QUERY)
@Operation(summary = "查看代码")
@GetMapping("/view/{id}")
@@ -91,6 +103,13 @@ public class CodeBuildController {
return codeBuildService.getCode(id);
}
/**
* 下载生成的代码(ZIP格式)
*
* @param id 代码构建唯一ID
* @param response HTTP响应对象
* @throws IOException IO异常
*/
@Log(title = "下载代码", operateType = OperateType.EXPORT)
@Operation(summary = "下载代码")
@GetMapping("/download/{id}")
@@ -100,6 +119,12 @@ public class CodeBuildController {
codeBuildService.downloadCode(id, response);
}
/**
* 将生成的代码保存到本地IDE目录
*
* @param id 代码构建唯一ID
* @return 返回保存结果
*/
@Log(title = "保存代码到IDE", operateType = OperateType.QUERY)
@Operation(summary = "保存代码到IDE")
@GetMapping("/saveLocal/{id}")
@@ -63,6 +63,15 @@ public class DbConnectController {
return dbConnectService.queryPageList(reqDbConnect, reqPage);
}
/**
* 获取数据库表信息列表
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 返回数据库表信息分页列表
*/
@Operation(summary = "获取数据库表信息")
@GetMapping("/tables")
@RequiresPermissions("sys:database:query")
@@ -76,6 +85,12 @@ public class DbConnectController {
return Result.ok(new PageResult<>(tableService.getTableList(connectId, tableSchema, tableName, reqPage)), "获取表列表成功");
}
/**
* 获取数据库树形结构信息(分层查询:父节点为空时查询数据库列表,有值时查询库下的表列表)
*
* @param parentId 父节点ID
* @return 返回数据库树形节点列表
*/
@Operation(summary = "获取数据库树形结构信息-只能分层查询")
@GetMapping("/tree")
@Parameters({
@@ -100,6 +115,15 @@ public class DbConnectController {
return Result.ok(treeNodes, "查询标列表成功");
}
/**
* 获取表字段信息列表
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 返回字段信息分页列表
*/
@Operation(summary = "获取表字段信息")
@GetMapping("/fields")
@RequiresPermissions("sys:database:query")
@@ -113,6 +137,15 @@ public class DbConnectController {
return Result.ok(new PageResult<>(tableService.getFieldList(connectId, tableSchema, tableName, reqPage)), "获取字段列表成功");
}
/**
* 获取表数据(带列头信息)
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 返回带列头的数据表
*/
@Operation(summary = "获取表数据")
@GetMapping("/data")
@Parameters({
@@ -125,6 +158,15 @@ public class DbConnectController {
return tableService.getHeaderDataTable(connectId, tableSchema, tableName, reqPage);
}
/**
* 获取表列头信息
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 返回列头信息列表
*/
@Operation(summary = "获取表列头信息")
@GetMapping("/headers")
@Parameters({
@@ -204,6 +246,12 @@ public class DbConnectController {
return dbConnectService.queryById(id);
}
/**
* 测试数据库连接是否可用
*
* @param dbConnect 数据库连接对象
* @return 返回连接测试结果
*/
@Log(title = "测试数据库连接", operateType = OperateType.OTHER)
@Operation(summary = "测试数据库库连接")
@PostMapping("/test")
@@ -212,6 +260,11 @@ public class DbConnectController {
return dbConnectService.testConnect(dbConnect);
}
/**
* 校验数据库连接是否存在且属于当前租户
*
* @param id 数据库连接ID
*/
private void verifyTenantId(String id) {
if (!dbConnectService.exists(new LambdaQueryWrapper<DbConnect>().eq(DbConnect::getId, id))) {
throw new MyRuntimeException("错误:数据库连接不存在或无权限!");
@@ -50,6 +50,12 @@ public class DictCategoryController {
return dictCategoryService.queryCategoryTree(reqDictCategory, reqPage);
}
/**
* 查询树形分类列表(不分页)
*
* @param reqDictCategory 树形分类请求参数
* @return 返回树形分类列表
*/
@Operation(summary = "树形分类查询", description = "树形分类")
@GetMapping("/tree")
@RequiresPermissions("sys:dictCategory:query")
@@ -112,6 +118,13 @@ public class DictCategoryController {
return Result.ok(dictCategoryService.listByIds(Arrays.asList(ids.split(","))), "树形分类-查询成功!");
}
/**
* 通过分类编码查询分类树
*
* @param code 分类编码
* @param direction 查询方向(all:所有父子节点, up:父节点, down:子节点)
* @return 返回分类树列表
*/
@Operation(summary = "分类树-通过分类编码查询", description = "分类树-通过分类编码查询")
@GetMapping("/tree/{code}")
@Parameters({
@@ -121,6 +134,13 @@ public class DictCategoryController {
return Result.ok(dictCategoryService.queryCategoryTreeByCode(code, TreeDirection.getDirection(direction)), "分类树-查询成功!");
}
/**
* 通过分类编码查询分类列表(扁平结构)
*
* @param code 分类编码
* @param direction 查询方向(all:所有父子节点, up:父节点, down:子节点)
* @return 返回分类列表
*/
@Operation(summary = "分类列表-通过分类编码查询", description = "分类列表-通过分类编码查询")
@GetMapping("/list/{code}")
@Parameters({
@@ -130,6 +150,13 @@ public class DictCategoryController {
return Result.ok(dictCategoryService.queryCategoryListByCode(code, TreeDirection.getDirection(direction)), "分类列表-查询成功!");
}
/**
* 通过分类ID查询分类树
*
* @param id 分类唯一ID
* @param direction 查询方向(all:所有父子节点, up:父节点, down:子节点)
* @return 返回分类树列表
*/
@Operation(summary = "分类树-通过分类id查询", description = "分类树-通过分类id查询")
@GetMapping("/tree/id/{id}")
@Parameters({
@@ -139,6 +166,13 @@ public class DictCategoryController {
return Result.ok(dictCategoryService.queryCategoryTreeById(id, TreeDirection.getDirection(direction)), "分类树-查询成功!");
}
/**
* 通过分类ID查询分类列表(扁平结构)
*
* @param id 分类唯一ID
* @param direction 查询方向(all:所有父子节点, up:父节点, down:子节点)
* @return 返回分类列表
*/
@Operation(summary = "分类列表-通过分类id查询", description = "分类列表-通过分类id查询")
@GetMapping("/list/id/{id}")
@Parameters({
@@ -148,6 +182,12 @@ public class DictCategoryController {
return Result.ok(dictCategoryService.queryCategoryListById(id, TreeDirection.getDirection(direction)), "分类列表-查询成功!");
}
/**
* 通过分类编码查询单个分类信息
*
* @param code 分类编码
* @return 返回分类对象
*/
@Operation(summary = "分类-通过编码查询")
@GetMapping("/one/{code}")
public Result<DictCategory> queryOneByCode(@Parameter(name = "code", description = "唯一性ID") @PathVariable String code) {
@@ -155,6 +195,14 @@ public class DictCategoryController {
.eq(DictCategory::getCategoryCode, code)), "物料分类-查询成功!");
}
/**
* 判断分类ID是否在指定固定编码的分类范围内
*
* @param categoryId 分类ID
* @param fixCode 固定编码
* @param direction 查询方向(all:所有父子节点, up:父节点, down:子节点)
* @return 返回是否包含该分类
*/
@Operation(summary = "判断分类id是否在指定固定编码分类范围中")
@GetMapping("/include")
@Parameters({
@@ -57,6 +57,12 @@ public class DictItemController {
return Result.ok(new PageResult<>(dictItemService.getDictItems(reqDictItem)), "字典项-查询成功!");
}
/**
* 根据字典编码获取字典项(值根据类型设置进行转换)
*
* @param dictCode 字典编码
* @return 返回字典项列表
*/
@Operation(summary = "根据字典编码获取字典项(值根据类型设置进行转换)")
@SuppressWarnings("rawtypes")
@GetMapping("/{dictCode}")
@@ -28,11 +28,23 @@ public class SysAiController {
private final SysAssistant sysAssistant;
/**
* 与摸鱼小助手聊天
*
* @param aiRequest AI请求参数,包含会话ID和提示词
* @return 聊天响应流
*/
@PostMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponseVo> chat(@RequestBody AiRequest aiRequest) {
return mfishAssistant.chat(aiRequest);
}
/**
* 与系统中心助手聊天
*
* @param aiRequest AI请求参数,包含会话ID和提示词
* @return 聊天响应流
*/
@PostMapping(value = "/assist", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponseVo> sysAssistant(@RequestBody AiRequest aiRequest) {
return sysAssistant.chat(aiRequest);
@@ -41,6 +41,12 @@ public class SysConfigController {
return Result.ok(sysConfigService.querySysConfig(AuthInfoUtils.getCurrentUserId()), "界面配置-查询成功!");
}
/**
* 根据配置类型查询当前用户的界面配置
*
* @param type 配置类型(0:样式风格, 1:表格配置)
* @return 返回界面配置对象
*/
@Operation(summary = "界面配置-通过token查询")
@GetMapping("/{type}")
public Result<SysConfig> queryConfig(@PathVariable("type") Integer type) {
@@ -14,10 +14,29 @@ import java.util.List;
* @version: V2.3.1
*/
public interface DictCategoryMapper extends BaseMapper<DictCategory> {
/**
* 新增分类(使用自定义插入以自动生成树编码)
*
* @param dictCategory 分类对象
* @return 影响行数
*/
int insertCategory(DictCategory dictCategory);
/**
* 查询满足条件的分类最大等级
*
* @param reqDictCategory 过滤参数
* @return 最大分类等级
*/
Integer queryMaxCategoryLevel(@Param("reqDictCategory") ReqDictCategory reqDictCategory);
/**
* 查询分类列表
*
* @param reqDictCategory 过滤参数
* @param levels 父级等级列表
* @return 分类列表
*/
List<DictCategory> queryCategory(@Param("reqDictCategory") ReqDictCategory reqDictCategory, @Param("levels") List<Integer> levels);
/**
@@ -11,5 +11,12 @@ import org.apache.ibatis.annotations.Param;
* @version: V2.3.1
*/
public interface DictMapper extends BaseMapper<Dict> {
/**
* 判断字典编码是否已存在(排除指定ID)
*
* @param id 字典ID(排除自身)
* @param dictCode 字典编码
* @return 存在的记录数
*/
Integer isDictCodeExist(@Param("id") String id, @Param("dictCode") String dictCode);
}
@@ -15,6 +15,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
@Schema(description = "代码构建请求参数")
public class ReqCodeBuild {
@Schema(description = "表名")
private String tableName;
@Schema(description = "接口路径前缀 例如:/oauth2/user接口前缀为oauth2(不传会使用packageName,最底层包名 例如:cn.com.mfish.sys包会使用sys)")
private String apiPrefix;
@@ -41,6 +41,12 @@ public class CodeBuildServiceImpl extends ServiceImpl<CodeBuildMapper, CodeBuild
@Resource
private RemoteMenuService remoteMenuService;
/**
* 新增代码构建配置
*
* @param codeBuild 代码构建配置对象
* @return 新增结果
*/
@Override
@Transactional
public Result<CodeBuild> insertCodeBuild(CodeBuild codeBuild) {
@@ -54,6 +60,12 @@ public class CodeBuildServiceImpl extends ServiceImpl<CodeBuildMapper, CodeBuild
throw new MyRuntimeException("错误:代码生成失败");
}
/**
* 更新代码构建配置
*
* @param codeBuild 代码构建配置对象
* @return 更新结果
*/
@Override
public Result<CodeBuild> updateCodeBuild(CodeBuild codeBuild) {
Result<CodeBuild> result = validateCodeBuild(codeBuild);
@@ -66,11 +78,24 @@ public class CodeBuildServiceImpl extends ServiceImpl<CodeBuildMapper, CodeBuild
throw new MyRuntimeException("错误:代码生成更新失败");
}
/**
* 获取代码生成结果
*
* @param id 代码构建ID
* @return 生成的代码列表
*/
@Override
public Result<List<CodeVo>> getCode(Long id) {
return Result.ok(freemarkerUtils.getCode(buildReqCode(id)), "获取代码成功");
}
/**
* 下载生成的代码(ZIP格式)
*
* @param id 代码构建ID
* @param response HTTP响应对象
* @throws IOException IO异常
*/
@Override
public void downloadCode(Long id, HttpServletResponse response) throws IOException {
ReqCode reqCode = buildReqCode(id);
@@ -83,6 +108,12 @@ public class CodeBuildServiceImpl extends ServiceImpl<CodeBuildMapper, CodeBuild
IOUtils.write(data, response.getOutputStream());
}
/**
* 将生成的代码保存到本地IDE目录
*
* @param id 代码构建ID
* @return 保存结果
*/
@Override
public Result<Boolean> saveLocal(Long id) {
if (!freemarkerUtils.saveCode(buildReqCode(id))) {
@@ -91,6 +122,12 @@ public class CodeBuildServiceImpl extends ServiceImpl<CodeBuildMapper, CodeBuild
return Result.ok(true, "代码保存成功,请重新启动后端");
}
/**
* 校验代码构建参数(数据库连接ID和表名不能为空)
*
* @param codeBuild 代码构建对象
* @return 校验结果
*/
private Result<CodeBuild> validateCodeBuild(CodeBuild codeBuild) {
if (StringUtils.isEmpty(codeBuild.getConnectId()) || StringUtils.isEmpty(codeBuild.getTableName())) {
return Result.fail(codeBuild, "错误:请选择数据库和表");
@@ -120,6 +157,12 @@ public class CodeBuildServiceImpl extends ServiceImpl<CodeBuildMapper, CodeBuild
return reqCode;
}
/**
* 根据代码构建配置创建菜单及按钮权限
*
* @param reqMenuCreate 菜单创建请求参数
* @return 创建的菜单对象
*/
@Override
public Result<SsoMenu> createMenu(ReqMenuCreate reqMenuCreate) {
ReqCode reqCode = buildReqCode(reqMenuCreate.getId());
@@ -146,6 +189,15 @@ public class CodeBuildServiceImpl extends ServiceImpl<CodeBuildMapper, CodeBuild
return result;
}
/**
* 构建按钮菜单对象(查询按钮仅含查询权限,其他按钮同时包含查询和对应操作权限)
*
* @param id 父菜单ID
* @param reqCode 代码请求参数
* @param type 操作类型
* @param sort 排序号
* @return 按钮菜单对象
*/
private SsoMenu buildButtonMenu(String id, ReqCode reqCode, OperateType type, int sort) {
SsoMenu menu = new SsoMenu();
menu.setParentId(id)
@@ -30,6 +30,13 @@ import java.util.stream.Collectors;
*/
@Service
public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, DictCategory> implements DictCategoryService {
/**
* 分页查询分类树(先查一级分类,再查子分类构建树)
*
* @param reqDictCategory 查询条件
* @param reqPage 分页参数
* @return 分类树分页结果
*/
@Override
public Result<PageResult<DictCategory>> queryCategoryTree(ReqDictCategory reqDictCategory, ReqPage reqPage) {
List<Integer> levels = buildParentLevel(reqDictCategory);
@@ -43,6 +50,12 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return Result.ok(result, "分类树-查询成功!");
}
/**
* 查询分类树(不分页)
*
* @param reqDictCategory 查询条件
* @return 分类树列表
*/
@Override
public Result<List<DictCategory>> queryCategoryTree(ReqDictCategory reqDictCategory) {
List<DictCategory> list = queryCategory(reqDictCategory);
@@ -51,23 +64,50 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return Result.ok(trees, "分类树-查询成功!");
}
/**
* 查询分类列表(扁平结构)
*
* @param reqDictCategory 查询条件
* @return 分类列表
*/
@Override
public List<DictCategory> queryCategory(ReqDictCategory reqDictCategory) {
List<Integer> list = buildParentLevel(reqDictCategory);
return baseMapper.queryCategory(reqDictCategory, list);
}
/**
* 分页查询一级分类列表
*
* @param reqDictCategory 查询条件
* @param reqPage 分页参数
* @return 一级分类分页结果
*/
@Override
public PageResult<DictCategory> queryOneLevelCategory(ReqDictCategory reqDictCategory, ReqPage reqPage) {
List<Integer> list = buildParentLevel(reqDictCategory);
return queryOneLevelCategory(reqDictCategory, reqPage, list);
}
/**
* 根据父级等级列表分页查询一级分类
*
* @param reqDictCategory 查询条件
* @param reqPage 分页参数
* @param list 父级等级列表
* @return 一级分类分页结果
*/
public PageResult<DictCategory> queryOneLevelCategory(ReqDictCategory reqDictCategory, ReqPage reqPage, List<Integer> list) {
PageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize());
return new PageResult<>(baseMapper.queryOneLevelCategory(reqDictCategory, list));
}
/**
* 构建父级等级列表,用于查询满足条件的父级节点
*
* @param reqDictCategory 查询条件
* @return 父级等级列表
*/
private List<Integer> buildParentLevel(ReqDictCategory reqDictCategory) {
//查询满足条件的父级节点等级
Integer level = baseMapper.queryMaxCategoryLevel(reqDictCategory);
@@ -80,6 +120,12 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return list;
}
/**
* 新增分类
*
* @param category 分类对象
* @return 新增结果
*/
@Override
public Result<DictCategory> insertCategory(DictCategory category) {
Result<DictCategory> result = verifyCategory(category);
@@ -92,6 +138,12 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return Result.fail("错误:分类-添加失败!");
}
/**
* 校验分类信息(父节点不能设为自身,分类编码不允许重复)
*
* @param dictCategory 分类对象
* @return 校验结果
*/
private Result<DictCategory> verifyCategory(DictCategory dictCategory) {
//parentId为空时,设置为空字符串
if (StringUtils.isEmpty(dictCategory.getParentId())) {
@@ -107,6 +159,12 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return Result.ok("分类校验成功");
}
/**
* 删除分类(包含子节点时不允许删除)
*
* @param categoryId 分类ID
* @return 删除结果
*/
@Override
@Transactional
public Result<Boolean> deleteCategory(String categoryId) {
@@ -123,6 +181,12 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return Result.fail("错误:分类-删除失败!");
}
/**
* 更新分类(父节点变更时重新生成树编码序列)
*
* @param category 分类对象
* @return 更新结果
*/
@Override
@Transactional
public Result<DictCategory> updateCategory(DictCategory category) {
@@ -158,6 +222,13 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
}
/**
* 通过分类编码查询分类树
*
* @param fixCode 固定编码
* @param direction 查询方向
* @return 分类树列表
*/
@Override
public List<DictCategory> queryCategoryTreeByCode(String fixCode, TreeDirection direction) {
if (StringUtils.isEmpty(fixCode)) {
@@ -167,6 +238,13 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return queryCategoryTree(category, direction);
}
/**
* 通过分类编码查询分类列表(扁平结构)
*
* @param fixCode 固定编码
* @param direction 查询方向
* @return 分类列表
*/
@Override
public List<DictCategory> queryCategoryListByCode(String fixCode, TreeDirection direction) {
if (StringUtils.isEmpty(fixCode)) {
@@ -177,6 +255,13 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
}
/**
* 通过分类ID查询分类树
*
* @param id 分类ID
* @param direction 查询方向
* @return 分类树列表
*/
@Override
public List<DictCategory> queryCategoryTreeById(String id, TreeDirection direction) {
if (StringUtils.isEmpty(id)) {
@@ -186,6 +271,13 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return queryCategoryTree(category, direction);
}
/**
* 通过分类ID查询分类列表(扁平结构)
*
* @param id 分类ID
* @param direction 查询方向
* @return 分类列表
*/
@Override
public List<DictCategory> queryCategoryListById(String id, TreeDirection direction) {
if (StringUtils.isEmpty(id)) {
@@ -195,12 +287,27 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return queryCategory(category, direction);
}
/**
* 判断分类ID是否在指定编码的分类范围内
*
* @param categoryId 分类ID
* @param fixCode 固定编码
* @param direction 查询方向
* @return 是否包含
*/
@Override
public boolean includeCategory(String categoryId, String fixCode, TreeDirection direction) {
List<DictCategory> list = queryCategoryListByCode(fixCode, direction);
return list.stream().anyMatch((item) -> item.getId().equals(categoryId));
}
/**
* 查询分类树(根据方向构建不同范围的树结构)
*
* @param category 分类对象
* @param direction 查询方向
* @return 分类树列表
*/
private List<DictCategory> queryCategoryTree(DictCategory category, TreeDirection direction) {
if (category == null) {
return new ArrayList<>();
@@ -215,6 +322,13 @@ public class DictCategoryServiceImpl extends ServiceImpl<DictCategoryMapper, Dic
return categoryTree;
}
/**
* 根据方向查询分类列表(支持向上、向下、当前、全部方向)
*
* @param category 分类对象
* @param direction 查询方向
* @return 分类列表
*/
private List<DictCategory> queryCategory(DictCategory category, TreeDirection direction) {
if (category == null) {
return new ArrayList<>();
@@ -28,16 +28,34 @@ public class DictItemServiceImpl extends ServiceImpl<DictItemMapper, DictItem> i
@Resource
private DictCache dictCache;
/**
* 查询字典项列表
*
* @param reqDictItem 查询条件
* @return 字典项列表
*/
@Override
public List<DictItem> getDictItems(ReqDictItem reqDictItem) {
return baseMapper.selectList(buildCondition(reqDictItem));
}
/**
* 根据字典编码删除所有字典项
*
* @param dictCode 字典编码
* @return 是否删除成功
*/
@Override
public boolean deleteDictItemsByCode(String dictCode) {
return baseMapper.delete(new LambdaQueryWrapper<DictItem>().eq(DictItem::getDictCode, dictCode)) > 0;
}
/**
* 构建字典项查询条件
*
* @param reqDictItem 查询参数
* @return Lambda查询条件包装器
*/
private LambdaQueryWrapper buildCondition(ReqDictItem reqDictItem) {
return new LambdaQueryWrapper<DictItem>()
.eq(StringUtils.isNotEmpty(reqDictItem.getDictCode()), DictItem::getDictCode, reqDictItem.getDictCode())
@@ -48,6 +66,12 @@ public class DictItemServiceImpl extends ServiceImpl<DictItemMapper, DictItem> i
.orderByAsc(DictItem::getDictSort);
}
/**
* 根据字典编码查询字典项(优先从缓存获取)
*
* @param dictCode 字典编码
* @return 字典项列表
*/
@Override
public Result<List<DictItem>> queryByCode(String dictCode) {
List<DictItem> list = dictCache.getFromCacheAndDB(dictCode);
@@ -36,6 +36,12 @@ public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements Di
@Resource
DictCache dictCache;
/**
* 更新字典(如果字典编码变更,同步更新所有关联的字典项编码)
*
* @param dict 字典对象
* @return 更新结果
*/
@Override
@Transactional
public Result<Dict> updateDict(Dict dict) {
@@ -59,11 +65,24 @@ public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements Di
throw new MyRuntimeException("错误:编辑字典失败!");
}
/**
* 判断字典编码是否已存在(排除自身)
*
* @param id 字典ID
* @param dictCode 字典编码
* @return 是否存在
*/
@Override
public boolean isDictCodeExist(String id, String dictCode) {
return baseMapper.isDictCodeExist(id, dictCode) > 0;
}
/**
* 删除字典及其关联的所有字典项
*
* @param id 字典ID
* @return 删除结果
*/
@Override
@Transactional
public Result<Boolean> deleteDict(String id) {
@@ -76,6 +95,13 @@ public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements Di
return Result.fail(false, "错误:字典-删除失败!");
}
/**
* 分页查询字典列表
*
* @param reqDict 查询参数
* @param reqPage 分页参数
* @return 字典列表
*/
@Override
public List<Dict> queryList(ReqDict reqDict, ReqPage reqPage) {
PageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize());
@@ -23,6 +23,12 @@ import java.util.List;
@Service
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig> implements SysConfigService {
/**
* 查询用户所有系统配置
*
* @param userId 用户ID
* @return 系统配置列表
*/
@Override
public List<SysConfig> querySysConfig(String userId) {
if (StringUtils.isEmpty(userId)) {
@@ -31,6 +37,13 @@ public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig
return baseMapper.selectList(new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getUserId, userId));
}
/**
* 根据用户ID和配置类型查询系统配置
*
* @param userId 用户ID
* @param type 配置类型
* @return 系统配置对象
*/
@Override
public SysConfig querySysConfig(String userId, Integer type) {
if (StringUtils.isEmpty(userId) || null == type) {
@@ -40,6 +53,12 @@ public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig
.eq(SysConfig::getType, type));
}
/**
* 保存系统配置(已存在则更新,否则新增)
*
* @param sysConfig 系统配置对象
* @return 保存结果
*/
@Override
public Result<SysConfig> saveSysConfig(SysConfig sysConfig) {
String userId = AuthInfoUtils.getCurrentUserId();
@@ -42,12 +42,30 @@ public class TableServiceImpl implements TableService {
@Value("${DBConnect.password.privateKey}")
private String privateKey;
/**
* 获取表字段信息列表
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 字段信息列表
*/
@Override
public List<FieldInfo> getFieldList(String connectId, String tableSchema, String tableName, ReqPage reqPage) {
verifyTableName(tableName);
return queryT(connectId, FieldInfo.class, (build, dbName) -> build.getColumns(dbName, tableSchema, tableName), reqPage);
}
/**
* 获取单个表信息
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 表信息对象
*/
@Override
public TableInfo getTableInfo(String connectId, String tableSchema, String tableName, ReqPage reqPage) {
verifyTableName(tableName);
@@ -58,12 +76,30 @@ public class TableServiceImpl implements TableService {
return list.getFirst();
}
/**
* 获取表信息列表
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 表信息列表
*/
@Override
public List<TableInfo> getTableList(String connectId, String tableSchema, String tableName, ReqPage reqPage) {
verifyTableName(tableName);
return queryT(connectId, TableInfo.class, (build, dbName) -> build.getTableInfo(dbName, tableSchema, tableName), reqPage);
}
/**
* 获取带列头的数据表
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 带列头的数据表结果
*/
@Override
public Result<MetaHeaderDataTable> getHeaderDataTable(String connectId, String tableSchema, String tableName, ReqPage reqPage) {
verifyTableName(tableName);
@@ -71,6 +107,15 @@ public class TableServiceImpl implements TableService {
return Result.ok(new MetaHeaderDataTable(table), "获取表数据成功");
}
/**
* 获取数据表内容
*
* @param connectId 数据库连接ID
* @param tableSchema 表前缀(schema
* @param tableName 表名
* @param reqPage 分页参数
* @return 数据表对象
*/
@Override
public MetaDataTable getDataTable(String connectId, String tableSchema, String tableName, ReqPage reqPage) {
verifyTableName(tableName);
@@ -85,6 +130,11 @@ public class TableServiceImpl implements TableService {
}
/**
* 校验表名合法性(不允许空格、特殊字符,长度不超过128)
*
* @param tableName 表名
*/
private void verifyTableName(String tableName) {
if (tableName == null) {
return;
@@ -100,6 +150,12 @@ public class TableServiceImpl implements TableService {
}
}
/**
* 根据连接ID构建数据源配置
*
* @param connectId 数据库连接ID
* @return 数据源配置对象
*/
private DataSourceOptions<?> buildDBQuery(String connectId) {
Result<DbConnect> result = dbConnectService.queryById(connectId);
if (!result.isSuccess()) {
@@ -121,6 +177,14 @@ public class TableServiceImpl implements TableService {
return query(dataSourceOptions, sql, reqPage);
}
/**
* 使用数据源配置执行SQL查询
*
* @param dataSourceOptions 数据源配置
* @param sql SQL语句
* @param reqPage 分页参数
* @return 数据表结果
*/
private MetaDataTable query(DataSourceOptions<?> dataSourceOptions, String sql, ReqPage reqPage) {
if (reqPage != null) {
MfPageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize());
@@ -128,11 +192,31 @@ public class TableServiceImpl implements TableService {
return QueryHandler.query(dataSourceOptions, sql);
}
/**
* 通过连接ID执行泛型查询
*
* @param connectId 数据库连接ID
* @param cls 目标类型
* @param function 查询函数
* @param reqPage 分页参数
* @param <T> 返回类型
* @return 查询结果列表
*/
private <T> List<T> queryT(String connectId, Class<T> cls, BiFunction<DBDialect, String, BoundSql> function, ReqPage reqPage) {
DataSourceOptions<?> dataSourceOptions = buildDBQuery(connectId);
return queryT(dataSourceOptions, cls, function, reqPage);
}
/**
* 使用数据源配置执行泛型查询
*
* @param dataSourceOptions 数据源配置
* @param cls 目标类型
* @param function 查询函数
* @param reqPage 分页参数
* @param <T> 返回类型
* @return 查询结果列表
*/
private <T> List<T> queryT(DataSourceOptions<?> dataSourceOptions, Class<T> cls, BiFunction<DBDialect, String, BoundSql> function, ReqPage reqPage) {
DBDialect dialect = DBAdapter.getDBDialect(dataSourceOptions.getDbType());
if (reqPage != null) {
@@ -29,11 +29,23 @@ public class DictTools {
@Resource
private DictItemService dictItemService;
/**
* 根据字典名称查询字典编码
*
* @param dictName 字典名称
* @return 字典列表
*/
@Tool(description = "查询字典编码", returnDirect = true)
public List<Dict> queryCodeByName(@ToolParam(description = "字典名称") String dictName) {
return dictService.queryList(new ReqDict().setDictName(dictName), new ReqPage(1, 100));
}
/**
* 根据字典编码查询字典项列表
*
* @param dictCode 字典编码
* @return 字典项列表
*/
@Tool(description = "查询字典项", returnDirect = true)
public List<DictItem> queryByCode(@ToolParam(description = "字典编码") String dictCode) {
Result<List<DictItem>> result = dictItemService.queryByCode(dictCode);
@@ -23,7 +23,9 @@ import java.util.List;
@Data
public class FlowAuthority {
/** 当前用户账号 */
private String account;
/** 用户所属组列表(包括组织和角色) */
private List<String> groupList;
/**
@@ -39,100 +39,208 @@ public class ProcessController {
private final FlowableService flowableService;
/**
* 构造方法,注入工作流服务
*
* @param flowableService 工作流服务
*/
public ProcessController(FlowableService flowableService) {
this.flowableService = flowableService;
}
/**
* 手动部署流程
*
* @param id 流程定义key
* @return 部署结果(返回流程版本号)
*/
@Operation(summary = "手动部署流程")
@GetMapping("/deploy/{id}")
public Result<Integer> deployProcess(@Parameter(name = "id", description = "流程定义key") @PathVariable String id) {
return Result.ok(flowableService.deployProcess(id), "部署成功");
}
/**
* 分页查询运行中的流程实例列表
*
* @param reqProcess 流程查询参数
* @param reqPage 分页参数
* @return 流程实例分页列表
*/
@Operation(summary = "分页查询流程列表")
@GetMapping
public Result<PageResult<MfProcess>> getProcessList(ReqProcess reqProcess, ReqPage reqPage) {
return Result.ok(flowableService.getProcessList(reqProcess, reqPage), "查询流程列表成功");
}
/**
* 分页查询历史流程实例列表(包括已结束的流程)
*
* @param reqProcess 流程查询参数
* @param reqPage 分页参数
* @return 历史流程实例分页列表
*/
@Operation(summary = "分页查询历史流程列表")
@GetMapping("/history")
public Result<PageResult<MfProcess>> getHistoryProcessList(ReqProcess reqProcess, ReqPage reqPage) {
return Result.ok(flowableService.getHistoryProcessList(reqProcess, reqPage), "查询历史流程列表成功");
}
/**
* 启动流程实例
*
* @param flowableParam 流程启动参数
* @return 流程实例ID
*/
@Operation(summary = "启动流程")
@PostMapping("/start")
public Result<String> startProcess(@RequestBody FlowableParam<?> flowableParam) {
return Result.ok(flowableService.startProcess(flowableParam), "启动流程实例成功");
}
/**
* 通过流程实例ID删除流程实例
*
* @param processInstanceId 流程实例ID
* @param reason 删除原因
* @return 删除结果
*/
@Operation(summary = "通过业务key删除流程实例")
@DeleteMapping("/{processInstanceId}")
public Result<String> delProcess(@Parameter(name = "processInstanceId", description = "流程实例id") @PathVariable String processInstanceId, @RequestParam String reason) {
return flowableService.delProcess(processInstanceId, null, reason);
}
/**
* 通过业务Key删除流程实例
*
* @param businessKey 业务Key
* @param reason 删除原因
* @return 删除结果
*/
@Operation(summary = "通过业务key删除流程实例")
@DeleteMapping("/businessKey/{businessKey}")
public Result<String> delProcessByBusinessKey(@Parameter(name = "businessKey", description = "业务id") @PathVariable String businessKey, @RequestParam String reason) {
return flowableService.delProcess(null, businessKey, reason);
}
/**
* 查询流程实例图片(带高亮的流程图)
*
* @param processInstanceId 流程实例ID
* @return 图片Base64编码字符串
*/
@Operation(summary = "查询流程图片")
@GetMapping("/image/{processInstanceId}")
public Result<String> getImage(@Parameter(name = "processInstanceId", description = "流程实例id") @PathVariable("processInstanceId") String processInstanceId) {
return Result.ok(flowableService.getImage(processInstanceId), "查询流程图片成功");
}
/**
* 查询流程实例的审批意见列表
*
* @param processInstanceId 流程实例ID
* @return 审批意见列表
*/
@Operation(summary = "查询实例的审批意见")
@GetMapping("/comments/{processInstanceId}")
public Result<List<AuditComment>> getAuditComments(@Parameter(name = "processInstanceId", description = "流程实例id") @PathVariable String processInstanceId) {
return Result.ok(flowableService.getAuditComments(processInstanceId), "查询审批意见列表成功");
}
/**
* 查询流程实例的任务进度列表
*
* @param processInstanceId 流程实例ID
* @return 任务列表
*/
@Operation(summary = "查询实例的任务进度")
@GetMapping("/tasks/{processInstanceId}")
public Result<List<MfTask>> getProcessTasks(@Parameter(name = "processInstanceId", description = "流程实例id") @PathVariable String processInstanceId) {
return Result.ok(flowableService.getProcessTasks(processInstanceId), "查询任务列表成功");
}
/**
* 根据业务Key获取最新流程实例的任务列表
*
* @param businessKey 业务Key
* @return 任务列表
*/
@Operation(summary = "根据业务key获取最新流程实例任务列表")
@GetMapping("/tasks/businessKey/{businessKey}")
public Result<List<MfTask>> getProcessTasksByBusinessKey(@Parameter(name = "businessKey", description = "业务id") @PathVariable String businessKey) {
return Result.ok(flowableService.getProcessTasksByBusinessKey(businessKey), "查询任务列表成功");
}
/**
* 查询当前用户待处理的任务列表
*
* @param reqTask 任务查询参数
* @param reqPage 分页参数
* @return 待处理任务分页列表
*/
@Operation(summary = "查询待处理任务列表")
@GetMapping("/tasks/pending")
public Result<PageResult<MfTask>> getPendingTasks(ReqTask reqTask, ReqPage reqPage) {
return Result.ok(flowableService.getPendingTasks(reqTask, reqPage), "查询待处理任务列表成功");
}
/**
* 查询所有任务列表(包含历史任务)
*
* @param reqAllTask 全部任务查询参数
* @param reqPage 分页参数
* @return 任务分页列表
*/
@Operation(summary = "查询所有任务列表")
@GetMapping("/tasks/all")
public Result<PageResult<MfTask>> getAllTasks(ReqAllTask reqAllTask, ReqPage reqPage) {
return Result.ok(flowableService.getAllTasks(reqAllTask, reqPage), "查询所有任务列表成功");
}
/**
* 查询当前用户发起的任务列表
*
* @param reqAllTask 任务查询参数
* @param reqPage 分页参数
* @return 任务分页列表
*/
@Operation(summary = "查询我发起任务列表")
@GetMapping("/tasks/apply")
public Result<PageResult<MfTask>> getApplyTasks(ReqAllTask reqAllTask, ReqPage reqPage) {
return Result.ok(flowableService.getApplyTasks(reqAllTask, reqPage), "查询我发起任务列表成功");
}
/**
* 查询流程实例的历史任务(包括当前正在处理的任务)
*
* @param processInstanceId 流程实例ID
* @return 历史任务列表
*/
@Operation(summary = "查询历史任务-包括当前正在处理的任务")
@GetMapping("/tasks/history/{processInstanceId}")
public Result<List<MfTask>> getHistoryTasks(@Parameter(name = "processInstanceId", description = "流程实例id") @PathVariable String processInstanceId) {
return Result.ok(flowableService.getProcessTasks(processInstanceId, true), "查询历史任务列表成功");
}
/**
* 查询当前用户任务统计(待办数、已完成数、已取消数)
*
* @param reqTask 任务查询参数
* @return 任务统计信息
*/
@Operation(summary = "查询当前用户任务统计")
@GetMapping("/tasks/total")
public Result<TaskTotal> getTaskTotal(ReqTask reqTask) {
return Result.ok(flowableService.getTaskTotal(reqTask), "查询当前用户任务统计成功");
}
/**
* 审核通过
*
* @param approveInfo 审批信息
* @return 审核结果
*/
@Operation(summary = "审核通过")
@PostMapping("/tasks/approved")
public Result<String> approvedTask(@RequestBody ApproveInfo approveInfo) {
@@ -140,6 +248,12 @@ public class ProcessController {
return Result.ok("审核通过");
}
/**
* 审核不通过
*
* @param approveInfo 审批信息
* @return 审核结果
*/
@Operation(summary = "审核不通过")
@PostMapping("/tasks/rejected")
public Result<String> rejectedTask(@RequestBody ApproveInfo approveInfo) {
@@ -147,6 +261,12 @@ public class ProcessController {
return Result.ok("审核不通过");
}
/**
* 构建流程定义XML并下载
* 将流程配置转换为BPMN 2.0 XML格式并返回给客户端下载
*
* @param flowManage 流程管理对象(包含流程配置信息)
*/
@Operation(summary = "构建流程xml")
@PostMapping("/buildXml")
public void buildXml(@RequestBody FlowManage flowManage) {
@@ -179,12 +299,24 @@ public class ProcessController {
}
}
/**
* 根据流程实例ID查询对应的流程管理信息
*
* @param processInstanceId 流程实例ID
* @return 流程管理信息
*/
@Operation(summary = "查询流程管理信息")
@GetMapping("/flowManage/{processInstanceId}")
public Result<FlowManage> queryFlowManage(@Parameter(name = "processInstanceId", description = "流程实例id") @PathVariable String processInstanceId) {
return Result.ok(flowableService.queryFlowManage(processInstanceId), "查询流程管理信息成功");
}
/**
* 查询流程实例中当前活跃的任务定义Key列表
*
* @param processInstanceId 流程实例ID
* @return 活跃任务定义Key列表
*/
@Operation(summary = "查询当前流程激活的流程定义key")
@GetMapping("/activeDefinitionKeys/{processInstanceId}")
public Result<List<String>> getActiveDefinitionKeys(@Parameter(name = "processInstanceId", description = "流程实例id") @PathVariable String processInstanceId) {
@@ -20,5 +20,6 @@ import lombok.Data;
@JsonSubTypes.Type(value = ConditionGroup.class, name = "group")
})
public abstract class Condition {
/** 条件类型(simple 或 group */
private String type;
}
@@ -13,7 +13,8 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
public class ConditionGroup extends Condition{
// 'AND' or 'OR'
/** 条件组合逻辑关系(AND 或 OR) */
private String logic;
/** 子条件列表 */
private List<Condition> children;
}
@@ -3,6 +3,11 @@ package cn.com.mfish.workflow.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @description: 节点执行监听器信息
* @author: mfish
* @date: 2026/3/24
*/
@Data
@Schema(description = "节点监听器")
public class ExecutionListenersInfo {
@@ -16,11 +16,17 @@ import org.flowable.bpmn.model.SequenceFlow;
@Data
@Schema(description = "连线对象", name = "FlowEdge")
public class FlowEdge {
/** 审核通过标识 */
private static final String APPROVED = "approved";
/** 审核拒绝标识 */
private static final String REJECTED = "rejected";
/** 条件分支-if前缀 */
private static final String IF = "if_";
/** 条件分支-else前缀 */
private static final String ELSE = "else_";
/** 条件分支-case前缀 */
private static final String CASE = "case_";
/** 审批类型变量名 */
private static final String AUDIT_TYPE = "auditType";
@Schema(description = "连线 ID")
private String id;
@@ -35,6 +41,12 @@ public class FlowEdge {
@Schema(description = "连线业务数据")
private EdgeData data;
/**
* 将连线对象转换为 Flowable 的 SequenceFlow
* 根据连线数据构建条件表达式(如审批结果条件或网关分支条件)
*
* @return Flowable 连线对象
*/
public SequenceFlow create() {
SequenceFlow flow = new SequenceFlow(source, target);
// BPMN ID 不能包含连字符,做简单替换处理
@@ -21,6 +21,12 @@ public class FlowNode {
@Schema(description = "节点业务数据")
private NodeData data;
/**
* 根据节点类型创建对应的基础节点对象
* 将节点业务数据转换为具体的节点实现(开始节点、结束节点、审批节点、排他网关)
*
* @return 基础节点对象
*/
public BaseNode create() {
BaseNode node;
switch (data.getType()) {
@@ -1,5 +1,6 @@
package cn.com.mfish.workflow.entity;
import cn.com.mfish.workflow.enums.ApproveType;
import cn.com.mfish.workflow.enums.NodeType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -33,7 +34,7 @@ public class NodeData {
@Schema(description = "组织名称列表")
private List<String> orgNames;
@Schema(description = "审批类型(OR 或签,AND 会签)")
private String approvalType;
private ApproveType approvalType;
@Schema(description = "执行监听器列表")
private List<ExecutionListenersInfo> executionListeners;
@Schema(description = "完成百分比")
@@ -11,8 +11,12 @@ import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class SimpleCondition extends Condition{
/** 条件变量名 */
private String variable;
/** 比较操作符(eq, ne, gt, lt, ge, le */
private String operator;
/** 比较值 */
private Object value;
/** 值类型(variable 表示值为变量名,其他表示常量) */
private String valueType;
}
@@ -3,16 +3,31 @@ package cn.com.mfish.workflow.enums;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;
/**
* @description: 审批类型枚举
* 定义工作流审批节点的审批方式
* @author: mfish
* @date: 2026/4/2
*/
@Getter
public enum ApproveType {
SINGLE("single", "任何人审批"),
SEQUENTIAL("sequential", "顺序审批"),
CONCURRENT("concurrent", "并行审批");
/** 会签:所有审批人必须审批通过 */
AND("AND", "会签"),
/** 或签:任意一人审批通过即可审批通过 */
OR("OR", "或签");
/** 审批类型标识 */
@JsonValue
private final String type;
/** 审批类型描述 */
private final String comment;
/**
* 审批类型构造方法
*
* @param type 审批类型标识
* @param comment 审批类型描述
*/
ApproveType(String type, String comment) {
this.type = type;
this.comment = comment;
@@ -24,6 +24,12 @@ import org.flowable.engine.delegate.ExecutionListener;
public class CompleteCallbackHandler implements ExecutionListener {
private final RuntimeService runtimeService = SpringBeanFactory.getBean(RuntimeService.class);
/**
* 流程审核完成后的回调通知方法
* 根据审批操作类型(通过、拒绝、取消)调用对应的远程审核接口完成业务回调
*
* @param execution 流程执行上下文对象
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public void notify(DelegateExecution execution) {
@@ -1,5 +1,6 @@
package cn.com.mfish.workflow.nodes;
import cn.com.mfish.workflow.enums.ApproveType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -24,15 +25,28 @@ public class ApprovalNode extends BaseNode {
private List<String> candidateUsers;
@Schema(description = "候选组列表")
private List<String> candidateGroups;
@Schema(description = "审批类型(OR 或签,AND 会签PERCENT 按比例会签")
private String approvalType;
@Schema(description = "审批类型(OR 或签,AND 会签)")
private ApproveType approvalType;
@Schema(description = "会签完成比例(0-100 之间的整数,仅在 approvalType 为 PERCENT 时有效)")
private Integer completionPercent;
/**
* 审批节点构造方法
*
* @param id 节点ID
* @param name 节点名称
* @param documentation 节点说明
*/
public ApprovalNode(String id, String name, String documentation) {
super(id, name, documentation);
}
/**
* 创建用户任务节点,并根据审批类型初始化多实例配置
*
* @param sequenceFlows 所有连线列表
* @return 用户任务节点
*/
@Override
protected FlowNode create(List<SequenceFlow> sequenceFlows) {
UserTask node = new UserTask();
@@ -40,22 +54,27 @@ public class ApprovalNode extends BaseNode {
return node;
}
/**
* 初始化用户任务节点
* 根据审批类型(会签/或签)配置不同的候选人策略和多实例特征
*
* @param userTask 用户任务对象
*/
private void initUserTask(UserTask userTask) {
// 1. 根据审批类型决定如何处理
if ("AND".equalsIgnoreCase(this.getApprovalType())) {
// 全部完成模式(100% 会签)
initMultiInstance(userTask, null);
// 会签模式下只需设置候选人列表(用于任务查询),assignee 会在多实例中动态设置
setCandidateInfo(userTask);
} else if ("PERCENT".equalsIgnoreCase(this.getApprovalType())
&& this.getCompletionPercent() != null
&& this.getCompletionPercent() > 0
&& this.getCompletionPercent() <= 100) {
// 按比例完成模式
initMultiInstance(userTask, this.getCompletionPercent());
// 会签模式下只需设置候选人列表(用于任务查询),assignee 会在多实例中动态设置
setCandidateInfo(userTask);
if (this.getApprovalType() == ApproveType.AND) {
if (this.getCompletionPercent() == null || this.getCompletionPercent() == 100) {
// 全部完成模式(100% 会签)
initMultiInstance(userTask, null);
// 会签模式下只需设置候选人列表(用于任务查询),assignee 会在多实例中动态设置
setCandidateInfo(userTask);
} else {
// 按比例完成模式
initMultiInstance(userTask, this.getCompletionPercent());
// 会签模式下只需设置候选人列表(用于任务查询),assignee 会在多实例中动态设置
setCandidateInfo(userTask);
}
} else {
// 或签 (OR) 或无指定类型:普通单签模式
// Flowable 中,当 UserTask 设置了多个候选人(candidateUsers 或 candidateGroups),
@@ -13,10 +13,23 @@ import java.util.List;
*/
public class EndNode extends BaseNode {
/**
* 结束节点构造方法
*
* @param id 节点ID
* @param name 节点名称
* @param documentation 节点说明
*/
public EndNode(String id, String name, String documentation) {
super(id, name, documentation);
}
/**
* 创建结束事件节点
*
* @param sequenceFlows 所有连线列表
* @return 结束事件节点
*/
@Override
protected FlowNode create(List<SequenceFlow> sequenceFlows) {
return new EndEvent();
@@ -23,11 +23,26 @@ public class ExclusiveNode extends BaseNode {
@Schema(description = "以当前网关为源节点的线条列表")
private List<Branch> branches;
/**
* 排他网关节点构造方法
*
* @param id 节点ID
* @param name 节点名称
* @param documentation 节点说明
* @param branches 分支列表
*/
public ExclusiveNode(String id, String name, String documentation, List<Branch> branches) {
super(id, name, documentation);
this.setBranches(branches);
}
/**
* 创建排他网关节点
* 自动查找 else 分支并设置为默认流转路径
*
* @param sequenceFlows 所有连线列表
* @return 排他网关节点
*/
@Override
protected FlowNode create(List<SequenceFlow> sequenceFlows) {
ExclusiveGateway gateway = new ExclusiveGateway();
@@ -12,10 +12,23 @@ import java.util.List;
* @date: 2026/4/2
*/
public class StartNode extends BaseNode {
/**
* 开始节点构造方法
*
* @param id 节点ID
* @param name 节点名称
* @param documentation 节点说明
*/
public StartNode(String id, String name, String documentation) {
super(id, name, documentation);
}
/**
* 创建开始事件节点
*
* @param sequenceFlows 所有连线列表
* @return 开始事件节点
*/
@Override
protected FlowNode create(List<SequenceFlow> sequenceFlows) {
return new StartEvent();
@@ -1,11 +1,14 @@
package cn.com.mfish.workflow.service.impl;
import cn.com.mfish.common.core.constants.RPCConstants;
import cn.com.mfish.common.core.exception.MyRuntimeException;
import cn.com.mfish.common.core.utils.AuthInfoUtils;
import cn.com.mfish.common.core.utils.StringUtils;
import cn.com.mfish.common.core.web.PageResult;
import cn.com.mfish.common.core.web.ReqPage;
import cn.com.mfish.common.core.web.Result;
import cn.com.mfish.common.oauth.api.entity.UserInfo;
import cn.com.mfish.common.oauth.api.remote.RemoteUserService;
import cn.com.mfish.common.workflow.api.entity.*;
import cn.com.mfish.common.workflow.api.enums.FlowKey;
import cn.com.mfish.common.workflow.api.req.ReqAllTask;
@@ -68,6 +71,8 @@ public class FlowableServiceImpl implements FlowableService {
private final ProcessEngineConfiguration processEngineConfiguration;
@Resource
private FlowManageMapper flowManageMapper;
@Resource
private RemoteUserService remoteUserService;
/**
* 项目启动后自动执行初始化流程任务
@@ -98,6 +103,12 @@ public class FlowableServiceImpl implements FlowableService {
log.info("项目启动后自动执行初始化流程任务完成!");
}
/**
* 根据流程管理ID部署流程
*
* @param id 流程管理记录ID
* @return 部署后的流程版本号
*/
@Override
public Integer deployProcess(String id) {
FlowManage flowManage = flowManageMapper.selectById(id);
@@ -107,6 +118,16 @@ public class FlowableServiceImpl implements FlowableService {
return deployProcess(flowManage.getFlowKey(), flowManage.getName(), flowManage.getRemark(), flowManage.getFlowConfig());
}
/**
* 部署流程定义到 Flowable 引擎
* 将前端流程配置转换为 BPMN 模型并部署
*
* @param flowKey 流程定义Key
* @param name 流程名称
* @param desc 流程描述
* @param flowConfig 前端流程配置JSON字符串
* @return 部署后的流程版本号
*/
@Override
public Integer deployProcess(String flowKey, String name, String desc, String flowConfig) {
if (StringUtils.isEmpty(flowKey) || StringUtils.isEmpty(flowConfig)) {
@@ -127,6 +148,13 @@ public class FlowableServiceImpl implements FlowableService {
return processDefinition.getVersion();
}
/**
* 删除流程部署
* 如果存在未完成的流程实例则不允许删除
*
* @param flowKey 流程定义Key
* @param version 流程版本号(为null时删除所有版本)
*/
@Override
public void deleteDeploy(String flowKey, Integer version) {
ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery()
@@ -155,6 +183,14 @@ public class FlowableServiceImpl implements FlowableService {
}
}
/**
* 启动流程实例
* 设置启动人、流程变量,并根据流程参数启动对应的流程定义
*
* @param param 流程启动参数,包含流程Key、业务ID、回调信息等
* @param <T> 业务ID类型
* @return 流程实例ID
*/
@Override
public <T> String startProcess(FlowableParam<T> param) {
if (param.getKey() == null) {
@@ -199,6 +235,13 @@ public class FlowableServiceImpl implements FlowableService {
}
}
/**
* 分页查询运行中的流程实例列表
*
* @param reqProcess 流程查询参数(支持按流程Key、业务Key等条件筛选)
* @param reqPage 分页参数
* @return 流程实例分页结果
*/
@Override
public PageResult<MfProcess> getProcessList(ReqProcess reqProcess, ReqPage reqPage) {
if (FlowKey.UNKNOWN.toString().equals(reqProcess.getFlowKey())) {
@@ -238,6 +281,13 @@ public class FlowableServiceImpl implements FlowableService {
).collect(Collectors.toList()), reqPage.getPageNum(), reqPage.getPageSize(), query.count());
}
/**
* 分页查询历史流程实例列表(包括已结束的流程)
*
* @param reqProcess 流程查询参数
* @param reqPage 分页参数
* @return 历史流程实例分页结果
*/
@Override
public PageResult<MfProcess> getHistoryProcessList(ReqProcess reqProcess, ReqPage reqPage) {
if (FlowKey.UNKNOWN.toString().equals(reqProcess.getFlowKey())) {
@@ -279,12 +329,27 @@ public class FlowableServiceImpl implements FlowableService {
).collect(Collectors.toList()), reqPage.getPageNum(), reqPage.getPageSize(), query.count());
}
/**
* 判断流程实例是否已存在
*
* @param reqProcess 流程查询参数
* @return true表示流程实例已存在
*/
@Override
public boolean existProcess(ReqProcess reqProcess) {
return getProcessList(reqProcess, new ReqPage()).getTotal() > 0;
}
/**
* 删除流程实例(支持通过流程实例ID或业务Key删除)
* 非超管用户只能删除自己启动的流程
*
* @param processInstanceId 流程实例ID(与businessKey二选一)
* @param businessKey 业务Key(与processInstanceId二选一)
* @param reason 删除原因
* @return 删除结果
*/
@Override
@Transactional
public Result<String> delProcess(String processInstanceId, String businessKey, String reason) {
@@ -392,6 +457,12 @@ public class FlowableServiceImpl implements FlowableService {
return buildFlowImage(bpmnModel, activeIds);
}
/**
* 获取流程定义信息
*
* @param processInstanceId 流程实例ID
* @return 流程定义信息(包含流程Key、版本号、定义ID)
*/
@Override
public FlowDefinition getFlowDefinition(String processInstanceId) {
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery()
@@ -407,6 +478,12 @@ public class FlowableServiceImpl implements FlowableService {
.setId(pi.getProcessDefinitionId());
}
/**
* 获取流程实例中当前活跃的(未完成的)任务定义Key列表
*
* @param processInstanceId 流程实例ID
* @return 活跃任务定义Key列表
*/
@Override
public List<String> getActiveDefinitionKeys(String processInstanceId) {
List<HistoricTaskInstance> htiList = historyService.createHistoricTaskInstanceQuery().processInstanceId(processInstanceId).list();
@@ -518,6 +595,14 @@ public class FlowableServiceImpl implements FlowableService {
return mfTasks;
}
/**
* 分页查询当前用户待处理的任务列表
* 根据当前用户权限(候选人/候选组)过滤任务
*
* @param reqTask 任务查询参数(支持按任务名称、流程Key、时间范围等条件筛选)
* @param reqPage 分页参数
* @return 待处理任务分页结果
*/
@Override
public PageResult<MfTask> getPendingTasks(ReqTask reqTask, ReqPage reqPage) {
TaskQuery query = FlowAuthority.getAuthTaskQuery(taskService);
@@ -558,6 +643,7 @@ public class FlowableServiceImpl implements FlowableService {
.setStartTime(task.getCreateTime())
.setDescription(task.getDescription())
.setFormKey(task.getFormKey());
setUserInfo(task.getAssignee(), mfTask);
setProcessKey(task.getProcessVariables(), mfTask);
mfTasks.add(mfTask);
}
@@ -565,6 +651,13 @@ public class FlowableServiceImpl implements FlowableService {
}
/**
* 分页查询所有任务列表(包含当前用户有权限的所有历史任务)
*
* @param reqAllTask 全部任务查询参数
* @param reqPage 分页参数
* @return 任务分页结果
*/
@Override
public PageResult<MfTask> getAllTasks(ReqAllTask reqAllTask, ReqPage reqPage) {
FlowAuthority auth = FlowAuthority.getFlowAuthority();
@@ -572,6 +665,14 @@ public class FlowableServiceImpl implements FlowableService {
return buildPageResult(query, reqPage);
}
/**
* 分页查询当前用户发起的任务列表
* 按流程启动人过滤,只返回当前登录用户发起的流程任务
*
* @param reqAllTask 任务查询参数
* @param reqPage 分页参数
* @return 任务分页结果
*/
@Override
public PageResult<MfTask> getApplyTasks(ReqAllTask reqAllTask, ReqPage reqPage) {
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
@@ -581,6 +682,13 @@ public class FlowableServiceImpl implements FlowableService {
return buildPageResult(query, reqPage);
}
/**
* 构建历史任务分页结果
*
* @param query 历史任务查询对象
* @param reqPage 分页参数
* @return 任务分页结果
*/
private PageResult<MfTask> buildPageResult(HistoricTaskInstanceQuery query, ReqPage reqPage) {
int first = (reqPage.getPageNum() - 1) * reqPage.getPageSize();
List<HistoricTaskInstance> list = query.listPage(first, reqPage.getPageSize());
@@ -613,9 +721,27 @@ public class FlowableServiceImpl implements FlowableService {
.setFormKey(his.getFormKey())
.setEndTime(his.getEndTime());
setProcessKey(his.getProcessVariables(), mfTask);
setUserInfo(his.getAssignee(), mfTask);
return mfTask;
}
/**
* 设置任务办理人的用户信息(昵称、账号)
*
* @param assignee 办理人账号
* @param mfTask 任务对象,用于设置用户信息
*/
private void setUserInfo(String assignee, MfTask mfTask) {
if (StringUtils.isEmpty(assignee)) {
return;
}
Result<UserInfo> userInfo = remoteUserService.getUserById(RPCConstants.INNER, assignee);
if (userInfo.isSuccess()) {
mfTask.setAssigneeName(userInfo.getData().getNickname());
mfTask.setAssigneeAccount(userInfo.getData().getAccount());
}
}
/**
* 获取任务状态,已完成任务如果有删除原因则状态为已取消
*
@@ -630,6 +756,12 @@ public class FlowableServiceImpl implements FlowableService {
return state;
}
/**
* 查询当前用户任务统计(待办数、已完成数、已取消数)
*
* @param reqTask 任务查询参数
* @return 任务统计信息
*/
@Override
public TaskTotal getTaskTotal(ReqTask reqTask) {
FlowAuthority auth = FlowAuthority.getFlowAuthority();
@@ -699,6 +831,13 @@ public class FlowableServiceImpl implements FlowableService {
return query.orderByTaskCreateTime().desc();
}
/**
* 完成任务审批(通过或拒绝)
* 验证任务权限后设置办理人、添加审批意见并完成任务
*
* @param flowOperator 审批操作类型
* @param approveInfo 审批信息(包含任务ID和审批意见)
*/
@Override
@Transactional
public void completeTask(AuditOperator flowOperator, ApproveInfo approveInfo) {
@@ -714,6 +853,12 @@ public class FlowableServiceImpl implements FlowableService {
identityService.setAuthenticatedUserId(null);
}
/**
* 获取流程实例的所有审批意见
*
* @param processInstanceId 流程实例ID
* @return 审批意见列表
*/
@Override
public List<AuditComment> getAuditComments(String processInstanceId) {
List<HistoricTaskInstance> history = historyService
@@ -781,6 +926,12 @@ public class FlowableServiceImpl implements FlowableService {
return Result.ok(taskMap, processDefinitionId);
}
/**
* 从流程变量中提取流程Key并设置到任务对象中
*
* @param variables 流程变量Map
* @param mfTask 任务对象
*/
private void setProcessKey(Map<String, Object> variables, MfTask mfTask) {
if (variables == null) {
return;
@@ -804,25 +955,35 @@ public class FlowableServiceImpl implements FlowableService {
* @return 审批意见列表
*/
private List<AuditComment> buildAuditComment(List<Comment> comments, HistoricTaskInstance his) {
return comments.stream().map(comment ->
new AuditComment()
.setTaskId(his.getId())
.setProcessInstanceId(his.getProcessInstanceId())
.setProcessDefinitionId(his.getProcessDefinitionId())
.setTaskName(his.getName())
.setAssignee(comment.getUserId())
.setComment(comment.getFullMessage())
.setTime(comment.getTime())
.setType(comment.getType())
).collect(Collectors.toList());
return comments.stream().map(comment -> {
AuditComment ac = new AuditComment()
.setTaskId(his.getId())
.setProcessInstanceId(his.getProcessInstanceId())
.setProcessDefinitionId(his.getProcessDefinitionId())
.setTaskName(his.getName())
.setAssignee(comment.getUserId())
.setComment(comment.getFullMessage())
.setTime(comment.getTime())
.setType(comment.getType());
String userId = comment.getUserId();
if (StringUtils.isNotEmpty(userId)) {
Result<UserInfo> userInfo = remoteUserService.getUserById(RPCConstants.INNER, userId);
if (userInfo.isSuccess()) {
ac.setAssigneeName(userInfo.getData().getNickname());
ac.setAssigneeAccount(userInfo.getData().getAccount());
}
}
return ac;
}).collect(Collectors.toList());
}
/**
* 查询流程管理信息
* 根据流程实例ID查询流程管理信息
*
* @param processInstanceId 流程实例id
* @param processInstanceId 流程实例ID
* @return 流程管理信息
*/
@Override
public FlowManage queryFlowManage(String processInstanceId) {
FlowDefinition flowDefinition = getFlowDefinition(processInstanceId);
return flowManageMapper.selectOne(new LambdaQueryWrapper<FlowManage>()
@@ -11,6 +11,8 @@ import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class AiMessage {
/** 消息角色 */
private String role;
/** 消息内容 */
private String content;
}
@@ -11,8 +11,11 @@ import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class ChatResponseVo {
/** 消息ID */
private String id;
/** 消息内容 */
private String content;
/** 结束原因 */
private String finishReason;
}
@@ -6,6 +6,7 @@ import cn.com.mfish.common.core.web.Result;
import java.util.Map;
/**
* @description: 验证码校验服务接口
* @author: mfish
* @date: 2021/8/12 11:06
*/
+1 -1
View File
@@ -36,7 +36,7 @@
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<!-- Jackson -->
<dependency>
@@ -13,6 +13,8 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "mfish")
public class ServiceProperties {
/** 服务版本号 */
private String version;
/** 服务类型(cloud/boot */
private String type = "cloud";
}
@@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.List;
/**
* @description: XSS防护配置属性
* @author: mfish
* @date: 2021/8/12 10:08
*/
@@ -1,13 +1,17 @@
package cn.com.mfish.common.core.constants;
/**
* @description: 常量
* @description: 数据相关常量
* @author: mfish
* @date: 2023/3/21
*/
public class DataConstant {
/** Oracle行号别名 */
public static final String ORACLE_ROW = "ROWNUM_";
/**
* @description: 数据库字段类型常量
*/
public static class DataType {
public static final String ARRAY = "ARRAY";
public static final String TEXT = "TEXT";
@@ -16,6 +16,12 @@ public class ServiceConstants {
public static final String NOCODE_SERVICE = "mf-nocode";
public static final String DEMO_SERVICE = "mf-demo";
/**
* 判断是否为单体服务类型
*
* @param type 服务类型
* @return 是否为单体服务
*/
public static boolean isBoot(String type) {
return ServiceConstants.SERVER_BOOT.equals(type);
}
@@ -15,6 +15,7 @@ public class DefaultField {
public static final String UPDATE_BY = "update_by";
public static final String UPDATE_TIME = "update_time";
/** 缺省字段名称集合 */
public static Set<String> values = new HashSet<>();
static {
@@ -14,12 +14,39 @@ import org.springframework.web.bind.annotation.RequestHeader;
*/
public interface RemoteAuditApi<T> {
/**
* 审批通过接口
*
* @param origin 请求来源
* @param prefix 业务前缀
* @param id 业务ID
* @param result 工作流回调结果
* @return 操作结果
*/
@PostMapping("/{prefix}/approved/{id}")
Result<String> approved(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable("prefix") String prefix, @PathVariable("id") T id, @RequestBody WorkflowCompleteResult result);
/**
* 审批驳回接口
*
* @param origin 请求来源
* @param prefix 业务前缀
* @param id 业务ID
* @param result 工作流回调结果
* @return 操作结果
*/
@PostMapping("/{prefix}/rejected/{id}")
Result<String> rejected(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable("prefix") String prefix, @PathVariable("id") T id, @RequestBody WorkflowCompleteResult result);
/**
* 审批撤销接口
*
* @param origin 请求来源
* @param prefix 业务前缀
* @param id 业务ID
* @param result 工作流回调结果
* @return 操作结果
*/
@PostMapping("/{prefix}/canceled/{id}")
Result<String> canceled(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable("prefix") String prefix, @PathVariable("id") T id, @RequestBody WorkflowCompleteResult result);
@@ -11,7 +11,10 @@ import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class WorkflowCompleteResult {
/** 流程实例ID */
private String processInstanceId;
/** 审批意见 */
private String comment;
/** 事件名称 */
private String eventName;
}
@@ -22,6 +22,12 @@ public enum HttpType {
return String.valueOf(value);
}
/**
* 根据URL判断HTTP类型
*
* @url 请求URL
* @return HTTP类型枚举
*/
public static HttpType getHttpType(String url) {
if (url.startsWith("https:")) {
return HttpType.HTTPS;
@@ -6,6 +6,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* @description: 验证码异常
* @author: mfish
* @date: 2021/8/12 11:32
*/
@@ -15,9 +16,12 @@ public class CaptchaException extends RuntimeException {
TIMEOUT("timeout", "错误:验证码已失效"),
ERROR("error", "错误:验证码不正确");
/** 异常名称标识 */
@Getter
private final String name;
/** 异常描述信息 */
private final String value;
/** 异常名称到枚举的映射 */
private static final Map<String, Info> map = new HashMap<>();
static {
@@ -1,6 +1,7 @@
package cn.com.mfish.common.core.exception;
/**
* @description: 自定义运行时异常
* @author: mfish
* @date: 2020/2/10 17:17
*/
@@ -1,6 +1,7 @@
package cn.com.mfish.common.core.exception;
/**
* @description: OAuth验证异常
* @author: mfish
* @date: 2020/2/17 16:32
*/
@@ -43,6 +43,7 @@ public class AuthInfoUtils {
*/
public static final String SUPER_ORG_ID = "1";
/** 全部权限标识 */
public static final String ALL_PERMISSION = "*:*:*";
/**
@@ -18,7 +18,9 @@ import java.util.Date;
*/
@Slf4j
public class DataUtils {
/** 数值为空错误提示 */
private static final String DATA_NULL = "错误:数值不允许为空";
/** 未知数据类型错误提示 */
private static final String UNKNOWN_DATA = "错误:未知的数值类型";
/**
@@ -35,26 +35,63 @@ public class DateUtils {
return dateTimeNow(YYYY_MM_DD);
}
/**
* 获取当前时间,格式为yyyy-MM-dd HH:mm:ss
*
* @return 当前时间字符串
*/
public static String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}
/**
* 获取当前时间戳字符串,格式为yyyyMMddHHmmss
*
* @return 时间戳字符串
*/
public static String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
}
/**
* 按指定格式获取当前时间字符串
*
* @param format 日期格式
* @return 格式化后的当前时间字符串
*/
public static String dateTimeNow(final String format) {
return parseDateToStr(format, new Date());
}
/**
* 将日期格式化为yyyy-MM-dd字符串
*
* @param date 日期对象
* @return 格式化后的日期字符串
*/
public static String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
}
/**
* 将日期按指定格式转换为字符串
*
* @param format 日期格式
* @param date 日期对象
* @return 格式化后的日期字符串
*/
public static String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
/**
* 将字符串按指定格式解析为日期对象
*
* @param format 日期格式
* @param ts 日期字符串
* @return 解析后的日期对象
* @throws MyRuntimeException 解析失败时抛出异常
*/
public static Date dateTime(final String format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
@@ -10,6 +10,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.*;
/**
* @description: Servlet工具类,提供请求、响应、会话等操作
* @author: mfish
* @date: 2021/8/13 9:38
*/
@@ -84,6 +85,11 @@ public class ServletUtils {
return Objects.requireNonNull(getRequest()).getSession();
}
/**
* 获取ServletRequestAttributes
*
* @return ServletRequestAttributes对象,获取失败返回null
*/
public static ServletRequestAttributes getRequestAttributes() {
try {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
@@ -93,6 +99,12 @@ public class ServletUtils {
}
}
/**
* 获取请求中所有header信息
*
* @param request HTTP请求对象
* @return header键值对Map
*/
public static Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
@@ -272,7 +272,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
* 是否邮箱地址
*
* @param email 邮箱
* @return
* @return 是否为合法邮箱地址
*/
public static boolean isEmail(String email) {
if (isEmpty(email)) {
@@ -34,6 +34,12 @@ public class Utils {
return getAllFields(clazz);
}
/**
* 反射获取类的所有字段,包括父类字段
*
* @param clazz 类对象
* @return 返回包含所有字段的列表
*/
public static List<Field> getAllFields(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
while (clazz != null) {
@@ -141,6 +147,11 @@ public class Utils {
return ip;
}
/**
* 获取当前主机IP地址
*
* @return 主机IP地址,获取失败时返回127.0.0.1
*/
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
@@ -160,6 +171,11 @@ public class Utils {
return StringUtils.isEmpty(properties.getType()) ? "cloud" : properties.getType();
}
/**
* 打印服务启动信息,包括swagger地址
*
* @param application Spring应用上下文
*/
public static void printServerRun(ConfigurableApplicationContext application) {
Environment env = application.getEnvironment();
String ip = getHostIp();
@@ -12,8 +12,14 @@ import java.util.function.Consumer;
* @date: 2023/12/18
*/
public class HeadReadListener extends AnalysisEventListener<Map<Integer, String>> {
/** 头信息回调消费者 */
private final Consumer<Map<Integer, String>> consumer;
/**
* 构造方法
*
* @param consumer 头信息回调消费者
*/
public HeadReadListener(Consumer<Map<Integer, String>> consumer) {
this.consumer = consumer;
}
@@ -5,9 +5,10 @@ import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 上传存储操作
*
* @param <T>
* @description: 上传数据存储操作接口
* @author: mfish
* @date: 2023/12/18
* @param <T> 数据类型
*/
@Repository
public interface UploadDAO<T> {
@@ -3,6 +3,7 @@ package cn.com.mfish.common.core.utils.http;
import cn.com.mfish.common.core.utils.StringUtils;
/**
* @description: HTML转义工具类,用于防止XSS攻击
* @author: mfish
* @date: 2021/8/12 10:48
*/
@@ -37,10 +37,17 @@ public class OkHttpUtils {
@Data
@Accessors(chain = true)
/**
* 超时配置类
*/
public static class TimeOut {
/** 连接超时时间(秒) */
private long connectTimeOut = 30;
/** 读取超时时间(秒) */
private long readTimeOut = 30;
/** 写入超时时间(秒) */
private long writeTimeout = 60;
/** 时间单位 */
private TimeUnit timeUnit = TimeUnit.SECONDS;
}
@@ -355,8 +362,15 @@ public class OkHttpUtils {
}
}
/**
* SSL构建工具类,用于构建HTTPS请求所需的SSL相关组件
*/
public static class SSLBuild {
//获取SSLSocketFactory
/**
* 获取SSLSocketFactory
*
* @return SSLSocketFactory实例
*/
public static SSLSocketFactory getSSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
@@ -367,7 +381,11 @@ public class OkHttpUtils {
}
}
//获取TrustManager
/**
* 获取TrustManager数组,信任所有证书
*
* @return TrustManager数组
*/
private static TrustManager[] getTrustManager() {
return new TrustManager[]{
new X509TrustManager() {
@@ -387,6 +405,11 @@ public class OkHttpUtils {
};
}
/**
* 获取X509TrustManager实例
*
* @return X509TrustManager实例
*/
public static X509TrustManager getX509TrustManager() {
X509TrustManager trustManager = null;
try {
@@ -13,6 +13,14 @@ import java.util.List;
*/
public class WebRequest {
/**
* 从不同类型的请求对象中获取参数值
*
* @param t 请求对象(支持NativeWebRequest、HttpServletRequest、ServerHttpRequest
* @param var1 参数名
* @param <T> 请求类型
* @return 参数值,未找到返回null
*/
public static <T> String getParameter(T t, String var1) {
if (t instanceof NativeWebRequest) {
return ((NativeWebRequest) t).getParameter(var1);
@@ -29,6 +37,14 @@ public class WebRequest {
return null;
}
/**
* 从不同类型的请求对象中获取请求头值
*
* @param t 请求对象(支持NativeWebRequest、HttpServletRequest、ServerHttpRequest
* @param var1 请求头名称
* @param <T> 请求类型
* @return 请求头值,未找到返回null
*/
public static <T> String getHeader(T t, String var1) {
if (t instanceof NativeWebRequest) {
return ((NativeWebRequest) t).getHeader(var1);
@@ -18,6 +18,12 @@ public class ReqPage implements Serializable {
public ReqPage() {
}
/**
* 带参数的构造方法
*
* @param pageNum 页码
* @param pageSize 每页条数
*/
public ReqPage(Integer pageNum, Integer pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
@@ -29,6 +35,12 @@ public class ReqPage implements Serializable {
@Schema(description = "每页条数(默认值:10)")
private Integer pageSize = 10;
/**
* 设置页码,空值不设置
*
* @param pageNum 页码
* @return 当前对象
*/
public ReqPage setPageNum(Integer pageNum) {
if (pageNum == null) {
return this;
@@ -37,6 +49,12 @@ public class ReqPage implements Serializable {
return this;
}
/**
* 设置每页条数,空值不设置
*
* @param pageSize 每页条数
* @return 当前对象
*/
public ReqPage setPageSize(Integer pageSize) {
if (pageSize == null) {
return this;
@@ -38,42 +38,115 @@ public class Result<T> implements Serializable {
@Schema(description = "补充参数 用于多次检查时携带上一次参数提供下次使用")
private Map<String, String> param = new HashMap<>();
/**
* 构建返回结果
*
* @param data 数据对象
* @param code 状态码
* @param msg 返回消息
* @param <T> 数据类型
* @return 结果对象
*/
public static <T> Result<T> buildResult(T data, int code, String msg) {
return new Result<T>().setCode(code).setData(data).setMsg(msg).setSuccess(code == Constants.SUCCESS);
}
/**
* 返回成功结果(无数据无消息)
*
* @param <T> 数据类型
* @return 成功结果
*/
public static <T> Result<T> ok() {
return buildResult(null, Constants.SUCCESS, null);
}
/**
* 返回成功结果(带消息)
*
* @param msg 返回消息
* @param <T> 数据类型
* @return 成功结果
*/
public static <T> Result<T> ok(String msg) {
return buildResult(null, Constants.SUCCESS, msg);
}
/**
* 返回成功结果(带数据)
*
* @param data 数据对象
* @param <T> 数据类型
* @return 成功结果
*/
public static <T> Result<T> ok(T data) {
return buildResult(data, Constants.SUCCESS, null);
}
/**
* 返回成功结果(带数据和消息)
*
* @param data 数据对象
* @param msg 返回消息
* @param <T> 数据类型
* @return 成功结果
*/
public static <T> Result<T> ok(T data, String msg) {
return buildResult(data, Constants.SUCCESS, msg);
}
/**
* 返回失败结果(无数据无消息)
*
* @param <T> 数据类型
* @return 失败结果
*/
public static <T> Result<T> fail() {
return buildResult(null, Constants.FAIL, null);
}
/**
* 返回失败结果(带消息)
*
* @param msg 返回消息
* @param <T> 数据类型
* @return 失败结果
*/
public static <T> Result<T> fail(String msg) {
return buildResult(null, Constants.FAIL, msg);
}
/**
* 返回失败结果(带数据)
*
* @param data 数据对象
* @param <T> 数据类型
* @return 失败结果
*/
public static <T> Result<T> fail(T data) {
return buildResult(data, Constants.FAIL, null);
}
/**
* 返回失败结果(带数据和消息)
*
* @param data 数据对象
* @param msg 返回消息
* @param <T> 数据类型
* @return 失败结果
*/
public static <T> Result<T> fail(T data, String msg) {
return buildResult(data, Constants.FAIL, msg);
}
/**
* 返回失败结果(带状态码和消息)
*
* @param code 状态码
* @param msg 返回消息
* @param <T> 数据类型
* @return 失败结果
*/
public static <T> Result<T> fail(int code, String msg) {
return buildResult(null, code, msg);
}

Some files were not shown because too many files have changed in this diff Show More