feat(other): 产品基础功能提交

This commit is contained in:
2026-04-18 14:19:45 +08:00
parent 0c91f5deaa
commit 38c69c748c
75 changed files with 5139 additions and 1047 deletions

View File

@@ -26,10 +26,11 @@
顶层模块: 顶层模块:
1. `rdms-system` 1. `rdms-system`
2. `rdms-framework` 2. `rdms-project`
3. `rdms-gateway` 3. `rdms-framework`
4. `rdms-gateway`
当前业务实现主要集中在 `rdms-system`,但这只是现阶段结构,不应被理解为长期只保留个业务模块。 当前系统域能力主要集中在 `rdms-system`RDMS 核心交付域能力主要集中在 `rdms-project`但这只是现阶段结构,不应被理解为长期只保留这两个业务模块。
后续如果新增独立业务服务,例如项目/产品管理模块、工作流模块,应继续沿用当前仓库的模块拆分方式,而不是把所有后续业务长期堆进 `rdms-system` 后续如果新增独立业务服务,例如项目/产品管理模块、工作流模块,应继续沿用当前仓库的模块拆分方式,而不是把所有后续业务长期堆进 `rdms-system`
@@ -54,6 +55,24 @@
- 如果后续只是给系统域补充新的系统子能力,可以继续在 `rdms-system` 内按现有结构扩展。 - 如果后续只是给系统域补充新的系统子能力,可以继续在 `rdms-system` 内按现有结构扩展。
- 如果后续形成独立业务域,例如 `rdms-project``rdms-workflow`,应优先建设为新的独立业务模块,而不是默认继续塞进 `rdms-system` - 如果后续形成独立业务域,例如 `rdms-project``rdms-workflow`,应优先建设为新的独立业务模块,而不是默认继续塞进 `rdms-system`
### `rdms-project`
当前已存在的 RDMS 核心交付业务聚合模块。
- `rdms-project/rdms-project-boot`
- 主应用模块
- 启动入口:`rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/ProjectServerApplication.java`
- 主包路径:`com.njcn.rdms.module.project`
- 常见子包:`api``controller``convert``dal``framework``service`
- `rdms-project/rdms-project-api`
- 供其他服务依赖的共享 API 模块
- 包含对外 API 契约与枚举定义
说明:
- 当前项目集、项目、产品、需求、任务、工单、执行等 RDMS 核心业务能力应优先落在这里。
- 需要复用用户、组织、岗位、权限等系统能力时,应通过 `rdms-system-api` 调用,不要反向依赖 `rdms-system-boot`
### `rdms-framework` ### `rdms-framework`
共享框架与内部 starter 模块。 共享框架与内部 starter 模块。
@@ -189,7 +208,7 @@ rdms-xxx
- 服务层放在 `service` - 服务层放在 `service`
- 持久层放在 `dal` - 持久层放在 `dal`
- DTO/VO 转换放在 `convert` - DTO/VO 转换放在 `convert`
6. 当前业务代码主要在 `rdms-system`,但这不是永久约束;新增业务能力时,先判断应该落在现有系统域内,还是应建设为新的 `rdms-xxx` 业务模块。 6. 当前系统域代码主要在 `rdms-system`RDMS 核心交付域代码主要在 `rdms-project`但这不是永久约束;新增业务能力时,先判断应该落在现有系统域内、现有项目交付域内,还是应建设为新的 `rdms-xxx` 业务模块。
7. 新增共享能力时,优先扩展现有 `rdms-spring-boot-starter-*` 模块,不要在业务服务里重复堆配置。 7. 新增共享能力时,优先扩展现有 `rdms-spring-boot-starter-*` 模块,不要在业务服务里重复堆配置。
8. 修改跨模块使用的 API 时,需要同时更新提供方实现和对应的 `rdms-system-api` 或对应 `rdms-xxx-api` 契约。 8. 修改跨模块使用的 API 时,需要同时更新提供方实现和对应的 `rdms-system-api` 或对应 `rdms-xxx-api` 契约。
9. 除非用户明确要求,否则不执行任何编译、构建、测试、打包或其他会实际运行项目的命令,包括但不限于 `mvn`、启动命令和脚本。 9. 除非用户明确要求,否则不执行任何编译、构建、测试、打包或其他会实际运行项目的命令,包括但不限于 `mvn`、启动命令和脚本。

View File

@@ -9,6 +9,7 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>rdms-system</module> <module>rdms-system</module>
<module>rdms-project</module>
<module>rdms-framework</module> <module>rdms-framework</module>
<module>rdms-gateway</module> <module>rdms-gateway</module>
</modules> </modules>

View File

@@ -26,5 +26,17 @@ public interface RpcConstants {
*/ */
String SYSTEM_PREFIX = RPC_API_PREFIX + "/system"; String SYSTEM_PREFIX = RPC_API_PREFIX + "/system";
/**
* project 服务名
*
* 注意,需要保证和 spring.application.name 保持一致
*/
String PROJECT_NAME = "rdms-project-server";
/**
* project 服务的前缀
*/
String PROJECT_PREFIX = RPC_API_PREFIX + "/project";
} }

View File

@@ -49,6 +49,19 @@ spring:
uri: grayLb://rdms-system-server uri: grayLb://rdms-system-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/system/ws/** - Path=/system/ws/**
## project-server 服务
- id: project-admin-api # 路由的编号
uri: grayLb://rdms-project-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/admin-api/project/**
filters:
- RewritePath=/admin-api/project/v3/api-docs, /v3/api-docs
- id: project-app-api # 路由的编号
uri: grayLb://rdms-project-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/app-api/project/**
filters:
- RewritePath=/app-api/project/v3/api-docs, /v3/api-docs
## bpm-server 服务 ## bpm-server 服务
- id: bpm-admin-api # 路由的编号 - id: bpm-admin-api # 路由的编号
uri: grayLb://rdms-bpm-server uri: grayLb://rdms-bpm-server
@@ -76,6 +89,9 @@ knife4j:
- name: system-server - name: system-server
service-name: rdms-system-server service-name: rdms-system-server
url: /admin-api/system/v3/api-docs url: /admin-api/system/v3/api-docs
- name: project-server
service-name: rdms-project-server
url: /admin-api/project/v3/api-docs
- name: bpm-server - name: bpm-server
service-name: bpm-server service-name: bpm-server
url: /admin-api/bpm/v3/api-docs url: /admin-api/bpm/v3/api-docs

30
rdms-project/pom.xml Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>cn-rdms</artifactId>
<version>${revision}</version>
</parent>
<artifactId>rdms-project</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
RDMS 项目交付域模块
该模块承载项目集、项目、产品、需求、任务、工单、执行等 RDMS 核心交付业务能力。
</description>
<modules>
<module>rdms-project-boot</module>
<module>rdms-project-api</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>rdms-project</artifactId>
<version>${revision}</version>
</parent>
<artifactId>rdms-project-api</artifactId>
<description>
项目交付域接口,暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,4 @@
/**
* Project API 包,定义暴露给其它模块的 API
*/
package com.njcn.rdms.module.project.api;

View File

@@ -0,0 +1,24 @@
package com.njcn.rdms.module.project.enums;
import com.njcn.rdms.framework.common.enums.RpcConstants;
/**
* API 相关的枚举
*/
public class ApiConstants {
/**
* 服务名
*
* 注意,需要保证和 spring.application.name 保持一致
*/
public static final String NAME = RpcConstants.PROJECT_NAME;
public static final String PREFIX = RpcConstants.PROJECT_PREFIX;
public static final String VERSION = "1.0.0";
private ApiConstants() {
}
}

View File

@@ -0,0 +1,23 @@
package com.njcn.rdms.module.project.enums;
import com.njcn.rdms.framework.common.exception.ErrorCode;
/**
* Project 错误码枚举类
*
* 产品管理当前使用 1-008-001-000 段。
*/
public interface ErrorCodeConstants {
// ========== 产品管理 1-008-001-000 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_008_001_000, "产品不存在");
ErrorCode PRODUCT_CODE_DUPLICATE = new ErrorCode(1_008_001_001, "已经存在编码为【{}】的产品");
ErrorCode PRODUCT_NAME_DUPLICATE = new ErrorCode(1_008_001_002, "已经存在名称为【{}】的产品");
ErrorCode PRODUCT_CODE_NOT_MODIFIABLE = new ErrorCode(1_008_001_003, "产品编码创建后不允许修改");
ErrorCode PRODUCT_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_001_004, "当前产品状态不支持动作【{}】");
ErrorCode PRODUCT_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_001_005, "动作【{}】必须填写原因");
ErrorCode PRODUCT_DELETE_NAME_MISMATCH = new ErrorCode(1_008_001_006, "删除确认名称与当前产品名称不一致");
ErrorCode PRODUCT_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_001_007, "当前产品状态不允许编辑");
ErrorCode PRODUCT_PAUSED_ONLY_ALLOW_LIMITED_UPDATE = new ErrorCode(1_008_001_008, "产品暂停后仅允许变更产品经理、描述和备注");
}

View File

@@ -0,0 +1,13 @@
package com.njcn.rdms.module.project.enums;
/**
* 项目交付域字典类型常量
*/
public interface ProjectDictTypeConstants {
/**
* 产品方向
*/
String PRODUCT_DIRECTION = "rdms_product_direction";
}

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>rdms-project</artifactId>
<version>${revision}</version>
</parent>
<artifactId>rdms-project-boot</artifactId>
<description>项目交付域功能服务模块</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Cloud 基础 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-env</artifactId>
</dependency>
<!-- 依赖服务 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-project-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-system-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-redis</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-rpc</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-excel</artifactId>
</dependency>
<!-- Registry 注册中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Config 配置中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<addResources>true</addResources>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,49 @@
# 02-产品管理 SQL已确认口径
## 0. 文档说明
本文档用于记录产品管理 SQL 已确认的实现口径。
本文档只保留已确认结果,不保留待确认项、方案对比或历史演变说明。
## 1. 共享表承接边界
- `rdms_user_object_role`
- `rdms_object_status_model`
- `rdms_object_status_transition`
- `rdms_biz_audit_log`
以上共享表继续由 `rdms-project/rdms-project-boot/src/main/resources/sql/product/01_product_schema.sql` 承接。
## 2. 产品需求状态字段口径
- `rdms_product_requirement` 统一改为 `status_code` 口径。
- 产品需求状态与产品状态保持一致,统一使用状态编码模型。
## 3. 来源承接与需求拆分口径
- 产品需求既可能来自工单流转,也可能来自产品内手工新增。
- 产品需求不论来源,都允许继续拆分为 N 个子需求。
- 同一产品下,同一来源工单只生成 1 条源头需求记录。
- 源头需求记录可以拆分为 N 条子需求。
- 手工新增需求也可以拆分为 N 条子需求。
- 子需求不参与“来源唯一”约束。
- 来源追溯和拆分关系分开建模。
## 4. 需求终态原因承接口径
- 需求终态原因由主表承接当前结果态,同时审计日志保留完整留痕。
- 主表统一承接终态结果字段,覆盖 `reject``cancel``close` 等终态动作。
字段口径:
- `terminal_action_code`
- `terminal_reason`
- `terminal_time`
## 5. 当前确认结果
- 第 1 项:共享表继续由 `01_product_schema.sql` 承接
- 第 2 项:`rdms_product_requirement` 统一改为 `status_code`
- 第 3 项:来源承接与需求拆分分开建模
- 第 4 项:主表补终态结果字段,审计日志继续完整留痕

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,478 @@
# 03-产品管理范围内产品与产品需求链路与工作流方案
## 0. 文档目的
本文档用于回答以下问题:
- 在当前产品管理范围内,`product -> product_requirement` 是否适合做链路视图
- 后续工单进入产品侧时,应如何接入当前产品管理链路
- 是否适合引入 Flowable
- Flowable 在当前产品管理方案中应承接哪些节点,不应承接哪些节点
- 当前产品管理链路应如何建模
- 当前产品需求设计中的“终态原因”口径适用范围是什么
说明:
- 本文档中的“产品作为主上下文”仅限当前产品管理能力建设范围。
- 该口径用于当前产品管理方案收敛,不作为 RDMS 全局统一建模原则。
- 本文档只给方案不直接修改业务代码、SQL 或正式设计文档。
## 1. 当前现状判断
### 1.1 当前代码与文档现状
- 当前仓库未看到 Flowable 依赖、BPMN 定义或流程引擎接入实现。
- `rdms-project` 已开始承接通用对象状态模型,当前已有 `rdms_object_status_model``rdms_object_status_transition` 的 DO 和 Mapper。
- 产品管理当前已经明确:产品与产品需求承接轻量状态流转,但当前版本不展开正式工作流引擎。
- 当前产品需求设计已具备来源追溯、状态流转、认领 / 拒绝、拆分等业务语义。
- 当前要开发的是产品管理,不是项目管理;`project_requirement / execution / task` 不属于本期范围。
### 1.2 当前链路特征
在本期产品管理范围内,当前业务链路主要具备以下特征:
- 产品是当前方案的主上下文
- 产品侧可以手工新增产品需求
- 后续工单可以进入产品侧形成来源需求
- 一个产品需求可以拆分成 N 个子需求
- 某些节点需要人认领、人评审、人审批
- 某些节点只是普通业务状态推进,不适合上审批流
这意味着在本期产品管理范围内,该链路本质上是“产品上下文下的产品需求关系网络”,不是“一条从头跑到尾的单一审批流程”。
## 2. 核心结论
### 2.1 可行性判断
结论:可行,但本期不建议把产品管理范围内的所有动作都堆进单一 Flowable 流程。
推荐方向:
- 在本期产品管理方案内,产品作为当前链路的主上下文和主入口
- 当前链路聚合围绕 `product``product_requirement` 组织
- 需求链(`chain`)表示“本期产品管理范围内,产品下的一条源头需求链”
- 工单在后续接入时,只作为产品需求的来源关系之一,不作为链路根
- 业务主链统一由状态机控制
- Flowable 等以后开发评审流程时再接入,只承接评审、审批类协同节点
- 各业务对象继续维护自己的 `status_code`
- 流程状态不是业务真相源,业务表状态才是业务真相源
- 本期先按表和后端接口完成基础建模和后端闭环,不做前端页面和流程引擎接入
### 2.2 不建议单一长流程的原因
- 产品需求存在手工新增、父子拆分等多种情况;后续再叠加工单来源时,也难以用单流程表达。
- 认领、拒绝、拆分、关闭等动作很多是高频业务动作,不适合全部流程化。
- 流程状态和业务状态容易双写不一致。
- 当前真正需要先打通的是产品管理范围内的后端聚合能力,而不是先上流程图。
### 2.3 终态原因口径适用范围
“终态原因承接口径”不是只针对工单来源需求,而是针对产品需求对象本身。
也就是说,无论产品需求来自:
- 工单流转
- 手工新增
只要走到 `reject``cancel``close` 这类终态动作,都需要明确终态原因承接方式。
从后端聚合查询角度看,主表保留当前结果态原因字段,审计日志继续保留完整留痕。
## 3. 建议总体架构
当前建议拆成 4 层,再预留 1 个后续扩展层。
### 3.1 业务对象层
业务对象继续独立建模,独立维护生命周期和 `status_code`
本期纳入当前链路建模的对象:
- 产品 `product`
- 产品需求 `product_requirement`
- `work_order` 作为后续预留来源对象
要求:
- 在本期产品管理范围内,产品是当前链路的主上下文
- 产品需求是当前链路的核心业务对象
- 每个对象的状态变化由业务服务负责落库
- 后续工单进入产品侧后,只承接来源关系,不取代产品主上下文
- 业务主链先由状态机控制,不由流程引擎接管
### 3.2 关系模型层
关系模型用于描述对象之间的连接关系,而不是靠单个来源字段硬扛全部追溯逻辑。
本期至少需要表达以下关系:
- 产品和源头需求之间的主上下文关系
- 产品需求父子拆分
建议引入统一关系表,例如:
`rdms_biz_relation`
建议关键字段:
- `id`
- `chain_id`
- `product_id`
- `from_biz_type`
- `from_biz_id`
- `to_biz_type`
- `to_biz_id`
- `relation_type`
- `sort`
- `remark`
建议 `relation_type` 取值至少包括:
- `product_root`:产品主上下文
- `split_child`:拆分子需求
后续预留关系类型:
- `source_work_order`:来源工单
说明:
- `product_root` 用于表达本期产品管理范围内,产品和源头需求之间的主上下文关系。
- 手工新增不是对象间来源关系,不必强行补一条虚拟来源边;可由 `rdms_requirement_chain.entry_source_type = manual` 承接。
### 3.3 事件模型层
事件模型用于描述“产品下的一条需求链上发生了什么”,服务于后端聚合查询和后续时间线视图。
建议引入统一事件表,例如:
`rdms_biz_event`
建议关键字段:
- `id`
- `chain_id`
- `product_id`
- `biz_type`
- `biz_id`
- `event_type`
- `action_code`
- `from_status_code`
- `to_status_code`
- `operator_user_id`
- `operator_name`
- `reason`
- `event_time`
- `payload_json`
事件类型可覆盖:
- `create`
- `claim`
- `reject`
- `split`
- `review_pass`
- `review_reject`
- `close`
- `cancel`
后续预留事件类型:
- `source_attach`
### 3.4 需求链聚合层
为了做当前产品管理范围内的链路聚合,建议引入统一聚合对象。
建议引入需求链主表,例如:
`rdms_requirement_chain`
建议关键字段:
- `id`
- `chain_code`
- `product_id`
- `root_requirement_id`
- `entry_source_type`
- `entry_biz_type`
- `entry_biz_id`
- `title`
- `current_status_code`
- `closed_flag`
用途:
- 作为“产品下的一条源头需求链”的聚合单元
- 聚合关系和事件
- 支撑后端聚合查询;后续前端如接入,再由产品详情页和产品需求详情页承接
说明:
- 一个产品下会有多条需求链
- 一条需求链只属于一个产品
- 一条需求链只围绕一个源头产品需求展开
- 后续工单来源需求创建需求链时,工单只写入 `entry_biz_type / entry_biz_id``source_work_order` 关系,不作为根对象
### 3.5 流程绑定层(后续扩展)
流程引擎只负责协同过程,因此等以后开发评审流程时,可再补业务对象和流程实例的绑定层。
建议引入流程绑定表,例如:
`rdms_workflow_binding`
建议关键字段:
- `id`
- `chain_id`
- `product_id`
- `biz_type`
- `biz_id`
- `process_definition_key`
- `process_instance_id`
- `workflow_status`
- `current_task_key`
- `current_task_name`
- `starter_user_id`
- `start_time`
- `end_time`
说明:
- 该层不是本期前置条件
- 本期不接 Flowable 时,不需要先落这张表
- 等以后开发评审流程时再接入,只用于评审、审批类协同节点
## 4. Flowable 适用边界
### 4.1 适合接 Flowable 的节点
以下节点等以后开发评审流程时,可考虑接入 Flowable
- 产品需求待评审
- 高风险终态动作审批
- 其他明确的审批类节点
这类节点的共同特征是:
- 需要明确待办人
- 需要审批意见
- 需要驳回、转交、加签、会签等协作能力
### 4.2 不建议接 Flowable 的节点
以下节点不建议直接建成流程审批节点:
- 后续工单接入产品侧后的认领 / 拒绝
- 普通状态编辑
- 列表筛选查询
- 日常状态推进
- 产品需求拆分
这类节点更适合保留在业务状态机或普通业务服务里,否则流程实例会过多、过碎、过重。
## 5. 当前链路视图如何实现
本期只要求后端先具备聚合查询能力,不要求直接交付页面。
### 5.1 拓扑数据
展示本期产品管理范围内,以产品为入口的对象关系链:
```text
产品
-> 产品需求(源头需求)
-> 子需求
后续工单 -(source_work_order)-> 产品需求(可选来源)
```
后端主要聚合:
- `rdms_requirement_chain`
- `rdms_biz_relation`
- `product`
- `product_requirement`
### 5.2 时间线数据
展示产品下某条需求链上的关键事件:
- 产品需求创建
- 产品需求认领
- 产品需求评审
- 拆分子需求
- 关闭 / 拒绝 / 取消
后端主要聚合:
- `rdms_biz_event`
- `product_requirement` 当前状态摘要
说明:
- 时间线围绕单条需求链展开,不是把整个产品下所有动作混成一条总流水。
- 本期先交付后端聚合查询接口,不要求同时交付拓扑页和时间线页,也不进入前端联调。
## 6. 对现有产品管理设计的建议
### 6.1 本期产品管理范围内的主上下文口径
既然本期要开发的是产品管理,建议当前方案中的聚合查询和后端入口都优先挂在产品上下文下。
要求:
- `product_id` 是链路聚合模型的必填归属字段
- 后续前端如接入,产品详情页和产品需求详情页是主入口
- 后续工单详情页如需展示链路,只适合作为来源跳转入口,不适合作为主视图入口
### 6.2 状态口径
既然你已经确认本期只做产品和产品需求,建议当前阶段只要求这两个对象继续沿用统一的 `status_code` 口径。
说明:
- `product`
- `product_requirement`
项目管理阶段再单独设计 `project_requirement / execution / task` 的状态模型和流转规则,不并入本期范围。
### 6.3 来源与拆分口径
你已经确认:
- 来源承接和需求拆分分开建模
- 后续工单来源需求可拆分
- 手工新增需求也可拆分
当前产品需求口径应将来源追溯和拆分关系分开承接:
- `source_biz_*` 字段只承接来源追溯
- `parent_requirement_id``root_requirement_id` 承接拆分链路
- 后续同一产品下,同一来源工单只生成 1 条源头需求记录
- 子需求不参与来源唯一约束
### 6.4 终态原因口径
产品需求主表统一承接当前结果态字段。
当前字段方向:
- `terminal_action_code`
- `terminal_reason`
- `terminal_time`
这样后端做本期产品管理范围内的聚合查询时,可以直接读取当前结果态原因,而不用每次回扫审计日志。
## 7. 建议实施顺序
建议按三步走,但只有第一步属于本期必做范围。
### 第一步:先完成本期产品管理的基础建模和后端闭环
目标:
- 明确在本期产品管理范围内,产品作为当前链路的主上下文
- 建立链路主表
- 建立链路关系表
- 建立链路事件表
- 统一产品和产品需求的状态口径
- 先把业务主链的状态机闭环跑通
- 先提供产品上下文下查询单条需求链关系拓扑和事件时间线的聚合接口
这一阶段先不接 Flowable只按表和后端接口开发不做前端拓扑页和时间线页也不扩到项目管理对象。
### 第二步:后续开发评审流程时接入有限流程节点
目标:
- 产品需求评审
- 高风险终态审批
- 其他明确需要审批协同的节点
这类节点等以后开发评审流程时可接入 Flowable但不是本期前置条件。
### 第三步:后续在项目管理阶段扩展项目域对象
目标:
- 在后续项目管理建设时,再补 `project_requirement / execution / task` 等对象
- 届时再单独设计它们和产品需求之间的关系、事件和流程边界
说明:
- 这一步不属于本期产品管理开发范围。
## 8. 风险点与控制建议
### 8.1 范围扩散到项目管理
风险:
- 在当前产品管理开发过程中,又把 `project_requirement / execution / task` 一起拉进来
控制建议:
- 所有设计文档都明确“本期只做产品和产品需求”
- 项目域对象放到后续项目管理建设时再单独设计
### 8.2 状态双写不一致
风险:
- 流程状态更新了,但业务表状态没更新
- 业务状态更新了,但流程实例没推进
控制建议:
- 本期主链统一由状态机控制
- 等以后开发评审流程时接入 Flowable也只作为协同触发器
- 每次关键动作统一写 `rdms_biz_event`
### 8.3 来源与拆分关系混淆
风险:
- 来源关系和拆分关系混在一个字段里,后续追溯会失真
控制建议:
- 来源关系和父子拆分关系分开建模
- 统一走关系表,不靠单字段隐式表达
### 8.4 前端聚合过早介入
风险:
- 后端模型和接口还没稳住,就先开始拼页面,后面会频繁返工
控制建议:
- 本期先完成基础建模和聚合查询接口
- 前端页面放到后续接口稳定后再接入
## 9. 当前建议结论
当前建议拍板如下:
1. 在本期产品管理方案内,产品作为当前链路的主上下文。
2. 本期范围只包含 `product``product_requirement``work_order` 只作为后续预留来源对象,不纳入第一批实现。
3. 一条需求链(`chain`)表示“产品下的一条源头需求链”,不是整个产品,也不是某个工单。
4. 本期先做基础建模和后端闭环,包括 `requirement_chain / relation / event` 模型、状态机主链、关键动作落库和聚合查询接口。
5. 本期主链统一由状态机控制Flowable 等以后开发评审流程时再接入,只用于评审、审批类节点。
6. 来源追溯和需求拆分必须分开建模。
7. 产品需求终态原因由主表承接当前结果态,并继续保留审计日志完整留痕。
8. 项目域对象放到后续项目管理建设时再单独设计,不并入本期产品管理范围。
9. 以上口径仅用于当前产品管理建设,不作为 RDMS 全局统一建模原则。
## 10. 下一步建议产物
如果继续推进,建议下一步补 3 份专项文档:
1. `产品管理范围内链路接口设计.md`
明确聚合查询接口、状态动作接口的返回结构。
2. `产品管理范围内链路SQL与表结构设计.md`
明确 `requirement_chain / relation / event` 的表结构和索引设计。
3. `产品管理范围内链路开发顺序与任务拆分.md`
明确 SQL、DO、Mapper、Service、Controller 的开发顺序。

View File

