From 571df1c434cd693ea9d1c6dddb19d16b8d7589c3 Mon Sep 17 00:00:00 2001 From: qiufeng Date: Mon, 20 Apr 2026 22:39:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=AE=A1=E6=89=B9=E6=A0=B7=E4=BE=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .codex/agents/comment-checker.md | 59 ++ .codex/skills/crud-generator/SKILL.md | 622 ++++++++++++++++++ .codex/skills/frontend-crud/SKILL.md | 596 +++++++++++++++++ .codex/skills/workflow-audit/SKILL.md | 266 ++++++++ .gitignore | 2 + .qoder/skills/workflow-audit/SKILL.md | 25 +- db/mf_demo.sql | 20 + db/mf_oauth.sql | 1 + db/mf_system.sql | 1 + db/mf_workflow.sql | 3 + db/mfish_nocode.sql | 24 + mf-api/mf-demo-api/pom.xml | 25 + .../RemoteDemoLeaveApplyFallBack.java | 38 ++ .../remote/RemoteDemoLeaveApplyService.java | 15 + .../common/workflow/api/enums/FlowKey.java | 1 + .../api/fallback/RemoteWorkflowFallBack.java | 5 + .../api/remote/RemoteWorkflowService.java | 10 + mf-api/pom.xml | 3 +- mf-business/mf-demo/pom.xml | 14 +- .../controller/DemoLeaveApplyController.java | 109 +++ .../demo/mapper/DemoLeaveApplyMapper.java | 13 + .../demo/mapper/xml/DemoLeaveApplyMapper.xml | 5 + .../impl/DemoLeaveApplyServiceImpl.java | 208 ++++++ .../controller/ProcessController.java | 6 + .../service/impl/FlowManageServiceImpl.java | 3 + .../service/impl/FlowableServiceImpl.java | 28 +- mf-common/mf-common-api/pom.xml | 10 +- .../common/api/BootDemoLeaveApplyService.java | 34 + .../mfish/common/api/BootWorkflowService.java | 5 + .../core/constants/ServiceConstants.java | 1 + mf-common/mf-common-demo/pom.xml | 30 + .../common/demo/entity/DemoLeaveApply.java | 66 ++ .../common/demo/req/ReqDemoLeaveApply.java | 25 + .../demo/service/DemoLeaveApplyService.java | 40 ++ .../workflow/service/FlowableService.java | 9 +- mf-common/pom.xml | 3 +- .../lib/mf-common-bi-mf-2.3.1-pg.jar | Bin 189966 -> 189966 bytes .../mf-start-boot/lib/mf-nocode-mf-2.3.1.jar | Bin 209566 -> 209566 bytes .../lib/mf-common-bi-mf-2.3.1-pg.jar | Bin 189966 -> 189966 bytes .../lib/mf-nocode-mf-2.3.1.jar | Bin 209566 -> 209566 bytes pom.xml | 10 + 41 files changed, 2328 insertions(+), 7 deletions(-) create mode 100644 .codex/agents/comment-checker.md create mode 100644 .codex/skills/crud-generator/SKILL.md create mode 100644 .codex/skills/frontend-crud/SKILL.md create mode 100644 .codex/skills/workflow-audit/SKILL.md create mode 100644 mf-api/mf-demo-api/pom.xml create mode 100644 mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/fallback/RemoteDemoLeaveApplyFallBack.java create mode 100644 mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/remote/RemoteDemoLeaveApplyService.java create mode 100644 mf-business/mf-demo/src/main/java/cn/com/mfish/demo/controller/DemoLeaveApplyController.java create mode 100644 mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/DemoLeaveApplyMapper.java create mode 100644 mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/xml/DemoLeaveApplyMapper.xml create mode 100644 mf-business/mf-demo/src/main/java/cn/com/mfish/demo/service/impl/DemoLeaveApplyServiceImpl.java create mode 100644 mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootDemoLeaveApplyService.java create mode 100644 mf-common/mf-common-demo/pom.xml create mode 100644 mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/entity/DemoLeaveApply.java create mode 100644 mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/req/ReqDemoLeaveApply.java create mode 100644 mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/service/DemoLeaveApplyService.java diff --git a/.codex/agents/comment-checker.md b/.codex/agents/comment-checker.md new file mode 100644 index 00000000..5dd47ea9 --- /dev/null +++ b/.codex/agents/comment-checker.md @@ -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. 复杂业务逻辑方法必须添加注释 + +## 注意事项 + +- 使用中文编写注释 +- 保持与现有代码风格一致 +- 不要修改已有注释的内容 +- 确保注释准确描述代码功能 diff --git a/.codex/skills/crud-generator/SKILL.md b/.codex/skills/crud-generator/SKILL.md new file mode 100644 index 00000000..bd0d963a --- /dev/null +++ b/.codex/skills/crud-generator/SKILL.md @@ -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`、`ServiceImpl`、`IService`) +- 分页: **PageHelper**(`PageHelper.startPage(pageNum, pageSize)`) +- 权限: `@RequiresPermissions("模块:功能:操作")`(insert/update/delete/query/export) +- 日志: `@Log(title = "xxx-操作", operateType = OperateType.INSERT/UPDATE/DELETE)` +- 返回值: `Result`、`Result>`、`Result` +- 文档: SpringDoc `@Tag`、`@Operation`、`@Parameter` +- ID类型: String UUID → `@TableId(type = IdType.ASSIGN_UUID)`;数值自增 → `@TableId(type = IdType.AUTO)` +- 基类: `BaseEntity`(含 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 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 + + + + + +``` + +#### 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> queryPageList(Req{类名} req{类名}, ReqPage reqPage); + + /** 添加 */ + Result<{类名}> add({类名} {变量名}); + + /** 编辑 */ + Result<{类名}> edit({类名} {变量名}); + + /** 通过id删除 */ + Result delete(String id); + + /** 批量删除(ids 逗号分隔) */ + Result 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> 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 delete(String id) { + if (removeById(id)) { + return Result.ok(true, "{中文名称}-删除成功!"); + } + return Result.fail(false, "错误:{中文名称}-删除失败!"); + } + + @Override + public Result 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> 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 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 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`,`@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() + .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> 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 diff --git a/.codex/skills/frontend-crud/SKILL.md b/.codex/skills/frontend-crud/SKILL.md new file mode 100644 index 00000000..dcaae5ec --- /dev/null +++ b/.codex/skills/frontend-crud/SKILL.md @@ -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** + ` +``` + +--- + +#### 5. 新增/编辑弹窗(`views/{apiPrefix}/{entity-kebab-case}/{类名}Modal.vue`) + +```vue + + + +``` + +--- + +#### 6. 详情查看弹窗(`views/{apiPrefix}/{entity-kebab-case}/{类名}ViewModal.vue`) + +```vue + + + +``` + +--- + +## 命名约定 + +| 占位符 | 说明 | 示例 | +|--------|------|------| +| `{类名}` | 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` +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 diff --git a/.codex/skills/workflow-audit/SKILL.md b/.codex/skills/workflow-audit/SKILL.md new file mode 100644 index 00000000..081d5e4e --- /dev/null +++ b/.codex/skills/workflow-audit/SKILL.md @@ -0,0 +1,266 @@ +--- +name: workflow-audit +description: 为 mfish-nocode-pro 项目集成 Flowable 工作流审批能力,包括注册 FlowKey、实体审批状态字段、Service 启动/撤回流程、Controller 审批回调接口、Feign 回调接口注册五个步骤的完整代码模板。当用户说"带工作流审批"、"发布审核流程"、"集成工作流"、"审批回调"时使用此 skill。 +--- + +# 工作流审批集成 + +适用于需要**发布/审核**流程的业务模块(如大屏发布、内容审核等),在标准 CRUD 基础上叠加工作流能力。 + +## 核心概念 + +| 组件 | 说明 | +|------|------| +| `FlowableParam` | 启动工作流参数:`key`(流程定义key)、`id`(业务id)、`prefix`(回调URL前缀)、`callback`(回调Feign接口全路径) | +| `FlowKey` 枚举 | 流程定义key枚举,新业务须在此注册,路径:`mf-api/mf-workflow-api/.../FlowKey.java` | +| `RemoteAuditApi` | 审批回调接口,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 delete{类名}(String id) { + {类名} entity = getById(id); + if (entity == null) { + return Result.ok(false, "错误:记录不存在!"); + } + if (remove(new LambdaQueryWrapper<{类名}>().eq({类名}::getId, id))) { + Result 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 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 result = remoteWorkflowService.startProcess(RPCConstants.INNER, + new FlowableParam() + .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 approved( + @PathVariable String id, + @RequestBody WorkflowCompleteResult result) { + return {变量名}Service.audit(id, 1, result); +} + +@PostMapping("/rejected/{id}") +public Result rejected( + @PathVariable String id, + @RequestBody WorkflowCompleteResult result) { + return {变量名}Service.audit(id, 2, result); +} + +@PostMapping("/canceled/{id}") +public Result canceled( + @PathVariable String id, + @RequestBody WorkflowCompleteResult result) { + return {变量名}Service.audit(id, null, result); +} +``` + +### 第五步:注册 Feign 回调接口(微服务模式) + +在 `mf-api/mf-xxx-api` 模块中定义回调 Feign 接口,继承 `RemoteAuditApi`: + +```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 { + // 其他跨服务调用方法... +} +``` + +> - `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() + .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`,并提供 `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` 默认仅引入: + `com.baomidoumybatis-plus-spring`。 +2. 不要默认引入 `mybatis-plus-annotation`、`mybatis-plus-extension`、`fastexcel`;仅在确有直接编译依赖时再按最小集补充。 +3. 若公共模块实体/接口仅用于跨层共享,优先保持依赖最小化,避免把业务模块的技术依赖扩散到 `mf-common`。 diff --git a/.gitignore b/.gitignore index 8fd50dc0..bbc9ad00 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,5 @@ /mf-api/mf-workflow-api/target/ /mf-common/mf-common-nocode/target/ /mf-common/mf-common-prometheus/target/ +/mf-common/mf-common-demo/target/ +/mf-api/mf-demo-api/target/ diff --git a/.qoder/skills/workflow-audit/SKILL.md b/.qoder/skills/workflow-audit/SKILL.md index 5b24a8fa..081d5e4e 100644 --- a/.qoder/skills/workflow-audit/SKILL.md +++ b/.qoder/skills/workflow-audit/SKILL.md @@ -1,4 +1,4 @@ ---- +--- name: workflow-audit description: 为 mfish-nocode-pro 项目集成 Flowable 工作流审批能力,包括注册 FlowKey、实体审批状态字段、Service 启动/撤回流程、Controller 审批回调接口、Feign 回调接口注册五个步骤的完整代码模板。当用户说"带工作流审批"、"发布审核流程"、"集成工作流"、"审批回调"时使用此 skill。 --- @@ -241,3 +241,26 @@ public interface Remote{类名}Service extends RemoteAuditApi { - 工作流参数:[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`,并提供 `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` 默认仅引入: + `com.baomidoumybatis-plus-spring`。 +2. 不要默认引入 `mybatis-plus-annotation`、`mybatis-plus-extension`、`fastexcel`;仅在确有直接编译依赖时再按最小集补充。 +3. 若公共模块实体/接口仅用于跨层共享,优先保持依赖最小化,避免把业务模块的技术依赖扩散到 `mf-common`。 diff --git a/db/mf_demo.sql b/db/mf_demo.sql index fae6e125..412ea1b0 100644 --- a/db/mf_demo.sql +++ b/db/mf_demo.sql @@ -250,4 +250,24 @@ INSERT INTO `demo_order_detail` VALUES ('e319c6b94efd11eb820300163e11f4a0', 'O16 INSERT INTO `demo_order_detail` VALUES ('f36c3a074b0911eb820300163e11f4a0', 'O16093791675560001', '雕牌超效加酶无磷洗衣粉2.68千克/袋', 'https://www.ecishan.com.cn/storage/714991-1.png', 24.30, 24.30, 2, NULL, NULL, 0.00, 48.60, 0.00, '', NULL, '', NULL); INSERT INTO `demo_order_detail` VALUES ('f36c3a2f4b0911eb820300163e11f4a0', 'O16093791675560001', '福临门苏软香 10kg/袋', 'https://www.ecishan.com.cn/storage/file16049116832980001.png', 59.90, 59.90, 2, NULL, NULL, 0.00, 119.80, 0.00, '', NULL, '', NULL); +-- ---------------------------- +-- Table structure for demo_leave_apply +-- ---------------------------- +DROP TABLE IF EXISTS `demo_leave_apply`; +CREATE TABLE `demo_leave_apply` ( + `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '唯一ID', + `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '申请标题', + `leave_type` tinyint NULL DEFAULT NULL COMMENT '请假类型 1事假 2病假 3年假', + `start_time` datetime NULL DEFAULT NULL COMMENT '开始时间', + `end_time` datetime NULL DEFAULT NULL COMMENT '结束时间', + `leave_days` decimal(5, 1) NULL DEFAULT NULL COMMENT '请假天数', + `reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请假事由', + `audit_state` tinyint NULL DEFAULT NULL COMMENT '审核状态 null未提交 0审核中 1已通过 2未通过', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '请假申请审批Demo' ROW_FORMAT = Dynamic; + SET FOREIGN_KEY_CHECKS = 1; diff --git a/db/mf_oauth.sql b/db/mf_oauth.sql index 22194314..ecebd8f1 100644 --- a/db/mf_oauth.sql +++ b/db/mf_oauth.sql @@ -228,6 +228,7 @@ INSERT INTO `sso_menu` VALUES ('a7d3a7b61fa52964c5c9db477e3b1962', '8ae3ea763294 INSERT INTO `sso_menu` VALUES ('ad5b361ff34235e8ec85cd613a59bf6e', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300001', 3, '查询', '#', 1, 2, '', NULL, 'workflow:flowManage:query', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL); INSERT INTO `sso_menu` VALUES ('c5309ca3ba545da9950278637c90e674', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300002', 3, '新增', '#', 2, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:insert', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL); INSERT INTO `sso_menu` VALUES ('e92728a6d996d4b7cb3acb0fc031d183', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300004', 3, '删除', '#', 4, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:delete', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL); +INSERT INTO `sso_menu` VALUES ('c0adefdb601f6e41f82a06ff6513613e', '53e8eaceee36c1d54e43319fdd60811b', '0001300008', 2, '工作流样例', 'ant-design:fork-outlined', 11, 1, '/demo-leave-apply', '/demo/demo-leave-apply/index.vue', NULL, 0, 1, NULL, 1, '', 'admin', '2026-04-20 17:05:36', 'admin', '2026-04-20 20:05:50'); -- ---------------------------- -- Table structure for sso_org diff --git a/db/mf_system.sql b/db/mf_system.sql index 7a756bac..a34a109c 100644 --- a/db/mf_system.sql +++ b/db/mf_system.sql @@ -271,6 +271,7 @@ INSERT INTO `sys_dict_item` VALUES ('2cdf5389b1d6a35e52c14486e04a3a57', '220bbd3 INSERT INTO `sys_dict_item` VALUES ('261f489024bb90163e49ee85498df47e', '220bbd3b1dd32fd37d0abbd279a14774', 'workflow_task_status', '已审批', 'completed', 0, 2, NULL, 'green', 0, NULL, 'admin', '2025-10-10 16:28:02', 'admin', '2025-10-10 16:28:08'); INSERT INTO `sys_dict_item` VALUES ('07582f8cb8443cd3de3f5ab150386690', '220bbd3b1dd32fd37d0abbd279a14774', 'workflow_task_status', '已取消', 'terminated', 0, 3, '', 'red', 0, NULL, 'admin', '2025-10-10 16:29:10', 'admin', '2025-10-10 16:29:14'); INSERT INTO `sys_dict_item` VALUES ('4e14582f0b59762f2ca1cfda04539202', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '大屏发布', 'screen_release', 0, 1, 'ant-design:fund-projection-screen-outlined', '', 0, NULL, 'admin', '2025-10-15 16:35:15', 'admin', '2025-10-16 11:04:43'); +INSERT INTO `sys_dict_item` VALUES ('581a7e287feec3be8771175bdc5dab9b', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '工作流样例审批', 'demo_leave_apply_release', 0, 2, 'ant-design:fork-outlined', 'blue', 0, '工作流试用样例', 'admin', '2026-04-08 20:08:42', 'admin', '2026-04-20 21:38:45'); -- ---------------------------- -- Table structure for sys_log diff --git a/db/mf_workflow.sql b/db/mf_workflow.sql index f18a36fc..84fb149e 100644 --- a/db/mf_workflow.sql +++ b/db/mf_workflow.sql @@ -28,4 +28,7 @@ CREATE TABLE `flw_mf_manage` ( PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流程管理' ROW_FORMAT = DYNAMIC; +INSERT INTO `flw_mf_manage` VALUES ('b686750cc7edd180d4f5c4ac8d5299fa', 'demo_leave_apply_release', '工作流样例审批', '工作流试用样例', 1, 1, '{\"nodes\":[{\"id\":\"node_start_1\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":250,\"y\":50},\"data\":{\"type\":\"start\",\"label\":\"开始\",\"icon\":\"Play\"},\"label\":\"开始\"},{\"id\":\"node_approval_1776677052658\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":550,\"y\":50},\"data\":{\"type\":\"approval\",\"label\":\"审批\",\"approvalType\":\"OR\",\"userIds\":[\"c51fde3955594074bb4db31e654a4483\"],\"userNames\":[\"mfish\"]},\"label\":\"审批\"},{\"id\":\"node_approval_1776677060910\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":981.25,\"y\":47.5},\"data\":{\"type\":\"approval\",\"label\":\"审批\",\"approvalType\":\"OR\",\"roleIds\":[\"4b423f7b1ac0ed0b46a8e5ec3389ac14\"],\"roleNames\":[\"管理\"]},\"label\":\"审批\"},{\"id\":\"node_end_1776677072235\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":1360,\"y\":221.24999999999997},\"data\":{\"type\":\"end\",\"label\":\"结束\",\"executionListeners\":[{\"event\":\"start\",\"type\":\"class\",\"value\":\"cn.com.mfish.workflow.handler.CompleteCallbackHandler\"}]},\"label\":\"结束\"}],\"edges\":[{\"id\":\"e-node_start_1-right-node_approval_1776677052658-left\",\"type\":\"custom\",\"source\":\"node_start_1\",\"target\":\"node_approval_1776677052658\",\"sourceHandle\":\"right\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":null},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":492.5,\"sourceY\":80.66667175292969,\"targetX\":547.5,\"targetY\":110.66665649414062},{\"id\":\"e-node_approval_1776677052658-right-node_approval_1776677060910-left\",\"type\":\"custom\",\"source\":\"node_approval_1776677052658\",\"target\":\"node_approval_1776677060910\",\"sourceHandle\":\"right\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":\"approved\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":792.5,\"sourceY\":110.66665649414062,\"targetX\":978.75,\"targetY\":108.16665649414062},{\"id\":\"e-node_approval_1776677060910-top-source-node_approval_1776677052658-top-source\",\"type\":\"custom\",\"source\":\"node_approval_1776677060910\",\"target\":\"node_approval_1776677052658\",\"sourceHandle\":\"top-source\",\"targetHandle\":\"top-source\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":\"rejected\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":1101.25,\"sourceY\":45,\"targetX\":670,\"targetY\":47.5},{\"id\":\"e-node_approval_1776677060910-right-node_end_1776677072235-left\",\"type\":\"custom\",\"source\":\"node_approval_1776677060910\",\"target\":\"node_end_1776677072235\",\"sourceHandle\":\"right\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":\"approved\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":1223.75,\"sourceY\":108.16665649414062,\"targetX\":1357.5,\"targetY\":251.9166564941406},{\"id\":\"e-node_approval_1776677052658-bottom-node_end_1776677072235-left\",\"type\":\"custom\",\"source\":\"node_approval_1776677052658\",\"target\":\"node_end_1776677072235\",\"sourceHandle\":\"bottom\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"smoothstep\",\"condition\":\"rejected\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":670,\"sourceY\":173.83331298828125,\"targetX\":1357.5,\"targetY\":251.9166564941406}],\"position\":[-27.5,349.1],\"zoom\":0.8,\"viewport\":{\"x\":-27.5,\"y\":349.1,\"zoom\":0.8}}', 0, 'ca50d1785a07f71abbec0da4af6b0632d8d51f54c84ca5050debf1c46f529291', 'admin', '2026-04-20 17:25:00', 'admin', '2026-04-20 17:25:05'); + + SET FOREIGN_KEY_CHECKS = 1; diff --git a/db/mfish_nocode.sql b/db/mfish_nocode.sql index 0f8c6059..47c35c86 100644 --- a/db/mfish_nocode.sql +++ b/db/mfish_nocode.sql @@ -453,6 +453,7 @@ INSERT INTO `sso_menu` VALUES ('a7d3a7b61fa52964c5c9db477e3b1962', '8ae3ea763294 INSERT INTO `sso_menu` VALUES ('ad5b361ff34235e8ec85cd613a59bf6e', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300001', 3, '查询', '#', 1, 2, '', NULL, 'workflow:flowManage:query', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL); INSERT INTO `sso_menu` VALUES ('c5309ca3ba545da9950278637c90e674', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300002', 3, '新增', '#', 2, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:insert', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL); INSERT INTO `sso_menu` VALUES ('e92728a6d996d4b7cb3acb0fc031d183', '8ae3ea76329402ee495cccfaa4c4c38d', '000140000300004', 3, '删除', '#', 4, 2, '', NULL, 'workflow:flowManage:query,workflow:flowManage:delete', 0, 1, NULL, NULL, '', 'admin', '2026-03-30 19:43:52', '', NULL); +INSERT INTO `sso_menu` VALUES ('c0adefdb601f6e41f82a06ff6513613e', '53e8eaceee36c1d54e43319fdd60811b', '0001300008', 2, '工作流样例', 'ant-design:fork-outlined', 11, 1, '/demo-leave-apply', '/demo/demo-leave-apply/index.vue', NULL, 0, 1, NULL, 1, '', 'admin', '2026-04-20 17:05:36', 'admin', '2026-04-20 20:05:50'); -- ---------------------------- -- Table structure for sso_org @@ -1138,6 +1139,7 @@ INSERT INTO `sys_dict_item` VALUES ('2cdf5389b1d6a35e52c14486e04a3a57', '220bbd3 INSERT INTO `sys_dict_item` VALUES ('261f489024bb90163e49ee85498df47e', '220bbd3b1dd32fd37d0abbd279a14774', 'workflow_task_status', '已审批', 'completed', 0, 2, NULL, 'green', 0, NULL, 'admin', '2025-10-10 16:28:02', 'admin', '2025-10-10 16:28:08'); INSERT INTO `sys_dict_item` VALUES ('07582f8cb8443cd3de3f5ab150386690', '220bbd3b1dd32fd37d0abbd279a14774', 'workflow_task_status', '已取消', 'terminated', 0, 3, '', 'red', 0, NULL, 'admin', '2025-10-10 16:29:10', 'admin', '2025-10-10 16:29:14'); INSERT INTO `sys_dict_item` VALUES ('4e14582f0b59762f2ca1cfda04539202', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '大屏发布', 'screen_release', 0, 1, 'ant-design:fund-projection-screen-outlined', '', 0, NULL, 'admin', '2025-10-15 16:35:15', 'admin', '2025-10-16 11:04:43'); +INSERT INTO `sys_dict_item` VALUES ('581a7e287feec3be8771175bdc5dab9b', 'eeb27772c310addeae7c12d296521399', 'workflow_process_key', '工作流样例审批', 'demo_leave_apply_release', 0, 2, 'ant-design:fork-outlined', 'blue', 0, '工作流试用样例', 'admin', '2026-04-08 20:08:42', 'admin', '2026-04-20 21:38:45'); -- ---------------------------- -- Table structure for sys_log @@ -1514,6 +1516,26 @@ INSERT INTO `demo_order_detail` VALUES ('e319c6b94efd11eb820300163e11f4a0', 'O16 INSERT INTO `demo_order_detail` VALUES ('f36c3a074b0911eb820300163e11f4a0', 'O16093791675560001', '雕牌超效加酶无磷洗衣粉2.68千克/袋', 'https://www.ecishan.com.cn/storage/714991-1.png', 24.30, 24.30, 2, NULL, NULL, 0.00, 48.60, 0.00, '', NULL, '', NULL); INSERT INTO `demo_order_detail` VALUES ('f36c3a2f4b0911eb820300163e11f4a0', 'O16093791675560001', '福临门苏软香 10kg/袋', 'https://www.ecishan.com.cn/storage/file16049116832980001.png', 59.90, 59.90, 2, NULL, NULL, 0.00, 119.80, 0.00, '', NULL, '', NULL); +-- ---------------------------- +-- Table structure for demo_leave_apply +-- ---------------------------- +DROP TABLE IF EXISTS `demo_leave_apply`; +CREATE TABLE `demo_leave_apply` ( + `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '唯一ID', + `title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '申请标题', + `leave_type` tinyint NULL DEFAULT NULL COMMENT '请假类型 1事假 2病假 3年假', + `start_time` datetime NULL DEFAULT NULL COMMENT '开始时间', + `end_time` datetime NULL DEFAULT NULL COMMENT '结束时间', + `leave_days` decimal(5, 1) NULL DEFAULT NULL COMMENT '请假天数', + `reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请假事由', + `audit_state` tinyint NULL DEFAULT NULL COMMENT '审核状态 null未提交 0审核中 1已通过 2未通过', + `create_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '请假申请审批Demo' ROW_FORMAT = DYNAMIC; + -- ---------------------------- -- Table structure for mf_api -- ---------------------------- @@ -1925,4 +1947,6 @@ CREATE TABLE `flw_mf_manage` ( PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流程管理' ROW_FORMAT = DYNAMIC; +INSERT INTO `flw_mf_manage` VALUES ('b686750cc7edd180d4f5c4ac8d5299fa', 'demo_leave_apply_release', '工作流样例审批', '工作流试用样例', 1, 1, '{\"nodes\":[{\"id\":\"node_start_1\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":250,\"y\":50},\"data\":{\"type\":\"start\",\"label\":\"开始\",\"icon\":\"Play\"},\"label\":\"开始\"},{\"id\":\"node_approval_1776677052658\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":550,\"y\":50},\"data\":{\"type\":\"approval\",\"label\":\"审批\",\"approvalType\":\"OR\",\"userIds\":[\"c51fde3955594074bb4db31e654a4483\"],\"userNames\":[\"mfish\"]},\"label\":\"审批\"},{\"id\":\"node_approval_1776677060910\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":981.25,\"y\":47.5},\"data\":{\"type\":\"approval\",\"label\":\"审批\",\"approvalType\":\"OR\",\"roleIds\":[\"4b423f7b1ac0ed0b46a8e5ec3389ac14\"],\"roleNames\":[\"管理\"]},\"label\":\"审批\"},{\"id\":\"node_end_1776677072235\",\"type\":\"custom\",\"draggable\":true,\"initialized\":false,\"position\":{\"x\":1360,\"y\":221.24999999999997},\"data\":{\"type\":\"end\",\"label\":\"结束\",\"executionListeners\":[{\"event\":\"start\",\"type\":\"class\",\"value\":\"cn.com.mfish.workflow.handler.CompleteCallbackHandler\"}]},\"label\":\"结束\"}],\"edges\":[{\"id\":\"e-node_start_1-right-node_approval_1776677052658-left\",\"type\":\"custom\",\"source\":\"node_start_1\",\"target\":\"node_approval_1776677052658\",\"sourceHandle\":\"right\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":null},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":492.5,\"sourceY\":80.66667175292969,\"targetX\":547.5,\"targetY\":110.66665649414062},{\"id\":\"e-node_approval_1776677052658-right-node_approval_1776677060910-left\",\"type\":\"custom\",\"source\":\"node_approval_1776677052658\",\"target\":\"node_approval_1776677060910\",\"sourceHandle\":\"right\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":\"approved\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":792.5,\"sourceY\":110.66665649414062,\"targetX\":978.75,\"targetY\":108.16665649414062},{\"id\":\"e-node_approval_1776677060910-top-source-node_approval_1776677052658-top-source\",\"type\":\"custom\",\"source\":\"node_approval_1776677060910\",\"target\":\"node_approval_1776677052658\",\"sourceHandle\":\"top-source\",\"targetHandle\":\"top-source\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":\"rejected\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":1101.25,\"sourceY\":45,\"targetX\":670,\"targetY\":47.5},{\"id\":\"e-node_approval_1776677060910-right-node_end_1776677072235-left\",\"type\":\"custom\",\"source\":\"node_approval_1776677060910\",\"target\":\"node_end_1776677072235\",\"sourceHandle\":\"right\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"default\",\"condition\":\"approved\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":1223.75,\"sourceY\":108.16665649414062,\"targetX\":1357.5,\"targetY\":251.9166564941406},{\"id\":\"e-node_approval_1776677052658-bottom-node_end_1776677072235-left\",\"type\":\"custom\",\"source\":\"node_approval_1776677052658\",\"target\":\"node_end_1776677072235\",\"sourceHandle\":\"bottom\",\"targetHandle\":\"left\",\"data\":{\"showArrow\":true,\"pathType\":\"smoothstep\",\"condition\":\"rejected\"},\"label\":\"\",\"animated\":true,\"style\":{\"stroke\":\"#EE4F12\",\"strokeWidth\":2},\"markerEnd\":{\"type\":\"arrowclosed\",\"color\":\"#EE4F12\"},\"sourceX\":670,\"sourceY\":173.83331298828125,\"targetX\":1357.5,\"targetY\":251.9166564941406}],\"position\":[-27.5,349.1],\"zoom\":0.8,\"viewport\":{\"x\":-27.5,\"y\":349.1,\"zoom\":0.8}}', 0, 'ca50d1785a07f71abbec0da4af6b0632d8d51f54c84ca5050debf1c46f529291', 'admin', '2026-04-20 17:25:00', 'admin', '2026-04-20 17:25:05'); + SET FOREIGN_KEY_CHECKS = 1; diff --git a/mf-api/mf-demo-api/pom.xml b/mf-api/mf-demo-api/pom.xml new file mode 100644 index 00000000..6565c386 --- /dev/null +++ b/mf-api/mf-demo-api/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + cn.com.mfish + mf-api + mf-2.3.1 + + + mf-demo-api + + + ${java.version} + ${java.version} + UTF-8 + + + + cn.com.mfish + mf-common-core + + + diff --git a/mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/fallback/RemoteDemoLeaveApplyFallBack.java b/mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/fallback/RemoteDemoLeaveApplyFallBack.java new file mode 100644 index 00000000..2734e269 --- /dev/null +++ b/mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/fallback/RemoteDemoLeaveApplyFallBack.java @@ -0,0 +1,38 @@ +package cn.com.mfish.demo.api.fallback; + +import cn.com.mfish.common.core.entity.WorkflowCompleteResult; +import cn.com.mfish.common.core.web.Result; +import cn.com.mfish.demo.api.remote.RemoteDemoLeaveApplyService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; + +/** + * @description: demo请假申请审批回调接口降级处理 + * @author: mfish + * @date: 2026/04/18 + */ +@Component +@Slf4j +public class RemoteDemoLeaveApplyFallBack implements FallbackFactory { + @Override + public RemoteDemoLeaveApplyService create(Throwable cause) { + log.error("错误: demo请假申请审批回调接口调用异常", cause); + return new RemoteDemoLeaveApplyService() { + @Override + public Result approved(String origin, String prefix, String id, WorkflowCompleteResult result) { + return Result.fail("错误:请假审批通过回调接口异常"); + } + + @Override + public Result rejected(String origin, String prefix, String id, WorkflowCompleteResult result) { + return Result.fail("错误:请假审批拒绝回调接口异常"); + } + + @Override + public Result canceled(String origin, String prefix, String id, WorkflowCompleteResult result) { + return Result.fail("错误:请假审批取消回调接口异常"); + } + }; + } +} diff --git a/mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/remote/RemoteDemoLeaveApplyService.java b/mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/remote/RemoteDemoLeaveApplyService.java new file mode 100644 index 00000000..987ac139 --- /dev/null +++ b/mf-api/mf-demo-api/src/main/java/cn/com/mfish/demo/api/remote/RemoteDemoLeaveApplyService.java @@ -0,0 +1,15 @@ +package cn.com.mfish.demo.api.remote; + +import cn.com.mfish.common.core.constants.ServiceConstants; +import cn.com.mfish.common.core.entity.RemoteAuditApi; +import cn.com.mfish.demo.api.fallback.RemoteDemoLeaveApplyFallBack; +import org.springframework.cloud.openfeign.FeignClient; + +/** + * @description: demo请假申请审批回调接口 + * @author: mfish + * @date: 2026/04/18 + */ +@FeignClient(contextId = "remoteDemoLeaveApplyService", value = ServiceConstants.DEMO_SERVICE, fallbackFactory = RemoteDemoLeaveApplyFallBack.class) +public interface RemoteDemoLeaveApplyService extends RemoteAuditApi { +} diff --git a/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/enums/FlowKey.java b/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/enums/FlowKey.java index 6be67fb4..1b47d0bf 100644 --- a/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/enums/FlowKey.java +++ b/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/enums/FlowKey.java @@ -11,6 +11,7 @@ import lombok.Getter; public enum FlowKey { UNKNOWN("unknown"), 大屏发布("screen_release"), + 请假申请发布("demo_leave_apply_release"), TEST("test"); private final String key; diff --git a/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/fallback/RemoteWorkflowFallBack.java b/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/fallback/RemoteWorkflowFallBack.java index 532d9431..e1bbe457 100644 --- a/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/fallback/RemoteWorkflowFallBack.java +++ b/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/fallback/RemoteWorkflowFallBack.java @@ -67,6 +67,11 @@ public class RemoteWorkflowFallBack implements FallbackFactory> getProcessTasksByBusinessKey(String origin, String businessKey) { + return Result.fail("错误:查询流程任务失败" + cause.getMessage()); + } + @Override public Result> getAuditComments(String origin, String processInstanceId) { return Result.fail("错误:查询审批评论失败" + cause.getMessage()); diff --git a/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/remote/RemoteWorkflowService.java b/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/remote/RemoteWorkflowService.java index be0eabde..5dd2af04 100644 --- a/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/remote/RemoteWorkflowService.java +++ b/mf-api/mf-workflow-api/src/main/java/cn/com/mfish/common/workflow/api/remote/RemoteWorkflowService.java @@ -118,6 +118,16 @@ public interface RemoteWorkflowService { @GetMapping("/process/tasks/{processInstanceId}") Result> getProcessTasks(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable String processInstanceId); + /** + * 通过业务id查询流程实例任务列表 + * + * @param origin 来源 + * @param businessKey 业务key + * @return 任务列表 + */ + @GetMapping("/process/tasks/businessKey/{businessKey}") + Result> getProcessTasksByBusinessKey(@RequestHeader(RPCConstants.REQ_ORIGIN) String origin, @PathVariable String businessKey); + /** * 查询待处理任务列表 * diff --git a/mf-api/pom.xml b/mf-api/pom.xml index e6520ca5..3b4055dd 100644 --- a/mf-api/pom.xml +++ b/mf-api/pom.xml @@ -18,6 +18,7 @@ mf-storage-api mf-workflow-api mf-nocode-api + mf-demo-api @@ -25,4 +26,4 @@ ${java.version} - \ No newline at end of file + diff --git a/mf-business/mf-demo/pom.xml b/mf-business/mf-demo/pom.xml index 346d7fa2..bc2ee8b8 100644 --- a/mf-business/mf-demo/pom.xml +++ b/mf-business/mf-demo/pom.xml @@ -25,5 +25,17 @@ cn.com.mfish mf-common-web + + cn.com.mfish + mf-common-workflow + + + cn.com.mfish + mf-common-demo + + + cn.com.mfish + mf-demo-api + - \ No newline at end of file + diff --git a/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/controller/DemoLeaveApplyController.java b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/controller/DemoLeaveApplyController.java new file mode 100644 index 00000000..45d7e464 --- /dev/null +++ b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/controller/DemoLeaveApplyController.java @@ -0,0 +1,109 @@ +package cn.com.mfish.demo.controller; + +import cn.com.mfish.common.core.entity.WorkflowCompleteResult; +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.demo.entity.DemoLeaveApply; +import cn.com.mfish.common.demo.req.ReqDemoLeaveApply; +import cn.com.mfish.common.demo.service.DemoLeaveApplyService; +import cn.com.mfish.common.log.annotation.Log; +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: 请假申请审批Demo + * @author: mfish + * @date: 2026-04-18 + * @version: V2.3.1 + */ +@Slf4j +@Tag(name = "请假申请审批Demo") +@RestController +@RequestMapping("/demoLeaveApply") +public class DemoLeaveApplyController { + @Resource + private DemoLeaveApplyService demoLeaveApplyService; + + @Operation(summary = "请假申请审批Demo-分页列表查询", description = "请假申请审批Demo-分页列表查询") + @GetMapping + public Result> queryPageList(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) { + return demoLeaveApplyService.queryPageList(reqDemoLeaveApply, reqPage); + } + + @Log(title = "请假申请审批Demo-添加", operateType = OperateType.INSERT) + @Operation(summary = "请假申请审批Demo-添加") + @PostMapping + public Result add(@RequestBody DemoLeaveApply demoLeaveApply) { + return demoLeaveApplyService.add(demoLeaveApply); + } + + @Log(title = "请假申请审批Demo-编辑", operateType = OperateType.UPDATE) + @Operation(summary = "请假申请审批Demo-编辑") + @PutMapping + public Result edit(@RequestBody DemoLeaveApply demoLeaveApply) { + return demoLeaveApplyService.edit(demoLeaveApply); + } + + @Log(title = "请假申请审批Demo-通过id删除", operateType = OperateType.DELETE) + @Operation(summary = "请假申请审批Demo-通过id删除") + @DeleteMapping("/{id}") + public Result delete(@Parameter(name = "id", description = "唯一ID") @PathVariable String id) { + return demoLeaveApplyService.delete(id); + } + + @Log(title = "请假申请审批Demo-批量删除", operateType = OperateType.DELETE) + @Operation(summary = "请假申请审批Demo-批量删除") + @DeleteMapping("/batch/{ids}") + public Result deleteBatch(@Parameter(name = "ids", description = "唯一ID") @PathVariable String ids) { + return demoLeaveApplyService.deleteBatch(ids); + } + + @Operation(summary = "请假申请审批Demo-通过id查询") + @GetMapping("/{id}") + public Result queryById(@Parameter(name = "id", description = "唯一ID") @PathVariable String id) { + return demoLeaveApplyService.queryById(id); + } + + @Operation(summary = "导出请假申请审批Demo", description = "导出请假申请审批Demo") + @GetMapping("/export") + public void export(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) throws IOException { + demoLeaveApplyService.export(reqDemoLeaveApply, reqPage); + } + + @Log(title = "请假申请审批Demo-提交审批", operateType = OperateType.UPDATE) + @Operation(summary = "请假申请审批Demo-提交审批") + @PostMapping("/submit/{id}") + public Result submit(@PathVariable String id) { + return demoLeaveApplyService.submit(id); + } + + @Log(title = "请假申请审批Demo-撤回审批", operateType = OperateType.UPDATE) + @Operation(summary = "请假申请审批Demo-撤回审批") + @PostMapping("/revoke/{id}") + public Result revoke(@PathVariable String id) { + return demoLeaveApplyService.revoke(id); + } + + @PostMapping("/approved/{id}") + public Result approved(@PathVariable String id, @RequestBody WorkflowCompleteResult result) { + return demoLeaveApplyService.audit(id, 1, result); + } + + @PostMapping("/rejected/{id}") + public Result rejected(@PathVariable String id, @RequestBody WorkflowCompleteResult result) { + return demoLeaveApplyService.audit(id, 2, result); + } + + @PostMapping("/canceled/{id}") + public Result canceled(@PathVariable String id, @RequestBody WorkflowCompleteResult result) { + return demoLeaveApplyService.audit(id, null, result); + } +} diff --git a/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/DemoLeaveApplyMapper.java b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/DemoLeaveApplyMapper.java new file mode 100644 index 00000000..44157f6a --- /dev/null +++ b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/DemoLeaveApplyMapper.java @@ -0,0 +1,13 @@ +package cn.com.mfish.demo.mapper; + +import cn.com.mfish.common.demo.entity.DemoLeaveApply; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * @description: 请假申请审批Demo + * @author: mfish + * @date: 2026-04-18 + * @version: V2.3.1 + */ +public interface DemoLeaveApplyMapper extends BaseMapper { +} diff --git a/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/xml/DemoLeaveApplyMapper.xml b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/xml/DemoLeaveApplyMapper.xml new file mode 100644 index 00000000..6b5f6ff2 --- /dev/null +++ b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/mapper/xml/DemoLeaveApplyMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/service/impl/DemoLeaveApplyServiceImpl.java b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/service/impl/DemoLeaveApplyServiceImpl.java new file mode 100644 index 00000000..73dd50c1 --- /dev/null +++ b/mf-business/mf-demo/src/main/java/cn/com/mfish/demo/service/impl/DemoLeaveApplyServiceImpl.java @@ -0,0 +1,208 @@ +package cn.com.mfish.demo.service.impl; + +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.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 cn.com.mfish.common.demo.entity.DemoLeaveApply; +import cn.com.mfish.common.demo.req.ReqDemoLeaveApply; +import cn.com.mfish.common.demo.service.DemoLeaveApplyService; +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 cn.com.mfish.demo.mapper.DemoLeaveApplyMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.pagehelper.PageHelper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @description: 请假申请审批Demo + * @author: mfish + * @date: 2026-04-18 + * @version: V2.3.1 + */ +@Service +public class DemoLeaveApplyServiceImpl extends ServiceImpl implements DemoLeaveApplyService { + @Resource + private RemoteWorkflowService remoteWorkflowService; + + @Override + public Result> queryPageList(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) { + return Result.ok(new PageResult<>(queryList(reqDemoLeaveApply, reqPage)), "请假申请审批Demo-查询成功!"); + } + + private List queryList(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) { + PageHelper.startPage(reqPage.getPageNum(), reqPage.getPageSize()); + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper() + .like(!StringUtils.isEmpty(reqDemoLeaveApply.getTitle()), DemoLeaveApply::getTitle, reqDemoLeaveApply.getTitle()) + .eq(null != reqDemoLeaveApply.getLeaveType(), DemoLeaveApply::getLeaveType, reqDemoLeaveApply.getLeaveType()) + .eq(null != reqDemoLeaveApply.getAuditState(), DemoLeaveApply::getAuditState, reqDemoLeaveApply.getAuditState()) + .orderByDesc(DemoLeaveApply::getCreateTime); + return list(lambdaQueryWrapper); + } + + @Override + public Result add(DemoLeaveApply demoLeaveApply) { + demoLeaveApply.setAuditState(-1); + // 根据开始时间和结束时间计算请假天数 + if (demoLeaveApply.getStartTime() != null && demoLeaveApply.getEndTime() != null) { + long diffInMillis = demoLeaveApply.getEndTime().getTime() - demoLeaveApply.getStartTime().getTime(); + // 将毫秒转换为天数,保留一位小数 + BigDecimal days = new BigDecimal(diffInMillis) + .divide(new BigDecimal(TimeUnit.DAYS.toMillis(1)), 1, RoundingMode.HALF_UP); + demoLeaveApply.setLeaveDays(days); + } + + if (save(demoLeaveApply)) { + return Result.ok(demoLeaveApply, "请假申请审批Demo-添加成功!"); + } + return Result.fail(demoLeaveApply, "错误:请假申请审批Demo-添加失败!"); + } + + @Override + public Result edit(DemoLeaveApply demoLeaveApply) { + DemoLeaveApply exist = getById(demoLeaveApply.getId()); + if (exist == null) { + throw new MyRuntimeException("错误:记录不存在!"); + } + if (!Integer.valueOf(-1).equals(exist.getAuditState())) { + throw new MyRuntimeException("错误:当前记录不可编辑!"); + } + demoLeaveApply.setAuditState(exist.getAuditState()); + + // 根据开始时间和结束时间计算请假天数 + if (demoLeaveApply.getStartTime() != null && demoLeaveApply.getEndTime() != null) { + long diffInMillis = demoLeaveApply.getEndTime().getTime() - demoLeaveApply.getStartTime().getTime(); + // 将毫秒转换为天数,保留一位小数 + BigDecimal days = new BigDecimal(diffInMillis) + .divide(new BigDecimal(TimeUnit.DAYS.toMillis(1)), 1, RoundingMode.HALF_UP); + demoLeaveApply.setLeaveDays(days); + } + + if (updateById(demoLeaveApply)) { + return Result.ok(demoLeaveApply, "请假申请审批Demo-编辑成功!"); + } + return Result.fail(demoLeaveApply, "错误:请假申请审批Demo-编辑失败!"); + } + + @Override + @Transactional + public Result delete(String id) { + DemoLeaveApply demoLeaveApply = getById(id); + if (demoLeaveApply == null) { + return Result.fail(false, "错误:记录不存在!"); + } + if (removeById(id)) { + if (Integer.valueOf(0).equals(demoLeaveApply.getAuditState())) { + Result result = remoteWorkflowService.delProcessByBusinessKey(RPCConstants.INNER, id, "用户删除请假审批申请"); + if (!result.isSuccess()) { + throw new MyRuntimeException(result.getMsg()); + } + } + return Result.ok(true, "请假申请审批Demo-删除成功!"); + } + return Result.fail(false, "错误:请假申请审批Demo-删除失败!"); + } + + @Override + @Transactional + public Result deleteBatch(String ids) { + String[] idList = ids.split(","); + for (String id : idList) { + Result result = delete(id); + if (!result.isSuccess()) { + return result; + } + } + return Result.ok(true, "请假申请审批Demo-批量删除成功!"); + } + + @Override + public Result queryById(String id) { + DemoLeaveApply demoLeaveApply = getById(id); + return Result.ok(demoLeaveApply, "请假申请审批Demo-查询成功!"); + } + + @Override + public void export(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) throws IOException { + ExcelUtils.write("请假申请审批Demo_" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()), queryList(reqDemoLeaveApply, reqPage)); + } + + @Override + @Transactional + public Result submit(String id) { + DemoLeaveApply demoLeaveApply = getById(id); + if (demoLeaveApply == null) { + throw new MyRuntimeException("错误:记录不存在!"); + } + if (Integer.valueOf(0).equals(demoLeaveApply.getAuditState())) { + throw new MyRuntimeException("错误:当前记录已在审核中,请勿重复提交!"); + } + demoLeaveApply.setAuditState(0); + if (!updateById(demoLeaveApply)) { + return Result.fail(demoLeaveApply, "错误:提交审批失败!"); + } + startProcess(demoLeaveApply); + return Result.ok(demoLeaveApply, "提交审批成功!"); + } + + @Override + @Transactional + public Result revoke(String id) { + DemoLeaveApply demoLeaveApply = getById(id); + if (demoLeaveApply == null) { + throw new MyRuntimeException("错误:记录不存在!"); + } + if (!Integer.valueOf(0).equals(demoLeaveApply.getAuditState())) { + throw new MyRuntimeException("错误:只有审核中记录支持撤回!"); + } + Result result = remoteWorkflowService.delProcessByBusinessKey(RPCConstants.INNER, id, "用户撤回请假审批申请"); + if (!result.isSuccess()) { + throw new MyRuntimeException(result.getMsg()); + } + demoLeaveApply.setAuditState(-1); + if (!updateById(demoLeaveApply)) { + throw new MyRuntimeException("错误:撤回后状态更新失败!"); + } + return Result.ok(demoLeaveApply, "撤回审批成功!"); + } + + @Override + public Result audit(String id, Integer auditState, WorkflowCompleteResult result) { + DemoLeaveApply demoLeaveApply = getById(id); + if (demoLeaveApply == null) { + throw new MyRuntimeException("错误:记录不存在!"); + } + demoLeaveApply.setAuditState(auditState); + if (!updateById(demoLeaveApply)) { + throw new MyRuntimeException("错误:审批操作异常!"); + } + return Result.ok(id, "审批操作成功!"); + } + + private void startProcess(DemoLeaveApply demoLeaveApply) { + Result result = remoteWorkflowService.startProcess(RPCConstants.INNER, new FlowableParam() + .setKey(FlowKey.请假申请发布.toString()) + .setId(demoLeaveApply.getId()) + .setPrefix("demoLeaveApply") + .setCallback("cn.com.mfish.demo.api.remote.RemoteDemoLeaveApplyService")); + if (!result.isSuccess()) { + throw new MyRuntimeException(result.getMsg()); + } + } +} diff --git a/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/controller/ProcessController.java b/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/controller/ProcessController.java index 8d34d03b..86114c2b 100644 --- a/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/controller/ProcessController.java +++ b/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/controller/ProcessController.java @@ -97,6 +97,12 @@ public class ProcessController { return Result.ok(flowableService.getProcessTasks(processInstanceId), "查询任务列表成功"); } + @Operation(summary = "根据业务key获取最新流程实例任务列表") + @GetMapping("/tasks/businessKey/{businessKey}") + public Result> getProcessTasksByBusinessKey(@Parameter(name = "businessKey", description = "业务id") @PathVariable String businessKey) { + return Result.ok(flowableService.getProcessTasksByBusinessKey(businessKey), "查询任务列表成功"); + } + @Operation(summary = "查询待处理任务列表") @GetMapping("/tasks/pending") public Result> getPendingTasks(ReqTask reqTask, ReqPage reqPage) { diff --git a/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/service/impl/FlowManageServiceImpl.java b/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/service/impl/FlowManageServiceImpl.java index 7769f62a..6f8aa37e 100644 --- a/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/service/impl/FlowManageServiceImpl.java +++ b/mf-business/mf-workflow/src/main/java/cn/com/mfish/workflow/service/impl/FlowManageServiceImpl.java @@ -108,6 +108,9 @@ public class FlowManageServiceImpl extends ServiceImpl getProcessTasksByBusinessKey(String businessKey) { + if (StringUtils.isEmpty(businessKey)) { + log.error("错误:业务id不能为空"); + throw new MyRuntimeException("错误:业务id不能为空"); + } + // 通过业务key查询流程实例,按开始时间降序排序,只取最新的一条 + List hpiList = historyService.createHistoricProcessInstanceQuery() + .processInstanceBusinessKey(businessKey) + .orderByProcessInstanceStartTime().desc() + .listPage(0, 1); + if (hpiList == null || hpiList.isEmpty()) { + log.error("错误:流程实例不存在,业务id:{}", businessKey); + throw new MyRuntimeException("错误:流程实例不存在"); + } + HistoricProcessInstance hpi = hpiList.get(0); + // 通过流程实例ID获取任务列表 + return getProcessTasks(hpi.getId()); + } + /** * 查询流程实例图片 * diff --git a/mf-common/mf-common-api/pom.xml b/mf-common/mf-common-api/pom.xml index e24bcc19..e42aeb19 100644 --- a/mf-common/mf-common-api/pom.xml +++ b/mf-common/mf-common-api/pom.xml @@ -57,9 +57,17 @@ cn.com.mfish mf-common-nocode + + cn.com.mfish + mf-common-demo + cn.com.mfish mf-nocode-api + + cn.com.mfish + mf-demo-api + - \ No newline at end of file + diff --git a/mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootDemoLeaveApplyService.java b/mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootDemoLeaveApplyService.java new file mode 100644 index 00000000..b157c64e --- /dev/null +++ b/mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootDemoLeaveApplyService.java @@ -0,0 +1,34 @@ +package cn.com.mfish.common.api; + +import cn.com.mfish.common.core.entity.WorkflowCompleteResult; +import cn.com.mfish.common.core.web.Result; +import cn.com.mfish.common.demo.service.DemoLeaveApplyService; +import cn.com.mfish.demo.api.remote.RemoteDemoLeaveApplyService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +/** + * @description: demo请假审批回调单体服务实现 + * @author: mfish + * @date: 2026/4/18 + */ +@Service("remoteDemoLeaveApplyService") +public class BootDemoLeaveApplyService implements RemoteDemoLeaveApplyService { + @Resource + private DemoLeaveApplyService demoLeaveApplyService; + + @Override + public Result approved(String origin, String prefix, String id, WorkflowCompleteResult result) { + return demoLeaveApplyService.audit(id, 1, result); + } + + @Override + public Result rejected(String origin, String prefix, String id, WorkflowCompleteResult result) { + return demoLeaveApplyService.audit(id, 2, result); + } + + @Override + public Result canceled(String origin, String prefix, String id, WorkflowCompleteResult result) { + return demoLeaveApplyService.audit(id, null, result); + } +} diff --git a/mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootWorkflowService.java b/mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootWorkflowService.java index 24622f1b..bdf468ca 100644 --- a/mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootWorkflowService.java +++ b/mf-common/mf-common-api/src/main/java/cn/com/mfish/common/api/BootWorkflowService.java @@ -71,6 +71,11 @@ public class BootWorkflowService implements RemoteWorkflowService { return Result.ok(flowableService.getProcessTasks(processInstanceId), "查询流程实例任务列表成功"); } + @Override + public Result> getProcessTasksByBusinessKey(String origin, String businessKey) { + return Result.ok(flowableService.getProcessTasksByBusinessKey(businessKey), "查询流程实例任务列表成功"); + } + @Override public Result> getPendingTasks(String origin, ReqTask reqTask, ReqPage reqPage) { return Result.ok(flowableService.getPendingTasks(reqTask, reqPage), "查询待办任务列表成功"); diff --git a/mf-common/mf-common-core/src/main/java/cn/com/mfish/common/core/constants/ServiceConstants.java b/mf-common/mf-common-core/src/main/java/cn/com/mfish/common/core/constants/ServiceConstants.java index 73a51fae..1f431114 100644 --- a/mf-common/mf-common-core/src/main/java/cn/com/mfish/common/core/constants/ServiceConstants.java +++ b/mf-common/mf-common-core/src/main/java/cn/com/mfish/common/core/constants/ServiceConstants.java @@ -14,6 +14,7 @@ public class ServiceConstants { public static final String STORAGE_SERVICE = "mf-storage"; public static final String WORKFLOW_SERVICE = "mf-workflow"; public static final String NOCODE_SERVICE = "mf-nocode"; + public static final String DEMO_SERVICE = "mf-demo"; public static boolean isBoot(String type) { return ServiceConstants.SERVER_BOOT.equals(type); diff --git a/mf-common/mf-common-demo/pom.xml b/mf-common/mf-common-demo/pom.xml new file mode 100644 index 00000000..b7edc835 --- /dev/null +++ b/mf-common/mf-common-demo/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + cn.com.mfish + mf-common + mf-2.3.1 + + + mf-common-demo + + + ${java.version} + ${java.version} + UTF-8 + + + + + cn.com.mfish + mf-common-core + + + com.baomidou + mybatis-plus-spring + + + diff --git a/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/entity/DemoLeaveApply.java b/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/entity/DemoLeaveApply.java new file mode 100644 index 00000000..1d38e093 --- /dev/null +++ b/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/entity/DemoLeaveApply.java @@ -0,0 +1,66 @@ +package cn.com.mfish.common.demo.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 com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * @description: leave apply demo + * @author: mfish + * @date: 2026-04-19 + * @version: V2.3.1 + */ +@Data +@TableName("demo_leave_apply") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "demo_leave_apply object") +public class DemoLeaveApply extends BaseEntity { + @ExcelProperty("Unique ID") + @Schema(description = "Unique ID") + @TableId(type = IdType.ASSIGN_UUID) + @Accessors(chain = true) + private String id; + + @ExcelProperty("Title") + @Schema(description = "Title") + private String title; + + @ExcelProperty("Leave Type") + @Schema(description = "Leave Type 1 personal 2 sick 3 annual") + private Integer leaveType; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ExcelProperty("Start Time") + @Schema(description = "Start Time") + private Date startTime; + + @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ExcelProperty("End Time") + @Schema(description = "End Time") + private Date endTime; + + @ExcelProperty("Leave Days") + @Schema(description = "Leave Days") + private BigDecimal leaveDays; + + @ExcelProperty("Reason") + @Schema(description = "Reason") + private String reason; + + @ExcelProperty("Audit State") + @Schema(description = "Audit State -1 draft 0 pending 1 approved 2 rejected") + private Integer auditState; +} diff --git a/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/req/ReqDemoLeaveApply.java b/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/req/ReqDemoLeaveApply.java new file mode 100644 index 00000000..d0caa21e --- /dev/null +++ b/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/req/ReqDemoLeaveApply.java @@ -0,0 +1,25 @@ +package cn.com.mfish.common.demo.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @description: leave apply request + * @author: mfish + * @date: 2026-04-19 + * @version: V2.3.1 + */ +@Data +@Accessors(chain = true) +@Schema(description = "Leave apply query request") +public class ReqDemoLeaveApply { + @Schema(description = "Title") + private String title; + + @Schema(description = "Leave Type") + private Integer leaveType; + + @Schema(description = "Audit State") + private Integer auditState; +} diff --git a/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/service/DemoLeaveApplyService.java b/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/service/DemoLeaveApplyService.java new file mode 100644 index 00000000..30791d76 --- /dev/null +++ b/mf-common/mf-common-demo/src/main/java/cn/com/mfish/common/demo/service/DemoLeaveApplyService.java @@ -0,0 +1,40 @@ +package cn.com.mfish.common.demo.service; + +import cn.com.mfish.common.core.entity.WorkflowCompleteResult; +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.demo.entity.DemoLeaveApply; +import cn.com.mfish.common.demo.req.ReqDemoLeaveApply; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.io.IOException; + +/** + * @description: leave apply service + * @author: mfish + * @date: 2026-04-19 + * @version: V2.3.1 + */ +public interface DemoLeaveApplyService extends IService { + + Result> queryPageList(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage); + + Result add(DemoLeaveApply demoLeaveApply); + + Result edit(DemoLeaveApply demoLeaveApply); + + Result delete(String id); + + Result deleteBatch(String ids); + + Result queryById(String id); + + void export(ReqDemoLeaveApply reqDemoLeaveApply, ReqPage reqPage) throws IOException; + + Result submit(String id); + + Result revoke(String id); + + Result audit(String id, Integer auditState, WorkflowCompleteResult result); +} diff --git a/mf-common/mf-common-workflow/src/main/java/cn/com/mfish/common/workflow/service/FlowableService.java b/mf-common/mf-common-workflow/src/main/java/cn/com/mfish/common/workflow/service/FlowableService.java index f2da9d2b..869fe2a8 100644 --- a/mf-common/mf-common-workflow/src/main/java/cn/com/mfish/common/workflow/service/FlowableService.java +++ b/mf-common/mf-common-workflow/src/main/java/cn/com/mfish/common/workflow/service/FlowableService.java @@ -91,7 +91,7 @@ public interface FlowableService { /** * 判断是否是流程启动人 * - * @param businessKey 业务id + * @param businessKey 业务key * @param userId 用户id * @return 是否是启动人 */ @@ -129,6 +129,13 @@ public interface FlowableService { */ List getProcessTasks(String processInstanceId); + /** + * 根据业务key获取最新流程实例任务列表 + * @param businessKey 业务key + * @return 任务列表 + */ + List getProcessTasksByBusinessKey(String businessKey); + /** * 查询历史任务列表 * diff --git a/mf-common/pom.xml b/mf-common/pom.xml index fb567223..e4b5f9ea 100644 --- a/mf-common/pom.xml +++ b/mf-common/pom.xml @@ -27,6 +27,7 @@ mf-common-captcha mf-common-code mf-common-api + mf-common-demo mf-common-sys mf-common-web mf-common-app @@ -37,4 +38,4 @@ mf-common-prometheus - \ No newline at end of file + diff --git a/mf-start/mf-start-boot/lib/mf-common-bi-mf-2.3.1-pg.jar b/mf-start/mf-start-boot/lib/mf-common-bi-mf-2.3.1-pg.jar index 288470abda3367a5bbfeb23b02301d7c25614b3c..0e6fb1f1a923ffd8f4d7c05072e713f5b06c5be8 100644 GIT binary patch delta 3599 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 D70jO| delta 3599 zcmX|?dt6l27RS$C3^2Tzx`gPZNVHHhOUVb1z(i3|1VRl3)KEW^8RfOq528p`7)cuD z_KJcx(|jW1hzAhlEh+;dAZUZ=fLy(Z5A@o@8*~`UhZ9c90trMYD5nkNY2! zq<1t~`dvc}H-nwgzV97Bgx9A|Cj0+h{BSBr(gV!Q4Sf$2p3VqH2%P1QkR0x2rL|17 ze29NV*5!nehRR46G#Qkz#t^EQ^q~ohUVtU>^P;WAJRPgO8Z2Gziv+&00PGBR7tWy$ z@oyt+SR97;VoV^#X?Y-qW+#rt=FbXauVf9(7-`V2&|!04*fT6Z@ovQCk5eN48; z{y&vF2tobRPiWW`sSW+l6nz@%`Kxpwj&iT4NJVsp#9n3LxA}XP2T5Fxe|eJ99Qd&$dvUM8Df+`52{ihlBFOhZz#G zg001F9Naa-N<4!7Lrxq9#b2z%Fh09fIi$}XM?8DgPZ?5{F4c$Ul!C+lq#SUvjAZY$ z{@?+PB)ymwndWWC-B*ZPw%|ZJKBh^BXW|rJ9gRk~e%#kelnyx*KEg%m7M`{3=y1an zy%)xp8`2T_3d+#KP&6D}O{Iw1_RX5?grHcKSj$ z<&wrJ|2Px4*~H$E%im`suto1OwiRjp2kfy{6m%}TYIao$UQ-IjV?q8Z=8b8spRp0R zN=-lQPqO&>^6Mt%D+jM-BQa%VGTVTMx}V1WWh++IdFoXkPvws1JjXjsY@FQq4I7I& z*59-JSae|Y%=)?;~~{P;t5 zO+48xeQYQi=RIa;(KzW3W-G3#U>)S)+#@wG73?kVvUOkP#{-PNse#e*K^t(z;vy|f z#ue>%glUe#dYT8|*Fv5@1oooOZ!f~XacOS83U_d6>PA45y{Opd3;n3=8xLmu0%e?B z5m%!8cPw$Vyu7cqQOQ1$|{KPEUpT_$1AF zU&b}rNs`%J{44OA-O3?cH^W_gOP_58dRpGwK<6aRGhr{x!b|%q2Y$mRo_Yk%i_SbO z4}vUdd~2T4!veQen0$hi>=Q6ogdU%OI05hGlX5hla;){F#HmoAhj5W>a}pv1#Hesg zg#i_!43u*~h2K>OJ4K-!75Y>NDWDKnp+|+l(-hjKLYE5TWeROkpmJ(F+_>b=fxDdPz;em z@jOHD=@|-DtKeQj%7-eHs^D5mp@dR$E>snVvlNO~L9c?Qj6(BOI8p`+#TeRhcuzn= zIm8R7E{7NaUKNzHp@N=ptE%``LbS-qsH83Et|Xt}DtK3fc2+^GfG1UyJgb_V4^=~g z2szil0s$*($fu@;s)n5-_(FxwbJQZR7Up4#TB>?jOUZN2lXCbx^|HP|%Q#PkpH=w% z0wqUXqyrwgNQ$P8$`{v>Qd|d%MAN?;VTpiLBfKx5+X%}AgxAAT0R{Dx*BQ7gT9oPcXMRs}%O@FV2>SL}@`|>OBxyYH+N}&U-kR%lMHmX|RM$J0g=w3pu z(n9Z2p;v|AYjlabuhB(6QeoyVFjss%GJIdwNWhs1{>Dpx0YB01sS*_sWg>@TCQ>x* z1hMTDI;|?s*GWmdPC4Z&4Cg?=}idgf;r+`+g0f5qQL@h(JJq`MOA;?qU5k{3LWW&rT+)C CYh<+m diff --git a/mf-start/mf-start-boot/lib/mf-nocode-mf-2.3.1.jar b/mf-start/mf-start-boot/lib/mf-nocode-mf-2.3.1.jar index 8d608315edd6b3b34f759b03c6cdaf7f5784707b..a2d66ccfea892ab7279e06c9468ff7cdf82346c2 100644 GIT binary patch delta 2780 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 D70jO| delta 3599 zcmX|?dt6l27RS$C3^2Tzx`gPZNVHHhOUVb1z(i3|1VRl3)KEW^8RfOq528p`7)cuD z_KJcx(|jW1hzAhlEh+;dAZUZ=fLy(Z5A@o@8*~`UhZ9c90trMYD5nkNY2! zq<1t~`dvc}H-nwgzV97Bgx9A|Cj0+h{BSBr(gV!Q4Sf$2p3VqH2%P1QkR0x2rL|17 ze29NV*5!nehRR46G#Qkz#t^EQ^q~ohUVtU>^P;WAJRPgO8Z2Gziv+&00PGBR7tWy$ z@oyt+SR97;VoV^#X?Y-qW+#rt=FbXauVf9(7-`V2&|!04*fT6Z@ovQCk5eN48; z{y&vF2tobRPiWW`sSW+l6nz@%`Kxpwj&iT4NJVsp#9n3LxA}XP2T5Fxe|eJ99Qd&$dvUM8Df+`52{ihlBFOhZz#G zg001F9Naa-N<4!7Lrxq9#b2z%Fh09fIi$}XM?8DgPZ?5{F4c$Ul!C+lq#SUvjAZY$ z{@?+PB)ymwndWWC-B*ZPw%|ZJKBh^BXW|rJ9gRk~e%#kelnyx*KEg%m7M`{3=y1an zy%)xp8`2T_3d+#KP&6D}O{Iw1_RX5?grHcKSj$ z<&wrJ|2Px4*~H$E%im`suto1OwiRjp2kfy{6m%}TYIao$UQ-IjV?q8Z=8b8spRp0R zN=-lQPqO&>^6Mt%D+jM-BQa%VGTVTMx}V1WWh++IdFoXkPvws1JjXjsY@FQq4I7I& z*59-JSae|Y%=)?;~~{P;t5 zO+48xeQYQi=RIa;(KzW3W-G3#U>)S)+#@wG73?kVvUOkP#{-PNse#e*K^t(z;vy|f z#ue>%glUe#dYT8|*Fv5@1oooOZ!f~XacOS83U_d6>PA45y{Opd3;n3=8xLmu0%e?B z5m%!8cPw$Vyu7cqQOQ1$|{KPEUpT_$1AF zU&b}rNs`%J{44OA-O3?cH^W_gOP_58dRpGwK<6aRGhr{x!b|%q2Y$mRo_Yk%i_SbO z4}vUdd~2T4!veQen0$hi>=Q6ogdU%OI05hGlX5hla;){F#HmoAhj5W>a}pv1#Hesg zg#i_!43u*~h2K>OJ4K-!75Y>NDWDKnp+|+l(-hjKLYE5TWeROkpmJ(F+_>b=fxDdPz;em z@jOHD=@|-DtKeQj%7-eHs^D5mp@dR$E>snVvlNO~L9c?Qj6(BOI8p`+#TeRhcuzn= zIm8R7E{7NaUKNzHp@N=ptE%``LbS-qsH83Et|Xt}DtK3fc2+^GfG1UyJgb_V4^=~g z2szil0s$*($fu@;s)n5-_(FxwbJQZR7Up4#TB>?jOUZN2lXCbx^|HP|%Q#PkpH=w% z0wqUXqyrwgNQ$P8$`{v>Qd|d%MAN?;VTpiLBfKx5+X%}AgxAAT0R{Dx*BQ7gT9oPcXMRs}%O@FV2>SL}@`|>OBxyYH+N}&U-kR%lMHmX|RM$J0g=w3pu z(n9Z2p;v|AYjlabuhB(6QeoyVFjss%GJIdwNWhs1{>Dpx0YB01sS*_sWg>@TCQ>x* z1hMTDI;|?s*GWmdPC4Z&4Cg?=}idgf;r+`+g0f5qQL@h(JJq`MOA;?qU5k{3LWW&rT+)C CYh<+m diff --git a/mf-start/mf-start-nocode/lib/mf-nocode-mf-2.3.1.jar b/mf-start/mf-start-nocode/lib/mf-nocode-mf-2.3.1.jar index 8d608315edd6b3b34f759b03c6cdaf7f5784707b..a2d66ccfea892ab7279e06c9468ff7cdf82346c2 100644 GIT binary patch delta 2780 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 ${mfish.version} + + cn.com.mfish + mf-demo-api + ${mfish.version} + + + cn.com.mfish + mf-common-demo + ${mfish.version} +