@@ -0,0 +1,221 @@
# 04-产品管理 编码前必看清单
## 0. 文档目的
本文档用于在开始产品管理相关编码前,明确必须先阅读的文档、必须锁死的业务口径,以及当前文档与可执行 SQL 的同步状态。
本文档只保留当前编码前必须关注的内容,不保留历史演变说明。
## 1. 文档分级
### 1.1 一级依据
以下文档为产品管理编码主依据,涉及业务口径、接口口径、状态口径时,必须优先对齐:
- `docs/temp/02-产品管理_业务设计.md`
- `docs/temp/02-产品管理_SQL已确认口径.md`
### 1.2 二级依据
以下文档用于补齐 SQL 结构说明:
- `docs/temp/02-产品管理_SQL口径说明.md`
### 1.3 SQL 阅读基线
以下文件是当前产品管理编码前唯一需要阅读的 SQL 文件,用于确认当前表结构、状态模型、状态流转和审计表设计:
- `rdms-project/rdms-project-boot/product/rdms_biz_audit_log.sql`
说明:
- 当前 SQL 阅读入口统一以该文件为准。
- 该文件当前包含 `rdms_biz_audit_log``rdms_object_status_model``rdms_object_status_transition``rdms_product``rdms_product_requirement``rdms_product_status_log``rdms_user_object_role`
- 编码前必须先识别该文件与已确认口径之间的差异,不能直接把该文件当作最终目标结构。
### 1.4 场景性依据
以下文档只在涉及工单来源、链路视图、Flowable 边界时必读:
- `docs/temp/03-工单到任务全链路与工作流方案.md`
## 2. 阅读顺序
开始编码前,按以下顺序阅读:
1. `02-产品管理_业务设计.md`
2. `02-产品管理_SQL已确认口径.md`
3. `02-产品管理_SQL口径说明.md`
4. `rdms-project/rdms-project-boot/product/rdms_biz_audit_log.sql`
5. `03-工单到任务全链路与工作流方案.md`仅在涉及工单链路、需求拆分、Flowable、全链路视图时阅读
## 3. 每份文档必须看的内容
### 3.1 `02-产品管理_业务设计.md`
必须重点看以下内容:
- 模块范围与非本期范围
- 页面与对象上下文承载方式
- 产品需求规则
- 对象关系与数据设计
- 产品状态与产品需求状态机
- 接口承接
- 权限与动作矩阵
- 测试关注点
阅读目标:
- 明确本次到底做哪些页面、动作、字段和权限。
- 明确哪些能力本次不做,避免编码时扩散。
- 明确关闭、认领、拒绝、分流等动作的业务边界。
### 3.2 `02-产品管理_SQL已确认口径.md`
必须逐条看完以下 4 项:
- 共享表承接边界
- 产品需求状态字段口径
- 来源承接与需求拆分口径
- 需求终态原因承接口径
阅读目标:
- 锁死产品需求 `status_code` 口径。
- 锁死来源追溯和需求拆分分开建模。
- 锁死终态结果字段由主表承接。
### 3.3 `02-产品管理_SQL口径说明.md`
必须重点看以下内容:
- 共享表与主数据口径
- 产品需求口径
- 状态与留痕口径
阅读目标:
- 明确当前确认后的 SQL 结构表达方式。
- 明确状态编码、来源追溯、拆分链路、终态结果字段应该如何落到表结构上。
### 3.4 `rdms-project/rdms-project-boot/product/rdms_biz_audit_log.sql`
必须重点看以下内容:
- `rdms_biz_audit_log`
- `rdms_object_status_model`
- `rdms_object_status_transition`
- `rdms_product`
- `rdms_product_requirement`
- `rdms_product_status_log`
- `rdms_user_object_role`
阅读目标:
- 明确当前统一 SQL 阅读入口下的表结构、索引、状态种子数据和审计字段现状。
- 明确哪些字段和状态定义仍未对齐已确认口径。
### 3.5 `03-工单到任务全链路与工作流方案.md`
仅在以下场景必须看:
- 涉及工单流转到产品需求
- 涉及来源追溯与需求拆分
- 涉及产品需求到项目需求的链路设计
- 涉及 Flowable 接入边界
- 涉及全链路视图
阅读目标:
- 明确 Flowable 只承接协同节点,不直接承接整条业务主链状态。
- 明确来源关系、拆分关系、事件关系和流程绑定关系要分开建模。
## 4. 编码前必须锁死的业务口径
### 4.1 模块边界
- 产品管理能力落在 `rdms-project`,不落到 `rdms-system``rdms-gateway`
- 跨模块共享能力通过 `*-api` 承接,不直接依赖其他 `*-boot` 实现。
### 4.2 产品需求状态口径
- `rdms_product_requirement` 主表统一使用 `status_code`
- 产品需求状态定义与流转统一通过 `rdms_object_status_model``rdms_object_status_transition` 承接。
- `object_type` 统一使用 `product_requirement`
### 4.3 来源追溯与需求拆分口径
- 产品需求来源至少包括 `manual``work_order`
- `source_type``source_biz_type``source_biz_id``source_biz_code` 只承接来源追溯。
- `parent_requirement_id``root_requirement_id` 只承接拆分链路。
- 同一产品下,同一来源工单只允许 1 条源头需求记录。
- 源头需求和手工新增需求都允许继续拆分子需求。
- 子需求不参与来源唯一约束。
### 4.4 终态结果口径
- `reject``cancel``close` 等终态动作统一回写主表。
- 主表统一保留以下字段:
- `terminal_action_code`
- `terminal_reason`
- `terminal_time`
- 审计日志继续保留完整过程留痕。
### 4.5 接口动作口径
- 产品状态动作统一走 `POST /products/{id}/change-status`
- 产品需求状态动作统一走 `POST /products/{id}/requirements/{requirementId}/change-status`
- 产品需求 `change-status` 统一支持 `claim``to_review``to_dispatch``dispatch``reject``cancel``close`
- 状态动作不得混入通用编辑接口。
### 4.6 权限与审计口径
- 对象上下文权限按产品对象角色控制。
- 产品团队、产品需求、状态动作、敏感操作都必须落审计。
- 产品状态日志由 `rdms_product_status_log` 承接。
- 产品经理变更、成员调整、需求认领、拒绝、分流、关闭、拆分等由 `rdms_biz_audit_log` 承接。
## 5. 当前明确不做的内容
以下内容当前不纳入本轮编码:
- 产品版本
- 产品路线图
- 正式评审流
- Flowable 引擎接入实现
- 产品基线
- 产品文档与附件
- 工单状态回写
- 目标版本字段
- 完整链路视图页面
## 6. 当前现状与目标口径差异
`rdms_biz_audit_log.sql` 当前仍存在以下差异:
- `rdms_product_requirement` 仍使用 `status`,尚未切到 `status_code`
- 主表仍保留 `closed_reason``closed_time`
- 主表尚未补齐 `terminal_action_code``terminal_reason``terminal_time`
- 主表尚未补齐 `parent_requirement_id``root_requirement_id`
- 产品需求状态模型和状态流转种子尚未按 `product_requirement` 完整落齐
这意味着:
- 不能直接以当前 `rdms_biz_audit_log.sql` 作为产品需求最终结构开始编码。
- 如果进入正式开发,优先动作之一是先同步当前统一 SQL 文件,再同步 DO、Mapper、Service、Controller、接口 VO。
## 7. 编码前建议执行顺序
1. 先对齐 `02-产品管理_业务设计.md``02-产品管理_SQL已确认口径.md`
2. 再对齐 `rdms_biz_audit_log.sql` 与已确认 SQL 口径
3. 再开始补 DO、Mapper、Service、Controller、ReqVO、RespVO
4. 最后对齐权限、审计、状态流转和接口返回字段
## 8. 编码时禁止自由发挥的点
- 不要把产品需求状态继续做成 `tinyint` 枚举字段。
- 不要把来源追溯和需求拆分混在同一组字段里。
- 不要把 `close` 再拆回独立接口。
- 不要把状态动作塞进通用编辑接口。
- 不要在本轮直接引入 Flowable 落地代码。
- 不要在产品模块里扩展未确认的版本、路线图、附件等能力。

View File

@@ -0,0 +1,130 @@
# 05-产品管理 当前开发完成度清单
## 0. 文档定位
本文档只回答 3 件事:
- 当前产品管理后端已经做了什么
- 当前产品管理后端还有什么没做
- 前端现在到底能调哪一段,不能把哪一段当成已完成
说明:
- 本文档以当前代码实际状态为准,不写历史方案,不写计划性口径。
- 本文档当前只覆盖 `rdms-project/rdms-project-boot` 下的产品管理后端实现现状。
- 本文档中的“已完成”表示代码已实现并已静态核对,不表示已经执行编译、测试或联调。
## 1. 当前已完成
### 1.1 已完成的接口
当前产品主数据以下 6 个接口已完成代码实现:
- `GET /project/product/page`
- `GET /project/product/get`
- `POST /project/product/create`
- `PUT /project/product/update`
- `POST /project/product/change-status`
- `POST /project/product/delete`
### 1.2 已完成的主数据能力
围绕产品主数据,当前已完成以下后端能力:
- 产品分页查询
- 产品详情查询
- 创建产品
- 更新产品
- 产品状态变更
- 删除产品
### 1.3 已完成的服务端校验
当前已补齐以下校验:
- 产品存在性校验
- 产品编码未删除范围唯一校验
- 产品名称未删除范围唯一校验
- 产品经理用户有效性校验
- 产品编码创建后不可修改校验
- 产品状态动作必须命中 `rdms_object_status_transition` 校验
- 状态动作原因是否必填校验
- 删除时产品名称二次确认一致校验
### 1.4 已完成的状态与留痕能力
当前已补齐以下状态处理和留痕:
- 创建时默认状态写入 `active`
- 未传产品编码时由服务端自动生成编码,格式按 `CNPDYYYYNNN` 处理
- 状态变更按 `action_code` 驱动,不允许直接透传目标状态
- 状态变更后同步回写 `rdms_product.status_code`
- 状态变更后同步回写 `rdms_product.last_status_reason`
- 产品状态动作写入 `rdms_product_status_log`
- 创建、编辑、状态变更、删除写入 `rdms_biz_audit_log`
### 1.5 已补齐的支撑代码
当前已补齐以下代码支撑:
- 产品域错误码常量
- `rdms_biz_audit_log` 对应 DO / Mapper
- `rdms_product_status_log` 对应 DO / Mapper
- `ProductMapper` 中产品编码前缀查询能力
- `ObjectStatusTransitionMapper` 中仅按启用流转配置查询
## 2. 当前未完成
以下内容当前还没有开发完成,不能视为“产品管理已完成”:
- 产品团队
- 产品需求
- 关联项目
- 最近动态 / `activities`
- 产品上下文 / `context`
- 对象级导航与按钮权限
- 产品团队维护时的 `rdms_user_object_role` 动态写入
- 团队维护引起的产品经理关系同步
## 3. 当前已确认不做
以下内容已按当前口径确认,本阶段不做,不再视为当前主数据闭环缺口:
- 创建产品时不写 `rdms_user_object_role`
- `rdms_user_object_role` 由后续产品团队维护时动态落库
- `pause` / `archive` / `abandon` / `delete` 当前不做关联项目、执行、任务阻塞校验
## 4. 前端现在可联调范围
前端当前可以开始联调的范围,仅限“产品主数据最小闭环”:
- 产品列表
- 产品详情
- 新建产品
- 编辑产品
- 产品状态变更
- 删除产品
前端当前不应开始联调整个“产品管理”模块,尤其不应把以下内容当成可用:
- 产品团队
- 产品需求
- 关联项目
- 最近动态
- 产品上下文能力
## 5. 当前结论
当前状态不是“产品管理开发完毕”,而是:
- 产品主数据最小闭环已完成代码实现
- 整个产品管理仍有明显未完成范围
- 前端现在可以先调产品主数据 6 个接口
联调前仍需单独确认权限是否齐备,当前主数据接口涉及权限码:
- `project:product:query`
- `project:product:create`
- `project:product:update`
- `project:product:status`
- `project:product:delete`

View File

@@ -0,0 +1,536 @@
# 06-产品主数据 API 接口文档
## 0. 文档说明
本文档用于提供“产品主数据最小闭环”当前已完成接口的标准联调口径,供前端直接调试。
当前文档只覆盖以下 6 个接口:
- `GET /project/product/page`
- `GET /project/product/get`
- `POST /project/product/create`
- `PUT /project/product/update`
- `POST /project/product/change-status`
- `POST /project/product/delete`
说明:
- 本文档以当前代码实现为准。
- 本文档不覆盖产品团队、产品需求、关联项目、最近动态、产品上下文等未完成能力。
- 本文档中的返回示例为标准结构示例,不代表数据库中的真实数据。
- 当前仅做静态对齐,未执行编译、启动和联调验证。
## 1. 接口基础信息
### 1.1 访问前缀
当前 Controller 暴露前缀为:
```text
/project/product
```
### 1.2 认证与权限
默认沿用当前系统 OAuth2 / Token 认证链路。
请求头建议:
```http
Authorization: Bearer {accessToken}
Content-Type: application/json
```
各接口所需权限如下:
| 接口 | 权限码 |
|---|---|
| `GET /project/product/page` | `project:product:query` |
| `GET /project/product/get` | `project:product:query` |
| `POST /project/product/create` | `project:product:create` |
| `PUT /project/product/update` | `project:product:update` |
| `POST /project/product/change-status` | `project:product:status` |
| `POST /project/product/delete` | `project:product:delete` |
### 1.3 统一返回结构
所有接口统一返回 `CommonResult<T>`
```json
{
"code": 0,
"msg": "",
"data": {}
}
```
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| `code` | `number` | 业务返回码,成功固定为 `0` |
| `msg` | `string` | 返回消息,成功时通常为空字符串 |
| `data` | `object / array / boolean / number / null` | 业务数据 |
### 1.4 分页返回结构
分页接口 `data` 统一为:
```json
{
"total": 1,
"list": []
}
```
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| `total` | `number` | 总记录数 |
| `list` | `array` | 当前页数据列表 |
### 1.5 日期时间格式
当前接口中的日期时间字段统一按下面格式处理:
```text
yyyy-MM-dd HH:mm:ss
```
例如:
```text
2026-04-18 15:30:00
```
## 2. 产品对象字段说明
产品详情与分页列表当前返回字段一致,对应 `ProductRespVO`
| 字段 | 类型 | 必返 | 说明 |
|---|---|---|---|
| `id` | `number` | 是 | 产品主键 ID |
| `code` | `string` | 是 | 产品编码 |
| `directionCode` | `string` | 是 | 产品方向字典值 |
| `name` | `string` | 是 | 产品名称 |
| `managerUserId` | `number` | 是 | 产品经理用户 ID |
| `description` | `string` | 否 | 产品描述 |
| `statusCode` | `string` | 是 | 产品状态编码 |
| `lastStatusReason` | `string` | 否 | 最近一次状态动作原因 |
| `remark` | `string` | 否 | 备注 |
| `createTime` | `string` | 是 | 创建时间 |
| `updateTime` | `string` | 是 | 更新时间 |
## 3. 通用业务口径
### 3.1 产品方向
`directionCode` 使用系统字典 `rdms_product_direction``value`
当前设计文档中的推荐初始值为:
- `embedded`
- `power_electronics`
- `system_group`
### 3.2 产品编码
- 创建时 `code` 可传可不传。
- 如果创建时不传 `code`,后端自动生成,格式为 `CNPDYYYYNNN`
- 更新时不允许修改 `code`
### 3.3 产品状态
当前产品主数据接口涉及的状态编码:
- `active`
- `paused`
- `archived`
- `abandoned`
### 3.4 状态动作
`POST /project/product/change-status` 当前仅支持以下动作:
- `pause`
- `resume`
- `archive`
- `abandon`
动作驱动规则:
- 前端传 `actionCode`
- 后端按 `rdms_object_status_transition` 校验是否允许流转
- 前端不能直接传目标状态编码
### 3.5 当前编辑限制
当前代码口径下:
- `archived``abandoned` 状态不允许编辑
- `paused` 状态下,仅允许调整 `managerUserId``description``remark`
- `paused` 状态下不允许修改 `directionCode``name`
## 4. 接口明细
## 4.1 获取产品分页
### 接口信息
| 项目 | 内容 |
|---|---|
| 请求方式 | `GET` |
| 接口路径 | `/project/product/page` |
| 权限码 | `project:product:query` |
### 请求参数
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| `pageNo` | query | `number` | 是 | 页码,从 `1` 开始 |
| `pageSize` | query | `number` | 是 | 每页条数,最大 `200` |
| `keyword` | query | `string` | 否 | 关键词,匹配产品编码或产品名称 |
| `directionCode` | query | `string` | 否 | 产品方向字典值 |
| `managerUserId` | query | `number` | 否 | 产品经理用户 ID |
| `statusCode` | query | `string` | 否 | 产品状态编码 |
| `updateTime` | query | `string[]` | 否 | 更新时间区间,建议传两个同名参数 |
`updateTime` 示例:
```text
/project/product/page?pageNo=1&pageSize=10&updateTime=2026-04-01 00:00:00&updateTime=2026-04-30 23:59:59
```
### 请求示例
```http
GET /project/product/page?pageNo=1&pageSize=10&keyword=RDMS&statusCode=active
```
### 成功返回示例
```json
{
"code": 0,
"msg": "",
"data": {
"total": 2,
"list": [
{
"id": 3200000000001,
"code": "CNPD2026001",
"directionCode": "embedded",
"name": "RDMS产品平台",
"managerUserId": 1024,
"description": "面向研发管理的一体化产品",
"statusCode": "active",
"lastStatusReason": null,
"remark": "首批试点产品",
"createTime": "2026-04-18 09:30:00",
"updateTime": "2026-04-18 09:30:00"
}
]
}
}
```
## 4.2 获取产品详情
### 接口信息
| 项目 | 内容 |
|---|---|
| 请求方式 | `GET` |
| 接口路径 | `/project/product/get` |
| 权限码 | `project:product:query` |
### 请求参数
| 参数 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| `id` | query | `number` | 是 | 产品 ID |
### 请求示例
```http
GET /project/product/get?id=3200000000001
```
### 成功返回示例
```json
{
"code": 0,
"msg": "",
"data": {
"id": 3200000000001,
"code": "CNPD2026001",
"directionCode": "embedded",
"name": "RDMS产品平台",
"managerUserId": 1024,
"description": "面向研发管理的一体化产品",
"statusCode": "active",
"lastStatusReason": null,
"remark": "首批试点产品",
"createTime": "2026-04-18 09:30:00",
"updateTime": "2026-04-18 09:30:00"
}
}
```
## 4.3 创建产品
### 接口信息
| 项目 | 内容 |
|---|---|
| 请求方式 | `POST` |
| 接口路径 | `/project/product/create` |
| 权限码 | `project:product:create` |
### 请求体字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | `number` | 否 | 创建时不需要传 |
| `code` | `string` | 否 | 产品编码;为空时后端自动生成 |
| `directionCode` | `string` | 是 | 产品方向字典值 |
| `name` | `string` | 是 | 产品名称 |
| `managerUserId` | `number` | 是 | 产品经理用户 ID |
| `description` | `string` | 否 | 产品描述 |
| `remark` | `string` | 否 | 备注 |
### 请求示例
```json
{
"directionCode": "embedded",
"name": "RDMS产品平台",
"managerUserId": 1024,
"description": "面向研发管理的一体化产品",
"remark": "首批试点产品"
}
```
### 成功返回示例
```json
{
"code": 0,
"msg": "",
"data": 3200000000001
}
```
### 当前服务端规则
- `name` 必填,长度最大 `128`
- `directionCode` 必填,长度最大 `32`
- `managerUserId` 必填
- `code` 最大长度 `64`
- `remark` 最大长度 `500`
- 产品编码未删除范围唯一
- 产品名称未删除范围唯一
- 产品经理必须是有效用户
- 创建成功后默认状态为 `active`
## 4.4 更新产品
### 接口信息
| 项目 | 内容 |
|---|---|
| 请求方式 | `PUT` |
| 接口路径 | `/project/product/update` |
| 权限码 | `project:product:update` |
### 请求体字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | `number` | 是 | 产品 ID |
| `code` | `string` | 否 | 如传入,必须与原产品编码一致 |
| `directionCode` | `string` | 是 | 产品方向字典值 |
| `name` | `string` | 是 | 产品名称 |
| `managerUserId` | `number` | 是 | 产品经理用户 ID |
| `description` | `string` | 否 | 产品描述 |
| `remark` | `string` | 否 | 备注 |
### 请求示例
```json
{
"id": 3200000000001,
"code": "CNPD2026001",
"directionCode": "embedded",
"name": "RDMS产品平台",
"managerUserId": 2048,
"description": "更新后的产品描述",
"remark": "已切换负责人"
}
```
### 成功返回示例
```json
{
"code": 0,
"msg": "",
"data": true
}
```
### 当前服务端规则
- `id` 必传
- 产品必须存在
- 产品经理必须是有效用户
- 产品编码不允许修改
- 产品名称未删除范围唯一
- `archived``abandoned` 状态不允许编辑
- `paused` 状态仅允许调整 `managerUserId``description``remark`
## 4.5 变更产品状态
### 接口信息
| 项目 | 内容 |
|---|---|
| 请求方式 | `POST` |
| 接口路径 | `/project/product/change-status` |
| 权限码 | `project:product:status` |
### 请求体字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | `number` | 是 | 产品 ID |
| `actionCode` | `string` | 是 | 动作编码 |
| `reason` | `string` | 否 | 动作原因;是否必填由流转配置决定 |
### `actionCode` 当前支持值
| `actionCode` | 含义 | 当前典型流转 | 原因是否必填 |
|---|---|---|---|
| `pause` | 暂停 | `active -> paused` | 是 |
| `resume` | 恢复 | `paused -> active` | 否 |
| `archive` | 归档 | `active / paused -> archived` | 是 |
| `abandon` | 废弃 | `active / paused -> abandoned` | 是 |
### 请求示例
```json
{
"id": 3200000000001,
"actionCode": "pause",
"reason": "当前阶段资源受限,先暂停推进"
}
```
### 成功返回示例
```json
{
"code": 0,
"msg": "",
"data": true
}
```
### 当前服务端规则
- 产品必须存在
- 动作必须命中 `rdms_object_status_transition`
- 前端不能直接传目标状态
- 若当前流转配置要求必须填写原因,则 `reason` 必填
- 状态变更后会同步回写:
- `rdms_product.status_code`
- `rdms_product.last_status_reason`
- 状态变更后会写入:
- `rdms_product_status_log`
- `rdms_biz_audit_log`
## 4.6 删除产品
### 接口信息
| 项目 | 内容 |
|---|---|
| 请求方式 | `POST` |
| 接口路径 | `/project/product/delete` |
| 权限码 | `project:product:delete` |
### 请求体字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `id` | `number` | 是 | 产品 ID |
| `productName` | `string` | 是 | 二次确认输入的产品名称 |
| `reason` | `string` | 是 | 删除原因 |
### 请求示例
```json
{
"id": 3200000000001,
"productName": "RDMS产品平台",
"reason": "产品录入错误,需重新创建"
}
```
### 成功返回示例
```json
{
"code": 0,
"msg": "",
"data": true
}
```
### 当前服务端规则
- 产品必须存在
- `productName` 必须与当前产品名称完全一致
- `reason` 必填
- 当前删除实现为逻辑删除
- 删除后会写入:
- `rdms_product_status_log`
- `rdms_biz_audit_log`
## 5. 业务错误码
当前产品主数据接口已使用的产品域错误码如下:
| 错误码 | 常量 | 含义 |
|---|---|---|
| `1008001000` | `PRODUCT_NOT_EXISTS` | 产品不存在 |
| `1008001001` | `PRODUCT_CODE_DUPLICATE` | 已存在相同产品编码 |
| `1008001002` | `PRODUCT_NAME_DUPLICATE` | 已存在相同产品名称 |
| `1008001003` | `PRODUCT_CODE_NOT_MODIFIABLE` | 产品编码创建后不允许修改 |
| `1008001004` | `PRODUCT_STATUS_ACTION_NOT_ALLOWED` | 当前状态不支持指定动作 |
| `1008001005` | `PRODUCT_STATUS_ACTION_REASON_REQUIRED` | 当前动作必须填写原因 |
| `1008001006` | `PRODUCT_DELETE_NAME_MISMATCH` | 删除确认名称与当前产品名称不一致 |
| `1008001007` | `PRODUCT_STATUS_NOT_ALLOW_EDIT` | 当前产品状态不允许编辑 |
| `1008001008` | `PRODUCT_PAUSED_ONLY_ALLOW_LIMITED_UPDATE` | 产品暂停后仅允许变更产品经理、描述和备注 |
此外还可能返回全局错误码:
| 错误码 | 含义 |
|---|---|
| `0` | 成功 |
| `400` | 请求参数不正确 |
| `401` | 账号未登录 |
| `403` | 没有该操作权限 |
| `500` | 系统异常 |
## 6. 联调注意事项
当前前端联调时请注意:
- 当前只联调产品主数据,不要把产品团队、产品需求、关联项目等能力一起接入。
- 创建产品时不写 `rdms_user_object_role`,产品团队关系留待后续团队维护接口处理。
- `pause` / `archive` / `abandon` / `delete` 当前不做关联项目、执行、任务阻塞校验。
- 若联调账号缺少权限,会直接返回 `403`
- 若产品方向字典值未准备好,创建和更新接口会触发字典校验失败。

View File

@@ -0,0 +1,287 @@
/*
产品管理初始化 SQL
说明:
1. 本文件作为当前产品管理唯一执行 SQL。
2. 产品方向 `direction_code` 统一存系统字典 `value`;系统字典数据本文件不重复创建。
3. 产品与产品需求状态统一走状态编码模型。
4. 产品需求当前先按已确认状态集落库;补齐流转动作码 `start_execution`、`accept`。
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for rdms_biz_audit_log
-- ----------------------------
DROP TABLE IF EXISTS `rdms_biz_audit_log`;
CREATE TABLE `rdms_biz_audit_log` (
`id` bigint NOT NULL COMMENT '主键ID',
`biz_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '业务对象类型product、product_requirement、rdms_user_object_role、project、project_requirement、execution、task',
`biz_id` bigint NOT NULL COMMENT '业务对象ID',
`action_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '动作类型create、update、change_manager、add_member、remove_member、claim、split、dispatch、reject、cancel、close、start_execution、accept、export',
`from_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '原状态',
`to_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '目标状态',
`field_changes` json NULL COMMENT '关键字段变更摘要(用于负责人变更、成员调整等)',
`reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '动作原因或说明',
`operator_user_id` bigint NOT NULL COMMENT '操作人用户ID',
`operator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '操作人名称快照',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_rdms_biz_audit_log_biz_deleted`(`biz_type` ASC, `biz_id` ASC, `deleted` ASC) USING BTREE COMMENT '业务对象索引',
INDEX `idx_rdms_biz_audit_log_action_deleted`(`action_type` ASC, `deleted` ASC) USING BTREE COMMENT '动作类型索引',
INDEX `idx_rdms_biz_audit_log_operator_deleted`(`operator_user_id` ASC, `deleted` ASC) USING BTREE COMMENT '操作人索引',
INDEX `idx_rdms_biz_audit_log_create_time`(`create_time` ASC) USING BTREE COMMENT '创建时间索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'RDMS通用业务审计日志表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for rdms_object_status_model
-- ----------------------------
DROP TABLE IF EXISTS `rdms_object_status_model`;
CREATE TABLE `rdms_object_status_model` (
`id` bigint NOT NULL COMMENT '主键ID',
`object_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '对象类型product、project、product_requirement、project_requirement、execution、task',
`status_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '状态编码',
`status_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '状态名称',
`sort` int NOT NULL DEFAULT 0 COMMENT '排序值',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '配置状态0启用 1停用',
`initial_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否初始状态',
`terminal_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否终态',
`allow_edit` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否允许编辑对象主数据',
`allow_create_project` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否允许新建项目',
`allow_create_requirement` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否允许新增需求',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_rdms_object_status_model_object_status_deleted`(`object_type` ASC, `status_code` ASC, `deleted` ASC) USING BTREE COMMENT '对象状态编码未删除范围唯一',
INDEX `idx_rdms_object_status_model_object_sort_deleted`(`object_type` ASC, `sort` ASC, `deleted` ASC) USING BTREE COMMENT '对象状态排序索引',
INDEX `idx_rdms_object_status_model_object_terminal_deleted`(`object_type` ASC, `terminal_flag` ASC, `deleted` ASC) USING BTREE COMMENT '对象终态索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'RDMS对象状态模型表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of rdms_object_status_model
-- ----------------------------
INSERT INTO `rdms_object_status_model` VALUES (3100000001001, 'product', 'active', '启用', 10, 0, b'1', b'0', b'1', b'1', b'1', '产品正常可用状态', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001002, 'product', 'paused', '暂停', 20, 0, b'0', b'0', b'1', b'0', b'0', '受环境或资源限制临时暂停推进', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001003, 'product', 'archived', '归档', 30, 0, b'0', b'1', b'0', b'0', b'0', '历史留存,只读为主', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001004, 'product', 'abandoned', '废弃', 40, 0, b'0', b'1', b'0', b'0', b'0', '确认不再继续推进', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001201, 'product_requirement', 'pending_confirm', '待确认', 10, 0, b'1', b'0', b'0', b'0', b'0', '工单流转到产品侧后的初始状态', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001202, 'product_requirement', 'pending_process', '待处理', 20, 0, b'1', b'0', b'1', b'0', b'0', '手工新增后的默认状态', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001203, 'product_requirement', 'pending_review', '待评审', 30, 0, b'0', b'0', b'1', b'0', b'0', '待产品侧完成业务评审判断', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001204, 'product_requirement', 'pending_dispatch', '待分流', 40, 0, b'0', b'0', b'1', b'0', b'0', '需求成立,等待明确承接方向', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001205, 'product_requirement', 'dispatched', '已分流', 50, 0, b'0', b'0', b'1', b'0', b'0', '已明确承接方向', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001206, 'product_requirement', 'in_progress', '实施中', 60, 0, b'0', b'0', b'1', b'0', b'0', '承接项目已进入正式执行', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001207, 'product_requirement', 'accepted', '已验收', 70, 0, b'0', b'0', b'1', b'0', b'0', '承接结果已完成验收', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001208, 'product_requirement', 'closed', '已关闭', 80, 0, b'0', b'1', b'0', b'0', b'0', '生命周期闭环完成', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001209, 'product_requirement', 'rejected', '已拒绝', 90, 0, b'0', b'1', b'0', b'0', b'0', '需求确认不成立或产品侧不接收', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_model` VALUES (3100000001210, 'product_requirement', 'canceled', '已取消', 100, 0, b'0', b'1', b'0', b'0', b'0', '推进过程中终止', '', NOW(), '', NOW(), b'0');
-- ----------------------------
-- Table structure for rdms_object_status_transition
-- ----------------------------
DROP TABLE IF EXISTS `rdms_object_status_transition`;
CREATE TABLE `rdms_object_status_transition` (
`id` bigint NOT NULL COMMENT '主键ID',
`object_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '对象类型product、project、product_requirement、project_requirement、execution、task',
`action_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '动作编码pause、resume、archive、abandon、claim、to_review、to_dispatch、dispatch、start_execution、accept、reject、cancel、close',
`action_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '动作名称',
`from_status_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '起始状态编码',
`to_status_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '目标状态编码',
`need_reason` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否必须填写原因1必须 0非必须',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '配置状态0启用 1停用',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_rdms_object_status_transition_object_from_action_deleted`(`object_type` ASC, `from_status_code` ASC, `action_code` ASC, `deleted` ASC) USING BTREE COMMENT '对象起始状态动作未删除范围唯一',
INDEX `idx_rdms_object_status_transition_object_from_deleted`(`object_type` ASC, `from_status_code` ASC, `status` ASC, `deleted` ASC) USING BTREE COMMENT '对象起始状态流转索引',
INDEX `idx_rdms_object_status_transition_object_to_deleted`(`object_type` ASC, `to_status_code` ASC, `status` ASC, `deleted` ASC) USING BTREE COMMENT '对象目标状态流转索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'RDMS对象状态流转表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of rdms_object_status_transition
-- ----------------------------
INSERT INTO `rdms_object_status_transition` VALUES (3100000001101, 'product', 'pause', '暂停', 'active', 'paused', b'1', 0, '启用转暂停', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001102, 'product', 'resume', '恢复', 'paused', 'active', b'0', 0, '暂停恢复启用', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001103, 'product', 'archive', '归档', 'active', 'archived', b'1', 0, '启用转归档', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001104, 'product', 'archive', '归档', 'paused', 'archived', b'1', 0, '暂停转归档', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001105, 'product', 'abandon', '废弃', 'active', 'abandoned', b'1', 0, '启用转废弃', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001106, 'product', 'abandon', '废弃', 'paused', 'abandoned', b'1', 0, '暂停转废弃', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001301, 'product_requirement', 'claim', '认领', 'pending_confirm', 'pending_process', b'0', 0, '待确认转待处理', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001302, 'product_requirement', 'reject', '拒绝', 'pending_confirm', 'rejected', b'1', 0, '待确认转已拒绝', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001303, 'product_requirement', 'cancel', '取消', 'pending_confirm', 'canceled', b'1', 0, '待确认转已取消', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001304, 'product_requirement', 'to_review', '转待评审', 'pending_process', 'pending_review', b'0', 0, '待处理转待评审', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001305, 'product_requirement', 'to_dispatch', '转待分流', 'pending_process', 'pending_dispatch', b'0', 0, '待处理转待分流', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001306, 'product_requirement', 'reject', '拒绝', 'pending_process', 'rejected', b'1', 0, '待处理转已拒绝', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001307, 'product_requirement', 'cancel', '取消', 'pending_process', 'canceled', b'1', 0, '待处理转已取消', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001308, 'product_requirement', 'to_dispatch', '转待分流', 'pending_review', 'pending_dispatch', b'0', 0, '待评审转待分流', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001309, 'product_requirement', 'reject', '拒绝', 'pending_review', 'rejected', b'1', 0, '待评审转已拒绝', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001310, 'product_requirement', 'cancel', '取消', 'pending_review', 'canceled', b'1', 0, '待评审转已取消', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001311, 'product_requirement', 'dispatch', '分流', 'pending_dispatch', 'dispatched', b'0', 0, '待分流转已分流', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001312, 'product_requirement', 'cancel', '取消', 'pending_dispatch', 'canceled', b'1', 0, '待分流转已取消', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001313, 'product_requirement', 'start_execution', '开始实施', 'dispatched', 'in_progress', b'0', 0, '已分流转实施中', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001314, 'product_requirement', 'cancel', '取消', 'dispatched', 'canceled', b'1', 0, '已分流转已取消', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001315, 'product_requirement', 'accept', '验收', 'in_progress', 'accepted', b'0', 0, '实施中转已验收', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001316, 'product_requirement', 'cancel', '取消', 'in_progress', 'canceled', b'1', 0, '实施中转已取消', '', NOW(), '', NOW(), b'0');
INSERT INTO `rdms_object_status_transition` VALUES (3100000001317, 'product_requirement', 'close', '关闭', 'accepted', 'closed', b'1', 0, '已验收转已关闭', '', NOW(), '', NOW(), b'0');
-- ----------------------------
-- Table structure for rdms_product
-- ----------------------------
DROP TABLE IF EXISTS `rdms_product`;
CREATE TABLE `rdms_product` (
`id` bigint NOT NULL COMMENT '主键ID',
`code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '产品编码格式CNPDYYYYNNN支持手工录入或系统自动生成',
`direction_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品方向字典值system_dict_data.value推荐字典类型 rdms_product_direction',
`status_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active' COMMENT '产品状态编码(引用 rdms_object_status_model.status_codeobject_type = product',
`name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '产品名称',
`manager_user_id` bigint NOT NULL COMMENT '当前产品经理用户ID冗余读模型字段权威来源仍为 rdms_user_object_role',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '产品描述',
`last_status_reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '最近一次状态动作原因',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_rdms_product_code_deleted`(`code` ASC, `deleted` ASC) USING BTREE COMMENT '产品编码未删除范围唯一',
UNIQUE INDEX `uk_rdms_product_name_deleted`(`name` ASC, `deleted` ASC) USING BTREE COMMENT '产品名称未删除范围唯一',
INDEX `idx_rdms_product_direction_status_code_deleted`(`direction_code` ASC, `status_code` ASC, `deleted` ASC) USING BTREE COMMENT '产品方向状态索引',
INDEX `idx_rdms_product_manager_status_code_deleted`(`manager_user_id` ASC, `status_code` ASC, `deleted` ASC) USING BTREE COMMENT '产品经理状态索引',
INDEX `idx_rdms_product_status_code_deleted`(`status_code` ASC, `deleted` ASC) USING BTREE COMMENT '产品状态索引',
INDEX `idx_rdms_product_update_time`(`update_time` ASC) USING BTREE COMMENT '更新时间索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '产品主表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for rdms_product_rd_order
-- ----------------------------
DROP TABLE IF EXISTS `rdms_product_rd_order`;
CREATE TABLE `rdms_product_rd_order` (
`id` bigint NOT NULL COMMENT '主键ID',
`product_id` bigint NOT NULL COMMENT '产品ID',
`order_year` int NOT NULL COMMENT '研发令号年度',
`rd_order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '研发令号',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态0有效 1失效',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_rdms_product_rd_order_product_year_deleted`(`product_id` ASC, `order_year` ASC, `deleted` ASC) USING BTREE COMMENT '同一产品同一年度未删除范围唯一',
INDEX `idx_rdms_product_rd_order_product_status_deleted`(`product_id` ASC, `status` ASC, `deleted` ASC) USING BTREE COMMENT '产品研发令号状态索引',
INDEX `idx_rdms_product_rd_order_no_deleted`(`rd_order_no` ASC, `deleted` ASC) USING BTREE COMMENT '研发令号检索索引',
INDEX `idx_rdms_product_rd_order_year_deleted`(`order_year` ASC, `deleted` ASC) USING BTREE COMMENT '研发令年度索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '产品研发令号表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for rdms_product_requirement
-- ----------------------------
DROP TABLE IF EXISTS `rdms_product_requirement`;
CREATE TABLE `rdms_product_requirement` (
`id` bigint NOT NULL COMMENT '主键ID',
`product_id` bigint NOT NULL COMMENT '所属产品ID',
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '需求标题',
`category` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '需求分类',
`source_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '需求来源类型manual、work_order',
`source_biz_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '来源业务类型work_order',
`source_biz_id` bigint NULL DEFAULT NULL COMMENT '来源业务ID',
`source_biz_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '来源业务编号',
`root_requirement_id` bigint NULL DEFAULT NULL COMMENT '源头需求ID同一来源链路根节点',
`parent_requirement_id` bigint NULL DEFAULT NULL COMMENT '直接父需求ID拆分子需求回指父需求',
`priority` tinyint NOT NULL DEFAULT 1 COMMENT '优先级0低 1中 2高 3紧急',
`status_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending_process' COMMENT '需求状态编码(引用 rdms_object_status_model.status_codeobject_type = product_requirement',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '需求描述',
`proposer_id` bigint NOT NULL COMMENT '提出人用户ID',
`current_handler_user_id` bigint NULL DEFAULT NULL COMMENT '当前处理人用户ID',
`implement_project_id` bigint NULL DEFAULT NULL COMMENT '默认实现项目ID',
`terminal_action_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '终态动作编码reject、cancel、close',
`terminal_reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '终态原因',
`terminal_time` datetime NULL DEFAULT NULL COMMENT '终态时间',
`sort` int NOT NULL DEFAULT 0 COMMENT '排序值',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_rdms_product_requirement_product_status_deleted`(`product_id` ASC, `status_code` ASC, `deleted` ASC) USING BTREE COMMENT '产品需求状态索引',
INDEX `idx_rdms_product_requirement_product_source_deleted`(`product_id` ASC, `source_type` ASC, `deleted` ASC) USING BTREE COMMENT '产品需求来源索引',
INDEX `idx_rdms_product_requirement_product_priority_deleted`(`product_id` ASC, `priority` ASC, `deleted` ASC) USING BTREE COMMENT '产品需求优先级索引',
INDEX `idx_rdms_product_requirement_source_biz_deleted`(`source_biz_type` ASC, `source_biz_id` ASC, `deleted` ASC) USING BTREE COMMENT '来源业务索引',
INDEX `idx_rdms_product_requirement_root_deleted`(`root_requirement_id` ASC, `deleted` ASC) USING BTREE COMMENT '源头需求索引',
INDEX `idx_rdms_product_requirement_parent_deleted`(`parent_requirement_id` ASC, `deleted` ASC) USING BTREE COMMENT '父需求索引',
INDEX `idx_rdms_product_requirement_handler_deleted`(`current_handler_user_id` ASC, `deleted` ASC) USING BTREE COMMENT '当前处理人索引',
INDEX `idx_rdms_product_requirement_terminal_action_deleted`(`terminal_action_code` ASC, `deleted` ASC) USING BTREE COMMENT '终态动作索引',
INDEX `idx_rdms_product_requirement_implement_project_deleted`(`implement_project_id` ASC, `deleted` ASC) USING BTREE COMMENT '默认实现项目索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '产品需求表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for rdms_product_status_log
-- ----------------------------
DROP TABLE IF EXISTS `rdms_product_status_log`;
CREATE TABLE `rdms_product_status_log` (
`id` bigint NOT NULL COMMENT '主键ID',
`product_id` bigint NOT NULL COMMENT '产品ID',
`action_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '动作类型pause、resume、archive、abandon、delete',
`from_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变更前状态编码',
`to_status` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变更后状态编码',
`reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '动作原因',
`operator_user_id` bigint NOT NULL COMMENT '操作人用户ID',
`operator_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '操作人名称快照',
`product_code_snapshot` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '产品编码快照',
`product_name_snapshot` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '产品名称快照',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_rdms_product_status_log_product_deleted`(`product_id` ASC, `deleted` ASC) USING BTREE COMMENT '产品状态日志索引',
INDEX `idx_rdms_product_status_log_operator_deleted`(`operator_user_id` ASC, `deleted` ASC) USING BTREE COMMENT '操作人索引',
INDEX `idx_rdms_product_status_log_create_time`(`create_time` ASC) USING BTREE COMMENT '创建时间索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '产品状态日志表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for rdms_user_object_role
-- ----------------------------
DROP TABLE IF EXISTS `rdms_user_object_role`;
CREATE TABLE `rdms_user_object_role` (
`id` bigint NOT NULL COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`object_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '对象类型product、project',
`object_id` bigint NOT NULL COMMENT '对象ID',
`role_id` bigint NOT NULL COMMENT '对象角色ID指向 system_role.id要求 scope_type = object',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态0有效 1失效成员关系是否有效的唯一判定字段',
`joined_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '加入时间',
`left_time` datetime NULL DEFAULT NULL COMMENT '退出时间,仅用于留痕,不参与权限判断',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_rdms_user_object_role_user_object_deleted`(`user_id` ASC, `object_type` ASC, `object_id` ASC, `deleted` ASC) USING BTREE COMMENT '用户对象关系未删除范围唯一',
INDEX `idx_rdms_user_object_role_object_status_deleted`(`object_type` ASC, `object_id` ASC, `status` ASC, `deleted` ASC) USING BTREE COMMENT '对象成员索引',
INDEX `idx_rdms_user_object_role_role_deleted`(`role_id` ASC, `deleted` ASC) USING BTREE COMMENT '对象角色索引',
INDEX `idx_rdms_user_object_role_user_deleted`(`user_id` ASC, `deleted` ASC) USING BTREE COMMENT '用户索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'RDMS对象成员角色关系表' ROW_FORMAT = DYNAMIC;
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,16 @@
package com.njcn.rdms.module.project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 项目交付域服务启动类
*/
@SpringBootApplication
public class ProjectServerApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectServerApplication.class, args);
}
}

View File

@@ -0,0 +1,4 @@
/**
* Project API 实现包,放置对外暴露 RPC 接口的实现类
*/
package com.njcn.rdms.module.project.api;

View File

@@ -0,0 +1,4 @@
/**
* 管理端控制器包
*/
package com.njcn.rdms.module.project.controller.admin;

View File

@@ -0,0 +1,81 @@
package com.njcn.rdms.module.project.controller.admin.product;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
import com.njcn.rdms.module.project.service.product.ProductService;
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 jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 产品管理")
@RestController
@RequestMapping("/project/product")
@Validated
public class ProductController {
@Resource
private ProductService productService;
@PostMapping("/create")
@Operation(summary = "创建产品")
@PreAuthorize("@ss.hasPermission('project:product:create')")
public CommonResult<Long> createProduct(@Valid @RequestBody ProductSaveReqVO createReqVO) {
return success(productService.createProduct(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品")
@PreAuthorize("@ss.hasPermission('project:product:update')")
public CommonResult<Boolean> updateProduct(@Valid @RequestBody ProductSaveReqVO updateReqVO) {
productService.updateProduct(updateReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获取产品详情")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('project:product:query')")
public CommonResult<ProductRespVO> getProduct(@RequestParam("id") Long id) {
ProductDO product = productService.getProduct(id);
return success(BeanUtils.toBean(product, ProductRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获取产品分页")
@PreAuthorize("@ss.hasPermission('project:product:query')")
public CommonResult<PageResult<ProductRespVO>> getProductPage(@Valid ProductPageReqVO pageReqVO) {
PageResult<ProductDO> pageResult = productService.getProductPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ProductRespVO.class));
}
@PostMapping("/change-status")
@Operation(summary = "变更产品状态")
@PreAuthorize("@ss.hasPermission('project:product:status')")
public CommonResult<Boolean> changeProductStatus(@Valid @RequestBody ProductStatusActionReqVO reqVO) {
productService.changeProductStatus(reqVO);
return success(true);
}
@PostMapping("/delete")
@Operation(summary = "删除产品")
@PreAuthorize("@ss.hasPermission('project:product:delete')")
public CommonResult<Boolean> deleteProduct(@Valid @RequestBody ProductDeleteReqVO reqVO) {
productService.deleteProduct(reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品删除 Request VO")
@Data
public class ProductDeleteReqVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long id;
@Schema(description = "确认输入的产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
@NotBlank(message = "确认产品名称不能为空")
@Size(max = 128, message = "确认产品名称长度不能超过128个字符")
private String productName;
@Schema(description = "删除原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品录入错误")
@NotBlank(message = "删除原因不能为空")
@Size(max = 500, message = "删除原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,39 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import com.njcn.rdms.framework.common.pojo.PageParam;
import com.njcn.rdms.framework.dict.validation.InDict;
import com.njcn.rdms.module.project.enums.ProjectDictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 产品分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductPageReqVO extends PageParam {
@Schema(description = "关键词,匹配产品编码或产品名称", example = "CNPD2026001")
private String keyword;
@Schema(description = "产品方向字典值", example = "embedded")
@InDict(type = ProjectDictTypeConstants.PRODUCT_DIRECTION)
private String directionCode;
@Schema(description = "产品经理用户编号", example = "1024")
private Long managerUserId;
@Schema(description = "产品状态编码", example = "active")
@Size(max = 32, message = "产品状态编码长度不能超过32个字符")
private String statusCode;
@Schema(description = "更新时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
}

View File

@@ -0,0 +1,45 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 产品 Response VO")
@Data
public class ProductRespVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPD2026001")
private String code;
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "embedded")
private String directionCode;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
private String name;
@Schema(description = "产品经理用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long managerUserId;
@Schema(description = "产品描述", example = "面向研发管理的一体化产品")
private String description;
@Schema(description = "产品状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "active")
private String statusCode;
@Schema(description = "最近一次状态动作原因", example = "阶段性暂停")
private String lastStatusReason;
@Schema(description = "备注", example = "预留")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,44 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import com.njcn.rdms.framework.dict.validation.InDict;
import com.njcn.rdms.module.project.enums.ProjectDictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品保存 Request VO")
@Data
public class ProductSaveReqVO {
@Schema(description = "产品编号", example = "1024")
private Long id;
@Schema(description = "产品编码,为空时由系统自动生成", example = "CNPD2026001")
@Size(max = 64, message = "产品编码长度不能超过64个字符")
private String code;
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "embedded")
@NotBlank(message = "产品方向不能为空")
@Size(max = 32, message = "产品方向长度不能超过32个字符")
@InDict(type = ProjectDictTypeConstants.PRODUCT_DIRECTION)
private String directionCode;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
@NotBlank(message = "产品名称不能为空")
@Size(max = 128, message = "产品名称长度不能超过128个字符")
private String name;
@Schema(description = "产品经理用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品经理不能为空")
private Long managerUserId;
@Schema(description = "产品描述", example = "面向研发管理的一体化产品")
private String description;
@Schema(description = "备注", example = "预留")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
}

View File

@@ -0,0 +1,26 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品状态动作 Request VO")
@Data
public class ProductStatusActionReqVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long id;
@Schema(description = "动作编码,如 pause、resume、archive、abandon", requiredMode = Schema.RequiredMode.REQUIRED, example = "pause")
@NotBlank(message = "动作编码不能为空")
@Size(max = 32, message = "动作编码长度不能超过32个字符")
private String actionCode;
@Schema(description = "动作原因;是否必填由状态流转配置决定", example = "当前阶段受环境限制暂停推进")
@Size(max = 500, message = "动作原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,4 @@
/**
* 应用端控制器包
*/
package com.njcn.rdms.module.project.controller.app;

View File

@@ -0,0 +1,6 @@
/**
* 提供 RESTful API 给前端:
* 1. admin 包:提供给管理后台 rdms-ui-admin 前端项目
* 2. app 包:提供给用户 APP rdms-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分
*/
package com.njcn.rdms.module.project.controller;

View File

@@ -0,0 +1,4 @@
/**
* DTO、VO、DO 等对象转换包
*/
package com.njcn.rdms.module.project.convert;

View File

@@ -0,0 +1,63 @@
package com.njcn.rdms.module.project.dal.dataobject.audit;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* RDMS通用业务审计日志表
*/
@TableName("rdms_biz_audit_log")
@Data
@EqualsAndHashCode(callSuper = true)
public class BizAuditLogDO extends BaseDO {
/**
* 主键ID
*/
@TableId
private Long id;
/**
* 业务对象类型
*/
private String bizType;
/**
* 业务对象ID
*/
private Long bizId;
/**
* 动作类型
*/
private String actionType;
/**
* 原状态
*/
private String fromStatus;
/**
* 目标状态
*/
private String toStatus;
/**
* 关键字段变更摘要
*/
private String fieldChanges;
/**
* 动作原因或说明
*/
private String reason;
/**
* 操作人用户ID
*/
private Long operatorUserId;
/**
* 操作人名称快照
*/
private String operatorName;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,4 @@
/**
* 数据对象包
*/
package com.njcn.rdms.module.project.dal.dataobject;

View File

@@ -0,0 +1,55 @@
package com.njcn.rdms.module.project.dal.dataobject.product;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 产品主表
*/
@TableName("rdms_product")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductDO extends BaseDO {
/**
* 产品编号
*/
@TableId
private Long id;
/**
* 产品编码
*/
private String code;
/**
* 产品方向字典值
*/
private String directionCode;
/**
* 产品状态编码
*/
private String statusCode;
/**
* 产品名称
*/
private String name;
/**
* 产品经理用户编号
*/
private Long managerUserId;
/**
* 产品描述
*/
private String description;
/**
* 最近一次状态动作原因
*/
private String lastStatusReason;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,63 @@
package com.njcn.rdms.module.project.dal.dataobject.product;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 产品状态日志表
*/
@TableName("rdms_product_status_log")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductStatusLogDO extends BaseDO {
/**
* 主键ID
*/
@TableId
private Long id;
/**
* 产品ID
*/
private Long productId;
/**
* 动作类型
*/
private String actionType;
/**
* 变更前状态编码
*/
private String fromStatus;
/**
* 变更后状态编码
*/
private String toStatus;
/**
* 动作原因
*/
private String reason;
/**
* 操作人用户ID
*/
private Long operatorUserId;
/**
* 操作人名称快照
*/
private String operatorName;
/**
* 产品编码快照
*/
private String productCodeSnapshot;
/**
* 产品名称快照
*/
private String productNameSnapshot;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,67 @@
package com.njcn.rdms.module.project.dal.dataobject.status;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* RDMS对象状态模型表
*/
@TableName("rdms_object_status_model")
@Data
@EqualsAndHashCode(callSuper = true)
public class ObjectStatusModelDO extends BaseDO {
/**
* 主键ID
*/
@TableId
private Long id;
/**
* 对象类型
*/
private String objectType;
/**
* 状态编码
*/
private String statusCode;
/**
* 状态名称
*/
private String statusName;
/**
* 排序值
*/
private Integer sort;
/**
* 配置状态
*/
private Integer status;
/**
* 是否初始状态
*/
private Boolean initialFlag;
/**
* 是否终态
*/
private Boolean terminalFlag;
/**
* 是否允许编辑对象主数据
*/
private Boolean allowEdit;
/**
* 是否允许新建项目
*/
private Boolean allowCreateProject;
/**
* 是否允许新增需求
*/
private Boolean allowCreateRequirement;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,55 @@
package com.njcn.rdms.module.project.dal.dataobject.status;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* RDMS对象状态流转表
*/
@TableName("rdms_object_status_transition")
@Data
@EqualsAndHashCode(callSuper = true)
public class ObjectStatusTransitionDO extends BaseDO {
/**
* 主键ID
*/
@TableId
private Long id;
/**
* 对象类型
*/
private String objectType;
/**
* 动作编码
*/
private String actionCode;
/**
* 动作名称
*/
private String actionName;
/**
* 起始状态编码
*/
private String fromStatusCode;
/**
* 目标状态编码
*/
private String toStatusCode;
/**
* 是否必须填写原因
*/
private Boolean needReason;
/**
* 配置状态
*/
private Integer status;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,9 @@
package com.njcn.rdms.module.project.dal.mysql.audit;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BizAuditLogMapper extends BaseMapperX<BizAuditLogDO> {
}

View File

@@ -0,0 +1,4 @@
/**
* MyBatis Mapper 包
*/
package com.njcn.rdms.module.project.dal.mysql;

View File

@@ -0,0 +1,46 @@
package com.njcn.rdms.module.project.dal.mysql.product;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.StringUtils;
import java.util.List;
@Mapper
public interface ProductMapper extends BaseMapperX<ProductDO> {
default PageResult<ProductDO> selectPage(ProductPageReqVO reqVO) {
LambdaQueryWrapperX<ProductDO> queryWrapper = new LambdaQueryWrapperX<>();
if (StringUtils.hasText(reqVO.getKeyword())) {
queryWrapper.and(wrapper -> wrapper.like(ProductDO::getCode, reqVO.getKeyword())
.or()
.like(ProductDO::getName, reqVO.getKeyword()));
}
queryWrapper.eqIfPresent(ProductDO::getDirectionCode, reqVO.getDirectionCode())
.eqIfPresent(ProductDO::getManagerUserId, reqVO.getManagerUserId())
.eqIfPresent(ProductDO::getStatusCode, reqVO.getStatusCode())
.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime())
.orderByDesc(BaseDO::getUpdateTime);
return selectPage(reqVO, queryWrapper);
}
default ProductDO selectByCode(String code) {
return selectOne(ProductDO::getCode, code);
}
default ProductDO selectByName(String name) {
return selectOne(ProductDO::getName, name);
}
default List<ProductDO> selectListByCodePrefix(String codePrefix) {
return selectList(new LambdaQueryWrapperX<ProductDO>()
.likeRight(ProductDO::getCode, codePrefix)
.orderByDesc(ProductDO::getCode));
}
}

View File

@@ -0,0 +1,9 @@
package com.njcn.rdms.module.project.dal.mysql.product;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductStatusLogMapper extends BaseMapperX<ProductStatusLogDO> {
}

View File

@@ -0,0 +1,25 @@
package com.njcn.rdms.module.project.dal.mysql.status;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ObjectStatusModelMapper extends BaseMapperX<ObjectStatusModelDO> {
default ObjectStatusModelDO selectByObjectTypeAndStatusCode(String objectType, String statusCode) {
return selectOne(new LambdaQueryWrapperX<ObjectStatusModelDO>()
.eq(ObjectStatusModelDO::getObjectType, objectType)
.eq(ObjectStatusModelDO::getStatusCode, statusCode));
}
default List<ObjectStatusModelDO> selectListByObjectType(String objectType) {
return selectList(new LambdaQueryWrapperX<ObjectStatusModelDO>()
.eq(ObjectStatusModelDO::getObjectType, objectType)
.orderByAsc(ObjectStatusModelDO::getSort));
}
}

View File

@@ -0,0 +1,30 @@
package com.njcn.rdms.module.project.dal.mysql.status;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface ObjectStatusTransitionMapper extends BaseMapperX<ObjectStatusTransitionDO> {
default ObjectStatusTransitionDO selectByObjectTypeAndFromStatusAndAction(String objectType,
String fromStatusCode,
String actionCode) {
return selectOne(new LambdaQueryWrapperX<ObjectStatusTransitionDO>()
.eq(ObjectStatusTransitionDO::getObjectType, objectType)
.eq(ObjectStatusTransitionDO::getFromStatusCode, fromStatusCode)
.eq(ObjectStatusTransitionDO::getActionCode, actionCode)
.eq(ObjectStatusTransitionDO::getStatus, 0));
}
default List<ObjectStatusTransitionDO> selectListByObjectTypeAndFromStatus(String objectType, String fromStatusCode) {
return selectList(new LambdaQueryWrapperX<ObjectStatusTransitionDO>()
.eq(ObjectStatusTransitionDO::getObjectType, objectType)
.eq(ObjectStatusTransitionDO::getFromStatusCode, fromStatusCode)
.eq(ObjectStatusTransitionDO::getStatus, 0));
}
}

View File

@@ -0,0 +1,4 @@
/**
* 持久层包
*/
package com.njcn.rdms.module.project.dal;

View File

@@ -0,0 +1,13 @@
package com.njcn.rdms.module.project.framework.rpc.config;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
/**
* Project 模块的 RPC 配置
*/
@Configuration(value = "projectRpcConfiguration", proxyBeanMethods = false)
@EnableFeignClients(clients = {AdminUserApi.class})
public class RpcConfiguration {
}

View File

@@ -0,0 +1,39 @@
package com.njcn.rdms.module.project.framework.security.config;
import com.njcn.rdms.framework.security.config.AuthorizeRequestsCustomizer;
import com.njcn.rdms.module.project.enums.ApiConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
/**
* Project 模块的 Security 配置
*/
@Configuration(proxyBeanMethods = false, value = "projectSecurityConfiguration")
public class SecurityConfiguration {
@Bean("projectAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
return new AuthorizeRequestsCustomizer() {
@Override
public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
// Swagger 接口文档
registry.requestMatchers("/v3/api-docs/**").permitAll()
.requestMatchers("/webjars/**").permitAll()
.requestMatchers("/swagger-ui").permitAll()
.requestMatchers("/swagger-ui/**").permitAll();
// Druid 监控
registry.requestMatchers("/druid/**").permitAll();
// Spring Boot Actuator 的安全配置
registry.requestMatchers("/actuator").permitAll()
.requestMatchers("/actuator/**").permitAll();
// RPC 服务的安全配置
registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll();
}
};
}
}

View File

@@ -0,0 +1,4 @@
/**
* 服务层包
*/
package com.njcn.rdms.module.project.service;

View File

@@ -0,0 +1,60 @@
package com.njcn.rdms.module.project.service.product;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
/**
* 产品 Service 接口
*/
public interface ProductService {
/**
* 创建产品
*
* @param createReqVO 创建请求
* @return 产品编号
*/
Long createProduct(ProductSaveReqVO createReqVO);
/**
* 更新产品
*
* @param updateReqVO 更新请求
*/
void updateProduct(ProductSaveReqVO updateReqVO);
/**
* 获取产品详情
*
* @param id 产品编号
* @return 产品信息
*/
ProductDO getProduct(Long id);
/**
* 获取产品分页
*
* @param pageReqVO 分页请求
* @return 分页结果
*/
PageResult<ProductDO> getProductPage(ProductPageReqVO pageReqVO);
/**
* 变更产品状态
*
* @param reqVO 状态动作请求
*/
void changeProductStatus(ProductStatusActionReqVO reqVO);
/**
* 删除产品
*
* @param reqVO 删除请求
*/
void deleteProduct(ProductDeleteReqVO reqVO);
}

View File

@@ -0,0 +1,347 @@
package com.njcn.rdms.module.project.service.product;
import com.google.common.annotations.VisibleForTesting;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.json.JsonUtils;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
/**
* 产品 Service 实现类
*/
@Service
public class ProductServiceImpl implements ProductService {
private static final String PRODUCT_OBJECT_TYPE = "product";
private static final String PRODUCT_ACTIVE_STATUS = "active";
private static final String PRODUCT_PAUSED_STATUS = "paused";
private static final String PRODUCT_ARCHIVED_STATUS = "archived";
private static final String PRODUCT_ABANDONED_STATUS = "abandoned";
private static final String PRODUCT_CREATE_ACTION = "create";
private static final String PRODUCT_UPDATE_ACTION = "update";
private static final String PRODUCT_DELETE_ACTION = "delete";
private static final String PRODUCT_CODE_PREFIX = "CNPD";
@Resource
private ProductMapper productMapper;
@Resource
private ProductStatusLogMapper productStatusLogMapper;
@Resource
private BizAuditLogMapper bizAuditLogMapper;
@Resource
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
@Resource
private AdminUserApi adminUserApi;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createProduct(ProductSaveReqVO createReqVO) {
validateCreateReqVO(createReqVO);
validateManagerUser(createReqVO.getManagerUserId());
ProductDO product = new ProductDO();
product.setCode(generateProductCode(createReqVO.getCode()));
product.setDirectionCode(createReqVO.getDirectionCode());
product.setStatusCode(PRODUCT_ACTIVE_STATUS);
product.setName(createReqVO.getName().trim());
product.setManagerUserId(createReqVO.getManagerUserId());
product.setDescription(normalizeNullableText(createReqVO.getDescription()));
product.setRemark(normalizeNullableText(createReqVO.getRemark()));
productMapper.insert(product);
writeBizAuditLog(product, PRODUCT_CREATE_ACTION, null, PRODUCT_ACTIVE_STATUS,
buildFieldChanges(null, product), null);
return product.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateProduct(ProductSaveReqVO updateReqVO) {
if (updateReqVO.getId() == null) {
throw invalidParamException("产品编号不能为空");
}
ProductDO product = validateProductExists(updateReqVO.getId());
validateManagerUser(updateReqVO.getManagerUserId());
validateProductCodeUnchanged(product, updateReqVO.getCode());
validateProductEditable(product, updateReqVO);
validateProductNameUnique(updateReqVO.getId(), updateReqVO.getName());
ProductDO before = BeanUtils.toBean(product, ProductDO.class);
product.setDirectionCode(updateReqVO.getDirectionCode());
product.setName(updateReqVO.getName().trim());
product.setManagerUserId(updateReqVO.getManagerUserId());
product.setDescription(normalizeNullableText(updateReqVO.getDescription()));
product.setRemark(normalizeNullableText(updateReqVO.getRemark()));
productMapper.updateById(product);
writeBizAuditLog(product, PRODUCT_UPDATE_ACTION, product.getStatusCode(), product.getStatusCode(),
buildFieldChanges(before, product), null);
}
@Override
public ProductDO getProduct(Long id) {
return validateProductExists(id);
}
@Override
public PageResult<ProductDO> getProductPage(ProductPageReqVO pageReqVO) {
return productMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void changeProductStatus(ProductStatusActionReqVO reqVO) {
ProductDO product = validateProductExists(reqVO.getId());
String actionCode = reqVO.getActionCode().trim();
ObjectStatusTransitionDO transition = validateProductTransition(product.getStatusCode(), actionCode);
String reason = normalizeNullableText(reqVO.getReason());
validateTransitionReason(transition, reason);
String fromStatus = product.getStatusCode();
String toStatus = transition.getToStatusCode();
product.setStatusCode(toStatus);
product.setLastStatusReason(reason);
productMapper.updateById(product);
writeProductStatusLog(product, actionCode, fromStatus, toStatus, reason);
writeBizAuditLog(product, actionCode, fromStatus, toStatus, null, reason);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteProduct(ProductDeleteReqVO reqVO) {
ProductDO product = validateProductExists(reqVO.getId());
if (!Objects.equals(product.getName(), reqVO.getProductName().trim())) {
throw exception(ErrorCodeConstants.PRODUCT_DELETE_NAME_MISMATCH);
}
String reason = reqVO.getReason().trim();
String fromStatus = product.getStatusCode();
productMapper.deleteById(reqVO.getId());
writeProductStatusLog(product, PRODUCT_DELETE_ACTION, fromStatus, null, reason);
writeBizAuditLog(product, PRODUCT_DELETE_ACTION, fromStatus, null, null, reason);
}
@VisibleForTesting
void validateCreateReqVO(ProductSaveReqVO createReqVO) {
validateProductCodeUnique(null, createReqVO.getCode());
validateProductNameUnique(null, createReqVO.getName());
}
@VisibleForTesting
ProductDO validateProductExists(Long id) {
if (id == null) {
throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
}
ProductDO product = productMapper.selectById(id);
if (product == null) {
throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
}
return product;
}
@VisibleForTesting
void validateProductCodeUnique(Long id, String code) {
if (!StringUtils.hasText(code)) {
return;
}
String normalizedCode = code.trim();
ProductDO product = productMapper.selectByCode(normalizedCode);
if (product == null) {
return;
}
if (id == null || !product.getId().equals(id)) {
throw exception(ErrorCodeConstants.PRODUCT_CODE_DUPLICATE, normalizedCode);
}
}
@VisibleForTesting
void validateProductNameUnique(Long id, String name) {
String normalizedName = name.trim();
ProductDO product = productMapper.selectByName(normalizedName);
if (product == null) {
return;
}
if (id == null || !product.getId().equals(id)) {
throw exception(ErrorCodeConstants.PRODUCT_NAME_DUPLICATE, normalizedName);
}
}
@VisibleForTesting
void validateManagerUser(Long managerUserId) {
adminUserApi.validateUser(managerUserId);
}
@VisibleForTesting
void validateProductCodeUnchanged(ProductDO product, String code) {
if (!StringUtils.hasText(code)) {
return;
}
if (!Objects.equals(product.getCode(), code.trim())) {
throw exception(ErrorCodeConstants.PRODUCT_CODE_NOT_MODIFIABLE);
}
}
@VisibleForTesting
void validateProductEditable(ProductDO product, ProductSaveReqVO updateReqVO) {
if (PRODUCT_ARCHIVED_STATUS.equals(product.getStatusCode())
|| PRODUCT_ABANDONED_STATUS.equals(product.getStatusCode())) {
throw exception(ErrorCodeConstants.PRODUCT_STATUS_NOT_ALLOW_EDIT);
}
if (!PRODUCT_PAUSED_STATUS.equals(product.getStatusCode())) {
return;
}
if (!Objects.equals(product.getDirectionCode(), updateReqVO.getDirectionCode())
|| !Objects.equals(product.getName(), updateReqVO.getName().trim())) {
throw exception(ErrorCodeConstants.PRODUCT_PAUSED_ONLY_ALLOW_LIMITED_UPDATE);
}
}
@VisibleForTesting
ObjectStatusTransitionDO validateProductTransition(String fromStatusCode, String actionCode) {
ObjectStatusTransitionDO transition = objectStatusTransitionMapper
.selectByObjectTypeAndFromStatusAndAction(PRODUCT_OBJECT_TYPE, fromStatusCode, actionCode);
if (transition == null) {
throw exception(ErrorCodeConstants.PRODUCT_STATUS_ACTION_NOT_ALLOWED, actionCode);
}
return transition;
}
@VisibleForTesting
void validateTransitionReason(ObjectStatusTransitionDO transition, String reason) {
if (Boolean.TRUE.equals(transition.getNeedReason()) && !StringUtils.hasText(reason)) {
throw exception(ErrorCodeConstants.PRODUCT_STATUS_ACTION_REASON_REQUIRED, transition.getActionCode());
}
}
private String generateProductCode(String code) {
String normalizedCode = normalizeNullableText(code);
if (StringUtils.hasText(normalizedCode)) {
validateProductCodeUnique(null, normalizedCode);
return normalizedCode;
}
String year = String.valueOf(LocalDate.now().getYear());
String codePrefix = PRODUCT_CODE_PREFIX + year;
int nextSequence = 1;
for (ProductDO product : productMapper.selectListByCodePrefix(codePrefix)) {
String existedCode = product.getCode();
if (!StringUtils.hasText(existedCode) || !existedCode.matches(codePrefix + "\\d{3}")) {
continue;
}
nextSequence = Integer.parseInt(existedCode.substring(codePrefix.length())) + 1;
break;
}
if (nextSequence > 999) {
throw invalidParamException("{} 年产品自动编码序号已用尽", year);
}
String generatedCode = codePrefix + String.format("%03d", nextSequence);
validateProductCodeUnique(null, generatedCode);
return generatedCode;
}
private void writeProductStatusLog(ProductDO product, String actionType, String fromStatus,
String toStatus, String reason) {
ProductStatusLogDO statusLog = new ProductStatusLogDO();
statusLog.setProductId(product.getId());
statusLog.setActionType(actionType);
statusLog.setFromStatus(fromStatus);
statusLog.setToStatus(toStatus);
statusLog.setReason(defaultText(reason));
statusLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
statusLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
statusLog.setProductCodeSnapshot(product.getCode());
statusLog.setProductNameSnapshot(product.getName());
productStatusLogMapper.insert(statusLog);
}
private void writeBizAuditLog(ProductDO product, String actionType, String fromStatus, String toStatus,
String fieldChanges, String reason) {
BizAuditLogDO auditLog = new BizAuditLogDO();
auditLog.setBizType(PRODUCT_OBJECT_TYPE);
auditLog.setBizId(product.getId());
auditLog.setActionType(actionType);
auditLog.setFromStatus(fromStatus);
auditLog.setToStatus(toStatus);
auditLog.setFieldChanges(fieldChanges);
auditLog.setReason(reason);
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
bizAuditLogMapper.insert(auditLog);
}
private String buildFieldChanges(ProductDO before, ProductDO after) {
Map<String, Object> fieldChanges = new LinkedHashMap<>();
appendFieldChange(fieldChanges, "code", valueOf(before, ProductDO::getCode), valueOf(after, ProductDO::getCode));
appendFieldChange(fieldChanges, "directionCode", valueOf(before, ProductDO::getDirectionCode),
valueOf(after, ProductDO::getDirectionCode));
appendFieldChange(fieldChanges, "statusCode", valueOf(before, ProductDO::getStatusCode),
valueOf(after, ProductDO::getStatusCode));
appendFieldChange(fieldChanges, "name", valueOf(before, ProductDO::getName), valueOf(after, ProductDO::getName));
appendFieldChange(fieldChanges, "managerUserId", valueOf(before, ProductDO::getManagerUserId),
valueOf(after, ProductDO::getManagerUserId));
appendFieldChange(fieldChanges, "description", valueOf(before, ProductDO::getDescription),
valueOf(after, ProductDO::getDescription));
appendFieldChange(fieldChanges, "lastStatusReason", valueOf(before, ProductDO::getLastStatusReason),
valueOf(after, ProductDO::getLastStatusReason));
appendFieldChange(fieldChanges, "remark", valueOf(before, ProductDO::getRemark), valueOf(after, ProductDO::getRemark));
return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
}
private <T> T valueOf(ProductDO product, Function<ProductDO, T> getter) {
return product == null ? null : getter.apply(product);
}
private void appendFieldChange(Map<String, Object> fieldChanges, String fieldName, Object before, Object after) {
if (Objects.equals(before, after)) {
return;
}
Map<String, Object> value = new LinkedHashMap<>();
value.put("before", before);
value.put("after", after);
fieldChanges.put(fieldName, value);
}
private String normalizeNullableText(String value) {
if (!StringUtils.hasText(value)) {
return null;
}
return value.trim();
}
private String defaultText(String value) {
return StringUtils.hasText(value) ? value : "";
}
}

View File

@@ -0,0 +1,92 @@
#################### 注册中心 + 配置中心相关配置 ####################
spring:
cloud:
nacos:
server-addr: 192.168.1.103:18848 # Nacos 服务器地址
username: # Nacos 账号
password: # Nacos 密码
discovery: # 【配置中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
#################### 数据库相关配置 ####################
# 数据源配置项
autoconfigure:
exclude:
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/*
login-username: # 控制台管理用户名和密码
login-password:
filter:
stat:
enabled: true
log-slow-sql: true # 慢 SQL 记录
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 5 # 初始连接数
min-idle: 10 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 60000 # 配置获取连接等待超时的时间单位毫秒1 分钟)
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测检测需要关闭的空闲连接单位毫秒1 分钟)
min-evictable-idle-time-millis: 600000 # 配置一个连接在池中最小生存的时间单位毫秒10 分钟)
max-evictable-idle-time-millis: 1800000 # 配置一个连接在池中最大生存的时间单位毫秒30 分钟)
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true # 是否开启 PreparedStatement 缓存
max-pool-prepared-statement-per-connection-size: 20 # 每个连接缓存的 PreparedStatement 数量
primary: master
datasource:
master:
url: jdbc:mysql://192.168.1.22:13306/rdms_v3?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: root
password: njcnpqs
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
host: 192.168.1.22 # 地址
port: 16379 # 端口
database: 1 # 数据库索引
# password: njcnpqs # 密码,建议生产环境开启
#################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 *,可以开放所有端点。
# 日志文件配置
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
#################### RDMS 相关配置 ####################
# RDMS 配置项,设置当前项目所有自定义的配置
rdms:
demo: true # 开启演示模式

View File

@@ -0,0 +1,98 @@
#################### 注册中心 + 配置中心相关配置 ####################
spring:
cloud:
nacos:
server-addr: 192.168.1.103:18848 # Nacos 服务器地址
username: # Nacos 账号
password: # Nacos 密码
discovery: # 【配置中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
#################### 数据库相关配置 ####################
# 数据源配置项
autoconfigure:
exclude:
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/*
login-username: # 控制台管理用户名和密码
login-password:
filter:
stat:
enabled: true
log-slow-sql: true # 慢 SQL 记录
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 5 # 初始连接数
min-idle: 10 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 60000 # 配置获取连接等待超时的时间单位毫秒1 分钟)
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测检测需要关闭的空闲连接单位毫秒1 分钟)
min-evictable-idle-time-millis: 600000 # 配置一个连接在池中最小生存的时间单位毫秒10 分钟)
max-evictable-idle-time-millis: 1800000 # 配置一个连接在池中最大生存的时间单位毫秒30 分钟)
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true # 是否开启 PreparedStatement 缓存
max-pool-prepared-statement-per-connection-size: 20 # 每个连接缓存的 PreparedStatement 数量
primary: master
datasource:
master:
url: jdbc:mysql://192.168.1.22:13306/rdms_v3?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: root
password: njcnpqs
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
host: 127.0.0.1 # 地址
port: 16379 # 端口
database: 1 # 数据库索引
# password: njcnpqs # 密码,建议生产环境开启
#################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 *,可以开放所有端点。
# 日志文件配置
logging:
level:
# 配置本模块 MyBatis Mapper 打印日志
com.njcn.rdms.module.project.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR
# RDMS 配置项,设置当前项目所有自定义的本地扩展配置
rdms:
env: # 多环境的配置项
tag: ${HOSTNAME}
captcha:
enable: false
security:
mock-enable: true
access-log: # 访问日志的配置项
enable: true

View File

@@ -0,0 +1,105 @@
spring:
application:
name: rdms-project-server
profiles:
active: local
main:
allow-circular-references: true # 允许循环依赖,因为项目当前沿用三层架构组织方式。
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如 Feign 等会存在重复定义的服务
config:
import:
- optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式,例如 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
# Cache 配置项
cache:
type: REDIS
redis:
time-to-live: 1h # 设置过期时间为 1 小时
data:
redis:
repositories:
enabled: false # 项目未使用到 Spring Data Redis 的 Repository所以直接禁用保证启动速度
# 热部署配置
devtools:
restart:
enabled: true
server:
port: 48082
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
--- #################### 接口文档配置 ####################
springdoc:
api-docs:
enabled: true # 1. 是否开启 Swagger 接口文档的元数据
path: /v3/api-docs
swagger-ui:
enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
path: /swagger-ui
default-flat-param-object: true
knife4j:
enable: true
setting:
language: zh_cn
# MyBatis Plus 的配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true但是还是显示指定下。
global-config:
db-config:
id-type: ASSIGN_ID # 分配 ID默认使用雪花算法
logic-delete-value: 1 # 逻辑已删除值(默认为 1
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0
banner: false # 关闭控制台的 Banner 打印
type-aliases-package: ${rdms.info.base-package}.dal.dataobject
encryptor:
password: cDHvwsYb9eyLNBHp # 加解密秘钥,生产环境务必通过 Nacos 注入,切勿硬编码
mybatis-plus-join:
banner: false # 关闭控制台的 Banner 打印
# VO 转换(数据翻译)相关
easy-trans:
is-enable-global: false # 默认禁用全局翻译,避免额外性能开销
--- #################### RDMS 相关配置 ####################
rdms:
info:
version: 1.0.0
base-package: com.njcn.rdms.module.project
web:
admin-ui:
url: https://www.baidu.com # Admin 管理后台 UI 的占位地址,联调时替换成实际前端入口
xss:
enable: false
exclude-urls:
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
swagger:
title: 项目交付域管理后台
description: 提供项目集、项目、产品、需求、任务、工单、执行等管理能力
author: RDMS
version: ${rdms.info.version}
url: https://example.com
email: dev@example.com
license: Apache 2.0
license-url: https://www.apache.org/licenses/LICENSE-2.0.html
debug: false

View File

@@ -31,6 +31,7 @@ public interface ErrorCodeConstants {
ErrorCode MENU_ROUTE_PROPS_JSON_INVALID = new ErrorCode(1_002_001_009, "路由 props JSON 不合法"); ErrorCode MENU_ROUTE_PROPS_JSON_INVALID = new ErrorCode(1_002_001_009, "路由 props JSON 不合法");
ErrorCode MENU_ROUTE_IFRAME_URL_REQUIRED = new ErrorCode(1_002_001_010, "iframe 路由必须配置 props.url"); ErrorCode MENU_ROUTE_IFRAME_URL_REQUIRED = new ErrorCode(1_002_001_010, "iframe 路由必须配置 props.url");
ErrorCode MENU_ROUTE_NAME_DUPLICATE = new ErrorCode(1_002_001_011, "路由名重复,请检查菜单数据:{}"); ErrorCode MENU_ROUTE_NAME_DUPLICATE = new ErrorCode(1_002_001_011, "路由名重复,请检查菜单数据:{}");
ErrorCode MENU_SCOPE_NOT_MATCH = new ErrorCode(1_002_001_012, "菜单【{}】不属于当前作用域");
// ========== 角色模块 1-002-002-000 ========== // ========== 角色模块 1-002-002-000 ==========
ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在"); ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在");
@@ -39,6 +40,7 @@ public interface ErrorCodeConstants {
ErrorCode ROLE_CAN_NOT_DELETE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, "不能删除类型为系统内置的角色"); ErrorCode ROLE_CAN_NOT_DELETE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, "不能删除类型为系统内置的角色");
ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, "名字为【{}】的角色已被禁用"); ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, "名字为【{}】的角色已被禁用");
ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "标识【{}】不能使用"); ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "标识【{}】不能使用");
ErrorCode ROLE_SCOPE_NOT_MATCH = new ErrorCode(1_002_002_006, "角色【{}】不属于当前作用域");
ErrorCode ROLE_DISABLE_NOT_ALLOWED = new ErrorCode(1_002_005_006, "该角色还有用户在使用,不允许禁用"); ErrorCode ROLE_DISABLE_NOT_ALLOWED = new ErrorCode(1_002_005_006, "该角色还有用户在使用,不允许禁用");
// ========== 用户模块 1-002-003-000 ========== // ========== 用户模块 1-002-003-000 ==========

View File

@@ -0,0 +1,20 @@
package com.njcn.rdms.module.system.enums.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum PermissionScopeTypeEnum {
GLOBAL("global"),
OBJECT("object");
/**
* 全局作用域的 objectType 固定为空字符串。
*/
public static final String GLOBAL_OBJECT_TYPE = "";
private final String scopeType;
}

View File

@@ -18,6 +18,7 @@ import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.rdms.module.system.dal.dataobject.permission.RoleDO; import com.njcn.rdms.module.system.dal.dataobject.permission.RoleDO;
import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO; import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.rdms.module.system.enums.logger.LoginLogTypeEnum; import com.njcn.rdms.module.system.enums.logger.LoginLogTypeEnum;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.service.auth.AdminAuthService; import com.njcn.rdms.module.system.service.auth.AdminAuthService;
import com.njcn.rdms.module.system.service.permission.MenuService; import com.njcn.rdms.module.system.service.permission.MenuService;
import com.njcn.rdms.module.system.service.permission.PermissionService; import com.njcn.rdms.module.system.service.permission.PermissionService;
@@ -54,6 +55,9 @@ import static com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils.
@Slf4j @Slf4j
public class AuthController { public class AuthController {
private static final String GLOBAL_SCOPE_TYPE = PermissionScopeTypeEnum.GLOBAL.getScopeType();
private static final String GLOBAL_OBJECT_TYPE = PermissionScopeTypeEnum.GLOBAL_OBJECT_TYPE;
@Resource @Resource
private AdminAuthService authService; private AdminAuthService authService;
@Resource @Resource
@@ -154,7 +158,7 @@ public class AuthController {
return Collections.emptyList(); return Collections.emptyList();
} }
List<RoleDO> roles = roleService.getRoleList(roleIds); List<RoleDO> roles = roleService.getRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
return roles; return roles;
} }
@@ -164,8 +168,9 @@ public class AuthController {
return Collections.emptyList(); return Collections.emptyList();
} }
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId),
List<MenuDO> menuList = menuService.getMenuList(menuIds); GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
List<MenuDO> menuList = menuService.getMenuList(menuIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
return menuService.filterDisableMenus(menuList); return menuService.filterDisableMenus(menuList);
} }

View File

@@ -1,13 +1,16 @@
package com.njcn.rdms.module.system.controller.admin.permission; package com.njcn.rdms.module.system.controller.admin.permission;
import cn.hutool.core.util.StrUtil;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum; import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.validation.ValidationUtils;
import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuRespVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuRespVO;
import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuSaveVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuSimpleRespVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuSimpleRespVO;
import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO; import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.service.permission.MenuService; import com.njcn.rdms.module.system.service.permission.MenuService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@@ -36,6 +39,10 @@ import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Validated @Validated
public class MenuController { public class MenuController {
private static final String GLOBAL_SCOPE_TYPE = PermissionScopeTypeEnum.GLOBAL.getScopeType();
private static final String OBJECT_SCOPE_TYPE = PermissionScopeTypeEnum.OBJECT.getScopeType();
private static final String GLOBAL_OBJECT_TYPE = PermissionScopeTypeEnum.GLOBAL_OBJECT_TYPE;
@Resource @Resource
private MenuService menuService; private MenuService menuService;
@@ -43,7 +50,7 @@ public class MenuController {
@Operation(summary = "创建菜单") @Operation(summary = "创建菜单")
@PreAuthorize("@ss.hasPermission('system:menu:create')") @PreAuthorize("@ss.hasPermission('system:menu:create')")
public CommonResult<Long> createMenu(@Valid @RequestBody MenuSaveVO createReqVO) { public CommonResult<Long> createMenu(@Valid @RequestBody MenuSaveVO createReqVO) {
Long menuId = menuService.createMenu(createReqVO); Long menuId = menuService.createMenu(normalizeScopeReqVO(createReqVO));
return success(menuId); return success(menuId);
} }
@@ -77,15 +84,20 @@ public class MenuController {
@Operation(summary = "获取菜单列表", description = "用于【菜单管理】界面") @Operation(summary = "获取菜单列表", description = "用于【菜单管理】界面")
@PreAuthorize("@ss.hasPermission('system:menu:query')") @PreAuthorize("@ss.hasPermission('system:menu:query')")
public CommonResult<List<MenuRespVO>> getMenuList(MenuListReqVO reqVO) { public CommonResult<List<MenuRespVO>> getMenuList(MenuListReqVO reqVO) {
List<MenuDO> list = menuService.getMenuList(reqVO); MenuListReqVO effectiveReqVO = normalizeScopeReqVO(reqVO);
List<MenuDO> list = menuService.getMenuList(effectiveReqVO,
effectiveReqVO.getScopeType(), effectiveReqVO.getObjectType());
list.sort(Comparator.comparing(MenuDO::getSort)); list.sort(Comparator.comparing(MenuDO::getSort));
return success(BeanUtils.toBean(list, MenuRespVO.class)); return success(BeanUtils.toBean(list, MenuRespVO.class));
} }
@GetMapping("/simple-list") @GetMapping("/simple-list")
@Operation(summary = "获取菜单精简信息列表", description = "只包含已启用的菜单,用于【角色分配菜单】功能的选项") @Operation(summary = "获取菜单精简信息列表", description = "只包含已启用的菜单,用于【角色分配菜单】功能的选项")
public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList() { public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList(MenuListReqVO reqVO) {
List<MenuDO> list = menuService.getMenuList(new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); MenuListReqVO effectiveReqVO = normalizeScopeReqVO(reqVO);
effectiveReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
List<MenuDO> list = menuService.getMenuList(effectiveReqVO,
effectiveReqVO.getScopeType(), effectiveReqVO.getObjectType());
list = menuService.filterDisableMenus(list); list = menuService.filterDisableMenus(list);
list.sort(Comparator.comparing(MenuDO::getSort)); list.sort(Comparator.comparing(MenuDO::getSort));
return success(BeanUtils.toBean(list, MenuSimpleRespVO.class)); return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
@@ -99,4 +111,30 @@ public class MenuController {
return success(BeanUtils.toBean(menu, MenuRespVO.class)); return success(BeanUtils.toBean(menu, MenuRespVO.class));
} }
private MenuListReqVO normalizeScopeReqVO(MenuListReqVO reqVO) {
MenuListReqVO effectiveReqVO = reqVO == null ? new MenuListReqVO() : reqVO;
String scopeType = normalizeScopeType(effectiveReqVO.getScopeType());
effectiveReqVO.setScopeType(scopeType);
effectiveReqVO.setObjectType(normalizeObjectType(scopeType, effectiveReqVO.getObjectType()));
ValidationUtils.validate(effectiveReqVO);
return effectiveReqVO;
}
private MenuSaveVO normalizeScopeReqVO(MenuSaveVO reqVO) {
MenuSaveVO effectiveReqVO = reqVO == null ? new MenuSaveVO() : reqVO;
String scopeType = normalizeScopeType(effectiveReqVO.getScopeType());
effectiveReqVO.setScopeType(scopeType);
effectiveReqVO.setObjectType(normalizeObjectType(scopeType, effectiveReqVO.getObjectType()));
ValidationUtils.validate(effectiveReqVO);
return effectiveReqVO;
}
private String normalizeScopeType(String scopeType) {
return StrUtil.blankToDefault(StrUtil.trim(scopeType), GLOBAL_SCOPE_TYPE);
}
private String normalizeObjectType(String scopeType, String objectType) {
return OBJECT_SCOPE_TYPE.equals(scopeType) ? StrUtil.trim(objectType) : GLOBAL_OBJECT_TYPE;
}
} }

View File

@@ -1,16 +1,19 @@
package com.njcn.rdms.module.system.controller.admin.permission; package com.njcn.rdms.module.system.controller.admin.permission;
import cn.hutool.core.util.StrUtil;
import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog; import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum; import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageParam; import com.njcn.rdms.framework.common.pojo.PageParam;
import com.njcn.rdms.framework.common.pojo.PageResult; import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.common.util.validation.ValidationUtils;
import com.njcn.rdms.framework.excel.core.util.ExcelUtils; import com.njcn.rdms.framework.excel.core.util.ExcelUtils;
import com.njcn.rdms.module.system.controller.admin.permission.vo.role.RolePageReqVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.role.RolePageReqVO;
import com.njcn.rdms.module.system.controller.admin.permission.vo.role.RoleRespVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.role.RoleRespVO;
import com.njcn.rdms.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;
import com.njcn.rdms.module.system.dal.dataobject.permission.RoleDO; import com.njcn.rdms.module.system.dal.dataobject.permission.RoleDO;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.service.permission.RoleService; import com.njcn.rdms.module.system.service.permission.RoleService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@@ -36,6 +39,10 @@ import static java.util.Collections.singleton;
@Validated @Validated
public class RoleController { public class RoleController {
private static final String GLOBAL_SCOPE_TYPE = PermissionScopeTypeEnum.GLOBAL.getScopeType();
private static final String OBJECT_SCOPE_TYPE = PermissionScopeTypeEnum.OBJECT.getScopeType();
private static final String GLOBAL_OBJECT_TYPE = PermissionScopeTypeEnum.GLOBAL_OBJECT_TYPE;
@Resource @Resource
private RoleService roleService; private RoleService roleService;
@@ -43,7 +50,7 @@ public class RoleController {
@Operation(summary = "创建角色") @Operation(summary = "创建角色")
@PreAuthorize("@ss.hasPermission('system:role:create')") @PreAuthorize("@ss.hasPermission('system:role:create')")
public CommonResult<Long> createRole(@Valid @RequestBody RoleSaveReqVO createReqVO) { public CommonResult<Long> createRole(@Valid @RequestBody RoleSaveReqVO createReqVO) {
return success(roleService.createRole(createReqVO, null)); return success(roleService.createRole(normalizeScopeReqVO(createReqVO), null));
} }
@PutMapping("/update") @PutMapping("/update")
@@ -84,15 +91,19 @@ public class RoleController {
@Operation(summary = "获得角色分页") @Operation(summary = "获得角色分页")
@PreAuthorize("@ss.hasPermission('system:role:query')") @PreAuthorize("@ss.hasPermission('system:role:query')")
public CommonResult<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) { public CommonResult<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); RolePageReqVO effectiveReqVO = normalizeScopeReqVO(pageReqVO);
PageResult<RoleDO> pageResult = roleService.getRolePage(pageReqVO); effectiveReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
PageResult<RoleDO> pageResult = roleService.getRolePage(effectiveReqVO,
effectiveReqVO.getScopeType(), effectiveReqVO.getObjectType());
return success(BeanUtils.toBean(pageResult, RoleRespVO.class)); return success(BeanUtils.toBean(pageResult, RoleRespVO.class));
} }
@GetMapping("/simple-list") @GetMapping("/simple-list")
@Operation(summary = "获取角色精简信息列表", description = "只包含被开启的角色,主要用于前端的下拉选项") @Operation(summary = "获取角色精简信息列表", description = "只包含被开启的角色,主要用于前端的下拉选项")
public CommonResult<List<RoleRespVO>> getSimpleRoleList() { public CommonResult<List<RoleRespVO>> getSimpleRoleList(RolePageReqVO reqVO) {
List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus())); RolePageReqVO effectiveReqVO = normalizeScopeReqVO(reqVO);
List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus()),
effectiveReqVO.getScopeType(), effectiveReqVO.getObjectType());
list.sort(Comparator.comparing(RoleDO::getSort)); list.sort(Comparator.comparing(RoleDO::getSort));
return success(BeanUtils.toBean(list, RoleRespVO.class)); return success(BeanUtils.toBean(list, RoleRespVO.class));
} }
@@ -109,4 +120,30 @@ public class RoleController {
BeanUtils.toBean(list, RoleRespVO.class)); BeanUtils.toBean(list, RoleRespVO.class));
} }
private RolePageReqVO normalizeScopeReqVO(RolePageReqVO reqVO) {
RolePageReqVO effectiveReqVO = reqVO == null ? new RolePageReqVO() : reqVO;
String scopeType = normalizeScopeType(effectiveReqVO.getScopeType());
effectiveReqVO.setScopeType(scopeType);
effectiveReqVO.setObjectType(normalizeObjectType(scopeType, effectiveReqVO.getObjectType()));
ValidationUtils.validate(effectiveReqVO);
return effectiveReqVO;
}
private RoleSaveReqVO normalizeScopeReqVO(RoleSaveReqVO reqVO) {
RoleSaveReqVO effectiveReqVO = reqVO == null ? new RoleSaveReqVO() : reqVO;
String scopeType = normalizeScopeType(effectiveReqVO.getScopeType());
effectiveReqVO.setScopeType(scopeType);
effectiveReqVO.setObjectType(normalizeObjectType(scopeType, effectiveReqVO.getObjectType()));
ValidationUtils.validate(effectiveReqVO);
return effectiveReqVO;
}
private String normalizeScopeType(String scopeType) {
return StrUtil.blankToDefault(StrUtil.trim(scopeType), GLOBAL_SCOPE_TYPE);
}
private String normalizeObjectType(String scopeType, String objectType) {
return OBJECT_SCOPE_TYPE.equals(scopeType) ? StrUtil.trim(objectType) : GLOBAL_OBJECT_TYPE;
}
} }

View File

@@ -1,6 +1,9 @@
package com.njcn.rdms.module.system.controller.admin.permission.vo.menu; package com.njcn.rdms.module.system.controller.admin.permission.vo.menu;
import cn.hutool.core.util.StrUtil;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import lombok.Data; import lombok.Data;
@Schema(description = "管理后台 - 菜单列表 Request VO") @Schema(description = "管理后台 - 菜单列表 Request VO")
@@ -13,4 +16,23 @@ public class MenuListReqVO {
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1") @Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
private Integer status; private Integer status;
@Schema(description = "作用域类型global 或 object", example = "global")
private String scopeType;
@Schema(description = "对象类型,当 scopeType=object 时必填", example = "product")
private String objectType;
@AssertTrue(message = "scopeType 只能是 global 或 object")
public boolean isScopeTypeValid() {
return StrUtil.isBlank(scopeType)
|| PermissionScopeTypeEnum.GLOBAL.getScopeType().equals(scopeType)
|| PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType);
}
@AssertTrue(message = "scopeType=object 时 objectType 不能为空")
public boolean isObjectTypeValid() {
return !PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType)
|| StrUtil.isNotBlank(objectType);
}
} }

View File

@@ -1,6 +1,9 @@
package com.njcn.rdms.module.system.controller.admin.permission.vo.menu; package com.njcn.rdms.module.system.controller.admin.permission.vo.menu;
import cn.hutool.core.util.StrUtil;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
@@ -68,4 +71,23 @@ public class MenuSaveVO {
@Schema(description = "是否总是显示", example = "false") @Schema(description = "是否总是显示", example = "false")
private Boolean alwaysShow; private Boolean alwaysShow;
@Schema(description = "作用域类型global 或 object", example = "global")
private String scopeType;
@Schema(description = "对象类型,当 scopeType=object 时必填", example = "product")
private String objectType;
@AssertTrue(message = "scopeType 只能是 global 或 object")
public boolean isScopeTypeValid() {
return StrUtil.isBlank(scopeType)
|| PermissionScopeTypeEnum.GLOBAL.getScopeType().equals(scopeType)
|| PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType);
}
@AssertTrue(message = "scopeType=object 时 objectType 不能为空")
public boolean isObjectTypeValid() {
return !PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType)
|| StrUtil.isNotBlank(objectType);
}
} }

View File

@@ -1,7 +1,10 @@
package com.njcn.rdms.module.system.controller.admin.permission.vo.role; package com.njcn.rdms.module.system.controller.admin.permission.vo.role;
import cn.hutool.core.util.StrUtil;
import com.njcn.rdms.framework.common.pojo.PageParam; import com.njcn.rdms.framework.common.pojo.PageParam;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
@@ -28,4 +31,23 @@ public class RolePageReqVO extends PageParam {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime; private LocalDateTime[] createTime;
@Schema(description = "作用域类型global 或 object", example = "global")
private String scopeType;
@Schema(description = "对象类型,当 scopeType=object 时必填", example = "product")
private String objectType;
@AssertTrue(message = "scopeType 只能是 global 或 object")
public boolean isScopeTypeValid() {
return StrUtil.isBlank(scopeType)
|| PermissionScopeTypeEnum.GLOBAL.getScopeType().equals(scopeType)
|| PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType);
}
@AssertTrue(message = "scopeType=object 时 objectType 不能为空")
public boolean isObjectTypeValid() {
return !PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType)
|| StrUtil.isNotBlank(objectType);
}
} }

View File

@@ -1,9 +1,12 @@
package com.njcn.rdms.module.system.controller.admin.permission.vo.role; package com.njcn.rdms.module.system.controller.admin.permission.vo.role;
import cn.hutool.core.util.StrUtil;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum; import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.validation.InEnum; import com.njcn.rdms.framework.common.validation.InEnum;
import com.mzt.logapi.starter.annotation.DiffLogField; import com.mzt.logapi.starter.annotation.DiffLogField;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
@@ -44,4 +47,23 @@ public class RoleSaveReqVO {
@DiffLogField(name = "备注") @DiffLogField(name = "备注")
private String remark; private String remark;
@Schema(description = "作用域类型global 或 object", example = "global")
private String scopeType;
@Schema(description = "对象类型,当 scopeType=object 时必填", example = "product")
private String objectType;
@AssertTrue(message = "scopeType 只能是 global 或 object")
public boolean isScopeTypeValid() {
return StrUtil.isBlank(scopeType)
|| PermissionScopeTypeEnum.GLOBAL.getScopeType().equals(scopeType)
|| PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType);
}
@AssertTrue(message = "scopeType=object 时 objectType 不能为空")
public boolean isObjectTypeValid() {
return !PermissionScopeTypeEnum.OBJECT.getScopeType().equals(scopeType)
|| StrUtil.isNotBlank(objectType);
}
} }

View File

@@ -3,6 +3,7 @@ package com.njcn.rdms.module.system.dal.dataobject.permission;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum; import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum; import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
@@ -45,6 +46,18 @@ public class MenuDO extends BaseDO {
* - 对于前端,配合前端标签,配置按钮是否展示,避免用户没有该权限时,结果可以看到该操作。 * - 对于前端,配合前端标签,配置按钮是否展示,避免用户没有该权限时,结果可以看到该操作。
*/ */
private String permission; private String permission;
/**
* 作用域类型
*
* 枚举 {@link PermissionScopeTypeEnum}
*/
private String scopeType;
/**
* 对象类型
*
* 全局资源固定为空字符串
*/
private String objectType;
/** /**
* 菜单类型 * 菜单类型
* *

View File

@@ -2,6 +2,7 @@ package com.njcn.rdms.module.system.dal.dataobject.permission;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum; import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.enums.permission.RoleTypeEnum; import com.njcn.rdms.module.system.enums.permission.RoleTypeEnum;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
@@ -33,6 +34,18 @@ public class RoleDO extends BaseDO {
* 枚举 * 枚举
*/ */
private String code; private String code;
/**
* 作用域类型
*
* 枚举 {@link PermissionScopeTypeEnum}
*/
private String scopeType;
/**
* 对象类型
*
* 全局角色固定为空字符串
*/
private String objectType;
/** /**
* 角色排序 * 角色排序
*/ */

View File

@@ -5,6 +5,7 @@ import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import com.njcn.rdms.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO; import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.springframework.lang.Nullable;
import java.util.List; import java.util.List;
@@ -12,7 +13,15 @@ import java.util.List;
public interface MenuMapper extends BaseMapperX<MenuDO> { public interface MenuMapper extends BaseMapperX<MenuDO> {
default MenuDO selectByParentIdAndName(Long parentId, String name) { default MenuDO selectByParentIdAndName(Long parentId, String name) {
return selectOne(MenuDO::getParentId, parentId, MenuDO::getName, name); return selectByParentIdAndName(parentId, name, null, null);
}
default MenuDO selectByParentIdAndName(Long parentId, String name, @Nullable String scopeType, @Nullable String objectType) {
return selectOne(new LambdaQueryWrapperX<MenuDO>()
.eq(MenuDO::getParentId, parentId)
.eq(MenuDO::getName, name)
.eq(scopeType != null, MenuDO::getScopeType, scopeType)
.eq(objectType != null, MenuDO::getObjectType, objectType));
} }
default Long selectCountByParentId(Long parentId) { default Long selectCountByParentId(Long parentId) {
@@ -20,17 +29,37 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
} }
default List<MenuDO> selectList(MenuListReqVO reqVO) { default List<MenuDO> selectList(MenuListReqVO reqVO) {
return selectList(reqVO, null, null);
}
default List<MenuDO> selectList(MenuListReqVO reqVO, @Nullable String scopeType, @Nullable String objectType) {
return selectList(new LambdaQueryWrapperX<MenuDO>() return selectList(new LambdaQueryWrapperX<MenuDO>()
.likeIfPresent(MenuDO::getName, reqVO.getName()) .likeIfPresent(MenuDO::getName, reqVO.getName())
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus())); .eqIfPresent(MenuDO::getStatus, reqVO.getStatus())
.eq(scopeType != null, MenuDO::getScopeType, scopeType)
.eq(objectType != null, MenuDO::getObjectType, objectType));
} }
default List<MenuDO> selectListByPermission(String permission) { default List<MenuDO> selectListByPermission(String permission) {
return selectList(MenuDO::getPermission, permission); return selectListByPermission(permission, null, null);
}
default List<MenuDO> selectListByPermission(String permission, @Nullable String scopeType, @Nullable String objectType) {
return selectList(new LambdaQueryWrapperX<MenuDO>()
.eq(MenuDO::getPermission, permission)
.eq(scopeType != null, MenuDO::getScopeType, scopeType)
.eq(objectType != null, MenuDO::getObjectType, objectType));
} }
default MenuDO selectByComponentName(String componentName) { default MenuDO selectByComponentName(String componentName) {
return selectOne(MenuDO::getComponentName, componentName); return selectByComponentName(componentName, null, null);
}
default MenuDO selectByComponentName(String componentName, @Nullable String scopeType, @Nullable String objectType) {
return selectOne(new LambdaQueryWrapperX<MenuDO>()
.eq(MenuDO::getComponentName, componentName)
.eq(scopeType != null, MenuDO::getScopeType, scopeType)
.eq(objectType != null, MenuDO::getObjectType, objectType));
} }
} }

View File

@@ -17,6 +17,10 @@ import java.util.List;
public interface RoleMapper extends BaseMapperX<RoleDO> { public interface RoleMapper extends BaseMapperX<RoleDO> {
default PageResult<RoleDO> selectPage(RolePageReqVO reqVO) { default PageResult<RoleDO> selectPage(RolePageReqVO reqVO) {
return selectPageByScope(reqVO, null, null);
}
default PageResult<RoleDO> selectPageByScope(RolePageReqVO reqVO, @Nullable String scopeType, @Nullable String objectType) {
LambdaQueryWrapperX<RoleDO> queryWrapper = new LambdaQueryWrapperX<>(); LambdaQueryWrapperX<RoleDO> queryWrapper = new LambdaQueryWrapperX<>();
boolean hasName = StringUtils.hasText(reqVO.getName()); boolean hasName = StringUtils.hasText(reqVO.getName());
boolean hasCode = StringUtils.hasText(reqVO.getCode()); boolean hasCode = StringUtils.hasText(reqVO.getCode());
@@ -29,21 +33,47 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
.likeIfPresent(RoleDO::getCode, reqVO.getCode()); .likeIfPresent(RoleDO::getCode, reqVO.getCode());
} }
queryWrapper.eqIfPresent(RoleDO::getStatus, reqVO.getStatus()) queryWrapper.eqIfPresent(RoleDO::getStatus, reqVO.getStatus())
.eq(scopeType != null, RoleDO::getScopeType, scopeType)
.eq(objectType != null, RoleDO::getObjectType, objectType)
.betweenIfPresent(BaseDO::getCreateTime, reqVO.getCreateTime()) .betweenIfPresent(BaseDO::getCreateTime, reqVO.getCreateTime())
.orderByAsc(RoleDO::getSort); .orderByAsc(RoleDO::getSort);
return selectPage(reqVO, queryWrapper); return BaseMapperX.super.selectPage(reqVO, queryWrapper);
} }
default RoleDO selectByName(String name) { default RoleDO selectByName(String name) {
return selectOne(RoleDO::getName, name); return selectByName(name, null, null);
}
default RoleDO selectByName(String name, @Nullable String scopeType, @Nullable String objectType) {
return selectOne(new LambdaQueryWrapperX<RoleDO>()
.eq(RoleDO::getName, name)
.eq(scopeType != null, RoleDO::getScopeType, scopeType)
.eq(objectType != null, RoleDO::getObjectType, objectType));
} }
default RoleDO selectByCode(String code) { default RoleDO selectByCode(String code) {
return selectOne(RoleDO::getCode, code); return selectByCode(code, null, null);
}
default RoleDO selectByCode(String code, @Nullable String scopeType, @Nullable String objectType) {
return selectOne(new LambdaQueryWrapperX<RoleDO>()
.eq(RoleDO::getCode, code)
.eq(scopeType != null, RoleDO::getScopeType, scopeType)
.eq(objectType != null, RoleDO::getObjectType, objectType));
} }
default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) { default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) {
return selectList(RoleDO::getStatus, statuses); return selectListByStatus(statuses, null, null);
}
default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses,
@Nullable String scopeType,
@Nullable String objectType) {
return selectList(new LambdaQueryWrapperX<RoleDO>()
.inIfPresent(RoleDO::getStatus, statuses)
.eq(scopeType != null, RoleDO::getScopeType, scopeType)
.eq(objectType != null, RoleDO::getObjectType, objectType)
.orderByAsc(RoleDO::getSort));
} }
} }

View File

@@ -23,12 +23,42 @@ public interface MenuService {
List<MenuDO> getMenuList(MenuListReqVO reqVO); List<MenuDO> getMenuList(MenuListReqVO reqVO);
/**
* 获得指定作用域下的菜单列表
*
* @param reqVO 菜单查询条件
* @param scopeType 作用域类型
* @param objectType 对象类型
* @return 菜单列表
*/
List<MenuDO> getMenuList(MenuListReqVO reqVO, String scopeType, String objectType);
List<Long> getMenuIdListByPermissionFromCache(String permission); List<Long> getMenuIdListByPermissionFromCache(String permission);
/**
* 从缓存中获得指定作用域下的权限菜单编号集合
*
* @param permission 权限标识
* @param scopeType 作用域类型
* @param objectType 对象类型
* @return 菜单编号集合
*/
List<Long> getMenuIdListByPermissionFromCache(String permission, String scopeType, String objectType);
MenuDO getMenu(Long id); MenuDO getMenu(Long id);
List<MenuDO> getMenuList(Collection<Long> ids); List<MenuDO> getMenuList(Collection<Long> ids);
/**
* 获得指定作用域下的菜单列表
*
* @param ids 菜单编号数组
* @param scopeType 作用域类型
* @param objectType 对象类型
* @return 菜单列表
*/
List<MenuDO> getMenuList(Collection<Long> ids, String scopeType, String objectType);
/** /**
* 校验菜单们是否有效。如下情况,视为无效: * 校验菜单们是否有效。如下情况,视为无效:
* 1. 菜单编号不存在 * 1. 菜单编号不存在
@@ -38,4 +68,13 @@ public interface MenuService {
*/ */
void validateMenuList(Collection<Long> ids); void validateMenuList(Collection<Long> ids);
/**
* 校验指定作用域下的菜单们是否有效
*
* @param ids 菜单编号数组
* @param scopeType 作用域类型
* @param objectType 对象类型
*/
void validateMenuList(Collection<Long> ids, String scopeType, String objectType);
} }

View File

@@ -14,6 +14,7 @@ import com.njcn.rdms.module.system.dal.mysql.permission.MenuMapper;
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants; import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
import com.njcn.rdms.module.system.enums.permission.MenuRouteKindEnum; import com.njcn.rdms.module.system.enums.permission.MenuRouteKindEnum;
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum; import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -25,6 +26,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.*; import java.util.*;
import java.util.Objects;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.convertList; import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.convertList;
@@ -41,6 +43,10 @@ import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.*;
@Slf4j @Slf4j
public class MenuServiceImpl implements MenuService { public class MenuServiceImpl implements MenuService {
private static final String GLOBAL_SCOPE_TYPE = PermissionScopeTypeEnum.GLOBAL.getScopeType();
private static final String OBJECT_SCOPE_TYPE = PermissionScopeTypeEnum.OBJECT.getScopeType();
private static final String GLOBAL_OBJECT_TYPE = PermissionScopeTypeEnum.GLOBAL_OBJECT_TYPE;
@Resource @Resource
private MenuMapper menuMapper; private MenuMapper menuMapper;
@Resource @Resource
@@ -48,18 +54,23 @@ public class MenuServiceImpl implements MenuService {
@Override @Override
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission", @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, allEntries = true)
condition = "#createReqVO.permission != null")
public Long createMenu(MenuSaveVO createReqVO) { public Long createMenu(MenuSaveVO createReqVO) {
String scopeType = StrUtil.blankToDefault(StrUtil.trim(createReqVO.getScopeType()), GLOBAL_SCOPE_TYPE);
String objectType = OBJECT_SCOPE_TYPE.equals(scopeType) ? StrUtil.trim(createReqVO.getObjectType()) : GLOBAL_OBJECT_TYPE;
// 校验父菜单存在 // 校验父菜单存在
validateParentMenu(createReqVO.getParentId(), null); validateParentMenu(createReqVO.getParentId(), null, scopeType, objectType);
// 校验菜单(自己) // 校验菜单(自己)
validateMenuName(createReqVO.getParentId(), createReqVO.getName(), null); validateMenuName(createReqVO.getParentId(), createReqVO.getName(), null,
validateMenuComponentName(createReqVO.getComponentName(), null); scopeType, objectType);
validateMenuComponentName(createReqVO.getComponentName(), null,
scopeType, objectType);
validateMenuRoute(createReqVO); validateMenuRoute(createReqVO);
// 插入数据库 // 插入数据库
MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class); MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class);
menu.setScopeType(scopeType);
menu.setObjectType(objectType);
initMenuProperty(menu); initMenuProperty(menu);
menuMapper.insert(menu); menuMapper.insert(menu);
// 返回 // 返回
@@ -71,18 +82,24 @@ public class MenuServiceImpl implements MenuService {
allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理简单有效 allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理简单有效
public void updateMenu(MenuSaveVO updateReqVO) { public void updateMenu(MenuSaveVO updateReqVO) {
// 校验更新的菜单是否存在 // 校验更新的菜单是否存在
if (menuMapper.selectById(updateReqVO.getId()) == null) { MenuDO menu = menuMapper.selectById(updateReqVO.getId());
if (menu == null) {
throw exception(MENU_NOT_EXISTS); throw exception(MENU_NOT_EXISTS);
} }
// 校验父菜单存在 // 校验父菜单存在
validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId()); validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId(),
menu.getScopeType(), menu.getObjectType());
// 校验菜单(自己) // 校验菜单(自己)
validateMenuName(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId()); validateMenuName(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId(),
validateMenuComponentName(updateReqVO.getComponentName(), updateReqVO.getId()); menu.getScopeType(), menu.getObjectType());
validateMenuComponentName(updateReqVO.getComponentName(), updateReqVO.getId(),
menu.getScopeType(), menu.getObjectType());
validateMenuRoute(updateReqVO); validateMenuRoute(updateReqVO);
// 更新到数据库 // 更新到数据库
MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class); MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);
updateObj.setScopeType(menu.getScopeType());
updateObj.setObjectType(menu.getObjectType());
initMenuProperty(updateObj); initMenuProperty(updateObj);
menuMapper.updateById(updateObj); menuMapper.updateById(updateObj);
} }
@@ -129,7 +146,7 @@ public class MenuServiceImpl implements MenuService {
@Override @Override
public List<MenuDO> getMenuList() { public List<MenuDO> getMenuList() {
return menuMapper.selectList(); return getMenuList(new MenuListReqVO(), GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
} }
@Override @Override
@@ -180,13 +197,25 @@ public class MenuServiceImpl implements MenuService {
@Override @Override
public List<MenuDO> getMenuList(MenuListReqVO reqVO) { public List<MenuDO> getMenuList(MenuListReqVO reqVO) {
return menuMapper.selectList(reqVO); return getMenuList(reqVO, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
public List<MenuDO> getMenuList(MenuListReqVO reqVO, String scopeType, String objectType) {
return menuMapper.selectList(reqVO, scopeType, objectType);
} }
@Override @Override
@Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission") @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission")
public List<Long> getMenuIdListByPermissionFromCache(String permission) { public List<Long> getMenuIdListByPermissionFromCache(String permission) {
List<MenuDO> menus = menuMapper.selectListByPermission(permission); return getMenuIdListByPermissionFromCache(permission, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
@Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
key = "#permission + ':' + #scopeType + ':' + #objectType")
public List<Long> getMenuIdListByPermissionFromCache(String permission, String scopeType, String objectType) {
List<MenuDO> menus = menuMapper.selectListByPermission(permission, scopeType, objectType);
return convertList(menus, MenuDO::getId); return convertList(menus, MenuDO::getId);
} }
@@ -201,11 +230,28 @@ public class MenuServiceImpl implements MenuService {
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {
return Lists.newArrayList(); return Lists.newArrayList();
} }
return menuMapper.selectByIds(ids); List<MenuDO> menus = menuMapper.selectByIds(ids);
menus.removeIf(menu -> !matchesScope(menu, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE));
return menus;
}
@Override
public List<MenuDO> getMenuList(Collection<Long> ids, String scopeType, String objectType) {
if (CollUtil.isEmpty(ids)) {
return Lists.newArrayList();
}
List<MenuDO> menus = menuMapper.selectByIds(ids);
menus.removeIf(menu -> !matchesScope(menu, scopeType, objectType));
return menus;
} }
@Override @Override
public void validateMenuList(Collection<Long> ids) { public void validateMenuList(Collection<Long> ids) {
validateMenuList(ids, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
public void validateMenuList(Collection<Long> ids, String scopeType, String objectType) {
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {
return; return;
} }
@@ -216,6 +262,9 @@ public class MenuServiceImpl implements MenuService {
if (menu == null) { if (menu == null) {
throw exception(MENU_NOT_EXISTS); throw exception(MENU_NOT_EXISTS);
} }
if (!matchesScope(menu, scopeType, objectType)) {
throw exception(MENU_SCOPE_NOT_MATCH, menu.getName());
}
if (CommonStatusEnum.isDisable(menu.getStatus())) { if (CommonStatusEnum.isDisable(menu.getStatus())) {
throw exception(MENU_NOT_ENABLE, menu.getName()); throw exception(MENU_NOT_ENABLE, menu.getName());
} }
@@ -234,6 +283,11 @@ public class MenuServiceImpl implements MenuService {
*/ */
@VisibleForTesting @VisibleForTesting
void validateParentMenu(Long parentId, Long childId) { void validateParentMenu(Long parentId, Long childId) {
validateParentMenu(parentId, childId, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@VisibleForTesting
void validateParentMenu(Long parentId, Long childId, String scopeType, String objectType) {
if (parentId == null || ID_ROOT.equals(parentId)) { if (parentId == null || ID_ROOT.equals(parentId)) {
return; return;
} }
@@ -246,6 +300,9 @@ public class MenuServiceImpl implements MenuService {
if (menu == null) { if (menu == null) {
throw exception(MENU_PARENT_NOT_EXISTS); throw exception(MENU_PARENT_NOT_EXISTS);
} }
if (!matchesScope(menu, scopeType, objectType)) {
throw exception(MENU_SCOPE_NOT_MATCH, menu.getName());
}
// 父菜单必须是目录或者菜单类型 // 父菜单必须是目录或者菜单类型
if (!MenuTypeEnum.DIR.getType().equals(menu.getType()) if (!MenuTypeEnum.DIR.getType().equals(menu.getType())
&& !MenuTypeEnum.MENU.getType().equals(menu.getType())) { && !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
@@ -264,7 +321,12 @@ public class MenuServiceImpl implements MenuService {
*/ */
@VisibleForTesting @VisibleForTesting
void validateMenuName(Long parentId, String name, Long id) { void validateMenuName(Long parentId, String name, Long id) {
MenuDO menu = menuMapper.selectByParentIdAndName(parentId, name); validateMenuName(parentId, name, id, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@VisibleForTesting
void validateMenuName(Long parentId, String name, Long id, String scopeType, String objectType) {
MenuDO menu = menuMapper.selectByParentIdAndName(parentId, name, scopeType, objectType);
if (menu == null) { if (menu == null) {
return; return;
} }
@@ -285,10 +347,15 @@ public class MenuServiceImpl implements MenuService {
*/ */
@VisibleForTesting @VisibleForTesting
void validateMenuComponentName(String componentName, Long id) { void validateMenuComponentName(String componentName, Long id) {
validateMenuComponentName(componentName, id, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@VisibleForTesting
void validateMenuComponentName(String componentName, Long id, String scopeType, String objectType) {
if (StrUtil.isBlank(componentName)) { if (StrUtil.isBlank(componentName)) {
return; return;
} }
MenuDO menu = menuMapper.selectByComponentName(componentName); MenuDO menu = menuMapper.selectByComponentName(componentName, scopeType, objectType);
if (menu == null) { if (menu == null) {
return; return;
} }
@@ -364,4 +431,10 @@ public class MenuServiceImpl implements MenuService {
return routeKindEnum != null ? routeKindEnum.getKind() : null; return routeKindEnum != null ? routeKindEnum.getKind() : null;
} }
private boolean matchesScope(MenuDO menu, String scopeType, String objectType) {
return menu != null
&& (scopeType == null || Objects.equals(scopeType, menu.getScopeType()))
&& (objectType == null || Objects.equals(objectType, menu.getObjectType()));
}
} }

View File

@@ -73,6 +73,18 @@ public interface PermissionService {
*/ */
Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds); Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
/**
* 获得指定作用域下的角色们拥有的菜单编号集合
*
* @param roleIds 角色编号数组
* @param scopeType 作用域类型
* @param objectType 对象类型
* @return 菜单编号集合
*/
default Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds, String scopeType, String objectType) {
return getRoleMenuListByRoleId(roleIds);
}
/** /**
* 获得拥有指定菜单的角色编号数组,从缓存中获取 * 获得拥有指定菜单的角色编号数组,从缓存中获取
* *

View File

@@ -13,6 +13,7 @@ import com.njcn.rdms.module.system.dal.dataobject.permission.UserRoleDO;
import com.njcn.rdms.module.system.dal.mysql.permission.RoleMenuMapper; import com.njcn.rdms.module.system.dal.mysql.permission.RoleMenuMapper;
import com.njcn.rdms.module.system.dal.mysql.permission.UserRoleMapper; import com.njcn.rdms.module.system.dal.mysql.permission.UserRoleMapper;
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants; import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.service.user.AdminUserService; import com.njcn.rdms.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional; import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@@ -38,6 +39,9 @@ import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.con
@Slf4j @Slf4j
public class PermissionServiceImpl implements PermissionService { public class PermissionServiceImpl implements PermissionService {
private static final String GLOBAL_SCOPE_TYPE = PermissionScopeTypeEnum.GLOBAL.getScopeType();
private static final String GLOBAL_OBJECT_TYPE = PermissionScopeTypeEnum.GLOBAL_OBJECT_TYPE;
@Resource @Resource
private RoleMenuMapper roleMenuMapper; private RoleMenuMapper roleMenuMapper;
@Resource @Resource
@@ -82,12 +86,13 @@ public class PermissionServiceImpl implements PermissionService {
* @return 是否拥有 * @return 是否拥有
*/ */
private boolean hasAnyPermission(List<RoleDO> roles, String permission) { private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission); List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission,
GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
// 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限 // 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限
if (CollUtil.isEmpty(menuIds)) { if (CollUtil.isEmpty(menuIds)) {
return false; return false;
} }
List<MenuDO> menus = getEnablePermissionMenus(menuIds); List<MenuDO> menus = getEnablePermissionMenus(menuIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
if (CollUtil.isEmpty(menus)) { if (CollUtil.isEmpty(menus)) {
return false; return false;
} }
@@ -108,12 +113,12 @@ public class PermissionServiceImpl implements PermissionService {
/** /**
* 加载权限菜单自身及其父链后,再统一过滤禁用节点,避免仅查询按钮节点时误判父菜单缺失。 * 加载权限菜单自身及其父链后,再统一过滤禁用节点,避免仅查询按钮节点时误判父菜单缺失。
*/ */
private List<MenuDO> getEnablePermissionMenus(Collection<Long> menuIds) { private List<MenuDO> getEnablePermissionMenus(Collection<Long> menuIds, String scopeType, String objectType) {
Set<Long> targetMenuIds = new HashSet<>(menuIds); Set<Long> targetMenuIds = new HashSet<>(menuIds);
Map<Long, MenuDO> menuMap = new LinkedHashMap<>(); Map<Long, MenuDO> menuMap = new LinkedHashMap<>();
Set<Long> currentIds = new HashSet<>(menuIds); Set<Long> currentIds = new HashSet<>(menuIds);
while (CollUtil.isNotEmpty(currentIds)) { while (CollUtil.isNotEmpty(currentIds)) {
List<MenuDO> currentMenus = menuService.getMenuList(currentIds); List<MenuDO> currentMenus = menuService.getMenuList(currentIds, scopeType, objectType);
if (CollUtil.isEmpty(currentMenus)) { if (CollUtil.isEmpty(currentMenus)) {
break; break;
} }
@@ -131,15 +136,15 @@ public class PermissionServiceImpl implements PermissionService {
/** /**
* 为已选菜单补齐父链,避免只授权子菜单或按钮时,权限树缺少上级节点。 * 为已选菜单补齐父链,避免只授权子菜单或按钮时,权限树缺少上级节点。
*/ */
private Set<Long> expandMenuIdsWithAncestors(Collection<Long> menuIds) { private Set<Long> expandMenuIdsWithAncestors(Collection<Long> menuIds, String scopeType, String objectType) {
Set<Long> results = new LinkedHashSet<>(menuIds); Set<Long> results = new LinkedHashSet<>(menuIds);
menuIds.forEach(menuId -> { menuIds.forEach(menuId -> {
MenuDO menu = menuService.getMenu(menuId); MenuDO menu = getMenu(menuId, scopeType, objectType);
while (menu != null && !MenuDO.ID_ROOT.equals(menu.getParentId())) { while (menu != null && !MenuDO.ID_ROOT.equals(menu.getParentId())) {
if (!results.add(menu.getParentId())) { if (!results.add(menu.getParentId())) {
break; break;
} }
menu = menuService.getMenu(menu.getParentId()); menu = getMenu(menu.getParentId(), scopeType, objectType);
} }
}); });
return results; return results;
@@ -174,12 +179,14 @@ public class PermissionServiceImpl implements PermissionService {
allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快 allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
}) })
public void assignRoleMenu(Long roleId, Set<Long> menuIds) { public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
roleService.validateRoleList(Collections.singleton(roleId)); roleService.validateRoleList(Collections.singleton(roleId), GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
menuService.validateMenuList(menuIds); RoleDO role = roleService.getRole(roleId);
menuService.validateMenuList(menuIds, role.getScopeType(), role.getObjectType());
// 获得角色拥有菜单编号 // 获得角色拥有菜单编号
Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId); Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
// 计算新增和删除的菜单编号 // 计算新增和删除的菜单编号
Set<Long> menuIdList = expandMenuIdsWithAncestors(CollUtil.emptyIfNull(menuIds)); Set<Long> menuIdList = expandMenuIdsWithAncestors(CollUtil.emptyIfNull(menuIds),
role.getScopeType(), role.getObjectType());
Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds); Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList); Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
// 执行新增和删除。对于已经授权的菜单,不用做任何处理 // 执行新增和删除。对于已经授权的菜单,不用做任何处理
@@ -222,11 +229,24 @@ public class PermissionServiceImpl implements PermissionService {
if (CollUtil.isEmpty(roleIds)) { if (CollUtil.isEmpty(roleIds)) {
return Collections.emptySet(); return Collections.emptySet();
} }
roleService.validateRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
return getRoleMenuListByRoleId(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds, String scopeType, String objectType) {
if (CollUtil.isEmpty(roleIds)) {
return Collections.emptySet();
}
// 统一按角色实际授权返回当前仍然有效的菜单,并补齐其父链 // 统一按角色实际授权返回当前仍然有效的菜单,并补齐其父链
Set<Long> menuIds = convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId); Set<Long> scopedRoleIds = convertSet(roleService.getRoleList(roleIds, scopeType, objectType), RoleDO::getId);
List<MenuDO> menus = menuService.filterDisableMenus(menuService.getMenuList(menuIds)); if (CollUtil.isEmpty(scopedRoleIds)) {
return expandMenuIdsWithAncestors(convertSet(menus, MenuDO::getId)); return Collections.emptySet();
}
Set<Long> menuIds = convertSet(roleMenuMapper.selectListByRoleId(scopedRoleIds), RoleMenuDO::getMenuId);
List<MenuDO> menus = menuService.filterDisableMenus(menuService.getMenuList(menuIds, scopeType, objectType));
return expandMenuIdsWithAncestors(convertSet(menus, MenuDO::getId), scopeType, objectType);
} }
@Override @Override
@@ -242,14 +262,16 @@ public class PermissionServiceImpl implements PermissionService {
@CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
public void assignUserRole(Long userId, Set<Long> roleIds) { public void assignUserRole(Long userId, Set<Long> roleIds) {
userService.validateUserList(Collections.singleton(userId)); userService.validateUserList(Collections.singleton(userId));
roleService.validateRoleList(roleIds); roleService.validateRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
// 获得角色拥有角色编号 // 获得角色拥有角色编号
Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId), Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
UserRoleDO::getRoleId); UserRoleDO::getRoleId);
Set<Long> dbGlobalRoleIds = convertSet(
roleService.getRoleList(dbRoleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE), RoleDO::getId);
// 计算新增和删除的角色编号 // 计算新增和删除的角色编号
Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds); Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds);
Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds); Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbGlobalRoleIds);
Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList); Collection<Long> deleteRoleIds = CollUtil.subtract(dbGlobalRoleIds, roleIdList);
// 执行新增和删除。对于已经授权的角色,不用做任何处理 // 执行新增和删除。对于已经授权的角色,不用做任何处理
if (!CollectionUtil.isEmpty(createRoleIds)) { if (!CollectionUtil.isEmpty(createRoleIds)) {
userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> { userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
@@ -259,8 +281,8 @@ public class PermissionServiceImpl implements PermissionService {
return entity; return entity;
})); }));
} }
if (!CollectionUtil.isEmpty(deleteMenuIds)) { if (!CollectionUtil.isEmpty(deleteRoleIds)) {
userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds); userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteRoleIds);
} }
} }
@@ -273,7 +295,7 @@ public class PermissionServiceImpl implements PermissionService {
@Override @Override
public Set<Long> getUserRoleIdListByUserId(Long userId) { public Set<Long> getUserRoleIdListByUserId(Long userId) {
Set<Long> roleIds = getRawUserRoleIdListByUserId(userId); Set<Long> roleIds = getRawUserRoleIdListByUserId(userId);
List<RoleDO> roles = roleService.getRoleList(roleIds); List<RoleDO> roles = roleService.getRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
return convertSet(roles, RoleDO::getId); return convertSet(roles, RoleDO::getId);
} }
@@ -281,7 +303,8 @@ public class PermissionServiceImpl implements PermissionService {
@Override @Override
@Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) { public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
return getRawUserRoleIdListByUserId(userId); Set<Long> roleIds = getRawUserRoleIdListByUserId(userId);
return convertSet(roleService.getRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE), RoleDO::getId);
} }
@Override @Override
@@ -310,6 +333,11 @@ public class PermissionServiceImpl implements PermissionService {
* *
* @return 自己 * @return 自己
*/ */
private MenuDO getMenu(Long menuId, String scopeType, String objectType) {
List<MenuDO> menus = menuService.getMenuList(Collections.singleton(menuId), scopeType, objectType);
return CollUtil.isEmpty(menus) ? null : menus.get(0);
}
private PermissionServiceImpl getSelf() { private PermissionServiceImpl getSelf() {
return SpringUtil.getBean(getClass()); return SpringUtil.getBean(getClass());
} }

View File

@@ -70,6 +70,16 @@ public interface RoleService {
*/ */
List<RoleDO> getRoleList(Collection<Long> ids); List<RoleDO> getRoleList(Collection<Long> ids);
/**
* 获得指定作用域下的角色列表
*
* @param ids 角色编号数组
* @param scopeType 作用域类型
* @param objectType 对象类型
* @return 角色列表
*/
List<RoleDO> getRoleList(Collection<Long> ids, String scopeType, String objectType);
/** /**
* 获得角色数组,从缓存中 * 获得角色数组,从缓存中
* *
@@ -86,6 +96,16 @@ public interface RoleService {
*/ */
List<RoleDO> getRoleListByStatus(Collection<Integer> statuses); List<RoleDO> getRoleListByStatus(Collection<Integer> statuses);
/**
* 获得指定作用域下的角色列表
*
* @param statuses 筛选的状态
* @param scopeType 作用域类型
* @param objectType 对象类型
* @return 角色列表
*/
List<RoleDO> getRoleListByStatus(Collection<Integer> statuses, String scopeType, String objectType);
/** /**
* 获得所有角色列表 * 获得所有角色列表
* *
@@ -101,6 +121,16 @@ public interface RoleService {
*/ */
PageResult<RoleDO> getRolePage(RolePageReqVO reqVO); PageResult<RoleDO> getRolePage(RolePageReqVO reqVO);
/**
* 获得指定作用域下的角色分页
*
* @param reqVO 角色分页查询
* @param scopeType 作用域类型
* @param objectType 对象类型
* @return 角色分页结果
*/
PageResult<RoleDO> getRolePage(RolePageReqVO reqVO, String scopeType, String objectType);
/** /**
* 判断角色编号数组中,是否有管理员 * 判断角色编号数组中,是否有管理员
* *
@@ -118,4 +148,13 @@ public interface RoleService {
*/ */
void validateRoleList(Collection<Long> ids); void validateRoleList(Collection<Long> ids);
/**
* 校验指定作用域下的角色们是否有效
*
* @param ids 角色编号数组
* @param scopeType 作用域类型
* @param objectType 对象类型
*/
void validateRoleList(Collection<Long> ids, String scopeType, String objectType);
} }

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@@ -21,6 +22,7 @@ import com.njcn.rdms.module.system.dal.dataobject.permission.UserRoleDO;
import com.njcn.rdms.module.system.dal.mysql.permission.RoleMapper; import com.njcn.rdms.module.system.dal.mysql.permission.RoleMapper;
import com.njcn.rdms.module.system.dal.mysql.permission.UserRoleMapper; import com.njcn.rdms.module.system.dal.mysql.permission.UserRoleMapper;
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants; import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.enums.permission.RoleCodeEnum; import com.njcn.rdms.module.system.enums.permission.RoleCodeEnum;
import com.njcn.rdms.module.system.enums.permission.RoleTypeEnum; import com.njcn.rdms.module.system.enums.permission.RoleTypeEnum;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -35,6 +37,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.convertMap; import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.convertMap;
@@ -45,6 +48,10 @@ import static com.njcn.rdms.module.system.enums.LogRecordConstants.*;
@Slf4j @Slf4j
public class RoleServiceImpl implements RoleService { public class RoleServiceImpl implements RoleService {
private static final String GLOBAL_SCOPE_TYPE = PermissionScopeTypeEnum.GLOBAL.getScopeType();
private static final String OBJECT_SCOPE_TYPE = PermissionScopeTypeEnum.OBJECT.getScopeType();
private static final String GLOBAL_OBJECT_TYPE = PermissionScopeTypeEnum.GLOBAL_OBJECT_TYPE;
@Resource @Resource
private PermissionService permissionService; private PermissionService permissionService;
@@ -59,11 +66,16 @@ public class RoleServiceImpl implements RoleService {
@LogRecord(type = SYSTEM_ROLE_TYPE, subType = SYSTEM_ROLE_CREATE_SUB_TYPE, bizNo = "{{#role.id}}", @LogRecord(type = SYSTEM_ROLE_TYPE, subType = SYSTEM_ROLE_CREATE_SUB_TYPE, bizNo = "{{#role.id}}",
success = SYSTEM_ROLE_CREATE_SUCCESS) success = SYSTEM_ROLE_CREATE_SUCCESS)
public Long createRole(RoleSaveReqVO createReqVO, Integer type) { public Long createRole(RoleSaveReqVO createReqVO, Integer type) {
validateRoleDuplicate(createReqVO.getName(), createReqVO.getCode(), null); String scopeType = StrUtil.blankToDefault(StrUtil.trim(createReqVO.getScopeType()), GLOBAL_SCOPE_TYPE);
String objectType = OBJECT_SCOPE_TYPE.equals(scopeType) ? StrUtil.trim(createReqVO.getObjectType()) : GLOBAL_OBJECT_TYPE;
validateRoleDuplicate(createReqVO.getName(), createReqVO.getCode(), null,
scopeType, objectType);
RoleDO role = BeanUtils.toBean(createReqVO, RoleDO.class) RoleDO role = BeanUtils.toBean(createReqVO, RoleDO.class)
.setType(ObjectUtil.defaultIfNull(type, RoleTypeEnum.CUSTOM.getType())) .setType(ObjectUtil.defaultIfNull(type, RoleTypeEnum.CUSTOM.getType()))
.setStatus(ObjUtil.defaultIfNull(createReqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())); .setStatus(ObjUtil.defaultIfNull(createReqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus()))
.setScopeType(scopeType)
.setObjectType(objectType);
roleMapper.insert(role); roleMapper.insert(role);
LogRecordContext.putVariable("role", role); LogRecordContext.putVariable("role", role);
@@ -77,7 +89,8 @@ public class RoleServiceImpl implements RoleService {
public void updateRole(RoleSaveReqVO updateReqVO) { public void updateRole(RoleSaveReqVO updateReqVO) {
RoleDO role = validateRoleExists(updateReqVO.getId()); RoleDO role = validateRoleExists(updateReqVO.getId());
String effectiveCode = shouldPreserveBuiltInCode(role) ? role.getCode() : updateReqVO.getCode(); String effectiveCode = shouldPreserveBuiltInCode(role) ? role.getCode() : updateReqVO.getCode();
validateRoleDuplicate(updateReqVO.getName(), effectiveCode, updateReqVO.getId()); validateRoleDuplicate(updateReqVO.getName(), effectiveCode, updateReqVO.getId(),
role.getScopeType(), role.getObjectType());
//如果前端想要禁用,则去校验能否被禁用 //如果前端想要禁用,则去校验能否被禁用
if (updateReqVO.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { if (updateReqVO.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {
@@ -88,6 +101,8 @@ public class RoleServiceImpl implements RoleService {
if (shouldPreserveBuiltInCode(role)) { if (shouldPreserveBuiltInCode(role)) {
updateObj.setCode(role.getCode()); updateObj.setCode(role.getCode());
} }
updateObj.setScopeType(role.getScopeType());
updateObj.setObjectType(role.getObjectType());
roleMapper.updateById(updateObj); roleMapper.updateById(updateObj);
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(role, RoleSaveReqVO.class)); LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(role, RoleSaveReqVO.class));
@@ -135,6 +150,11 @@ public class RoleServiceImpl implements RoleService {
@VisibleForTesting @VisibleForTesting
void validateRoleDuplicate(String name, String code, Long id) { void validateRoleDuplicate(String name, String code, Long id) {
validateRoleDuplicate(name, code, id, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@VisibleForTesting
void validateRoleDuplicate(String name, String code, Long id, String scopeType, String objectType) {
if (RoleCodeEnum.isBuiltIn(code)) { if (RoleCodeEnum.isBuiltIn(code)) {
if (id == null) { if (id == null) {
throw exception(ROLE_ADMIN_CODE_ERROR, code); throw exception(ROLE_ADMIN_CODE_ERROR, code);
@@ -145,7 +165,7 @@ public class RoleServiceImpl implements RoleService {
} }
} }
RoleDO role = roleMapper.selectByName(name); RoleDO role = roleMapper.selectByName(name, scopeType, objectType);
if (role != null && !role.getId().equals(id)) { if (role != null && !role.getId().equals(id)) {
throw exception(ROLE_NAME_DUPLICATE, name); throw exception(ROLE_NAME_DUPLICATE, name);
} }
@@ -153,7 +173,7 @@ public class RoleServiceImpl implements RoleService {
if (!StringUtils.hasText(code)) { if (!StringUtils.hasText(code)) {
return; return;
} }
role = roleMapper.selectByCode(code); role = roleMapper.selectByCode(code, scopeType, objectType);
if (role != null && !role.getId().equals(id)) { if (role != null && !role.getId().equals(id)) {
throw exception(ROLE_CODE_DUPLICATE, code); throw exception(ROLE_CODE_DUPLICATE, code);
} }
@@ -196,20 +216,32 @@ public class RoleServiceImpl implements RoleService {
@Override @Override
public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) { public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) {
return roleMapper.selectListByStatus(statuses); return getRoleListByStatus(statuses, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses, String scopeType, String objectType) {
return roleMapper.selectListByStatus(statuses, scopeType, objectType);
} }
@Override @Override
public List<RoleDO> getRoleList() { public List<RoleDO> getRoleList() {
return roleMapper.selectList(); return getRoleListByStatus(null, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
} }
@Override @Override
public List<RoleDO> getRoleList(Collection<Long> ids) { public List<RoleDO> getRoleList(Collection<Long> ids) {
return getRoleList(ids, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
public List<RoleDO> getRoleList(Collection<Long> ids, String scopeType, String objectType) {
if (CollectionUtil.isEmpty(ids)) { if (CollectionUtil.isEmpty(ids)) {
return Collections.emptyList(); return Collections.emptyList();
} }
return roleMapper.selectByIds(ids); List<RoleDO> roles = roleMapper.selectByIds(ids);
roles.removeIf(role -> !matchesScope(role, scopeType, objectType));
return roles;
} }
@Override @Override
@@ -223,7 +255,12 @@ public class RoleServiceImpl implements RoleService {
@Override @Override
public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) { public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {
return roleMapper.selectPage(reqVO); return getRolePage(reqVO, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO, String scopeType, String objectType) {
return roleMapper.selectPageByScope(reqVO, scopeType, objectType);
} }
@Override @Override
@@ -240,6 +277,11 @@ public class RoleServiceImpl implements RoleService {
@Override @Override
public void validateRoleList(Collection<Long> ids) { public void validateRoleList(Collection<Long> ids) {
validateRoleList(ids, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
}
@Override
public void validateRoleList(Collection<Long> ids, String scopeType, String objectType) {
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {
return; return;
} }
@@ -250,12 +292,21 @@ public class RoleServiceImpl implements RoleService {
if (role == null) { if (role == null) {
throw exception(ROLE_NOT_EXISTS); throw exception(ROLE_NOT_EXISTS);
} }
if (!matchesScope(role, scopeType, objectType)) {
throw exception(ROLE_SCOPE_NOT_MATCH, role.getName());
}
if (!CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) { if (!CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) {
throw exception(ROLE_IS_DISABLE, role.getName()); throw exception(ROLE_IS_DISABLE, role.getName());
} }
}); });
} }
private boolean matchesScope(RoleDO role, String scopeType, String objectType) {
return role != null
&& (scopeType == null || Objects.equals(scopeType, role.getScopeType()))
&& (objectType == null || Objects.equals(objectType, role.getObjectType()));
}
private RoleServiceImpl getSelf() { private RoleServiceImpl getSelf() {
return SpringUtil.getBean(getClass()); return SpringUtil.getBean(getClass());
} }

View File

@@ -1,127 +0,0 @@
-- permission-v2 stage 1 schema for MySQL 8.0
-- execution target: existing rdms system schema
-- execution mode: manual
--
-- purpose:
-- 1. add target-model columns
-- 2. create visibility relation tables
-- 3. keep legacy fields temporarily for subsequent code migration
--
-- note:
-- 1. run this script before stage 2/3 code changes
-- 2. do not drop leader_user_id / post_ids / data_scope in this stage
START TRANSACTION;
ALTER TABLE system_dept
ADD COLUMN org_type VARCHAR(20) NOT NULL DEFAULT 'dept' COMMENT '组织节点类型company/dept/direction/team' AFTER parent_id,
ADD COLUMN path VARCHAR(1024) CHARACTER SET ascii NOT NULL DEFAULT '/' COMMENT '组织物化路径,格式如 /1/2/3/' AFTER org_type,
ADD COLUMN level INT NOT NULL DEFAULT 1 COMMENT '组织层级,根节点为 1' AFTER path,
ADD COLUMN code VARCHAR(64) NULL COMMENT '组织编码' AFTER level;
ALTER TABLE system_users
ADD COLUMN position_id BIGINT NULL COMMENT '主岗位ID' AFTER dept_id,
ADD COLUMN resigned_at DATETIME NULL COMMENT '离职时间' AFTER position_id;
ALTER TABLE system_post
ADD COLUMN post_type VARCHAR(20) NULL COMMENT '岗位类型management/technical/business' AFTER code,
ADD COLUMN level_rank INT NULL COMMENT '岗位等级排序,越大级别越高' AFTER post_type;
ALTER TABLE system_dept
ADD KEY idx_system_dept_parent_id (parent_id),
ADD KEY idx_system_dept_org_type (org_type),
ADD KEY idx_system_dept_path (path(191)),
ADD UNIQUE KEY uk_system_dept_code (code);
ALTER TABLE system_users
ADD KEY idx_system_users_dept_id (dept_id),
ADD KEY idx_system_users_position_id (position_id);
ALTER TABLE system_post
ADD KEY idx_system_post_type (post_type),
ADD KEY idx_system_post_level_rank (level_rank);
CREATE TABLE system_org_leader_relation (
id BIGINT NOT NULL COMMENT '主键ID',
dept_id BIGINT NOT NULL COMMENT '组织节点ID',
user_id BIGINT NOT NULL COMMENT '负责人用户ID',
effective_from DATETIME NULL COMMENT '生效开始时间,空表示立即长期生效',
effective_until DATETIME NULL COMMENT '生效结束时间,空表示长期有效',
remark VARCHAR(500) NULL COMMENT '备注',
creator VARCHAR(64) NULL DEFAULT '' COMMENT '创建者',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updater VARCHAR(64) NULL DEFAULT '' COMMENT '更新者',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (id),
KEY idx_org_leader_user (user_id, deleted, effective_from, effective_until),
KEY idx_org_leader_dept (dept_id, deleted, effective_from, effective_until),
UNIQUE KEY uk_org_leader_once (dept_id, user_id, effective_from)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='组织负责人关系表';
CREATE TABLE system_user_management_relation (
id BIGINT NOT NULL COMMENT '主键ID',
manager_user_id BIGINT NOT NULL COMMENT '管理者用户ID',
subordinate_user_id BIGINT NOT NULL COMMENT '被管理用户ID',
effective_from DATETIME NULL COMMENT '生效开始时间,空表示立即长期生效',
effective_until DATETIME NULL COMMENT '生效结束时间,空表示长期有效',
remark VARCHAR(500) NULL COMMENT '备注',
creator VARCHAR(64) NULL DEFAULT '' COMMENT '创建者',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updater VARCHAR(64) NULL DEFAULT '' COMMENT '更新者',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (id),
KEY idx_mgr_user (manager_user_id, deleted, effective_from, effective_until),
KEY idx_sub_user (subordinate_user_id, deleted, effective_from, effective_until),
UNIQUE KEY uk_mgr_sub_once (manager_user_id, subordinate_user_id, effective_from)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户直接管理关系表';
CREATE TABLE system_user_visibility_config (
id BIGINT NOT NULL COMMENT '主键ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
visibility_type VARCHAR(20) NOT NULL COMMENT '可见范围类型all/directions/projects',
visible_direction_ids JSON NULL COMMENT '补充可见方向ID集合',
visible_project_ids JSON NULL COMMENT '补充可见项目ID集合',
remark VARCHAR(500) NULL COMMENT '备注',
creator VARCHAR(64) NULL DEFAULT '' COMMENT '创建者',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updater VARCHAR(64) NULL DEFAULT '' COMMENT '更新者',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (id),
UNIQUE KEY uk_visibility_user (user_id),
KEY idx_visibility_type (visibility_type),
CONSTRAINT chk_visibility_type CHECK (visibility_type IN ('all', 'directions', 'projects'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户补充可见范围配置表';
CREATE TABLE system_project_member (
id BIGINT NOT NULL COMMENT '主键ID',
project_id BIGINT NOT NULL COMMENT '项目ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
project_role VARCHAR(20) NOT NULL COMMENT '项目角色pm/product/developer/tester/viewer',
member_type VARCHAR(20) NOT NULL DEFAULT 'core' COMMENT '成员类型core/support',
joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '加入时间',
left_at DATETIME NULL COMMENT '退出时间,空表示仍在项目中',
remark VARCHAR(500) NULL COMMENT '备注',
creator VARCHAR(64) NULL DEFAULT '' COMMENT '创建者',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updater VARCHAR(64) NULL DEFAULT '' COMMENT '更新者',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
active_user_id BIGINT GENERATED ALWAYS AS (
CASE WHEN left_at IS NULL THEN user_id ELSE NULL END
) STORED,
active_pm_project_id BIGINT GENERATED ALWAYS AS (
CASE WHEN project_role = 'pm' AND left_at IS NULL THEN project_id ELSE NULL END
) STORED,
PRIMARY KEY (id),
UNIQUE KEY uk_project_active_member (project_id, active_user_id),
UNIQUE KEY uk_project_active_pm (active_pm_project_id),
KEY idx_pm_user_active (user_id, left_at),
KEY idx_pm_project_role_active (project_id, project_role, left_at),
CONSTRAINT chk_project_role CHECK (project_role IN ('pm', 'product', 'developer', 'tester', 'viewer')),
CONSTRAINT chk_member_type CHECK (member_type IN ('core', 'support'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目成员关系表';
COMMIT;

View File

@@ -1,44 +0,0 @@
-- permission-v2 stage 1 organization backfill for MySQL 8.0
-- purpose:
-- 1. initialize org_type
-- 2. backfill path and level from parent_id
--
-- assumption:
-- 1. root organization nodes satisfy parent_id = 0
-- 2. system_dept currently has no parent cycle
START TRANSACTION;
UPDATE system_dept
SET org_type = 'dept'
WHERE org_type IS NULL OR org_type = '';
WITH RECURSIVE dept_tree AS (
SELECT
id,
parent_id,
CAST(CONCAT('/', id, '/') AS CHAR(1024)) AS new_path,
1 AS new_level
FROM system_dept
WHERE parent_id = 0
UNION ALL
SELECT
child.id,
child.parent_id,
CAST(CONCAT(parent.new_path, child.id, '/') AS CHAR(1024)) AS new_path,
parent.new_level + 1 AS new_level
FROM system_dept child
INNER JOIN dept_tree parent ON child.parent_id = parent.id
)
UPDATE system_dept d
INNER JOIN dept_tree t ON d.id = t.id
SET
d.path = t.new_path,
d.level = t.new_level;
COMMIT;
-- verification:
-- SELECT id, name, parent_id, org_type, path, level FROM system_dept ORDER BY parent_id, sort, id;

View File

@@ -1,69 +0,0 @@
-- permission-v2 stage 1 seed for MySQL 8.0
-- purpose:
-- 1. ensure there is at least one root organization node for user association
-- 2. ensure there is at least one available post for user association
-- 3. bind admin user to an organization and a primary position
--
-- note:
-- 1. the reserved ids below are only for cold-start initialization
-- 2. adjust them before execution if your database already uses the same ids
START TRANSACTION;
INSERT INTO system_dept (
id, name, parent_id, sort, org_type, path, level, code, phone, email, status,
creator, create_time, updater, update_time, deleted
)
SELECT
900000000000000001, '平台根组织', 0, 0, 'company', '/900000000000000001/', 1, 'ROOT',
NULL, NULL, 0,
'system', NOW(), 'system', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM system_dept WHERE parent_id = 0 AND deleted = b'0'
);
INSERT INTO system_post (
id, name, code, post_type, level_rank, sort, status, remark,
creator, create_time, updater, update_time, deleted
)
SELECT
900000000000000101, '系统管理员岗', 'SYS_ADMIN_POST', 'management', 100, 0, 0, 'permission-v2 cold-start seed',
'system', NOW(), 'system', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM system_post WHERE deleted = b'0'
);
UPDATE system_users
SET
dept_id = COALESCE(
dept_id,
(SELECT root_dept.id
FROM (
SELECT id
FROM system_dept
WHERE parent_id = 0 AND deleted = b'0'
ORDER BY sort ASC, id ASC
LIMIT 1
) root_dept)
),
position_id = COALESCE(
position_id,
(SELECT chosen_post.id
FROM (
SELECT id
FROM system_post
WHERE deleted = b'0'
ORDER BY sort ASC, id ASC
LIMIT 1
) chosen_post)
),
update_time = NOW(),
updater = 'system'
WHERE username = 'admin';
COMMIT;
-- verification:
-- SELECT id, username, dept_id, position_id FROM system_users WHERE username = 'admin';

View File

@@ -1,29 +0,0 @@
-- permission-v2 stage 1 finalize for MySQL 8.0
-- purpose:
-- 1. verify required user-position data is complete
-- 2. convert system_users.position_id to NOT NULL
--
-- execute this script only after:
-- 1. 01/02/03 have completed successfully
-- 2. every existing user has a valid position_id
SELECT COUNT(*) AS missing_position_count
FROM system_users
WHERE position_id IS NULL;
SET @missing_position_count := (
SELECT COUNT(*) FROM system_users WHERE position_id IS NULL
);
SET @finalize_sql := IF(
@missing_position_count = 0,
'ALTER TABLE system_users MODIFY COLUMN position_id BIGINT NOT NULL COMMENT ''主岗位ID''',
'SELECT ''skip finalize: system_users.position_id still has NULL rows'' AS message'
);
PREPARE stmt FROM @finalize_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- verification:
-- SHOW CREATE TABLE system_users;

View File

@@ -1,710 +0,0 @@
/*
Navicat Premium Dump SQL
Source Server : localMysql
Source Server Type : MySQL
Source Server Version : 80043 (8.0.43)
Source Host : localhost:13306
Source Schema : rdms
Target Server Type : MySQL
Target Server Version : 80043 (8.0.43)
File Encoding : 65001
Date: 19/03/2026 14:23:55
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for system_dept
-- ----------------------------
DROP TABLE IF EXISTS `system_dept`;
CREATE TABLE `system_dept` (
`id` bigint NOT NULL COMMENT '部门id',
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '部门名称',
`parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父部门id',
`org_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'dept' COMMENT '组织节点类型company/dept/direction/team',
`path` varchar(1024) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '/' COMMENT '组织物化路径,格式如 /1/2/3/',
`level` int NOT NULL DEFAULT 1 COMMENT '组织层级,根节点为 1',
`code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '组织编码',
`sort` int NOT NULL DEFAULT 0 COMMENT '显示顺序',
`leader_user_id` bigint NULL DEFAULT NULL COMMENT '负责人',
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '联系电话',
`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '邮箱',
`status` tinyint NOT NULL COMMENT '部门状态0正常 1停用',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_system_dept_code`(`code` ASC) USING BTREE,
INDEX `idx_system_dept_parent_id`(`parent_id` ASC) USING BTREE,
INDEX `idx_system_dept_org_type`(`org_type` ASC) USING BTREE,
INDEX `idx_system_dept_path`(`path`(191) ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_dept
-- ----------------------------
INSERT INTO `system_dept` VALUES (100, '灿能源码', 0, 'dept', '/100/', 1, NULL, 0, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2026-03-19 13:58:49', b'0');
INSERT INTO `system_dept` VALUES (101, '深圳总公司', 100, 'dept', '/100/101/', 2, NULL, 1, NULL, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2026-03-19 14:22:19', b'0');
INSERT INTO `system_dept` VALUES (103, '研发部门', 101, 'dept', '/100/101/103/', 3, NULL, 1, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2026-03-19 13:58:49', b'0');
INSERT INTO `system_dept` VALUES (107, '运维部门', 101, 'dept', '/100/101/107/', 3, NULL, 5, 1, '15888888888', 'ry@qq.com', 0, 'admin', '2021-01-05 17:03:47', '1', '2026-03-19 13:58:49', b'0');
INSERT INTO `system_dept` VALUES (112, '产品部门', 101, 'dept', '/100/101/112/', 3, NULL, 100, 1, NULL, NULL, 1, '1', '2023-12-02 09:45:13', '1', '2026-03-19 13:58:49', b'0');
-- ----------------------------
-- Table structure for system_menu
-- ----------------------------
DROP TABLE IF EXISTS `system_menu`;
CREATE TABLE `system_menu` (
`id` bigint NOT NULL COMMENT '菜单ID',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单名称',
`permission` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '权限标识',
`type` tinyint NOT NULL COMMENT '菜单类型',
`sort` int NOT NULL DEFAULT 0 COMMENT '显示顺序',
`parent_id` bigint NOT NULL DEFAULT 0 COMMENT '父菜单ID',
`path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '路由地址',
`icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '#' COMMENT '菜单图标',
`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '组件路径',
`component_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '组件名',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '菜单状态',
`visible` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否可见',
`keep_alive` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否缓存',
`always_show` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否总是显示',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_menu
-- ----------------------------
INSERT INTO `system_menu` VALUES (1, '系统管理', '', 1, 10, 0, '/system', 'ep:tools', NULL, NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2025-03-15 21:30:27', b'0');
INSERT INTO `system_menu` VALUES (2, '基础设施', '', 1, 20, 0, '/infra', 'ep:monitor', NULL, NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-03-01 08:28:40', b'0');
INSERT INTO `system_menu` VALUES (5, 'OA 示例', '', 1, 40, 1185, 'oa', 'fa:road', NULL, NULL, 0, b'1', b'1', b'1', 'admin', '2021-09-20 16:26:19', '1', '2024-02-29 12:38:13', b'0');
INSERT INTO `system_menu` VALUES (100, '用户管理', 'system:user:list', 2, 1, 1, 'user', 'ep:avatar', 'system/user/index', 'SystemUser', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2025-03-15 21:30:41', b'0');
INSERT INTO `system_menu` VALUES (101, '角色管理', '', 2, 2, 1, 'role', 'ep:user', 'system/role/index', 'SystemRole', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-05-01 18:35:29', b'0');
INSERT INTO `system_menu` VALUES (102, '菜单管理', '', 2, 3, 1, 'menu', 'ep:menu', 'system/menu/index', 'SystemMenu', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:03:50', b'0');
INSERT INTO `system_menu` VALUES (103, '部门管理', '', 2, 4, 1, 'dept', 'fa:address-card', 'system/dept/index', 'SystemDept', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:28', b'0');
INSERT INTO `system_menu` VALUES (104, '岗位管理', '', 2, 5, 1, 'post', 'fa:address-book-o', 'system/post/index', 'SystemPost', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:06:39', b'0');
INSERT INTO `system_menu` VALUES (105, '字典管理', '', 2, 6, 1, 'dict', 'ep:collection', 'system/dict/index', 'SystemDictType', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:07:12', b'0');
INSERT INTO `system_menu` VALUES (106, '配置管理', '', 2, 8, 2, 'config', 'fa:connectdevelop', 'infra/config/index', 'InfraConfig', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:02:45', b'0');
INSERT INTO `system_menu` VALUES (108, '审计日志', '', 1, 9, 1, 'log', 'ep:document-copy', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:08:30', b'0');
INSERT INTO `system_menu` VALUES (109, '令牌管理', '', 2, 2, 1261, 'token', 'fa:key', 'system/oauth2/token/index', 'SystemTokenClient', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:13:48', b'0');
INSERT INTO `system_menu` VALUES (111, 'MySQL 监控', '', 2, 1, 2740, 'druid', 'fa-solid:box', 'infra/druid/index', 'InfraDruid', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:05:58', b'0');
INSERT INTO `system_menu` VALUES (112, 'Java 监控', '', 2, 3, 2740, 'admin-server', 'ep:coffee-cup', 'infra/server/index', 'InfraAdminServer', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:06:57', b'0');
INSERT INTO `system_menu` VALUES (113, 'Redis 监控', '', 2, 2, 2740, 'redis', 'fa:reddit-square', 'infra/redis/index', 'InfraRedis', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:06:09', b'0');
INSERT INTO `system_menu` VALUES (114, '表单构建', 'infra:build:list', 2, 2, 2, 'build', 'fa:wpforms', 'infra/build/index', 'InfraBuild', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 08:51:35', b'0');
INSERT INTO `system_menu` VALUES (116, 'API 接口', 'infra:swagger:list', 2, 3, 2, 'swagger', 'fa:fighter-jet', 'infra/swagger/index', 'InfraSwagger', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-04-23 00:01:24', b'0');
INSERT INTO `system_menu` VALUES (500, '操作日志', '', 2, 1, 108, 'operate-log', 'ep:position', 'system/operatelog/index', 'SystemOperateLog', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:09:59', b'0');
INSERT INTO `system_menu` VALUES (501, '登录日志', '', 2, 2, 108, 'login-log', 'ep:promotion', 'system/loginlog/index', 'SystemLoginLog', 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2024-02-29 01:10:29', b'0');
INSERT INTO `system_menu` VALUES (1001, '用户查询', 'system:user:query', 3, 1, 100, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1002, '用户新增', 'system:user:create', 3, 2, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1003, '用户修改', 'system:user:update', 3, 3, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1004, '用户删除', 'system:user:delete', 3, 4, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1005, '用户导出', 'system:user:export', 3, 5, 100, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1006, '用户导入', 'system:user:import', 3, 6, 100, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1007, '重置密码', 'system:user:update-password', 3, 7, 100, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1008, '角色查询', 'system:role:query', 3, 1, 101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1009, '角色新增', 'system:role:create', 3, 2, 101, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1010, '角色修改', 'system:role:update', 3, 3, 101, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1011, '角色删除', 'system:role:delete', 3, 4, 101, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1012, '角色导出', 'system:role:export', 3, 5, 101, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1013, '菜单查询', 'system:menu:query', 3, 1, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1014, '菜单新增', 'system:menu:create', 3, 2, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1015, '菜单修改', 'system:menu:update', 3, 3, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1016, '菜单删除', 'system:menu:delete', 3, 4, 102, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1017, '部门查询', 'system:dept:query', 3, 1, 103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1018, '部门新增', 'system:dept:create', 3, 2, 103, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1019, '部门修改', 'system:dept:update', 3, 3, 103, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1020, '部门删除', 'system:dept:delete', 3, 4, 103, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1021, '岗位查询', 'system:post:query', 3, 1, 104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1022, '岗位新增', 'system:post:create', 3, 2, 104, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1023, '岗位修改', 'system:post:update', 3, 3, 104, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1024, '岗位删除', 'system:post:delete', 3, 4, 104, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1025, '岗位导出', 'system:post:export', 3, 5, 104, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1026, '字典查询', 'system:dict:query', 3, 1, 105, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1027, '字典新增', 'system:dict:create', 3, 2, 105, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1028, '字典修改', 'system:dict:update', 3, 3, 105, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1029, '字典删除', 'system:dict:delete', 3, 4, 105, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1030, '字典导出', 'system:dict:export', 3, 5, 105, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1031, '配置查询', 'infra:config:query', 3, 1, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1032, '配置新增', 'infra:config:create', 3, 2, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1033, '配置修改', 'infra:config:update', 3, 3, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1034, '配置删除', 'infra:config:delete', 3, 4, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1035, '配置导出', 'infra:config:export', 3, 5, 106, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1040, '操作查询', 'system:operate-log:query', 3, 1, 500, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1042, '日志导出', 'system:operate-log:export', 3, 2, 500, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1043, '登录查询', 'system:login-log:query', 3, 1, 501, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1045, '日志导出', 'system:login-log:export', 3, 3, 501, '#', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1046, '令牌列表', 'system:oauth2-token:page', 3, 1, 109, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-05-09 23:54:42', b'0');
INSERT INTO `system_menu` VALUES (1048, '令牌删除', 'system:oauth2-token:delete', 3, 2, 109, '', '', '', NULL, 0, b'1', b'1', b'1', 'admin', '2021-01-05 17:03:48', '1', '2022-05-09 23:54:53', b'0');
INSERT INTO `system_menu` VALUES (1063, '设置角色菜单权限', 'system:permission:assign-role-menu', 3, 6, 101, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-06 17:53:44', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1064, '设置角色数据权限', 'system:permission:assign-role-data-scope', 3, 7, 101, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-06 17:56:31', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1065, '设置用户角色', 'system:permission:assign-user-role', 3, 8, 101, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-07 10:23:28', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1066, '获得 Redis 监控信息', 'infra:redis:get-monitor-info', 3, 1, 113, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-26 01:02:31', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1067, '获得 Redis Key 列表', 'infra:redis:get-key-list', 3, 2, 113, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-01-26 01:02:52', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1070, '代码生成案例', '', 1, 1, 2, 'demo', 'ep:aim', 'infra/testDemo/index', NULL, 0, b'1', b'1', b'1', '', '2021-02-06 12:42:49', '1', '2023-11-15 23:45:53', b'0');
INSERT INTO `system_menu` VALUES (1077, '链路追踪', '', 2, 4, 2740, 'skywalking', 'fa:eye', 'infra/skywalking/index', 'InfraSkyWalking', 0, b'1', b'1', b'1', '', '2021-02-08 20:41:31', '1', '2024-04-23 00:07:15', b'0');
INSERT INTO `system_menu` VALUES (1078, '访问日志', '', 2, 1, 1083, 'api-access-log', 'ep:place', 'infra/apiAccessLog/index', 'InfraApiAccessLog', 0, b'1', b'1', b'1', '', '2021-02-26 01:32:59', '1', '2024-02-29 08:54:57', b'0');
INSERT INTO `system_menu` VALUES (1082, '日志导出', 'infra:api-access-log:export', 3, 2, 1078, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-26 01:32:59', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1083, 'API 日志', '', 2, 4, 2, 'log', 'fa:tasks', NULL, NULL, 0, b'1', b'1', b'1', '', '2021-02-26 02:18:24', '1', '2024-04-22 23:58:36', b'0');
INSERT INTO `system_menu` VALUES (1084, '错误日志', 'infra:api-error-log:query', 2, 2, 1083, 'api-error-log', 'ep:warning-filled', 'infra/apiErrorLog/index', 'InfraApiErrorLog', 0, b'1', b'1', b'1', '', '2021-02-26 07:53:20', '1', '2024-02-29 08:55:17', b'0');
INSERT INTO `system_menu` VALUES (1085, '日志处理', 'infra:api-error-log:update-status', 3, 2, 1084, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1086, '日志导出', 'infra:api-error-log:export', 3, 3, 1084, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-02-26 07:53:20', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1088, '日志查询', 'infra:api-access-log:query', 3, 1, 1078, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2021-03-10 01:28:04', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1089, '日志查询', 'infra:api-error-log:query', 3, 1, 1084, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2021-03-10 01:29:09', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1090, '文件列表', '', 2, 5, 1243, 'file', 'ep:upload-filled', 'infra/file/index', 'InfraFile', 0, b'1', b'1', b'1', '', '2021-03-12 20:16:20', '1', '2024-02-29 08:53:02', b'0');
INSERT INTO `system_menu` VALUES (1091, '文件查询', 'infra:file:query', 3, 1, 1090, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1092, '文件删除', 'infra:file:delete', 3, 4, 1090, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-03-12 20:16:20', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1118, '请假查询', '', 2, 0, 5, 'leave', 'fa:leanpub', 'bpm/oa/leave/index', 'BpmOALeave', 0, b'1', b'1', b'1', '', '2021-09-20 08:51:03', '1', '2024-02-29 12:38:21', b'0');
INSERT INTO `system_menu` VALUES (1119, '请假申请查询', 'bpm:oa-leave:query', 3, 1, 1118, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1120, '请假申请创建', 'bpm:oa-leave:create', 3, 2, 1118, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-09-20 08:51:03', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1185, '工作流程', '', 1, 50, 0, '/bpm', 'fa:medium', NULL, NULL, 0, b'1', b'1', b'1', '1', '2021-12-30 20:26:36', '1', '2024-02-29 12:43:43', b'0');
INSERT INTO `system_menu` VALUES (1186, '流程管理', '', 1, 10, 1185, 'manager', 'fa:dedent', NULL, NULL, 0, b'1', b'1', b'1', '1', '2021-12-30 20:28:30', '1', '2024-02-29 12:36:02', b'0');
INSERT INTO `system_menu` VALUES (1187, '流程表单', '', 2, 2, 1186, 'form', 'fa:hdd-o', 'bpm/form/index', 'BpmForm', 0, b'1', b'1', b'1', '', '2021-12-30 12:38:22', '1', '2024-03-19 12:25:25', b'0');
INSERT INTO `system_menu` VALUES (1188, '表单查询', 'bpm:form:query', 3, 1, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1189, '表单创建', 'bpm:form:create', 3, 2, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1190, '表单更新', 'bpm:form:update', 3, 3, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1191, '表单删除', 'bpm:form:delete', 3, 4, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1192, '表单导出', 'bpm:form:export', 3, 5, 1187, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2021-12-30 12:38:22', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1193, '流程模型', '', 2, 1, 1186, 'model', 'fa-solid:project-diagram', 'bpm/model/index', 'BpmModel', 0, b'1', b'1', b'1', '1', '2021-12-31 23:24:58', '1', '2024-03-19 12:25:19', b'0');
INSERT INTO `system_menu` VALUES (1194, '模型查询', 'bpm:model:query', 3, 1, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:01:10', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1195, '模型创建', 'bpm:model:create', 3, 2, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:01:24', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1197, '模型更新', 'bpm:model:update', 3, 4, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:02:28', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1198, '模型删除', 'bpm:model:delete', 3, 5, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:02:43', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1199, '模型发布', 'bpm:model:deploy', 3, 6, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-03 19:03:24', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1200, '审批中心', '', 2, 20, 1185, 'task', 'fa:tasks', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-01-07 23:51:48', '1', '2024-03-21 00:33:15', b'0');
INSERT INTO `system_menu` VALUES (1201, '我的流程', '', 2, 1, 1200, 'my', 'fa-solid:book', 'bpm/processInstance/index', 'BpmProcessInstanceMy', 0, b'1', b'1', b'1', '', '2022-01-07 15:53:44', '1', '2024-03-21 23:52:12', b'0');
INSERT INTO `system_menu` VALUES (1202, '流程实例的查询', 'bpm:process-instance:query', 3, 1, 1201, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-01-07 15:53:44', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1207, '待办任务', '', 2, 10, 1200, 'todo', 'fa:slack', 'bpm/task/todo/index', 'BpmTodoTask', 0, b'1', b'1', b'1', '1', '2022-01-08 10:33:37', '1', '2024-02-29 12:37:39', b'0');
INSERT INTO `system_menu` VALUES (1208, '已办任务', '', 2, 20, 1200, 'done', 'fa:delicious', 'bpm/task/done/index', 'BpmDoneTask', 0, b'1', b'1', b'1', '1', '2022-01-08 10:34:13', '1', '2024-02-29 12:37:54', b'0');
INSERT INTO `system_menu` VALUES (1209, '用户分组', '', 2, 4, 1186, 'user-group', 'fa:user-secret', 'bpm/group/index', 'BpmUserGroup', 0, b'1', b'1', b'1', '', '2022-01-14 02:14:20', '1', '2024-03-21 23:55:29', b'0');
INSERT INTO `system_menu` VALUES (1210, '用户组查询', 'bpm:user-group:query', 3, 1, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1211, '用户组创建', 'bpm:user-group:create', 3, 2, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1212, '用户组更新', 'bpm:user-group:update', 3, 3, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1213, '用户组删除', 'bpm:user-group:delete', 3, 4, 1209, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-01-14 02:14:20', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1215, '流程定义查询', 'bpm:process-definition:query', 3, 10, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:21:43', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1216, '流程任务分配规则查询', 'bpm:task-assign-rule:query', 3, 20, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:26:53', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1217, '流程任务分配规则创建', 'bpm:task-assign-rule:create', 3, 21, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:28:15', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1218, '流程任务分配规则更新', 'bpm:task-assign-rule:update', 3, 22, 1193, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:28:41', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1219, '流程实例的创建', 'bpm:process-instance:create', 3, 2, 1201, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:36:15', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1220, '流程实例的取消', 'bpm:process-instance:cancel', 3, 3, 1201, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:36:33', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1221, '流程任务的查询', 'bpm:task:query', 3, 1, 1207, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:38:52', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1222, '流程任务的更新', 'bpm:task:update', 3, 2, 1207, '', '', '', NULL, 0, b'1', b'1', b'1', '1', '2022-01-23 00:39:24', '1', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1237, '文件配置', '', 2, 0, 1243, 'file-config', 'fa-solid:file-signature', 'infra/fileConfig/index', 'InfraFileConfig', 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '1', '2024-02-29 08:52:54', b'0');
INSERT INTO `system_menu` VALUES (1238, '文件配置查询', 'infra:file-config:query', 3, 1, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1239, '文件配置创建', 'infra:file-config:create', 3, 2, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1240, '文件配置更新', 'infra:file-config:update', 3, 3, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1241, '文件配置删除', 'infra:file-config:delete', 3, 4, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1242, '文件配置导出', 'infra:file-config:export', 3, 5, 1237, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-03-15 14:35:28', '', '2022-04-20 17:03:10', b'0');
INSERT INTO `system_menu` VALUES (1243, '文件管理', '', 2, 6, 2, 'file', 'ep:files', NULL, '', 0, b'1', b'1', b'1', '1', '2022-03-16 23:47:40', '1', '2024-04-23 00:02:11', b'0');
INSERT INTO `system_menu` VALUES (1261, 'OAuth 2.0', '', 2, 10, 1, 'oauth2', 'fa:dashcube', NULL, NULL, 0, b'1', b'1', b'1', '1', '2022-05-09 23:38:17', '1', '2024-02-29 01:12:08', b'0');
INSERT INTO `system_menu` VALUES (1263, '应用管理', '', 2, 0, 1261, 'oauth2/application', 'fa:hdd-o', 'system/oauth2/client/index', 'SystemOAuth2Client', 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2024-02-29 01:13:14', b'0');
INSERT INTO `system_menu` VALUES (1264, '客户端查询', 'system:oauth2-client:query', 3, 1, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:06', b'0');
INSERT INTO `system_menu` VALUES (1265, '客户端创建', 'system:oauth2-client:create', 3, 2, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:23', b'0');
INSERT INTO `system_menu` VALUES (1266, '客户端更新', 'system:oauth2-client:update', 3, 3, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:28', b'0');
INSERT INTO `system_menu` VALUES (1267, '客户端删除', 'system:oauth2-client:delete', 3, 4, 1263, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2022-05-10 16:26:33', '1', '2022-05-11 00:31:33', b'0');
INSERT INTO `system_menu` VALUES (2083, '地区管理', '', 2, 14, 1, 'area', 'fa:map-marker', 'system/area/index', 'SystemArea', 0, b'1', b'1', b'1', '1', '2022-12-23 17:35:05', '1', '2024-02-29 08:50:28', b'0');
INSERT INTO `system_menu` VALUES (2472, '主子表(内嵌)', '', 2, 12, 1070, 'demo03-inner', 'fa:power-off', 'infra/demo/demo03/inner/index', 'Demo03StudentInner', 0, b'1', b'1', b'1', '', '2023-11-13 04:39:51', '1', '2023-11-16 23:53:46', b'0');
INSERT INTO `system_menu` VALUES (2478, '单表(增删改查)', '', 2, 1, 1070, 'demo01-contact', 'ep:bicycle', 'infra/demo/demo01/index', 'Demo01Contact', 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '1', '2023-11-16 20:34:40', b'0');
INSERT INTO `system_menu` VALUES (2479, '示例联系人查询', 'infra:demo01-contact:query', 3, 1, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
INSERT INTO `system_menu` VALUES (2480, '示例联系人创建', 'infra:demo01-contact:create', 3, 2, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
INSERT INTO `system_menu` VALUES (2481, '示例联系人更新', 'infra:demo01-contact:update', 3, 3, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
INSERT INTO `system_menu` VALUES (2482, '示例联系人删除', 'infra:demo01-contact:delete', 3, 4, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
INSERT INTO `system_menu` VALUES (2483, '示例联系人导出', 'infra:demo01-contact:export', 3, 5, 2478, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-15 14:42:30', '', '2023-11-15 14:42:30', b'0');
INSERT INTO `system_menu` VALUES (2484, '树表(增删改查)', '', 2, 2, 1070, 'demo02-category', 'fa:tree', 'infra/demo/demo02/index', 'Demo02Category', 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '1', '2023-11-16 20:35:01', b'0');
INSERT INTO `system_menu` VALUES (2485, '示例分类查询', 'infra:demo02-category:query', 3, 1, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
INSERT INTO `system_menu` VALUES (2486, '示例分类创建', 'infra:demo02-category:create', 3, 2, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
INSERT INTO `system_menu` VALUES (2487, '示例分类更新', 'infra:demo02-category:update', 3, 3, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
INSERT INTO `system_menu` VALUES (2488, '示例分类删除', 'infra:demo02-category:delete', 3, 4, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
INSERT INTO `system_menu` VALUES (2489, '示例分类导出', 'infra:demo02-category:export', 3, 5, 2484, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:18:27', '', '2023-11-16 12:18:27', b'0');
INSERT INTO `system_menu` VALUES (2490, '主子表(标准)', '', 2, 10, 1070, 'demo03-normal', 'fa:battery-3', 'infra/demo/demo03/normal/index', 'Demo03StudentNormal', 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '1', '2023-11-16 23:10:03', b'0');
INSERT INTO `system_menu` VALUES (2491, '学生查询', 'infra:demo03-student:query', 3, 1, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
INSERT INTO `system_menu` VALUES (2492, '学生创建', 'infra:demo03-student:create', 3, 2, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
INSERT INTO `system_menu` VALUES (2493, '学生更新', 'infra:demo03-student:update', 3, 3, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
INSERT INTO `system_menu` VALUES (2494, '学生删除', 'infra:demo03-student:delete', 3, 4, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
INSERT INTO `system_menu` VALUES (2495, '学生导出', 'infra:demo03-student:export', 3, 5, 2490, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-16 12:53:37', '', '2023-11-16 12:53:37', b'0');
INSERT INTO `system_menu` VALUES (2497, '主子表ERP', '', 2, 11, 1070, 'demo03-erp', 'ep:calendar', 'infra/demo/demo03/erp/index', 'Demo03StudentERP', 0, b'1', b'1', b'1', '', '2023-11-16 15:50:59', '1', '2023-11-17 13:19:56', b'0');
INSERT INTO `system_menu` VALUES (2525, 'WebSocket', '', 2, 5, 2, 'websocket', 'ep:connection', 'infra/webSocket/index', 'InfraWebSocket', 0, b'1', b'1', b'1', '1', '2023-11-23 19:41:55', '1', '2024-04-23 00:02:00', b'0');
INSERT INTO `system_menu` VALUES (2713, '抄送我的', 'bpm:process-instance-cc:query', 2, 30, 1200, 'copy', 'ep:copy-document', 'bpm/task/copy/index', 'BpmProcessInstanceCopy', 0, b'1', b'1', b'1', '1', '2024-03-17 21:50:23', '1', '2024-04-24 19:55:12', b'0');
INSERT INTO `system_menu` VALUES (2714, '流程分类', '', 2, 3, 1186, 'category', 'fa:object-ungroup', 'bpm/category/index', 'BpmCategory', 0, b'1', b'1', b'1', '', '2024-03-08 02:00:51', '1', '2024-03-21 23:51:18', b'0');
INSERT INTO `system_menu` VALUES (2715, '分类查询', 'bpm:category:query', 3, 1, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:25', b'0');
INSERT INTO `system_menu` VALUES (2716, '分类创建', 'bpm:category:create', 3, 2, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:31', b'0');
INSERT INTO `system_menu` VALUES (2717, '分类更新', 'bpm:category:update', 3, 3, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:35', b'0');
INSERT INTO `system_menu` VALUES (2718, '分类删除', 'bpm:category:delete', 3, 4, 2714, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-03-08 02:00:51', '1', '2024-03-19 14:36:41', b'0');
INSERT INTO `system_menu` VALUES (2720, '发起流程', '', 2, 0, 1200, 'create', 'fa-solid:grin-stars', 'bpm/processInstance/create/index', 'BpmProcessInstanceCreate', 0, b'1', b'0', b'1', '1', '2024-03-19 19:46:05', '1', '2024-03-23 19:03:42', b'0');
INSERT INTO `system_menu` VALUES (2721, '流程实例', '', 2, 10, 1186, 'process-instance/manager', 'fa:square', 'bpm/processInstance/manager/index', 'BpmProcessInstanceManager', 0, b'1', b'1', b'1', '1', '2024-03-21 23:57:30', '1', '2024-03-21 23:57:30', b'0');
INSERT INTO `system_menu` VALUES (2722, '流程实例的查询(管理员)', 'bpm:process-instance:manager-query', 3, 1, 2721, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-03-22 08:18:27', '1', '2024-03-22 08:19:05', b'0');
INSERT INTO `system_menu` VALUES (2723, '流程实例的取消(管理员)', 'bpm:process-instance:cancel-by-admin', 3, 2, 2721, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-03-22 08:19:25', '1', '2024-03-22 08:19:25', b'0');
INSERT INTO `system_menu` VALUES (2724, '流程任务', '', 2, 11, 1186, 'process-tasnk', 'ep:collection-tag', 'bpm/task/manager/index', 'BpmManagerTask', 0, b'1', b'1', b'1', '1', '2024-03-22 08:43:22', '1', '2024-03-22 08:43:27', b'0');
INSERT INTO `system_menu` VALUES (2725, '流程任务的查询(管理员)', 'bpm:task:mananger-query', 3, 1, 2724, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-03-22 08:43:49', '1', '2024-03-22 08:43:49', b'0');
INSERT INTO `system_menu` VALUES (2726, '流程监听器', '', 2, 5, 1186, 'process-listener', 'fa:assistive-listening-systems', 'bpm/processListener/index', 'BpmProcessListener', 0, b'1', b'1', b'1', '', '2024-03-09 16:05:34', '1', '2024-03-23 13:13:38', b'0');
INSERT INTO `system_menu` VALUES (2727, '流程监听器查询', 'bpm:process-listener:query', 3, 1, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0');
INSERT INTO `system_menu` VALUES (2728, '流程监听器创建', 'bpm:process-listener:create', 3, 2, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0');
INSERT INTO `system_menu` VALUES (2729, '流程监听器更新', 'bpm:process-listener:update', 3, 3, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0');
INSERT INTO `system_menu` VALUES (2730, '流程监听器删除', 'bpm:process-listener:delete', 3, 4, 2726, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 16:05:34', '', '2024-03-09 16:05:34', b'0');
INSERT INTO `system_menu` VALUES (2731, '流程表达式', '', 2, 6, 1186, 'process-expression', 'fa:wpexplorer', 'bpm/processExpression/index', 'BpmProcessExpression', 0, b'1', b'1', b'1', '', '2024-03-09 22:35:08', '1', '2024-03-23 19:43:05', b'0');
INSERT INTO `system_menu` VALUES (2732, '流程表达式查询', 'bpm:process-expression:query', 3, 1, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0');
INSERT INTO `system_menu` VALUES (2733, '流程表达式创建', 'bpm:process-expression:create', 3, 2, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0');
INSERT INTO `system_menu` VALUES (2734, '流程表达式更新', 'bpm:process-expression:update', 3, 3, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0');
INSERT INTO `system_menu` VALUES (2735, '流程表达式删除', 'bpm:process-expression:delete', 3, 4, 2731, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-03-09 22:35:08', '', '2024-03-09 22:35:08', b'0');
INSERT INTO `system_menu` VALUES (2740, '监控中心', '', 1, 10, 2, 'monitors', 'ep:monitor', '', '', 0, b'1', b'1', b'1', '1', '2024-04-23 00:04:44', '1', '2024-04-23 00:04:44', b'0');
INSERT INTO `system_menu` VALUES (2913, '流程清理', 'bpm:model:clean', 3, 7, 1193, '', '', '', '', 0, b'1', b'1', b'1', '1', '2025-01-17 19:32:06', '1', '2025-01-17 19:32:06', b'0');
INSERT INTO `system_menu` VALUES (900000, '系统管理', '', 1, 10, 0, '/system', 'ep:tools', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900001, '用户管理', 'system:user:list', 2, 1, 900000, 'user', 'ep:avatar', 'system/user/index', 'SystemUser', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900002, '角色管理', '', 2, 2, 900000, 'role', 'ep:user', 'system/role/index', 'SystemRole', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900003, '菜单管理', '', 2, 3, 900000, 'menu', 'ep:menu', 'system/menu/index', 'SystemMenu', 0, b'1', b'1', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900004, '模板参考', '', 1, 900, 0, '/template-reference', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900005, '异常页', '', 1, 7, 900004, '/exception', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900006, '403', '', 2, 0, 900005, '/exception/403', 'mdi:file-document-multiple-outline', 'exception/403/index', 'Exception403', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900007, '404', '', 2, 0, 900005, '/exception/404', 'mdi:file-document-multiple-outline', 'exception/404/index', 'Exception404', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900008, '500', '', 2, 0, 900005, '/exception/500', 'mdi:file-document-multiple-outline', 'exception/500/index', 'Exception500', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900009, '文档', '', 1, 2, 900004, '/document', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900010, 'Ant Design Vue文档', '', 2, 7, 900009, '/document/antd', 'mdi:file-document-multiple-outline', 'document/antd/index', 'DocumentAntd', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900011, 'Naive UI文档', '', 2, 6, 900009, '/document/naive', 'mdi:file-document-multiple-outline', 'document/naive/index', 'DocumentNaive', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900012, 'Element Plus文档', '', 2, 7, 900009, '/document/element-plus', 'mdi:file-document-multiple-outline', 'document/element-plus/index', 'DocumentElementPlus', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900013, 'Alova文档', '', 2, 8, 900009, '/document/alova', 'mdi:file-document-multiple-outline', 'document/alova/index', 'DocumentAlova', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900014, 'UnoCSS文档', '', 2, 5, 900009, '/document/unocss', 'mdi:file-document-multiple-outline', 'document/unocss/index', 'DocumentUnocss', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900015, 'Vite文档', '', 2, 4, 900009, '/document/vite', 'mdi:file-document-multiple-outline', 'document/vite/index', 'DocumentVite', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900016, 'Vue文档', '', 2, 3, 900009, '/document/vue', 'mdi:file-document-multiple-outline', 'document/vue/index', 'DocumentVue', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900017, '关于', '', 2, 10, 900004, '/about', 'mdi:file-document-multiple-outline', 'about/index', 'About', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900018, 'alova示例', '', 1, 7, 900004, '/alova', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900019, 'alova请求', '', 2, 1, 900018, '/alova/request', 'mdi:file-document-multiple-outline', 'alova/request/index', 'AlovaRequest', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900020, '场景化请求', '', 2, 3, 900018, '/alova/scenes', 'mdi:file-document-multiple-outline', 'alova/scenes/index', 'AlovaScenes', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900021, '用户列表', '', 2, 2, 900018, '/alova/user', 'mdi:file-document-multiple-outline', 'alova/user/index', 'AlovaUser', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900022, '系统功能', '', 1, 6, 900004, '/function', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900023, '请求', '', 2, 3, 900022, '/function/request', 'mdi:file-document-multiple-outline', 'function/request/index', 'FunctionRequest', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900024, '超级管理员可见', '', 2, 5, 900022, '/function/super-page', 'mdi:file-document-multiple-outline', 'function/super-page/index', 'FunctionSuperPage', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900025, '标签页', '', 2, 1, 900022, '/function/tab', 'mdi:file-document-multiple-outline', 'function/tab/index', 'FunctionTab', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900026, '切换权限', '', 2, 4, 900022, '/function/toggle-auth', 'mdi:file-document-multiple-outline', 'function/toggle-auth/index', 'FunctionToggleAuth', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900027, '首页', '', 2, 1, 900004, '/home', 'mdi:file-document-multiple-outline', 'home/index', 'Home', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900028, '多级菜单', '', 1, 8, 900004, '/multi-menu', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900029, '菜单一', '', 1, 1, 900028, '/multi-menu/first', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900030, '菜单一子菜单', '', 2, 0, 900029, '/multi-menu/first/child', 'mdi:file-document-multiple-outline', 'multi-menu/first/child/index', 'MultiMenuFirstChild', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900031, '菜单二', '', 1, 2, 900028, '/multi-menu/second', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900032, '菜单二子菜单', '', 1, 0, 900031, '/multi-menu/second/child', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900033, '菜单二子菜单首页', '', 2, 0, 900032, '/multi-menu/second/child/home', 'mdi:file-document-multiple-outline', 'multi-menu/second/child/home/index', 'MultiMenuSecondChildHome', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:02', 'admin', '2026-03-18 13:42:02', b'0');
INSERT INTO `system_menu` VALUES (900034, '插件示例', '', 1, 7, 900004, '/plugin', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900035, '条形码', '', 2, 0, 900034, '/plugin/barcode', 'mdi:file-document-multiple-outline', 'plugin/barcode/index', 'PluginBarcode', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900036, '图表', '', 1, 0, 900034, '/plugin/charts', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900037, 'AntV', '', 2, 0, 900036, '/plugin/charts/antv', 'mdi:file-document-multiple-outline', 'plugin/charts/antv/index', 'PluginChartsAntv', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900038, 'ECharts', '', 2, 0, 900036, '/plugin/charts/echarts', 'mdi:file-document-multiple-outline', 'plugin/charts/echarts/index', 'PluginChartsEcharts', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900039, 'VChart', '', 2, 0, 900036, '/plugin/charts/vchart', 'mdi:file-document-multiple-outline', 'plugin/charts/vchart/index', 'PluginChartsVchart', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900040, '剪贴板', '', 2, 0, 900034, '/plugin/copy', 'mdi:file-document-multiple-outline', 'plugin/copy/index', 'PluginCopy', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900041, '编辑器', '', 1, 0, 900034, '/plugin/editor', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900042, 'MD 编辑器', '', 2, 0, 900041, '/plugin/editor/markdown', 'mdi:file-document-multiple-outline', 'plugin/editor/markdown/index', 'PluginEditorMarkdown', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900043, '富文本编辑器', '', 2, 0, 900041, '/plugin/editor/quill', 'mdi:file-document-multiple-outline', 'plugin/editor/quill/index', 'PluginEditorQuill', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900044, 'Excel', '', 2, 0, 900034, '/plugin/excel', 'mdi:file-document-multiple-outline', 'plugin/excel/index', 'PluginExcel', 0, b'1', b'1', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900045, '甘特图', '', 1, 0, 900034, '/plugin/gantt', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900046, 'dhtmlxGantt', '', 2, 0, 900045, '/plugin/gantt/dhtmlx', 'mdi:file-document-multiple-outline', 'plugin/gantt/dhtmlx/index', 'PluginGanttDhtmlx', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900047, 'VTableGantt', '', 2, 0, 900045, '/plugin/gantt/vtable', 'mdi:file-document-multiple-outline', 'plugin/gantt/vtable/index', 'PluginGanttVtable', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900048, '图标', '', 2, 0, 900034, '/plugin/icon', 'mdi:file-document-multiple-outline', 'plugin/icon/index', 'PluginIcon', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900049, '地图', '', 2, 0, 900034, '/plugin/map', 'mdi:file-document-multiple-outline', 'plugin/map/index', 'PluginMap', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900050, 'PDF 预览', '', 2, 0, 900034, '/plugin/pdf', 'mdi:file-document-multiple-outline', 'plugin/pdf/index', 'PluginPdf', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900051, '拼音', '', 2, 0, 900034, '/plugin/pinyin', 'mdi:file-document-multiple-outline', 'plugin/pinyin/index', 'PluginPinyin', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900052, '打印', '', 2, 0, 900034, '/plugin/print', 'mdi:file-document-multiple-outline', 'plugin/print/index', 'PluginPrint', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900053, 'Swiper', '', 2, 0, 900034, '/plugin/swiper', 'mdi:file-document-multiple-outline', 'plugin/swiper/index', 'PluginSwiper', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900054, '表格', '', 1, 0, 900034, '/plugin/tables', 'mdi:file-document-multiple-outline', NULL, NULL, 0, b'1', b'0', b'1', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900055, 'VTable', '', 2, 0, 900054, '/plugin/tables/vtable', 'mdi:file-document-multiple-outline', 'plugin/tables/vtable/index', 'PluginTablesVtable', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900056, '打字机', '', 2, 0, 900034, '/plugin/typeit', 'mdi:file-document-multiple-outline', 'plugin/typeit/index', 'PluginTypeit', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_menu` VALUES (900057, '视频', '', 2, 0, 900034, '/plugin/video', 'mdi:file-document-multiple-outline', 'plugin/video/index', 'PluginVideo', 0, b'1', b'0', b'0', 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
-- ----------------------------
-- Table structure for system_post
-- ----------------------------
DROP TABLE IF EXISTS `system_post`;
CREATE TABLE `system_post` (
`id` bigint NOT NULL COMMENT '岗位ID',
`code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '岗位编码',
`post_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '岗位类型management/technical/business',
`level_rank` int NULL DEFAULT NULL COMMENT '岗位等级排序,越大级别越高',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '岗位名称',
`sort` int NOT NULL COMMENT '显示顺序',
`status` tinyint NOT NULL COMMENT '状态0正常 1停用',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_system_post_type`(`post_type` ASC) USING BTREE,
INDEX `idx_system_post_level_rank`(`level_rank` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '岗位信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_post
-- ----------------------------
INSERT INTO `system_post` VALUES (2, 'se', NULL, NULL, '项目经理', 2, 0, '', 'admin', '2021-01-05 17:03:48', '1', '2023-11-15 09:18:20', b'0');
-- ----------------------------
-- Table structure for system_role
-- ----------------------------
DROP TABLE IF EXISTS `system_role`;
CREATE TABLE `system_role` (
`id` bigint NOT NULL COMMENT '角色ID',
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称',
`code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色权限字符串',
`sort` int NOT NULL COMMENT '显示顺序',
`data_scope` tinyint NOT NULL DEFAULT 1 COMMENT '数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限',
`data_scope_dept_ids` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '数据范围(指定部门数组)',
`status` tinyint NOT NULL COMMENT '角色状态0正常 1停用',
`type` tinyint NOT NULL COMMENT '角色类型',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_role
-- ----------------------------
INSERT INTO `system_role` VALUES (1, '超级管理员', 'super_admin', 1, 1, '', 0, 1, '超级管理员', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:21', b'0');
INSERT INTO `system_role` VALUES (2, '普通角色', 'common', 2, 2, '', 0, 1, '普通角色', 'admin', '2021-01-05 17:03:48', '', '2022-02-22 05:08:20', b'0');
-- ----------------------------
-- Table structure for system_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `system_role_menu`;
CREATE TABLE `system_role_menu` (
`id` bigint NOT NULL COMMENT '自增编号',
`role_id` bigint NOT NULL COMMENT '角色ID',
`menu_id` bigint NOT NULL COMMENT '菜单ID',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_role_menu
-- ----------------------------
INSERT INTO `system_role_menu` VALUES (434, 2, 1, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (477, 2, 100, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (478, 2, 101, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (479, 2, 102, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (481, 2, 103, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (483, 2, 104, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (485, 2, 105, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (490, 2, 108, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (492, 2, 109, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (541, 2, 500, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (543, 2, 501, '1', '2022-02-22 13:09:12', '1', '2022-02-22 13:09:12', b'0');
INSERT INTO `system_role_menu` VALUES (675, 2, 2, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (689, 2, 1077, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (690, 2, 1078, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (692, 2, 1083, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (693, 2, 1084, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (699, 2, 1090, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (703, 2, 106, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (705, 2, 111, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (706, 2, 112, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (707, 2, 113, '1', '2022-02-22 13:16:57', '1', '2022-02-22 13:16:57', b'0');
INSERT INTO `system_role_menu` VALUES (1991, 2, 1024, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1992, 2, 1025, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1993, 2, 1026, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1994, 2, 1027, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1995, 2, 1028, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1996, 2, 1029, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1997, 2, 1030, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1998, 2, 1031, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (1999, 2, 1032, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2000, 2, 1033, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2001, 2, 1034, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2002, 2, 1035, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2007, 2, 1040, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2008, 2, 1042, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2009, 2, 1043, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2010, 2, 1045, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2011, 2, 1046, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2012, 2, 1048, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2021, 2, 2083, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2024, 2, 1063, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2025, 2, 1064, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2026, 2, 1065, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2027, 2, 1066, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2028, 2, 1067, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2029, 2, 1070, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2036, 2, 1082, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2037, 2, 1085, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2038, 2, 1086, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2040, 2, 1088, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2041, 2, 1089, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2042, 2, 1091, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2043, 2, 1092, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2072, 2, 114, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2076, 2, 116, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2103, 2, 1237, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2104, 2, 1238, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2105, 2, 1239, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2106, 2, 1240, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2107, 2, 1241, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2108, 2, 1242, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2109, 2, 1243, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2123, 2, 1261, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2124, 2, 1263, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2125, 2, 1264, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2126, 2, 1265, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2127, 2, 1266, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2128, 2, 1267, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2129, 2, 1001, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2130, 2, 1002, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2131, 2, 1003, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2132, 2, 1004, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2133, 2, 1005, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2134, 2, 1006, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2135, 2, 1007, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2136, 2, 1008, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2137, 2, 1009, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2138, 2, 1010, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2139, 2, 1011, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2140, 2, 1012, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2141, 2, 1013, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2143, 2, 1015, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2145, 2, 1017, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2146, 2, 1018, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2147, 2, 1019, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2148, 2, 1020, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2149, 2, 1021, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2150, 2, 1022, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (2151, 2, 1023, '1', '2023-01-25 08:42:52', '1', '2023-01-25 08:42:52', b'0');
INSERT INTO `system_role_menu` VALUES (5780, 2, 2740, '1', '2024-07-07 20:39:38', '1', '2024-07-07 20:39:38', b'0');
INSERT INTO `system_role_menu` VALUES (990000, 1, 900000, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990001, 1, 900001, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990002, 1, 900002, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990003, 1, 900003, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990004, 1, 900004, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990005, 1, 900005, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990006, 1, 900006, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990007, 1, 900007, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990008, 1, 900008, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990009, 1, 900009, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990010, 1, 900010, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990011, 1, 900011, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990012, 1, 900012, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990013, 1, 900013, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990014, 1, 900014, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990015, 1, 900015, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990016, 1, 900016, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990017, 1, 900017, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990018, 1, 900018, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990019, 1, 900019, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990020, 1, 900020, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990021, 1, 900021, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990022, 1, 900022, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990023, 1, 900023, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990024, 1, 900024, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990025, 1, 900025, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990026, 1, 900026, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990027, 1, 900027, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990028, 1, 900028, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990029, 1, 900029, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990030, 1, 900030, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990031, 1, 900031, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990032, 1, 900032, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990033, 1, 900033, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990034, 1, 900034, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990035, 1, 900035, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990036, 1, 900036, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990037, 1, 900037, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990038, 1, 900038, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990039, 1, 900039, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990040, 1, 900040, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990041, 1, 900041, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990042, 1, 900042, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990043, 1, 900043, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990044, 1, 900044, 'admin', '2026-03-18 13:42:03', 'admin', '2026-03-18 13:42:03', b'0');
INSERT INTO `system_role_menu` VALUES (990045, 1, 900045, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990046, 1, 900046, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990047, 1, 900047, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990048, 1, 900048, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990049, 1, 900049, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990050, 1, 900050, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990051, 1, 900051, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990052, 1, 900052, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990053, 1, 900053, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990054, 1, 900054, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990055, 1, 900055, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990056, 1, 900056, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
INSERT INTO `system_role_menu` VALUES (990057, 1, 900057, 'admin', '2026-03-18 13:42:04', 'admin', '2026-03-18 13:42:04', b'0');
-- ----------------------------
-- Table structure for system_user_post
-- ----------------------------
DROP TABLE IF EXISTS `system_user_post`;
CREATE TABLE `system_user_post` (
`id` bigint NOT NULL COMMENT 'id',
`user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户ID',
`post_id` bigint NOT NULL DEFAULT 0 COMMENT '岗位ID',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户岗位表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_user_post
-- ----------------------------
INSERT INTO `system_user_post` VALUES (112, 1, 1, 'admin', '2022-05-02 07:25:24', 'admin', '2022-05-02 07:25:24', b'0');
INSERT INTO `system_user_post` VALUES (125, 1, 2, '1', '2024-07-13 22:31:39', '1', '2024-07-13 22:31:39', b'0');
-- ----------------------------
-- Table structure for system_user_role
-- ----------------------------
DROP TABLE IF EXISTS `system_user_role`;
CREATE TABLE `system_user_role` (
`id` bigint NOT NULL COMMENT '自增编号',
`user_id` bigint NOT NULL COMMENT '用户ID',
`role_id` bigint NOT NULL COMMENT '角色ID',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_user_role
-- ----------------------------
INSERT INTO `system_user_role` VALUES (1, 1, 1, '', '2022-01-11 13:19:45', '', '2022-05-12 12:35:17', b'0');
INSERT INTO `system_user_role` VALUES (18, 1, 2, '1', '2022-05-12 20:39:29', '1', '2022-05-12 20:39:29', b'0');
-- ----------------------------
-- Table structure for system_user_visibility_config
-- ----------------------------
DROP TABLE IF EXISTS `system_user_visibility_config`;
CREATE TABLE `system_user_visibility_config` (
`id` bigint NOT NULL COMMENT '主键ID',
`user_id` bigint NOT NULL COMMENT '用户ID',
`visibility_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '可见范围类型all/directions/projects',
`visible_direction_ids` json NULL COMMENT '补充可见方向ID集合',
`visible_project_ids` json NULL COMMENT '补充可见项目ID集合',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_visibility_user`(`user_id` ASC) USING BTREE,
INDEX `idx_visibility_type`(`visibility_type` ASC) USING BTREE,
CONSTRAINT `chk_visibility_type` CHECK (`visibility_type` in (_utf8mb4'all',_utf8mb4'directions',_utf8mb4'projects'))
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户补充可见范围配置表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_user_visibility_config
-- ----------------------------
-- ----------------------------
-- Table structure for system_users
-- ----------------------------
DROP TABLE IF EXISTS `system_users`;
CREATE TABLE `system_users` (
`id` bigint NOT NULL COMMENT '用户ID',
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户账号',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '密码',
`nickname` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户昵称',
`remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注',
`dept_id` bigint NULL DEFAULT NULL COMMENT '部门ID',
`position_id` bigint NULL DEFAULT NULL COMMENT '主岗位ID',
`resigned_at` datetime NULL DEFAULT NULL COMMENT '离职时间',
`post_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '岗位编号数组',
`email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '用户邮箱',
`mobile` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '手机号码',
`sex` tinyint NULL DEFAULT 0 COMMENT '用户性别',
`avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '头像地址',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '帐号状态0正常 1停用',
`login_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_system_users_dept_id`(`dept_id` ASC) USING BTREE,
INDEX `idx_system_users_position_id`(`position_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of system_users
-- ----------------------------
INSERT INTO `system_users` VALUES (1, 'admin', '$2a$04$KljJDa/LK7QfDm0lF5OhuePhlPfjRH3tB2Wu351Uidz.oQGJXevPi', '灿能源码', '管理员', 103, 2, NULL, '[1,2]', '11aoteman@126.com', '18818260272', 2, 'http://test.rdms.iocoder.cn/20250921/avatar_1758423875594.png', 0, '192.168.2.125', '2026-03-18 16:18:02', 'admin', '2021-01-05 17:03:47', 'system', '2026-03-19 13:59:04', b'0');
-- ----------------------------
-- system_menu 路由扩展字段
-- ----------------------------
ALTER TABLE `system_menu`
ADD COLUMN `route_kind` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '路由类型dir/view/single/iframe/external/redirect' AFTER `component_name`,
ADD COLUMN `route_props_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '路由 props JSON' AFTER `route_kind`;
UPDATE `system_menu`
SET `component` = 'view.iframe-page',
`component_name` = 'document_antd',
`route_kind` = 'iframe',
`route_props_json` = '{\"url\":\"https://www.antdv.com/\"}'
WHERE `id` = 900010;
UPDATE `system_menu`
SET `component` = 'view.iframe-page',
`component_name` = 'document_naive',
`route_kind` = 'iframe',
`route_props_json` = '{\"url\":\"https://www.naiveui.com/\"}'
WHERE `id` = 900011;
UPDATE `system_menu`
SET `component` = 'view.iframe-page',
`component_name` = 'document_element_plus',
`route_kind` = 'iframe',
`route_props_json` = '{\"url\":\"https://cn.element-plus.org/\"}'
WHERE `id` = 900012;
UPDATE `system_menu`
SET `component` = 'view.iframe-page',
`component_name` = 'document_alova',
`route_kind` = 'iframe',
`route_props_json` = '{\"url\":\"https://alova.js.org/\"}'
WHERE `id` = 900013;
UPDATE `system_menu`
SET `component` = 'view.iframe-page',
`component_name` = 'document_unocss',
`route_kind` = 'iframe',
`route_props_json` = '{\"url\":\"https://unocss.dev/\"}'
WHERE `id` = 900014;
UPDATE `system_menu`
SET `component` = 'view.iframe-page',
`component_name` = 'document_vite',
`route_kind` = 'iframe',
`route_props_json` = '{\"url\":\"https://vite.dev/\"}'
WHERE `id` = 900015;
UPDATE `system_menu`
SET `component` = 'view.iframe-page',
`component_name` = 'document_vue',
`route_kind` = 'iframe',
`route_props_json` = '{\"url\":\"https://cn.vuejs.org/\"}'
WHERE `id` = 900016;
INSERT INTO `system_menu` VALUES (1036, '组织负责人查询', 'system:org-leader:query', 3, 5, 103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2026-03-25 00:00:00', 'admin', '2026-03-25 00:00:00', b'0');
INSERT INTO `system_menu` VALUES (1037, '组织负责人新增', 'system:org-leader:create', 3, 6, 103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2026-03-25 00:00:00', 'admin', '2026-03-25 00:00:00', b'0');
INSERT INTO `system_menu` VALUES (1038, '组织负责人修改', 'system:org-leader:update', 3, 7, 103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2026-03-25 00:00:00', 'admin', '2026-03-25 00:00:00', b'0');
INSERT INTO `system_menu` VALUES (1039, '组织负责人删除', 'system:org-leader:delete', 3, 8, 103, '', '#', '', NULL, 0, b'1', b'1', b'1', 'admin', '2026-03-25 00:00:00', 'admin', '2026-03-25 00:00:00', b'0');
SET FOREIGN_KEY_CHECKS = 1;