Kaynağa Gözat

feat: v0.1.0

ye-zhaojia 1 hafta önce
ebeveyn
işleme
58e87bb403
47 değiştirilmiş dosya ile 650 ekleme ve 673 silme
  1. 54 0
      BOOT-INF/classes/application.yml
  2. 52 0
      BOOT-INF/classes/data-dev.sql
  3. 134 0
      BOOT-INF/classes/schema-dev.sql
  4. 19 1
      pom.xml
  5. 52 0
      src/main/java/com/qqflow/engine/common/controller/FileUploadController.java
  6. 0 4
      src/main/java/com/qqflow/engine/common/util/JwtUtils.java
  7. 7 0
      src/main/java/com/qqflow/engine/config/WebConfig.java
  8. 3 5
      src/main/java/com/qqflow/engine/config/security/LoginUser.java
  9. 0 19
      src/main/java/com/qqflow/engine/config/security/PermissionService.java
  10. 2 2
      src/main/java/com/qqflow/engine/domain/flow/assembler/ProcessInstanceAssembler.java
  11. 17 0
      src/main/java/com/qqflow/engine/domain/flow/controller/ApprovalTaskController.java
  12. 3 0
      src/main/java/com/qqflow/engine/domain/flow/dto/ApproveTaskDTO.java
  13. 4 0
      src/main/java/com/qqflow/engine/domain/flow/dto/ProcessInstanceDTO.java
  14. 3 0
      src/main/java/com/qqflow/engine/domain/flow/dto/StartProcessDTO.java
  15. 5 0
      src/main/java/com/qqflow/engine/domain/flow/mapper/ApprovalTaskMapper.java
  16. 4 0
      src/main/java/com/qqflow/engine/domain/flow/po/ProcessInstance.java
  17. 4 0
      src/main/java/com/qqflow/engine/domain/flow/service/ApprovalTaskService.java
  18. 4 0
      src/main/java/com/qqflow/engine/domain/flow/service/FlowEngineService.java
  19. 64 1
      src/main/java/com/qqflow/engine/domain/flow/service/impl/ApprovalTaskServiceImpl.java
  20. 108 12
      src/main/java/com/qqflow/engine/domain/flow/service/impl/FlowEngineServiceImpl.java
  21. 32 1
      src/main/java/com/qqflow/engine/domain/flow/service/impl/ProcessDefinitionServiceImpl.java
  22. 6 1
      src/main/java/com/qqflow/engine/domain/flow/service/impl/ProcessInstanceServiceImpl.java
  23. 0 47
      src/main/java/com/qqflow/engine/domain/system/assembler/MenuAssembler.java
  24. 0 75
      src/main/java/com/qqflow/engine/domain/system/controller/SysMenuController.java
  25. 0 74
      src/main/java/com/qqflow/engine/domain/system/dto/MenuDTO.java
  26. 0 41
      src/main/java/com/qqflow/engine/domain/system/entity/SysMenu.java
  27. 2 2
      src/main/java/com/qqflow/engine/domain/system/entity/SysRole.java
  28. 0 23
      src/main/java/com/qqflow/engine/domain/system/entity/SysRoleMenu.java
  29. 0 22
      src/main/java/com/qqflow/engine/domain/system/mapper/SysMenuMapper.java
  30. 0 7
      src/main/java/com/qqflow/engine/domain/system/mapper/SysRoleMenuMapper.java
  31. 0 2
      src/main/java/com/qqflow/engine/domain/system/mapper/SysUserMapper.java
  32. 0 8
      src/main/java/com/qqflow/engine/domain/system/service/RoleAuthService.java
  33. 0 26
      src/main/java/com/qqflow/engine/domain/system/service/SysMenuService.java
  34. 0 3
      src/main/java/com/qqflow/engine/domain/system/service/SysUserService.java
  35. 0 92
      src/main/java/com/qqflow/engine/domain/system/service/impl/SysMenuServiceImpl.java
  36. 0 9
      src/main/java/com/qqflow/engine/domain/system/service/impl/SysRoleServiceImpl.java
  37. 3 8
      src/main/java/com/qqflow/engine/domain/system/service/impl/SysUserServiceImpl.java
  38. 10 4
      src/main/resources/application-dev.yml
  39. 14 37
      src/main/resources/data-dev.sql
  40. 14 37
      src/main/resources/data-test.sql
  41. 14 0
      src/main/resources/mapper/flow/ApprovalTaskMapper.xml
  42. 0 27
      src/main/resources/mapper/system/SysMenuMapper.xml
  43. 0 11
      src/main/resources/mapper/system/SysUserMapper.xml
  44. 4 21
      src/main/resources/schema-dev.sql
  45. 6 29
      src/main/resources/schema-mysql.sql
  46. 4 21
      src/main/resources/schema-test.sql
  47. 2 1
      src/test/java/com/qqflow/engine/domain/flow/service/FlowEngineServiceTest.java

+ 54 - 0
BOOT-INF/classes/application.yml

@@ -0,0 +1,54 @@
+server:
+  port: 8080
+
+spring:
+  application:
+    name: qqflowengine-backend
+  datasource:
+    url: jdbc:h2:file:./data/devdb;MODE=MySQL;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;CHARSET=UTF-8
+    driver-class-name: org.h2.Driver
+    username: sa
+    password:
+  sql:
+    init:
+      mode: always
+      schema-locations: classpath:schema-dev.sql
+      data-locations: classpath:data-dev.sql
+  redis:
+    host: localhost
+    port: 6379
+    password:
+    database: 0
+    timeout: 10s
+    lettuce:
+      pool:
+        max-active: 8
+        max-idle: 8
+        min-idle: 0
+  jackson:
+    date-format: yyyy-MM-dd HH:mm:ss
+    time-zone: GMT+8
+
+mybatis-plus:
+  mapper-locations: classpath*:/mapper/**/*.xml
+  type-aliases-package: com.qqflow.engine.**.po
+  configuration:
+    map-underscore-to-camel-case: true
+    cache-enabled: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      id-type: auto
+      logic-delete-field: deleted
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+
+jwt:
+  secret: qqflow-engine-secret-key-2024-very-long-and-secure
+  expiration: 86400000
+  header: Authorization
+  prefix: Bearer
+
+logging:
+  level:
+    com.qqflow.engine: debug

+ 52 - 0
BOOT-INF/classes/data-dev.sql

@@ -0,0 +1,52 @@
+MERGE INTO sys_dept (id, dept_name, dept_code, parent_id, sort_order, status) KEY(id) VALUES
+(1, '总裁办', 'CEO', 0, 1, 1),
+(2, '技术部', 'TECH', 0, 2, 1),
+(3, '财务部', 'FIN', 0, 3, 1),
+(4, '人事部', 'HR', 0, 4, 1),
+(5, '前端组', 'FE', 2, 1, 1),
+(6, '后端组', 'BE', 2, 2, 1);
+
+MERGE INTO sys_user (id, username, password, real_name, phone, email, dept_id, employee_type, status) KEY(id) VALUES
+(1, 'admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '超级管理员', '13800138000', 'admin@qqflow.com', 1, 'super_admin', 0),
+(2, 'zhangsan', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '张三', '13800138001', 'zhangsan@qqflow.com', 2, 'common_user', 0),
+(3, 'lisi', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '李四', '13800138002', 'lisi@qqflow.com', 3, 'dept_manager', 0),
+(4, 'wangwu', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '王五', '13800138003', 'wangwu@qqflow.com', 4, 'flow_manager', 0);
+
+MERGE INTO sys_role (id, role_code, role_name, role_scope, parent_id, dept_id, username, password, status) KEY(id) VALUES
+(1, 'super_admin', '超级管理员', 'platform', 0, 1, 'role_super_admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
+(2, 'flow_admin', '流程管理员', 'tenant', 0, 1, 'role_flow_admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
+(3, 'normal_user', '普通用户', 'tenant', 0, 2, 'role_normal_user', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
+(4, 'dept_manager', '部门经理', 'tenant', 3, 3, 'role_dept_manager', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1);
+
+MERGE INTO sys_menu (id, menu_name, menu_type, permission, parent_id, sort_order, component, icon, status) KEY(id) VALUES
+(1, '系统管理', 0, NULL, 0, 1, NULL, 'Setting', 1),
+(2, '用户管理', 1, 'system:user:list', 1, 1, 'system/user/index', 'User', 1),
+(3, '角色管理', 1, 'system:role:list', 1, 2, 'system/role/index', 'Role', 1),
+(4, '流程管理', 0, NULL, 0, 2, NULL, 'Connection', 1),
+(5, '流程设计', 1, 'flow:designer:list', 4, 1, 'flow/designer/index', 'Edit', 1),
+(6, '流程定义', 1, 'flow:definition:list', 4, 2, 'flow/definition/index', 'Document', 1),
+(7, '审批中心', 0, NULL, 0, 3, NULL, 'Stamp', 1),
+(8, '我的待办', 1, 'flow:task:todo', 7, 1, 'flow/task/todo', 'Bell', 1),
+(17, '我的已办', 1, 'flow:task:handled', 7, 2, 'flow/task/handled', 'CircleCheck', 1),
+(9, '我的流程', 1, 'flow:instance:mine', 7, 3, 'flow/instance/mine', 'List', 1),
+(10, '新增用户', 2, 'system:user:create', 2, 1, NULL, NULL, 1),
+(11, '编辑用户', 2, 'system:user:update', 2, 2, NULL, NULL, 1),
+(12, '删除用户', 2, 'system:user:delete', 2, 3, NULL, NULL, 1),
+(13, '新增流程', 2, 'flow:designer:create', 5, 1, NULL, NULL, 1),
+(14, '发布流程', 2, 'flow:designer:publish', 5, 2, NULL, NULL, 1),
+(15, '审批通过', 2, 'flow:task:approve', 8, 1, NULL, NULL, 1),
+(16, '审批拒绝', 2, 'flow:task:reject', 8, 2, NULL, NULL, 1);
+
+MERGE INTO sys_user_role (id, user_id, role_id) KEY(id) VALUES
+(1, 1, 1),
+(2, 2, 4),
+(3, 3, 3),
+(4, 4, 3);
+
+
+MERGE INTO sys_role_menu (id, role_id, menu_id) KEY(id) VALUES
+(1, 1, 1), (2, 1, 2), (3, 1, 3), (4, 1, 4), (5, 1, 5), (6, 1, 6), (7, 1, 7), (8, 1, 8), (9, 1, 9),
+(10, 1, 10), (11, 1, 11), (12, 1, 12), (13, 1, 13), (14, 1, 14), (15, 1, 15), (16, 1, 16), (32, 1, 17),
+(17, 2, 4), (18, 2, 5), (19, 2, 6), (20, 2, 13), (21, 2, 14),
+(22, 3, 7), (23, 3, 8), (24, 3, 9), (25, 3, 15), (26, 3, 16),
+(27, 4, 7), (28, 4, 8), (29, 4, 9), (30, 4, 15), (31, 4, 16);

+ 134 - 0
BOOT-INF/classes/schema-dev.sql

@@ -0,0 +1,134 @@
+CREATE TABLE IF NOT EXISTS sys_user (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    username VARCHAR(50) NOT NULL UNIQUE,
+    password VARCHAR(100) NOT NULL,
+    real_name VARCHAR(50),
+    phone VARCHAR(20),
+    email VARCHAR(100),
+    dept_id BIGINT DEFAULT 0,
+    employee_type VARCHAR(20) DEFAULT 'common_user',
+    status TINYINT DEFAULT 0,
+    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS sys_role (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    role_code VARCHAR(50) NOT NULL UNIQUE,
+    role_name VARCHAR(50) NOT NULL,
+    role_scope VARCHAR(20) DEFAULT 'tenant',
+    parent_id BIGINT DEFAULT 0,
+    dept_id BIGINT DEFAULT 0,
+    username VARCHAR(50),
+    password VARCHAR(100),
+    status TINYINT DEFAULT 1,
+    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS sys_menu (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    menu_name VARCHAR(50) NOT NULL,
+    menu_type TINYINT,
+    permission VARCHAR(100),
+    parent_id BIGINT DEFAULT 0,
+    sort_order INT DEFAULT 0,
+    component VARCHAR(200),
+    icon VARCHAR(50),
+    status TINYINT DEFAULT 1
+);
+
+CREATE TABLE IF NOT EXISTS sys_user_role (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    user_id BIGINT NOT NULL,
+    role_id BIGINT NOT NULL,
+    UNIQUE KEY uk_user_role (user_id, role_id)
+);
+
+CREATE TABLE IF NOT EXISTS sys_role_menu (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    role_id BIGINT NOT NULL,
+    menu_id BIGINT NOT NULL,
+    UNIQUE KEY uk_role_menu (role_id, menu_id)
+);
+
+CREATE TABLE IF NOT EXISTS sys_dept (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    dept_name VARCHAR(50) NOT NULL,
+    dept_code VARCHAR(50),
+    parent_id BIGINT DEFAULT 0,
+    leader_id BIGINT,
+    sort_order INT DEFAULT 0,
+    status TINYINT DEFAULT 1,
+    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS bpm_process_definition (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    process_code VARCHAR(50) NOT NULL UNIQUE,
+    process_name VARCHAR(100) NOT NULL,
+    category VARCHAR(50),
+    form_id BIGINT,
+    model_json TEXT NOT NULL,
+    version INT DEFAULT 1,
+    status TINYINT DEFAULT 0,
+    description VARCHAR(500),
+    create_by BIGINT,
+    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    update_by BIGINT,
+    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    deleted TINYINT DEFAULT 0
+);
+
+CREATE TABLE IF NOT EXISTS bpm_process_instance (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    instance_no VARCHAR(50) NOT NULL UNIQUE,
+    process_definition_id BIGINT NOT NULL,
+    version INT NOT NULL,
+    title VARCHAR(200),
+    applicant_id BIGINT NOT NULL,
+    applicant_dept_id BIGINT,
+    form_data TEXT,
+    current_node_id VARCHAR(50),
+    status TINYINT DEFAULT 0,
+    result TINYINT,
+    start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    end_time TIMESTAMP,
+    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    deleted TINYINT DEFAULT 0
+);
+
+CREATE TABLE IF NOT EXISTS bpm_approval_task (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    instance_id BIGINT NOT NULL,
+    node_id VARCHAR(50) NOT NULL,
+    node_name VARCHAR(100),
+    node_type VARCHAR(20),
+    assignee_id BIGINT,
+    assignee_type VARCHAR(20),
+    task_status TINYINT DEFAULT 0,
+    approval_result TINYINT,
+    approval_comment TEXT,
+    attachment_urls TEXT,
+    timeout_time TIMESTAMP,
+    timeout_action VARCHAR(20),
+    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    handle_time TIMESTAMP,
+    deleted TINYINT DEFAULT 0
+);
+
+CREATE TABLE IF NOT EXISTS bpm_approval_record (
+    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    task_id BIGINT NOT NULL,
+    instance_id BIGINT NOT NULL,
+    node_id VARCHAR(50) NOT NULL,
+    node_name VARCHAR(100),
+    operator_id BIGINT NOT NULL,
+    operator_name VARCHAR(50),
+    action_type VARCHAR(20) NOT NULL,
+    action_result VARCHAR(20),
+    comment TEXT,
+    attachment_urls TEXT,
+    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    deleted TINYINT DEFAULT 0
+);

+ 19 - 1
pom.xml

@@ -89,7 +89,7 @@
         <dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <artifactId>lombok</artifactId>
-            <optional>true</optional>
+            <scope>provided</scope>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <groupId>org.springframework.boot</groupId>
@@ -126,6 +126,24 @@
                     </excludes>
                     </excludes>
                 </configuration>
                 </configuration>
             </plugin>
             </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>17</source>
+                    <target>17</target>
+                    <compilerArgs>
+                        <arg>-proc:full</arg>
+                    </compilerArgs>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>1.18.32</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
         </plugins>
         </plugins>
     </build>
     </build>
 
 

+ 52 - 0
src/main/java/com/qqflow/engine/common/controller/FileUploadController.java

@@ -0,0 +1,52 @@
+package com.qqflow.engine.common.controller;
+
+import com.qqflow.engine.common.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/file")
+@Tag(name = "文件上传")
+public class FileUploadController {
+
+    private static final String UPLOAD_DIR = "uploads";
+
+    @PostMapping("/upload")
+    @Operation(summary = "上传文件")
+    public Result<String> upload(@RequestParam("file") MultipartFile file) {
+        if (file.isEmpty()) {
+            return Result.error("文件不能为空");
+        }
+        try {
+            Path uploadPath = Paths.get(UPLOAD_DIR, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMM")));
+            if (!Files.exists(uploadPath)) {
+                Files.createDirectories(uploadPath);
+            }
+            String originalFilename = file.getOriginalFilename();
+            String ext = originalFilename != null && originalFilename.contains(".")
+                    ? originalFilename.substring(originalFilename.lastIndexOf("."))
+                    : "";
+            String filename = UUID.randomUUID() + ext;
+            Path targetPath = uploadPath.resolve(filename);
+            Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
+            String url = "/uploads/" + uploadPath.getFileName() + "/" + filename;
+            return Result.ok(url);
+        } catch (IOException e) {
+            return Result.error("文件上传失败: " + e.getMessage());
+        }
+    }
+}

+ 0 - 4
src/main/java/com/qqflow/engine/common/util/JwtUtils.java

@@ -11,7 +11,6 @@ import javax.crypto.SecretKey;
 import java.nio.charset.StandardCharsets;
 import java.nio.charset.StandardCharsets;
 import java.util.Date;
 import java.util.Date;
 import java.util.List;
 import java.util.List;
-import java.util.Set;
 import java.util.UUID;
 import java.util.UUID;
 
 
 @Component
 @Component
@@ -39,7 +38,6 @@ public class JwtUtils {
                 .claim("deptId", loginUser.getDeptId())
                 .claim("deptId", loginUser.getDeptId())
                 .claim("employeeType", loginUser.getEmployeeType())
                 .claim("employeeType", loginUser.getEmployeeType())
                 .claim("userType", loginUser.getUserType())
                 .claim("userType", loginUser.getUserType())
-                .claim("permissions", loginUser.getPermissions())
                 .claim("roles", loginUser.getRoles())
                 .claim("roles", loginUser.getRoles())
                 .issuedAt(now)
                 .issuedAt(now)
                 .expiration(expiry)
                 .expiration(expiry)
@@ -73,8 +71,6 @@ public class JwtUtils {
         user.setDeptId(claims.get("deptId", Long.class));
         user.setDeptId(claims.get("deptId", Long.class));
         user.setEmployeeType(claims.get("employeeType", String.class));
         user.setEmployeeType(claims.get("employeeType", String.class));
         user.setUserType(claims.get("userType", String.class));
         user.setUserType(claims.get("userType", String.class));
-        List<String> perms = claims.get("permissions", List.class);
-        user.setPermissions(perms != null ? new java.util.HashSet<>(perms) : null);
         List<String> roles = claims.get("roles", List.class);
         List<String> roles = claims.get("roles", List.class);
         user.setRoles(roles != null ? roles : null);
         user.setRoles(roles != null ? roles : null);
         return user;
         return user;

+ 7 - 0
src/main/java/com/qqflow/engine/config/WebConfig.java

@@ -2,6 +2,7 @@ package com.qqflow.engine.config;
 
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 
 @Configuration
 @Configuration
@@ -16,4 +17,10 @@ public class WebConfig implements WebMvcConfigurer {
                 .allowCredentials(true)
                 .allowCredentials(true)
                 .maxAge(3600);
                 .maxAge(3600);
     }
     }
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/uploads/**")
+                .addResourceLocations("file:uploads/");
+    }
 }
 }

+ 3 - 5
src/main/java/com/qqflow/engine/config/security/LoginUser.java

@@ -8,7 +8,6 @@ import org.springframework.security.core.userdetails.UserDetails;
 
 
 import java.util.Collection;
 import java.util.Collection;
 import java.util.List;
 import java.util.List;
-import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 @Data
 @Data
@@ -20,7 +19,6 @@ public class LoginUser implements UserDetails {
     private Long deptId;
     private Long deptId;
     private String employeeType;
     private String employeeType;
     private String userType; // SYSTEM / ROLE
     private String userType; // SYSTEM / ROLE
-    private Set<String> permissions;
     private List<String> roles;
     private List<String> roles;
 
 
     @JsonIgnore
     @JsonIgnore
@@ -29,8 +27,8 @@ public class LoginUser implements UserDetails {
     @Override
     @Override
     @JsonIgnore
     @JsonIgnore
     public Collection<? extends GrantedAuthority> getAuthorities() {
     public Collection<? extends GrantedAuthority> getAuthorities() {
-        return permissions.stream()
-                .map(SimpleGrantedAuthority::new)
+        return roles.stream()
+                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());
     }
     }
 
 
@@ -59,6 +57,6 @@ public class LoginUser implements UserDetails {
     }
     }
 
 
     public boolean isAdmin() {
     public boolean isAdmin() {
-        return permissions != null && permissions.contains("*:*:*");
+        return roles != null && roles.contains("super_admin");
     }
     }
 }
 }

+ 0 - 19
src/main/java/com/qqflow/engine/config/security/PermissionService.java

@@ -1,19 +0,0 @@
-package com.qqflow.engine.config.security;
-
-import com.qqflow.engine.common.util.SecurityUtils;
-import org.springframework.stereotype.Service;
-
-import java.util.Set;
-
-@Service("ss")
-public class PermissionService {
-
-    public boolean hasPermi(String permission) {
-        LoginUser loginUser = SecurityUtils.getLoginUser();
-        if (loginUser == null) {
-            return false;
-        }
-        Set<String> permissions = loginUser.getPermissions();
-        return permissions != null && (permissions.contains("*:*:*") || permissions.contains(permission));
-    }
-}

+ 2 - 2
src/main/java/com/qqflow/engine/domain/flow/assembler/ProcessInstanceAssembler.java

@@ -11,7 +11,7 @@ public class ProcessInstanceAssembler {
     public ProcessInstance buildNew(String instanceNo, Long processDefinitionId,
     public ProcessInstance buildNew(String instanceNo, Long processDefinitionId,
                                      Integer version, String title, Long applicantId,
                                      Integer version, String title, Long applicantId,
                                      Long applicantDeptId, String formData,
                                      Long applicantDeptId, String formData,
-                                     String currentNodeId, Integer status) {
+                                     String attachmentUrls, Integer status) {
         ProcessInstance po = new ProcessInstance();
         ProcessInstance po = new ProcessInstance();
         po.setInstanceNo(instanceNo);
         po.setInstanceNo(instanceNo);
         po.setProcessDefinitionId(processDefinitionId);
         po.setProcessDefinitionId(processDefinitionId);
@@ -20,7 +20,7 @@ public class ProcessInstanceAssembler {
         po.setApplicantId(applicantId);
         po.setApplicantId(applicantId);
         po.setApplicantDeptId(applicantDeptId);
         po.setApplicantDeptId(applicantDeptId);
         po.setFormData(formData);
         po.setFormData(formData);
-        po.setCurrentNodeId(currentNodeId);
+        po.setAttachmentUrls(attachmentUrls);
         po.setStatus(status);
         po.setStatus(status);
         po.setStartTime(LocalDateTime.now());
         po.setStartTime(LocalDateTime.now());
         return po;
         return po;

+ 17 - 0
src/main/java/com/qqflow/engine/domain/flow/controller/ApprovalTaskController.java

@@ -104,4 +104,21 @@ public class ApprovalTaskController {
         String assigneeType = SecurityUtils.getLoginUser().getUserType();
         String assigneeType = SecurityUtils.getLoginUser().getUserType();
         return Result.ok(this.approvalTaskService.todoCount(assigneeId, assigneeType));
         return Result.ok(this.approvalTaskService.todoCount(assigneeId, assigneeType));
     }
     }
+
+    @GetMapping("/cc")
+    @Operation(summary = "我的抄送列表")
+    public Result<PageResult<ApprovalTaskDTO>> ccList(
+            @RequestParam(defaultValue = "1") Integer pageNum,
+            @RequestParam(defaultValue = "10") Integer pageSize,
+            @RequestParam(required = false) String processName) {
+        Long assigneeId = SecurityUtils.getUserId();
+        return Result.ok(this.approvalTaskService.ccList(assigneeId, processName, pageNum, pageSize));
+    }
+
+    @PostMapping("/cc/{taskId}/read")
+    @Operation(summary = "标记抄送已读")
+    public Result<Void> readCc(@PathVariable Long taskId) {
+        this.approvalTaskService.readCc(taskId);
+        return Result.ok();
+    }
 }
 }

+ 3 - 0
src/main/java/com/qqflow/engine/domain/flow/dto/ApproveTaskDTO.java

@@ -16,4 +16,7 @@ public class ApproveTaskDTO {
 
 
     @Schema(description = "附件URL")
     @Schema(description = "附件URL")
     private String attachmentUrls;
     private String attachmentUrls;
+
+    @Schema(description = "回退目标节点ID")
+    private String targetNodeId;
 }
 }

+ 4 - 0
src/main/java/com/qqflow/engine/domain/flow/dto/ProcessInstanceDTO.java

@@ -37,6 +37,9 @@ public class ProcessInstanceDTO {
     @Schema(description = "表单数据JSON")
     @Schema(description = "表单数据JSON")
     private String formData;
     private String formData;
 
 
+    @Schema(description = "附件URL列表JSON")
+    private String attachmentUrls;
+
     @Schema(description = "当前节点ID")
     @Schema(description = "当前节点ID")
     private String currentNodeId;
     private String currentNodeId;
 
 
@@ -77,6 +80,7 @@ public class ProcessInstanceDTO {
         dto.setApplicantId(po.getApplicantId());
         dto.setApplicantId(po.getApplicantId());
         dto.setApplicantDeptId(po.getApplicantDeptId());
         dto.setApplicantDeptId(po.getApplicantDeptId());
         dto.setFormData(po.getFormData());
         dto.setFormData(po.getFormData());
+        dto.setAttachmentUrls(po.getAttachmentUrls());
         dto.setCurrentNodeId(po.getCurrentNodeId());
         dto.setCurrentNodeId(po.getCurrentNodeId());
         dto.setStatus(po.getStatus());
         dto.setStatus(po.getStatus());
         dto.setResult(po.getResult());
         dto.setResult(po.getResult());

+ 3 - 0
src/main/java/com/qqflow/engine/domain/flow/dto/StartProcessDTO.java

@@ -17,4 +17,7 @@ public class StartProcessDTO {
 
 
     @Schema(description = "表单数据JSON")
     @Schema(description = "表单数据JSON")
     private String formData;
     private String formData;
+
+    @Schema(description = "附件URL列表JSON")
+    private String attachmentUrls;
 }
 }

+ 5 - 0
src/main/java/com/qqflow/engine/domain/flow/mapper/ApprovalTaskMapper.java

@@ -42,4 +42,9 @@ public interface ApprovalTaskMapper extends BaseMapper<ApprovalTask> {
             @Param("assigneeId") Long assigneeId,
             @Param("assigneeId") Long assigneeId,
             @Param("assigneeType") String assigneeType,
             @Param("assigneeType") String assigneeType,
             @Param("processName") String processName);
             @Param("processName") String processName);
+
+    com.baomidou.mybatisplus.extension.plugins.pagination.Page<ApprovalTask> selectCcList(
+            com.baomidou.mybatisplus.extension.plugins.pagination.Page<ApprovalTask> page,
+            @Param("assigneeId") Long assigneeId,
+            @Param("processName") String processName);
 }
 }

+ 4 - 0
src/main/java/com/qqflow/engine/domain/flow/po/ProcessInstance.java

@@ -47,6 +47,10 @@ public class ProcessInstance {
     @Schema(description = "表单数据JSON")
     @Schema(description = "表单数据JSON")
     private String formData;
     private String formData;
 
 
+    @TableField("attachment_urls")
+    @Schema(description = "附件URL列表JSON")
+    private String attachmentUrls;
+
     @TableField("current_node_id")
     @TableField("current_node_id")
     @Schema(description = "当前节点ID")
     @Schema(description = "当前节点ID")
     private String currentNodeId;
     private String currentNodeId;

+ 4 - 0
src/main/java/com/qqflow/engine/domain/flow/service/ApprovalTaskService.java

@@ -27,4 +27,8 @@ public interface ApprovalTaskService {
     List<ApprovalRecordDTO> history(Long instanceId);
     List<ApprovalRecordDTO> history(Long instanceId);
 
 
     Long todoCount(Long assigneeId, String assigneeType);
     Long todoCount(Long assigneeId, String assigneeType);
+
+    PageResult<ApprovalTaskDTO> ccList(Long assigneeId, String processName, Integer pageNum, Integer pageSize);
+
+    void readCc(Long taskId);
 }
 }

+ 4 - 0
src/main/java/com/qqflow/engine/domain/flow/service/FlowEngineService.java

@@ -15,6 +15,8 @@ public interface FlowEngineService {
 
 
     List<FlowNode> getNextNodes(FlowModel model, String currentNodeId);
     List<FlowNode> getNextNodes(FlowModel model, String currentNodeId);
 
 
+    List<FlowNode> getNextNodes(FlowModel model, String currentNodeId, ProcessInstance instance);
+
     List<Long> calculateAssignees(FlowNode node, ProcessInstance instance);
     List<Long> calculateAssignees(FlowNode node, ProcessInstance instance);
 
 
     String getApproveMode(FlowNode node);
     String getApproveMode(FlowNode node);
@@ -23,5 +25,7 @@ public interface FlowEngineService {
 
 
     void executeTransition(ProcessInstance instance, ApprovalTask currentTask, ApprovalAction action, String comment);
     void executeTransition(ProcessInstance instance, ApprovalTask currentTask, ApprovalAction action, String comment);
 
 
+    void executeTransition(ProcessInstance instance, ApprovalTask currentTask, ApprovalAction action, String comment, String targetNodeId);
+
     void startInstance(ProcessInstance instance, ProcessDefinition definition);
     void startInstance(ProcessInstance instance, ProcessDefinition definition);
 }
 }

+ 64 - 1
src/main/java/com/qqflow/engine/domain/flow/service/impl/ApprovalTaskServiceImpl.java

@@ -24,6 +24,8 @@ import com.qqflow.engine.domain.flow.po.ProcessInstance;
 import com.qqflow.engine.domain.flow.event.TaskCompletedEvent;
 import com.qqflow.engine.domain.flow.event.TaskCompletedEvent;
 import com.qqflow.engine.domain.flow.service.ApprovalTaskService;
 import com.qqflow.engine.domain.flow.service.ApprovalTaskService;
 import com.qqflow.engine.domain.flow.service.FlowEngineService;
 import com.qqflow.engine.domain.flow.service.FlowEngineService;
+import com.qqflow.engine.domain.system.entity.SysUserRole;
+import com.qqflow.engine.domain.system.mapper.SysUserRoleMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -43,6 +45,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     private final ApprovalTaskAssembler approvalTaskAssembler;
     private final ApprovalTaskAssembler approvalTaskAssembler;
     private final FlowEngineService flowEngineService;
     private final FlowEngineService flowEngineService;
     private final ApplicationEventPublisher eventPublisher;
     private final ApplicationEventPublisher eventPublisher;
+    private final SysUserRoleMapper sysUserRoleMapper;
 
 
     @Override
     @Override
     public PageResult<ApprovalTaskDTO> todoList(Long assigneeId, String assigneeType, String processName, Integer pageNum, Integer pageSize) {
     public PageResult<ApprovalTaskDTO> todoList(Long assigneeId, String assigneeType, String processName, Integer pageNum, Integer pageSize) {
@@ -68,6 +71,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     @Transactional
     public void approve(ApproveTaskDTO dto) {
     public void approve(ApproveTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         Long operatorId = SecurityUtils.getUserId();
         this.saveRecord(task, instance, operatorId, ApprovalAction.APPROVE, ApprovalResult.PASS.getCode(), dto.getComment(), dto.getAttachmentUrls());
         this.saveRecord(task, instance, operatorId, ApprovalAction.APPROVE, ApprovalResult.PASS.getCode(), dto.getComment(), dto.getAttachmentUrls());
@@ -79,6 +83,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     @Transactional
     public void reject(ApproveTaskDTO dto) {
     public void reject(ApproveTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         Long operatorId = SecurityUtils.getUserId();
         this.saveRecord(task, instance, operatorId, ApprovalAction.REJECT, ApprovalResult.REJECT.getCode(), dto.getComment(), dto.getAttachmentUrls());
         this.saveRecord(task, instance, operatorId, ApprovalAction.REJECT, ApprovalResult.REJECT.getCode(), dto.getComment(), dto.getAttachmentUrls());
@@ -90,10 +95,11 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     @Transactional
     public void returnTask(ApproveTaskDTO dto) {
     public void returnTask(ApproveTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         Long operatorId = SecurityUtils.getUserId();
         this.saveRecord(task, instance, operatorId, ApprovalAction.RETURN, ApprovalResult.RETURN.getCode(), dto.getComment(), dto.getAttachmentUrls());
         this.saveRecord(task, instance, operatorId, ApprovalAction.RETURN, ApprovalResult.RETURN.getCode(), dto.getComment(), dto.getAttachmentUrls());
-        this.flowEngineService.executeTransition(instance, task, ApprovalAction.RETURN, dto.getComment());
+        this.flowEngineService.executeTransition(instance, task, ApprovalAction.RETURN, dto.getComment(), dto.getTargetNodeId());
         this.publishTaskCompletedEvent(task, instance, operatorId, ApprovalAction.RETURN.getCode(), ApprovalResult.RETURN.getCode());
         this.publishTaskCompletedEvent(task, instance, operatorId, ApprovalAction.RETURN.getCode(), ApprovalResult.RETURN.getCode());
     }
     }
 
 
@@ -101,6 +107,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     @Transactional
     public void transfer(TransferTaskDTO dto) {
     public void transfer(TransferTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         Long operatorId = SecurityUtils.getUserId();
         Long transferToUserId = dto.getTransferToUserId() != null ? dto.getTransferToUserId() : dto.getTransferTo();
         Long transferToUserId = dto.getTransferToUserId() != null ? dto.getTransferToUserId() : dto.getTransferTo();
@@ -114,6 +121,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     @Transactional
     public void addSign(Long taskId, Long assigneeId) {
     public void addSign(Long taskId, Long assigneeId) {
         ApprovalTask task = this.getPendingTask(taskId);
         ApprovalTask task = this.getPendingTask(taskId);
+        this.checkTaskPermission(task);
         ApprovalTask newTask = this.approvalTaskAssembler.buildNew(
         ApprovalTask newTask = this.approvalTaskAssembler.buildNew(
                 task.getInstanceId(), task.getNodeId(), task.getNodeName(),
                 task.getInstanceId(), task.getNodeId(), task.getNodeName(),
                 task.getNodeType(), assigneeId, task.getAssigneeType(), TaskStatus.PENDING.getCode()
                 task.getNodeType(), assigneeId, task.getAssigneeType(), TaskStatus.PENDING.getCode()
@@ -132,6 +140,37 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     }
     }
 
 
     @Override
     @Override
+    public PageResult<ApprovalTaskDTO> ccList(Long assigneeId, String processName, Integer pageNum, Integer pageSize) {
+        Page<ApprovalTask> page = new Page<>(pageNum, pageSize);
+        this.approvalTaskMapper.selectCcList(page, assigneeId, processName);
+        List<ApprovalTaskDTO> records = page.getRecords().stream()
+                .map(ApprovalTaskDTO::of)
+                .collect(Collectors.toList());
+        return PageResult.of(page.getTotal(), records);
+    }
+
+    @Override
+    @Transactional
+    public void readCc(Long taskId) {
+        ApprovalTask task = this.approvalTaskMapper.selectById(taskId);
+        if (task == null) {
+            throw new BusinessException("抄送任务不存在");
+        }
+        Long operatorId = SecurityUtils.getUserId();
+        if (!task.getAssigneeId().equals(operatorId)) {
+            throw new BusinessException("无权操作该抄送");
+        }
+        if (!TaskStatus.PENDING.getCode().equals(task.getTaskStatus())) {
+            return; // 已经是已读状态
+        }
+        this.approvalTaskMapper.update(null,
+                new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getId, taskId)
+                        .set(ApprovalTask::getTaskStatus, TaskStatus.HANDLED.getCode())
+                        .set(ApprovalTask::getHandleTime, java.time.LocalDateTime.now()));
+    }
+
+    @Override
     public Long todoCount(Long assigneeId, String assigneeType) {
     public Long todoCount(Long assigneeId, String assigneeType) {
         com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalTask> wrapper =
         com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalTask> wrapper =
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalTask>();
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalTask>();
@@ -173,6 +212,30 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
                 || ProcessStatus.RETURNED.getCode().equals(status);
                 || ProcessStatus.RETURNED.getCode().equals(status);
     }
     }
 
 
+    private void checkTaskPermission(ApprovalTask task) {
+        Long operatorId = SecurityUtils.getUserId();
+        String assigneeType = task.getAssigneeType();
+        String userType = SecurityUtils.getLoginUser() != null ? SecurityUtils.getLoginUser().getUserType() : "SYSTEM";
+        if ("ROLE".equals(assigneeType)) {
+            // ROLE类型任务:如果当前登录用户就是该角色账号,直接允许
+            if ("ROLE".equals(userType) && task.getAssigneeId().equals(operatorId)) {
+                return;
+            }
+            // 否则检查SYSTEM用户是否拥有该角色
+            List<SysUserRole> userRoles = this.sysUserRoleMapper.selectList(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysUserRole>()
+                            .eq(SysUserRole::getUserId, operatorId)
+                            .eq(SysUserRole::getRoleId, task.getAssigneeId()));
+            if (userRoles == null || userRoles.isEmpty()) {
+                throw new BusinessException("无权操作该任务");
+            }
+        } else {
+            if (!task.getAssigneeId().equals(operatorId)) {
+                throw new BusinessException("无权操作该任务");
+            }
+        }
+    }
+
     private void saveRecord(ApprovalTask task, ProcessInstance instance, Long operatorId,
     private void saveRecord(ApprovalTask task, ProcessInstance instance, Long operatorId,
                             ApprovalAction action, String result, String comment, String attachmentUrls) {
                             ApprovalAction action, String result, String comment, String attachmentUrls) {
         String operatorName = null;
         String operatorName = null;

+ 108 - 12
src/main/java/com/qqflow/engine/domain/flow/service/impl/FlowEngineServiceImpl.java

@@ -65,15 +65,75 @@ public class FlowEngineServiceImpl implements FlowEngineService {
 
 
     @Override
     @Override
     public List<FlowNode> getNextNodes(FlowModel model, String currentNodeId) {
     public List<FlowNode> getNextNodes(FlowModel model, String currentNodeId) {
-        List<String> targetIds = model.getEdges().stream()
+        return this.getNextNodes(model, currentNodeId, null);
+    }
+
+    public List<FlowNode> getNextNodes(FlowModel model, String currentNodeId, ProcessInstance instance) {
+        List<FlowEdge> edges = model.getEdges().stream()
                 .filter(e -> Objects.equals(e.getSourceNodeId(), currentNodeId))
                 .filter(e -> Objects.equals(e.getSourceNodeId(), currentNodeId))
-                .map(FlowEdge::getTargetNodeId)
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());
+        List<String> targetIds = new ArrayList<>();
+        for (FlowEdge edge : edges) {
+            Object conditionObj = edge.getCondition() != null ? edge.getCondition().get("condition") : null;
+            String condition = conditionObj != null ? conditionObj.toString() : null;
+            if (condition == null || condition.trim().isEmpty()) {
+                targetIds.add(edge.getTargetNodeId());
+            } else if (instance != null && this.evaluateCondition(condition, instance.getFormData())) {
+                targetIds.add(edge.getTargetNodeId());
+            }
+        }
         return model.getNodes().stream()
         return model.getNodes().stream()
                 .filter(n -> targetIds.contains(n.getId()))
                 .filter(n -> targetIds.contains(n.getId()))
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());
     }
     }
 
 
+    private boolean evaluateCondition(String condition, String formDataJson) {
+        try {
+            Map<String, Object> formData = formDataJson != null ? OBJECT_MAPPER.readValue(formDataJson, Map.class) : Collections.emptyMap();
+            condition = condition.trim();
+            // 支持格式:field == value, field > value, field >= value, field < value, field <= value, field != value
+            String[] operators = {">=", "<=", "!=", "==", ">", "<"};
+            String foundOp = null;
+            for (String op : operators) {
+                if (condition.contains(op)) {
+                    foundOp = op;
+                    break;
+                }
+            }
+            if (foundOp == null) return false;
+            String[] parts = condition.split(java.util.regex.Pattern.quote(foundOp), 2);
+            if (parts.length != 2) return false;
+            String field = parts[0].trim();
+            String value = parts[1].trim();
+            Object actual = formData.get(field);
+            if (actual == null) return false;
+            String actualStr = actual.toString();
+            // 尝试数字比较
+            try {
+                double actualNum = Double.parseDouble(actualStr);
+                double valueNum = Double.parseDouble(value);
+                return switch (foundOp) {
+                    case "==" -> actualNum == valueNum;
+                    case "!=" -> actualNum != valueNum;
+                    case ">" -> actualNum > valueNum;
+                    case ">=" -> actualNum >= valueNum;
+                    case "<" -> actualNum < valueNum;
+                    case "<=" -> actualNum <= valueNum;
+                    default -> false;
+                };
+            } catch (NumberFormatException e) {
+                // 字符串比较
+                return switch (foundOp) {
+                    case "==" -> actualStr.equals(value);
+                    case "!=" -> !actualStr.equals(value);
+                    default -> false;
+                };
+            }
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
     @Override
     @Override
     public List<Long> calculateAssignees(FlowNode node, ProcessInstance instance) {
     public List<Long> calculateAssignees(FlowNode node, ProcessInstance instance) {
         Map<String, Object> props = node.getProperties();
         Map<String, Object> props = node.getProperties();
@@ -122,10 +182,15 @@ public class FlowEngineServiceImpl implements FlowEngineService {
 
 
     @Override
     @Override
     public void executeTransition(ProcessInstance instance, ApprovalTask currentTask, ApprovalAction action, String comment) {
     public void executeTransition(ProcessInstance instance, ApprovalTask currentTask, ApprovalAction action, String comment) {
+        this.executeTransition(instance, currentTask, action, comment, null);
+    }
+
+    @Override
+    public void executeTransition(ProcessInstance instance, ApprovalTask currentTask, ApprovalAction action, String comment, String targetNodeId) {
         switch (action) {
         switch (action) {
             case APPROVE -> this.handleApprove(instance, currentTask, comment);
             case APPROVE -> this.handleApprove(instance, currentTask, comment);
             case REJECT -> this.handleReject(instance, currentTask, comment);
             case REJECT -> this.handleReject(instance, currentTask, comment);
-            case RETURN -> this.handleReturn(instance, currentTask, comment);
+            case RETURN -> this.handleReturn(instance, currentTask, comment, targetNodeId);
             default -> throw new BusinessException("不支持的操作类型");
             default -> throw new BusinessException("不支持的操作类型");
         }
         }
     }
     }
@@ -134,7 +199,7 @@ public class FlowEngineServiceImpl implements FlowEngineService {
     public void startInstance(ProcessInstance instance, ProcessDefinition definition) {
     public void startInstance(ProcessInstance instance, ProcessDefinition definition) {
         FlowModel model = this.parseModel(definition.getModelJson());
         FlowModel model = this.parseModel(definition.getModelJson());
         FlowNode startNode = this.findStartNode(model);
         FlowNode startNode = this.findStartNode(model);
-        List<FlowNode> nextNodes = this.getNextNodes(model, startNode.getId());
+        List<FlowNode> nextNodes = this.getNextNodes(model, startNode.getId(), instance);
         this.createTasksForNodes(instance, nextNodes, model);
         this.createTasksForNodes(instance, nextNodes, model);
         this.updateInstanceNode(instance, nextNodes);
         this.updateInstanceNode(instance, nextNodes);
     }
     }
@@ -155,8 +220,14 @@ public class FlowEngineServiceImpl implements FlowEngineService {
             if (role == null) {
             if (role == null) {
                 return Collections.emptyList();
                 return Collections.emptyList();
             }
             }
-            // 角色用户独立登录,直接返回角色ID
-            return Collections.singletonList(role.getId());
+            // 查询该角色下的所有用户
+            List<SysUserRole> userRoles = this.sysUserRoleMapper.selectList(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysUserRole>()
+                            .eq(SysUserRole::getRoleId, role.getId()));
+            if (userRoles == null || userRoles.isEmpty()) {
+                return Collections.emptyList();
+            }
+            return userRoles.stream().map(SysUserRole::getUserId).distinct().collect(Collectors.toList());
         }
         }
         if ("LEADER".equals(assigneeType)) {
         if ("LEADER".equals(assigneeType)) {
             SysUser applicant = this.sysUserMapper.selectById(instance.getApplicantId());
             SysUser applicant = this.sysUserMapper.selectById(instance.getApplicantId());
@@ -204,7 +275,7 @@ public class FlowEngineServiceImpl implements FlowEngineService {
                             .set(ApprovalTask::getTaskStatus, TaskStatus.SKIPPED.getCode()));
                             .set(ApprovalTask::getTaskStatus, TaskStatus.SKIPPED.getCode()));
         }
         }
 
 
-        List<FlowNode> nextNodes = this.getNextNodes(model, currentTask.getNodeId());
+        List<FlowNode> nextNodes = this.getNextNodes(model, currentTask.getNodeId(), instance);
         if (this.isEndNode(nextNodes)) {
         if (this.isEndNode(nextNodes)) {
             this.completeInstance(instance);
             this.completeInstance(instance);
             return;
             return;
@@ -218,14 +289,23 @@ public class FlowEngineServiceImpl implements FlowEngineService {
         this.rejectInstance(instance);
         this.rejectInstance(instance);
     }
     }
 
 
-    private void handleReturn(ProcessInstance instance, ApprovalTask currentTask, String comment) {
+    private void handleReturn(ProcessInstance instance, ApprovalTask currentTask, String comment, String targetNodeId) {
         this.updateTaskStatus(currentTask, TaskStatus.RETURNED.getCode(), comment);
         this.updateTaskStatus(currentTask, TaskStatus.RETURNED.getCode(), comment);
         FlowModel model = this.getModelByInstance(instance);
         FlowModel model = this.getModelByInstance(instance);
-        FlowNode prevNode = this.findPreviousNode(model, currentTask.getNodeId());
-        if (prevNode == null) {
+        FlowNode targetNode = null;
+        if (targetNodeId != null && !targetNodeId.isEmpty()) {
+            targetNode = model.getNodes().stream()
+                    .filter(n -> n.getId().equals(targetNodeId))
+                    .findFirst()
+                    .orElse(null);
+        }
+        if (targetNode == null) {
+            targetNode = this.findPreviousNode(model, currentTask.getNodeId());
+        }
+        if (targetNode == null) {
             throw new BusinessException("无法找到回退目标节点");
             throw new BusinessException("无法找到回退目标节点");
         }
         }
-        List<FlowNode> returnTargets = Collections.singletonList(prevNode);
+        List<FlowNode> returnTargets = Collections.singletonList(targetNode);
         this.createTasksForNodes(instance, returnTargets, model);
         this.createTasksForNodes(instance, returnTargets, model);
         this.updateInstanceNode(instance, returnTargets);
         this.updateInstanceNode(instance, returnTargets);
     }
     }
@@ -267,8 +347,24 @@ public class FlowEngineServiceImpl implements FlowEngineService {
             if (NodeType.START.getCode().equals(node.getType()) || NodeType.END.getCode().equals(node.getType())) {
             if (NodeType.START.getCode().equals(node.getType()) || NodeType.END.getCode().equals(node.getType())) {
                 continue;
                 continue;
             }
             }
-            // CC节点:不创建审批任务,仅记录(后续可扩展抄送通知)
+            // CC节点:创建抄送任务并自动标记为已阅,同时继续推进下游
             if (NodeType.CC.getCode().equals(node.getType())) {
             if (NodeType.CC.getCode().equals(node.getType())) {
+                List<Long> ccAssignees = this.calculateAssignees(node, instance);
+                if (ccAssignees.isEmpty()) {
+                    ccAssignees = Collections.singletonList(instance.getApplicantId());
+                }
+                for (Long assigneeId : ccAssignees) {
+                    ApprovalTask ccTask = this.approvalTaskAssembler.buildNew(
+                            instance.getId(), node.getId(), node.getName(),
+                            node.getType(), assigneeId, "USER", TaskStatus.PENDING.getCode()
+                    );
+                    this.approvalTaskMapper.insert(ccTask);
+                }
+                // 自动推进到CC节点的下游
+                List<FlowNode> ccNextNodes = this.getNextNodes(model, node.getId(), instance);
+                if (!ccNextNodes.isEmpty()) {
+                    this.createTasksForNodes(instance, ccNextNodes, model);
+                }
                 continue;
                 continue;
             }
             }
             List<Long> assignees = this.calculateAssignees(node, instance);
             List<Long> assignees = this.calculateAssignees(node, instance);

+ 32 - 1
src/main/java/com/qqflow/engine/domain/flow/service/impl/ProcessDefinitionServiceImpl.java

@@ -7,12 +7,16 @@ import com.qqflow.engine.common.PageResult;
 import com.qqflow.engine.common.exception.BusinessException;
 import com.qqflow.engine.common.exception.BusinessException;
 import com.qqflow.engine.domain.flow.dto.ProcessDefinitionDTO;
 import com.qqflow.engine.domain.flow.dto.ProcessDefinitionDTO;
 import com.qqflow.engine.domain.flow.enums.DefinitionStatus;
 import com.qqflow.engine.domain.flow.enums.DefinitionStatus;
+import com.qqflow.engine.domain.flow.enums.ProcessStatus;
 import com.qqflow.engine.domain.flow.mapper.ProcessDefinitionMapper;
 import com.qqflow.engine.domain.flow.mapper.ProcessDefinitionMapper;
+import com.qqflow.engine.domain.flow.mapper.ProcessInstanceMapper;
 import com.qqflow.engine.domain.flow.po.ProcessDefinition;
 import com.qqflow.engine.domain.flow.po.ProcessDefinition;
+import com.qqflow.engine.domain.flow.po.ProcessInstance;
 import com.qqflow.engine.domain.flow.service.ProcessDefinitionService;
 import com.qqflow.engine.domain.flow.service.ProcessDefinitionService;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
@@ -21,9 +25,19 @@ import java.util.stream.Collectors;
 public class ProcessDefinitionServiceImpl implements ProcessDefinitionService {
 public class ProcessDefinitionServiceImpl implements ProcessDefinitionService {
 
 
     private final ProcessDefinitionMapper processDefinitionMapper;
     private final ProcessDefinitionMapper processDefinitionMapper;
+    private final ProcessInstanceMapper processInstanceMapper;
 
 
     @Override
     @Override
     public Long saveDefinition(ProcessDefinition po) {
     public Long saveDefinition(ProcessDefinition po) {
+        // 检查是否已存在设计中的同编码流程(防止新建时编码冲突)
+        LambdaQueryWrapper<ProcessDefinition> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(ProcessDefinition::getProcessCode, po.getProcessCode())
+                .eq(ProcessDefinition::getStatus, DefinitionStatus.DESIGNING.getCode())
+                .eq(ProcessDefinition::getDeleted, 0);
+        Long existingCount = this.processDefinitionMapper.selectCount(wrapper);
+        if (existingCount != null && existingCount > 0) {
+            throw new BusinessException("流程编码已存在,请更换编码或编辑现有流程");
+        }
         this.processDefinitionMapper.insert(po);
         this.processDefinitionMapper.insert(po);
         return po.getId();
         return po.getId();
     }
     }
@@ -65,7 +79,24 @@ public class ProcessDefinitionServiceImpl implements ProcessDefinitionService {
     @Override
     @Override
     public void deleteDefinition(Long id) {
     public void deleteDefinition(Long id) {
         ProcessDefinition existing = this.processDefinitionMapper.selectById(id);
         ProcessDefinition existing = this.processDefinitionMapper.selectById(id);
-        this.validateDesigning(existing);
+        if (existing == null) {
+            throw new BusinessException("流程定义不存在");
+        }
+        if (!DefinitionStatus.DESIGNING.getCode().equals(existing.getStatus())
+                && !DefinitionStatus.HISTORICAL.getCode().equals(existing.getStatus())) {
+            throw new BusinessException("只有设计中或已停用的流程才能删除");
+        }
+        // 检查是否存在运行中的流程实例
+        LambdaQueryWrapper<ProcessInstance> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(ProcessInstance::getProcessDefinitionId, id)
+                .in(ProcessInstance::getStatus, Arrays.asList(
+                        ProcessStatus.PENDING_RECEIVE.getCode(),
+                        ProcessStatus.PENDING.getCode(),
+                        ProcessStatus.RETURNED.getCode()));
+        long runningCount = this.processInstanceMapper.selectCount(wrapper);
+        if (runningCount > 0) {
+            throw new BusinessException("该流程存在运行中的实例,无法删除");
+        }
         this.processDefinitionMapper.deleteById(id);
         this.processDefinitionMapper.deleteById(id);
     }
     }
 
 

+ 6 - 1
src/main/java/com/qqflow/engine/domain/flow/service/impl/ProcessInstanceServiceImpl.java

@@ -57,6 +57,7 @@ public class ProcessInstanceServiceImpl implements ProcessInstanceService {
     public Long startProcess(StartProcessDTO dto) {
     public Long startProcess(StartProcessDTO dto) {
         ProcessDefinition definition = this.getEnabledDefinition(dto.getProcessDefinitionId());
         ProcessDefinition definition = this.getEnabledDefinition(dto.getProcessDefinitionId());
         ProcessInstance instance = this.buildInstance(dto, definition);
         ProcessInstance instance = this.buildInstance(dto, definition);
+        instance.setAttachmentUrls(dto.getAttachmentUrls());
         this.processInstanceMapper.insert(instance);
         this.processInstanceMapper.insert(instance);
         this.flowEngineService.startInstance(instance, definition);
         this.flowEngineService.startInstance(instance, definition);
         return instance.getId();
         return instance.getId();
@@ -85,6 +86,10 @@ public class ProcessInstanceServiceImpl implements ProcessInstanceService {
     public void revoke(Long id) {
     public void revoke(Long id) {
         ProcessInstance instance = this.processInstanceMapper.selectById(id);
         ProcessInstance instance = this.processInstanceMapper.selectById(id);
         this.validateRevocable(instance);
         this.validateRevocable(instance);
+        Long operatorId = SecurityUtils.getUserId();
+        if (instance != null && !instance.getApplicantId().equals(operatorId)) {
+            throw new BusinessException("无权撤回他人发起的流程");
+        }
         this.processInstanceMapper.update(null, Wrappers.<ProcessInstance>lambdaUpdate()
         this.processInstanceMapper.update(null, Wrappers.<ProcessInstance>lambdaUpdate()
                 .eq(ProcessInstance::getId, id)
                 .eq(ProcessInstance::getId, id)
                 .set(ProcessInstance::getStatus, ProcessStatus.REVOKED.getCode())
                 .set(ProcessInstance::getStatus, ProcessStatus.REVOKED.getCode())
@@ -202,7 +207,7 @@ public class ProcessInstanceServiceImpl implements ProcessInstanceService {
                 userId,
                 userId,
                 deptId,
                 deptId,
                 dto.getFormData(),
                 dto.getFormData(),
-                null,
+                dto.getAttachmentUrls(),
                 ProcessStatus.PENDING_RECEIVE.getCode()
                 ProcessStatus.PENDING_RECEIVE.getCode()
         );
         );
     }
     }

+ 0 - 47
src/main/java/com/qqflow/engine/domain/system/assembler/MenuAssembler.java

@@ -1,47 +0,0 @@
-package com.qqflow.engine.domain.system.assembler;
-
-import com.qqflow.engine.domain.system.dto.MenuDTO;
-import com.qqflow.engine.domain.system.entity.SysMenu;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public class MenuAssembler {
-
-    private MenuAssembler() {
-    }
-
-    public static MenuDTO toDTO(SysMenu menu) {
-        return MenuDTO.of(menu);
-    }
-
-    public static List<MenuDTO> toDTOList(List<SysMenu> menus) {
-        return menus.stream()
-                .map(MenuDTO::of)
-                .collect(Collectors.toList());
-    }
-
-    public static List<MenuDTO> toTree(List<SysMenu> menus) {
-        List<MenuDTO> dtoList = menus.stream()
-                .map(MenuDTO::of)
-                .sorted(Comparator.comparingInt(MenuDTO::getSortOrder))
-                .collect(Collectors.toList());
-        Map<Long, MenuDTO> map = dtoList.stream()
-                .collect(Collectors.toMap(MenuDTO::getId, dto -> dto));
-        List<MenuDTO> roots = new ArrayList<>();
-        for (MenuDTO dto : dtoList) {
-            if (dto.getParentId() == null || dto.getParentId() == 0L) {
-                roots.add(dto);
-                continue;
-            }
-            MenuDTO parent = map.get(dto.getParentId());
-            if (parent != null) {
-                parent.getChildren().add(dto);
-            }
-        }
-        return roots;
-    }
-}

+ 0 - 75
src/main/java/com/qqflow/engine/domain/system/controller/SysMenuController.java

@@ -1,75 +0,0 @@
-package com.qqflow.engine.domain.system.controller;
-
-import com.qqflow.engine.common.Result;
-import com.qqflow.engine.common.util.SecurityUtils;
-import com.qqflow.engine.domain.system.assembler.MenuAssembler;
-import com.qqflow.engine.domain.system.dto.MenuDTO;
-import com.qqflow.engine.domain.system.entity.SysMenu;
-import com.qqflow.engine.domain.system.service.SysMenuService;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.annotation.Resource;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-import java.util.Set;
-
-@Tag(name = "菜单管理")
-@RestController
-@RequestMapping("/system/menu")
-public class SysMenuController {
-
-    @Resource
-    private SysMenuService sysMenuService;
-
-    @Operation(summary = "查询菜单树")
-    @GetMapping("/list")
-    public Result<List<MenuDTO>> list() {
-        List<SysMenu> menus = sysMenuService.list();
-        return Result.ok(MenuAssembler.toTree(menus));
-    }
-
-    @Operation(summary = "根据ID查询菜单")
-    @GetMapping("/{id}")
-    public Result<MenuDTO> getById(@PathVariable Long id) {
-        SysMenu menu = sysMenuService.getMenuById(id);
-        return Result.ok(MenuAssembler.toDTO(menu));
-    }
-
-    @Operation(summary = "查询当前用户菜单树")
-    @GetMapping("/tree")
-    public Result<List<MenuDTO>> tree() {
-        Long userId = SecurityUtils.getUserId();
-        List<MenuDTO> trees = sysMenuService.listMenuTreeByUserId(userId);
-        return Result.ok(trees);
-    }
-
-    @Operation(summary = "查询当前用户权限标识")
-    @GetMapping("/permissions")
-    public Result<Set<String>> permissions() {
-        Long userId = SecurityUtils.getUserId();
-        Set<String> perms = sysMenuService.listPermissionsByUserId(userId);
-        return Result.ok(perms);
-    }
-
-    @Operation(summary = "新增菜单")
-    @PostMapping
-    public Result<Void> add(@RequestBody SysMenu menu) {
-        sysMenuService.addMenu(menu);
-        return Result.ok();
-    }
-
-    @Operation(summary = "修改菜单")
-    @PutMapping
-    public Result<Void> update(@RequestBody SysMenu menu) {
-        sysMenuService.updateMenu(menu);
-        return Result.ok();
-    }
-
-    @Operation(summary = "删除菜单")
-    @DeleteMapping("/{id}")
-    public Result<Void> delete(@PathVariable Long id) {
-        sysMenuService.removeMenu(id);
-        return Result.ok();
-    }
-}

+ 0 - 74
src/main/java/com/qqflow/engine/domain/system/dto/MenuDTO.java

@@ -1,74 +0,0 @@
-package com.qqflow.engine.domain.system.dto;
-
-import com.qqflow.engine.domain.system.entity.SysMenu;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Data
-@Schema(description = "菜单DTO")
-public class MenuDTO {
-
-    @Schema(description = "菜单ID")
-    private Long id;
-
-    @JsonProperty("title")
-    @Schema(description = "菜单名称")
-    private String menuName;
-
-    @JsonProperty("name")
-    @Schema(description = "路由名称")
-    private String routeName;
-
-    @JsonProperty("path")
-    @Schema(description = "路由路径")
-    private String routePath;
-
-    @JsonProperty("type")
-    @Schema(description = "菜单类型:0-目录 1-菜单 2-按钮 3-接口")
-    private Integer menuType;
-
-    @Schema(description = "权限标识")
-    private String permission;
-
-    @Schema(description = "父菜单ID")
-    private Long parentId;
-
-    @JsonProperty("sort")
-    @Schema(description = "排序")
-    private Integer sortOrder;
-
-    @Schema(description = "组件路径")
-    private String component;
-
-    @Schema(description = "图标")
-    private String icon;
-
-    @Schema(description = "状态:0-正常 1-禁用")
-    private Integer status;
-
-    @Schema(description = "子菜单")
-    private List<MenuDTO> children;
-
-    public static MenuDTO of(SysMenu menu) {
-        if (menu == null) {
-            return null;
-        }
-        MenuDTO dto = new MenuDTO();
-        dto.setId(menu.getId());
-        dto.setMenuName(menu.getMenuName());
-        dto.setMenuType(menu.getMenuType());
-        dto.setPermission(menu.getPermission());
-        dto.setParentId(menu.getParentId());
-        dto.setSortOrder(menu.getSortOrder());
-        dto.setComponent(menu.getComponent());
-        dto.setIcon(menu.getIcon());
-        // 后端数据库 0=禁用, 1=正常; 前端约定 0=正常, 1=禁用
-        dto.setStatus(menu.getStatus() != null && menu.getStatus() == 1 ? 0 : 1);
-        dto.setChildren(new ArrayList<>());
-        return dto;
-    }
-}

+ 0 - 41
src/main/java/com/qqflow/engine/domain/system/entity/SysMenu.java

@@ -1,41 +0,0 @@
-package com.qqflow.engine.domain.system.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@TableName("sys_menu")
-@Schema(description = "系统菜单")
-public class SysMenu {
-
-    @TableId(type = IdType.AUTO)
-    @Schema(description = "菜单ID")
-    private Long id;
-
-    @Schema(description = "菜单名称")
-    private String menuName;
-
-    @Schema(description = "菜单类型:0-目录 1-菜单 2-按钮 3-接口")
-    private Integer menuType;
-
-    @Schema(description = "权限标识")
-    private String permission;
-
-    @Schema(description = "父菜单ID")
-    private Long parentId;
-
-    @Schema(description = "排序")
-    private Integer sortOrder;
-
-    @Schema(description = "组件路径")
-    private String component;
-
-    @Schema(description = "图标")
-    private String icon;
-
-    @Schema(description = "状态:0-禁用 1-正常")
-    private Integer status;
-}

+ 2 - 2
src/main/java/com/qqflow/engine/domain/system/entity/SysRole.java

@@ -3,7 +3,7 @@ package com.qqflow.engine.domain.system.entity;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.TableName;
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.Data;
 
 
@@ -36,7 +36,7 @@ public class SysRole {
     @Schema(description = "登录账号")
     @Schema(description = "登录账号")
     private String username;
     private String username;
 
 
-    @JsonIgnore
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
     @Schema(description = "密码")
     @Schema(description = "密码")
     private String password;
     private String password;
 
 

+ 0 - 23
src/main/java/com/qqflow/engine/domain/system/entity/SysRoleMenu.java

@@ -1,23 +0,0 @@
-package com.qqflow.engine.domain.system.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@TableName("sys_role_menu")
-@Schema(description = "角色菜单关联")
-public class SysRoleMenu {
-
-    @TableId(type = IdType.AUTO)
-    @Schema(description = "ID")
-    private Long id;
-
-    @Schema(description = "角色ID")
-    private Long roleId;
-
-    @Schema(description = "菜单ID")
-    private Long menuId;
-}

+ 0 - 22
src/main/java/com/qqflow/engine/domain/system/mapper/SysMenuMapper.java

@@ -1,22 +0,0 @@
-package com.qqflow.engine.domain.system.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.qqflow.engine.domain.system.entity.SysMenu;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.List;
-
-public interface SysMenuMapper extends BaseMapper<SysMenu> {
-
-    default List<SysMenu> selectMenuList(Integer status) {
-        return this.selectList(
-                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysMenu>()
-                        .eq(status != null, SysMenu::getStatus, status)
-                        .orderByAsc(SysMenu::getSortOrder)
-        );
-    }
-
-    List<SysMenu> selectMenusByUserId(@Param("userId") Long userId);
-
-    List<String> selectPermissionsByUserId(@Param("userId") Long userId);
-}

+ 0 - 7
src/main/java/com/qqflow/engine/domain/system/mapper/SysRoleMenuMapper.java

@@ -1,7 +0,0 @@
-package com.qqflow.engine.domain.system.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.qqflow.engine.domain.system.entity.SysRoleMenu;
-
-public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenu> {
-}

+ 0 - 2
src/main/java/com/qqflow/engine/domain/system/mapper/SysUserMapper.java

@@ -16,6 +16,4 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
     }
     }
 
 
     List<String> selectRoleCodesByUserId(@Param("userId") Long userId);
     List<String> selectRoleCodesByUserId(@Param("userId") Long userId);
-
-    List<String> selectPermissionsByUserId(@Param("userId") Long userId);
 }
 }

+ 0 - 8
src/main/java/com/qqflow/engine/domain/system/service/RoleAuthService.java

@@ -11,7 +11,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import java.util.List;
 import java.util.List;
-import java.util.Set;
 
 
 @Service
 @Service
 public class RoleAuthService {
 public class RoleAuthService {
@@ -36,13 +35,6 @@ public class RoleAuthService {
         loginUser.setUsername(role.getUsername());
         loginUser.setUsername(role.getUsername());
         loginUser.setRealName(role.getRoleName());
         loginUser.setRealName(role.getRoleName());
         loginUser.setUserType("ROLE");
         loginUser.setUserType("ROLE");
-        loginUser.setPermissions(Set.of(
-                "flow:task:todo",
-                "flow:task:handled",
-                "flow:task:approve",
-                "flow:task:reject",
-                "flow:instance:mine"
-        ));
         loginUser.setRoles(List.of(role.getRoleCode()));
         loginUser.setRoles(List.of(role.getRoleCode()));
         return jwtUtils.generateToken(loginUser);
         return jwtUtils.generateToken(loginUser);
     }
     }

+ 0 - 26
src/main/java/com/qqflow/engine/domain/system/service/SysMenuService.java

@@ -1,26 +0,0 @@
-package com.qqflow.engine.domain.system.service;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.qqflow.engine.domain.system.dto.MenuDTO;
-import com.qqflow.engine.domain.system.entity.SysMenu;
-
-import java.util.List;
-import java.util.Set;
-
-public interface SysMenuService extends IService<SysMenu> {
-
-    boolean addMenu(SysMenu menu);
-
-    boolean updateMenu(SysMenu menu);
-
-    boolean removeMenu(Long id);
-
-    SysMenu getMenuById(Long id);
-
-    Page<SysMenu> pageMenus(Page<SysMenu> page);
-
-    List<MenuDTO> listMenuTreeByUserId(Long userId);
-
-    Set<String> listPermissionsByUserId(Long userId);
-}

+ 0 - 3
src/main/java/com/qqflow/engine/domain/system/service/SysUserService.java

@@ -7,7 +7,6 @@ import com.qqflow.engine.domain.system.dto.UserDTO;
 import com.qqflow.engine.domain.system.entity.SysUser;
 import com.qqflow.engine.domain.system.entity.SysUser;
 
 
 import java.util.List;
 import java.util.List;
-import java.util.Set;
 
 
 public interface SysUserService extends IService<SysUser> {
 public interface SysUserService extends IService<SysUser> {
 
 
@@ -23,8 +22,6 @@ public interface SysUserService extends IService<SysUser> {
 
 
     SysUser getByUsername(String username);
     SysUser getByUsername(String username);
 
 
-    Set<String> loadUserPermissions(Long userId);
-
     List<String> loadUserRoles(Long userId);
     List<String> loadUserRoles(Long userId);
 
 
     String login(LoginDTO loginDTO);
     String login(LoginDTO loginDTO);

+ 0 - 92
src/main/java/com/qqflow/engine/domain/system/service/impl/SysMenuServiceImpl.java

@@ -1,92 +0,0 @@
-package com.qqflow.engine.domain.system.service.impl;
-
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.qqflow.engine.common.exception.BusinessException;
-import com.qqflow.engine.domain.system.dto.MenuDTO;
-import com.qqflow.engine.domain.system.entity.SysMenu;
-import com.qqflow.engine.domain.system.entity.SysRoleMenu;
-import com.qqflow.engine.domain.system.mapper.SysMenuMapper;
-import com.qqflow.engine.domain.system.mapper.SysRoleMenuMapper;
-import com.qqflow.engine.domain.system.service.SysMenuService;
-import jakarta.annotation.Resource;
-import org.springframework.stereotype.Service;
-
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-@Service
-public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu>
-        implements SysMenuService {
-
-    @Resource
-    private SysRoleMenuMapper sysRoleMenuMapper;
-
-    @Override
-    public boolean addMenu(SysMenu menu) {
-        return this.save(menu);
-    }
-
-    @Override
-    public boolean updateMenu(SysMenu menu) {
-        return this.updateById(menu);
-    }
-
-    @Override
-    public boolean removeMenu(Long id) {
-        sysRoleMenuMapper.delete(
-                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysRoleMenu>()
-                        .eq(SysRoleMenu::getMenuId, id)
-        );
-        return this.removeById(id);
-    }
-
-    @Override
-    public SysMenu getMenuById(Long id) {
-        return this.getById(id);
-    }
-
-    @Override
-    public Page<SysMenu> pageMenus(Page<SysMenu> page) {
-        return this.page(page);
-    }
-
-    @Override
-    public List<MenuDTO> listMenuTreeByUserId(Long userId) {
-        if (userId == null) {
-            throw new BusinessException("用户ID不能为空");
-        }
-        List<SysMenu> menus = this.baseMapper.selectMenusByUserId(userId);
-        return this.buildMenuTree(menus);
-    }
-
-    @Override
-    public Set<String> listPermissionsByUserId(Long userId) {
-        List<String> permissions = this.baseMapper.selectPermissionsByUserId(userId);
-        return new java.util.HashSet<>(permissions);
-    }
-
-    private List<MenuDTO> buildMenuTree(List<SysMenu> menus) {
-        List<MenuDTO> dtoList = menus.stream()
-                .map(MenuDTO::of)
-                .sorted(Comparator.comparingInt(MenuDTO::getSortOrder))
-                .collect(Collectors.toList());
-        Map<Long, MenuDTO> map = dtoList.stream()
-                .collect(Collectors.toMap(MenuDTO::getId, dto -> dto));
-        List<MenuDTO> roots = new java.util.ArrayList<>();
-        for (MenuDTO dto : dtoList) {
-            if (dto.getParentId() == null || dto.getParentId() == 0L) {
-                roots.add(dto);
-                continue;
-            }
-            MenuDTO parent = map.get(dto.getParentId());
-            if (parent != null) {
-                parent.getChildren().add(dto);
-            }
-        }
-        return roots;
-    }
-}

+ 0 - 9
src/main/java/com/qqflow/engine/domain/system/service/impl/SysRoleServiceImpl.java

@@ -5,11 +5,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qqflow.engine.domain.system.entity.SysDept;
 import com.qqflow.engine.domain.system.entity.SysDept;
 import com.qqflow.engine.domain.system.entity.SysRole;
 import com.qqflow.engine.domain.system.entity.SysRole;
-import com.qqflow.engine.domain.system.entity.SysRoleMenu;
 import com.qqflow.engine.domain.system.entity.SysUserRole;
 import com.qqflow.engine.domain.system.entity.SysUserRole;
 import com.qqflow.engine.domain.system.mapper.SysDeptMapper;
 import com.qqflow.engine.domain.system.mapper.SysDeptMapper;
 import com.qqflow.engine.domain.system.mapper.SysRoleMapper;
 import com.qqflow.engine.domain.system.mapper.SysRoleMapper;
-import com.qqflow.engine.domain.system.mapper.SysRoleMenuMapper;
 import com.qqflow.engine.domain.system.mapper.SysUserRoleMapper;
 import com.qqflow.engine.domain.system.mapper.SysUserRoleMapper;
 import com.qqflow.engine.domain.system.service.SysRoleService;
 import com.qqflow.engine.domain.system.service.SysRoleService;
 import jakarta.annotation.Resource;
 import jakarta.annotation.Resource;
@@ -31,9 +29,6 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole>
     private SysUserRoleMapper sysUserRoleMapper;
     private SysUserRoleMapper sysUserRoleMapper;
 
 
     @Resource
     @Resource
-    private SysRoleMenuMapper sysRoleMenuMapper;
-
-    @Resource
     private SysDeptMapper sysDeptMapper;
     private SysDeptMapper sysDeptMapper;
 
 
     @Resource
     @Resource
@@ -65,10 +60,6 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole>
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysUserRole>()
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysUserRole>()
                         .eq(SysUserRole::getRoleId, id)
                         .eq(SysUserRole::getRoleId, id)
         );
         );
-        sysRoleMenuMapper.delete(
-                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysRoleMenu>()
-                        .eq(SysRoleMenu::getRoleId, id)
-        );
         return this.removeById(id);
         return this.removeById(id);
     }
     }
 
 

+ 3 - 8
src/main/java/com/qqflow/engine/domain/system/service/impl/SysUserServiceImpl.java

@@ -31,7 +31,6 @@ import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
-import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 @Service
 @Service
@@ -194,12 +193,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
     }
     }
 
 
     @Override
     @Override
-    public Set<String> loadUserPermissions(Long userId) {
-        List<String> permissions = this.baseMapper.selectPermissionsByUserId(userId);
-        return permissions != null ? new java.util.HashSet<>(permissions) : new java.util.HashSet<>();
-    }
-
-    @Override
     public List<String> loadUserRoles(Long userId) {
     public List<String> loadUserRoles(Long userId) {
         return this.baseMapper.selectRoleCodesByUserId(userId);
         return this.baseMapper.selectRoleCodesByUserId(userId);
     }
     }
@@ -238,6 +231,9 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
         if (user == null) {
         if (user == null) {
             throw new UsernameNotFoundException("用户不存在");
             throw new UsernameNotFoundException("用户不存在");
         }
         }
+        if (user.getStatus() != null && user.getStatus() != 0) {
+            throw new UsernameNotFoundException("用户已被禁用");
+        }
         LoginUser loginUser = new LoginUser();
         LoginUser loginUser = new LoginUser();
         loginUser.setUserId(user.getId());
         loginUser.setUserId(user.getId());
         loginUser.setUsername(user.getUsername());
         loginUser.setUsername(user.getUsername());
@@ -246,7 +242,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
         loginUser.setEmployeeType(user.getEmployeeType());
         loginUser.setEmployeeType(user.getEmployeeType());
         loginUser.setUserType("SYSTEM");
         loginUser.setUserType("SYSTEM");
         loginUser.setPassword(user.getPassword());
         loginUser.setPassword(user.getPassword());
-        loginUser.setPermissions(this.loadUserPermissions(user.getId()));
         loginUser.setRoles(this.loadUserRoles(user.getId()));
         loginUser.setRoles(this.loadUserRoles(user.getId()));
         return loginUser;
         return loginUser;
     }
     }

+ 10 - 4
src/main/resources/application-dev.yml

@@ -3,15 +3,21 @@ server:
 
 
 spring:
 spring:
   datasource:
   datasource:
-    url: jdbc:mysql://localhost:3306/qqflowengine?useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci&serverTimezone=Asia/Shanghai&createDatabaseIfNotExist=true
-    driver-class-name: com.mysql.cj.jdbc.Driver
-    username: root
-    password: root
+    url: jdbc:h2:file:./data/devdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+    driver-class-name: org.h2.Driver
+    username: sa
+    password:
   sql:
   sql:
     init:
     init:
       mode: always
       mode: always
       schema-locations: classpath:schema-dev.sql
       schema-locations: classpath:schema-dev.sql
       data-locations: classpath:data-dev.sql
       data-locations: classpath:data-dev.sql
+  h2:
+    console:
+      enabled: true
+      path: /h2-console
+      settings:
+        web-allow-others: true
   data:
   data:
     redis:
     redis:
       host: localhost
       host: localhost

+ 14 - 37
src/main/resources/data-dev.sql

@@ -1,4 +1,4 @@
-REPLACE INTO sys_dept (id, dept_name, dept_code, parent_id, sort_order, status) VALUES
+MERGE INTO sys_dept (id, dept_name, dept_code, parent_id, sort_order, status) KEY(id) VALUES
 (1, '总裁办', 'CEO', 0, 1, 1),
 (1, '总裁办', 'CEO', 0, 1, 1),
 (2, '技术部', 'TECH', 0, 2, 1),
 (2, '技术部', 'TECH', 0, 2, 1),
 (3, '财务部', 'FIN', 0, 3, 1),
 (3, '财务部', 'FIN', 0, 3, 1),
@@ -6,46 +6,23 @@ REPLACE INTO sys_dept (id, dept_name, dept_code, parent_id, sort_order, status)
 (5, '前端组', 'FE', 2, 1, 1),
 (5, '前端组', 'FE', 2, 1, 1),
 (6, '后端组', 'BE', 2, 2, 1);
 (6, '后端组', 'BE', 2, 2, 1);
 
 
-REPLACE INTO sys_user (id, username, password, real_name, phone, email, dept_id, employee_type, status) VALUES
-(1, 'admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '超级管理员', '13800138000', 'admin@qqflow.com', 1, 'super_admin', 0),
-(2, 'zhangsan', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '张三', '13800138001', 'zhangsan@qqflow.com', 2, 'common_user', 0),
-(3, 'lisi', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '李四', '13800138002', 'lisi@qqflow.com', 3, 'dept_manager', 0),
-(4, 'wangwu', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '王五', '13800138003', 'wangwu@qqflow.com', 4, 'flow_manager', 0);
+MERGE INTO sys_user (id, username, password, real_name, phone, email, dept_id, employee_type, status) KEY(id) VALUES
+(1, 'admin', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '超级管理员', '13800138000', 'admin@qqflow.com', 1, 'super_admin', 0),
+(2, 'zhangsan', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '张三', '13800138001', 'zhangsan@qqflow.com', 2, 'common_user', 0),
+(3, 'lisi', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '李四', '13800138002', 'lisi@qqflow.com', 3, 'dept_manager', 0),
+(4, 'wangwu', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '王五', '13800138003', 'wangwu@qqflow.com', 4, 'flow_manager', 0);
 
 
-REPLACE INTO sys_role (id, role_code, role_name, role_scope, parent_id, dept_id, username, password, status) VALUES
-(1, 'super_admin', '超级管理员', 'platform', 0, 1, 'role_super_admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
-(2, 'flow_admin', '流程管理员', 'tenant', 0, 1, 'role_flow_admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
-(3, 'normal_user', '普通用户', 'tenant', 0, 2, 'role_normal_user', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
-(4, 'dept_manager', '部门经理', 'tenant', 3, 3, 'role_dept_manager', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1);
+MERGE INTO sys_role (id, role_code, role_name, role_scope, parent_id, dept_id, username, password, status) KEY(id) VALUES
+(1, 'super_admin', '超级管理员', 'platform', 0, 1, 'role_super_admin', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1),
+(2, 'flow_admin', '流程管理员', 'tenant', 0, 1, 'role_flow_admin', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1),
+(3, 'normal_user', '普通用户', 'tenant', 0, 2, 'role_normal_user', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1),
+(4, 'dept_manager', '部门经理', 'tenant', 3, 3, 'role_dept_manager', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1);
 
 
-REPLACE INTO sys_menu (id, menu_name, menu_type, permission, parent_id, sort_order, component, icon, status) VALUES
-(1, '系统管理', 0, NULL, 0, 1, NULL, 'Setting', 1),
-(2, '用户管理', 1, 'system:user:list', 1, 1, 'system/user/index', 'User', 1),
-(3, '角色管理', 1, 'system:role:list', 1, 2, 'system/role/index', 'Role', 1),
-(4, '流程管理', 0, NULL, 0, 2, NULL, 'Connection', 1),
-(5, '流程设计', 1, 'flow:designer:list', 4, 1, 'flow/designer/index', 'Edit', 1),
-(6, '流程定义', 1, 'flow:definition:list', 4, 2, 'flow/definition/index', 'Document', 1),
-(7, '审批中心', 0, NULL, 0, 3, NULL, 'Stamp', 1),
-(8, '我的待办', 1, 'flow:task:todo', 7, 1, 'flow/task/todo', 'Bell', 1),
-(17, '我的已办', 1, 'flow:task:handled', 7, 2, 'flow/task/handled', 'CircleCheck', 1),
-(9, '我的流程', 1, 'flow:instance:mine', 7, 3, 'flow/instance/mine', 'List', 1),
-(10, '新增用户', 2, 'system:user:create', 2, 1, NULL, NULL, 1),
-(11, '编辑用户', 2, 'system:user:update', 2, 2, NULL, NULL, 1),
-(12, '删除用户', 2, 'system:user:delete', 2, 3, NULL, NULL, 1),
-(13, '新增流程', 2, 'flow:designer:create', 5, 1, NULL, NULL, 1),
-(14, '发布流程', 2, 'flow:designer:publish', 5, 2, NULL, NULL, 1),
-(15, '审批通过', 2, 'flow:task:approve', 8, 1, NULL, NULL, 1),
-(16, '审批拒绝', 2, 'flow:task:reject', 8, 2, NULL, NULL, 1);
-
-REPLACE INTO sys_user_role (id, user_id, role_id) VALUES
+MERGE INTO sys_user_role (id, user_id, role_id) KEY(id) VALUES
 (1, 1, 1),
 (1, 1, 1),
 (2, 2, 4),
 (2, 2, 4),
 (3, 3, 3),
 (3, 3, 3),
 (4, 4, 3);
 (4, 4, 3);
 
 
-REPLACE INTO sys_role_menu (id, role_id, menu_id) VALUES
-(1, 1, 1), (2, 1, 2), (3, 1, 3), (4, 1, 4), (5, 1, 5), (6, 1, 6), (7, 1, 7), (8, 1, 8), (9, 1, 9),
-(10, 1, 10), (11, 1, 11), (12, 1, 12), (13, 1, 13), (14, 1, 14), (15, 1, 15), (16, 1, 16), (32, 1, 17),
-(17, 2, 4), (18, 2, 5), (19, 2, 6), (20, 2, 13), (21, 2, 14),
-(22, 3, 7), (23, 3, 8), (24, 3, 9), (25, 3, 15), (26, 3, 16),
-(27, 4, 7), (28, 4, 8), (29, 4, 9), (30, 4, 15), (31, 4, 16);
+
+

+ 14 - 37
src/main/resources/data-test.sql

@@ -1,4 +1,4 @@
-REPLACE INTO sys_dept (id, dept_name, dept_code, parent_id, sort_order, status) VALUES
+MERGE INTO sys_dept (id, dept_name, dept_code, parent_id, sort_order, status) KEY(id) VALUES
 (1, '总裁办', 'CEO', 0, 1, 1),
 (1, '总裁办', 'CEO', 0, 1, 1),
 (2, '技术部', 'TECH', 0, 2, 1),
 (2, '技术部', 'TECH', 0, 2, 1),
 (3, '财务部', 'FIN', 0, 3, 1),
 (3, '财务部', 'FIN', 0, 3, 1),
@@ -6,46 +6,23 @@ REPLACE INTO sys_dept (id, dept_name, dept_code, parent_id, sort_order, status)
 (5, '前端组', 'FE', 2, 1, 1),
 (5, '前端组', 'FE', 2, 1, 1),
 (6, '后端组', 'BE', 2, 2, 1);
 (6, '后端组', 'BE', 2, 2, 1);
 
 
-REPLACE INTO sys_user (id, username, password, real_name, phone, email, dept_id, employee_type, status) VALUES
-(1, 'admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '超级管理员', '13800138000', 'admin@qqflow.com', 1, 'super_admin', 0),
-(2, 'zhangsan', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '张三', '13800138001', 'zhangsan@qqflow.com', 2, 'common_user', 0),
-(3, 'lisi', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '李四', '13800138002', 'lisi@qqflow.com', 3, 'dept_manager', 0),
-(4, 'wangwu', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', '王五', '13800138003', 'wangwu@qqflow.com', 4, 'flow_manager', 0);
+MERGE INTO sys_user (id, username, password, real_name, phone, email, dept_id, employee_type, status) KEY(id) VALUES
+(1, 'admin', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '超级管理员', '13800138000', 'admin@qqflow.com', 1, 'super_admin', 0),
+(2, 'zhangsan', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '张三', '13800138001', 'zhangsan@qqflow.com', 2, 'common_user', 0),
+(3, 'lisi', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '李四', '13800138002', 'lisi@qqflow.com', 3, 'dept_manager', 0),
+(4, 'wangwu', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', '王五', '13800138003', 'wangwu@qqflow.com', 4, 'flow_manager', 0);
 
 
-REPLACE INTO sys_role (id, role_code, role_name, role_scope, parent_id, dept_id, username, password, status) VALUES
-(1, 'super_admin', '超级管理员', 'platform', 0, 1, 'role_super_admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
-(2, 'flow_admin', '流程管理员', 'tenant', 0, 1, 'role_flow_admin', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
-(3, 'normal_user', '普通用户', 'tenant', 0, 2, 'role_normal_user', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1),
-(4, 'dept_manager', '部门经理', 'tenant', 0, 3, 'role_dept_manager', '$2a$10$Wnh8Jnt2AGHNNu1XTyyNveqkXvE1BnuzvHt1xS5AL3YdDif/7iXta', 1);
+MERGE INTO sys_role (id, role_code, role_name, role_scope, parent_id, dept_id, username, password, status) KEY(id) VALUES
+(1, 'super_admin', '超级管理员', 'platform', 0, 1, 'role_super_admin', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1),
+(2, 'flow_admin', '流程管理员', 'tenant', 0, 1, 'role_flow_admin', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1),
+(3, 'normal_user', '普通用户', 'tenant', 0, 2, 'role_normal_user', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1),
+(4, 'dept_manager', '部门经理', 'tenant', 3, 3, 'role_dept_manager', '$2a$10$je3eVNDSkYWM929JHtmsEOL0eS4GB6s7UObxx91PmmppoFwnfmTty', 1);
 
 
-REPLACE INTO sys_menu (id, menu_name, menu_type, permission, parent_id, sort_order, component, icon, status) VALUES
-(1, '系统管理', 0, NULL, 0, 1, NULL, 'Setting', 1),
-(2, '用户管理', 1, 'system:user:list', 1, 1, 'system/user/index', 'User', 1),
-(3, '角色管理', 1, 'system:role:list', 1, 2, 'system/role/index', 'Role', 1),
-(4, '流程管理', 0, NULL, 0, 2, NULL, 'Connection', 1),
-(5, '流程设计', 1, 'flow:designer:list', 4, 1, 'flow/designer/index', 'Edit', 1),
-(6, '流程定义', 1, 'flow:definition:list', 4, 2, 'flow/definition/index', 'Document', 1),
-(7, '审批中心', 0, NULL, 0, 3, NULL, 'Stamp', 1),
-(8, '我的待办', 1, 'flow:task:todo', 7, 1, 'flow/task/todo', 'Bell', 1),
-(17, '我的已办', 1, 'flow:task:handled', 7, 2, 'flow/task/handled', 'CircleCheck', 1),
-(9, '我的流程', 1, 'flow:instance:mine', 7, 3, 'flow/instance/mine', 'List', 1),
-(10, '新增用户', 2, 'system:user:create', 2, 1, NULL, NULL, 1),
-(11, '编辑用户', 2, 'system:user:update', 2, 2, NULL, NULL, 1),
-(12, '删除用户', 2, 'system:user:delete', 2, 3, NULL, NULL, 1),
-(13, '新增流程', 2, 'flow:designer:create', 5, 1, NULL, NULL, 1),
-(14, '发布流程', 2, 'flow:designer:publish', 5, 2, NULL, NULL, 1),
-(15, '审批通过', 2, 'flow:task:approve', 8, 1, NULL, NULL, 1),
-(16, '审批拒绝', 2, 'flow:task:reject', 8, 2, NULL, NULL, 1);
-
-REPLACE INTO sys_user_role (id, user_id, role_id) VALUES
+MERGE INTO sys_user_role (id, user_id, role_id) KEY(id) VALUES
 (1, 1, 1),
 (1, 1, 1),
 (2, 2, 4),
 (2, 2, 4),
 (3, 3, 3),
 (3, 3, 3),
 (4, 4, 3);
 (4, 4, 3);
 
 
-REPLACE INTO sys_role_menu (id, role_id, menu_id) VALUES
-(1, 1, 1), (2, 1, 2), (3, 1, 3), (4, 1, 4), (5, 1, 5), (6, 1, 6), (7, 1, 7), (8, 1, 8), (9, 1, 9),
-(10, 1, 10), (11, 1, 11), (12, 1, 12), (13, 1, 13), (14, 1, 14), (15, 1, 15), (16, 1, 16), (32, 1, 17),
-(17, 2, 4), (18, 2, 5), (19, 2, 6), (20, 2, 13), (21, 2, 14),
-(22, 3, 7), (23, 3, 8), (24, 3, 9), (25, 3, 15), (26, 3, 16),
-(27, 4, 7), (28, 4, 8), (29, 4, 9), (30, 4, 15), (31, 4, 16);
+
+

+ 14 - 0
src/main/resources/mapper/flow/ApprovalTaskMapper.xml

@@ -67,4 +67,18 @@
         ORDER BY t.handle_time DESC
         ORDER BY t.handle_time DESC
     </select>
     </select>
 
 
+    <select id="selectCcList" resultMap="BaseResultMap">
+        SELECT t.*, pi.title as instance_title, pi.instance_no as instance_no, pd.process_name as process_name
+        FROM bpm_approval_task t
+        INNER JOIN bpm_process_instance pi ON t.instance_id = pi.id
+        LEFT JOIN bpm_process_definition pd ON pi.process_definition_id = pd.id
+        WHERE t.deleted = 0
+          AND t.assignee_id = #{assigneeId}
+          AND t.node_type = 'cc'
+        <if test="processName != null and processName != ''">
+            AND pd.process_name LIKE CONCAT('%', #{processName}, '%')
+        </if>
+        ORDER BY t.create_time DESC
+    </select>
+
 </mapper>
 </mapper>

+ 0 - 27
src/main/resources/mapper/system/SysMenuMapper.xml

@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.qqflow.engine.domain.system.mapper.SysMenuMapper">
-
-    <select id="selectMenusByUserId" resultType="com.qqflow.engine.domain.system.entity.SysMenu">
-        SELECT DISTINCT m.id, m.menu_name, m.menu_type, m.permission, m.parent_id,
-                        m.sort_order, m.component, m.icon, m.status
-        FROM sys_menu m
-        INNER JOIN sys_role_menu rm ON m.id = rm.menu_id
-        INNER JOIN sys_user_role ur ON rm.role_id = ur.role_id
-        WHERE ur.user_id = #{userId}
-          AND m.status = 1
-        ORDER BY m.sort_order
-    </select>
-
-    <select id="selectPermissionsByUserId" resultType="java.lang.String">
-        SELECT DISTINCT m.permission
-        FROM sys_menu m
-        INNER JOIN sys_role_menu rm ON m.id = rm.menu_id
-        INNER JOIN sys_user_role ur ON rm.role_id = ur.role_id
-        WHERE ur.user_id = #{userId}
-          AND m.status = 1
-          AND m.permission IS NOT NULL
-          AND m.permission != ''
-    </select>
-
-</mapper>

+ 0 - 11
src/main/resources/mapper/system/SysUserMapper.xml

@@ -10,15 +10,4 @@
           AND r.status = 1
           AND r.status = 1
     </select>
     </select>
 
 
-    <select id="selectPermissionsByUserId" resultType="java.lang.String">
-        SELECT DISTINCT m.permission
-        FROM sys_menu m
-        INNER JOIN sys_role_menu rm ON m.id = rm.menu_id
-        INNER JOIN sys_user_role ur ON rm.role_id = ur.role_id
-        WHERE ur.user_id = #{userId}
-          AND m.status = 1
-          AND m.permission IS NOT NULL
-          AND m.permission != ''
-    </select>
-
 </mapper>
 </mapper>

+ 4 - 21
src/main/resources/schema-dev.sql

@@ -25,18 +25,6 @@ CREATE TABLE IF NOT EXISTS sys_role (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
-CREATE TABLE IF NOT EXISTS sys_menu (
-    id BIGINT PRIMARY KEY AUTO_INCREMENT,
-    menu_name VARCHAR(50) NOT NULL,
-    menu_type TINYINT,
-    permission VARCHAR(100),
-    parent_id BIGINT DEFAULT 0,
-    sort_order INT DEFAULT 0,
-    component VARCHAR(200),
-    icon VARCHAR(50),
-    status TINYINT DEFAULT 1
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
 CREATE TABLE IF NOT EXISTS sys_user_role (
 CREATE TABLE IF NOT EXISTS sys_user_role (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     user_id BIGINT NOT NULL,
     user_id BIGINT NOT NULL,
@@ -44,13 +32,6 @@ CREATE TABLE IF NOT EXISTS sys_user_role (
     UNIQUE KEY uk_user_role (user_id, role_id)
     UNIQUE KEY uk_user_role (user_id, role_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
-CREATE TABLE IF NOT EXISTS sys_role_menu (
-    id BIGINT PRIMARY KEY AUTO_INCREMENT,
-    role_id BIGINT NOT NULL,
-    menu_id BIGINT NOT NULL,
-    UNIQUE KEY uk_role_menu (role_id, menu_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
 CREATE TABLE IF NOT EXISTS sys_dept (
 CREATE TABLE IF NOT EXISTS sys_dept (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     dept_name VARCHAR(50) NOT NULL,
     dept_name VARCHAR(50) NOT NULL,
@@ -64,7 +45,7 @@ CREATE TABLE IF NOT EXISTS sys_dept (
 
 
 CREATE TABLE IF NOT EXISTS bpm_process_definition (
 CREATE TABLE IF NOT EXISTS bpm_process_definition (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
-    process_code VARCHAR(50) NOT NULL UNIQUE,
+    process_code VARCHAR(50) NOT NULL,
     process_name VARCHAR(100) NOT NULL,
     process_name VARCHAR(100) NOT NULL,
     category VARCHAR(50),
     category VARCHAR(50),
     form_id BIGINT,
     form_id BIGINT,
@@ -76,7 +57,8 @@ CREATE TABLE IF NOT EXISTS bpm_process_definition (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     update_by BIGINT,
     update_by BIGINT,
     update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    deleted TINYINT DEFAULT 0
+    deleted TINYINT DEFAULT 0,
+    UNIQUE KEY uk_process_code_version (process_code, version)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
 CREATE TABLE IF NOT EXISTS bpm_process_instance (
 CREATE TABLE IF NOT EXISTS bpm_process_instance (
@@ -88,6 +70,7 @@ CREATE TABLE IF NOT EXISTS bpm_process_instance (
     applicant_id BIGINT NOT NULL,
     applicant_id BIGINT NOT NULL,
     applicant_dept_id BIGINT,
     applicant_dept_id BIGINT,
     form_data TEXT,
     form_data TEXT,
+    attachment_urls TEXT,
     current_node_id VARCHAR(50),
     current_node_id VARCHAR(50),
     status TINYINT DEFAULT 0,
     status TINYINT DEFAULT 0,
     result VARCHAR(20),
     result VARCHAR(20),

+ 6 - 29
src/main/resources/schema-mysql.sql

@@ -35,22 +35,7 @@ CREATE TABLE IF NOT EXISTS sys_role (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统角色表';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统角色表';
 
 
 -- ----------------------------
 -- ----------------------------
--- 3. 系统菜单表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS sys_menu (
-    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '菜单ID',
-    menu_name VARCHAR(50) NOT NULL COMMENT '菜单名称',
-    menu_type TINYINT COMMENT '菜单类型:1-目录,2-菜单,3-按钮',
-    permission VARCHAR(100) COMMENT '权限标识,如 sys:user:add',
-    parent_id BIGINT DEFAULT 0 COMMENT '父菜单ID,0表示顶级菜单',
-    sort_order INT DEFAULT 0 COMMENT '排序号,数字越小越靠前',
-    component VARCHAR(200) COMMENT '前端组件路径',
-    icon VARCHAR(50) COMMENT '菜单图标',
-    status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统菜单表';
-
--- ----------------------------
--- 4. 用户角色关联表
+-- 3. 用户角色关联表
 -- ----------------------------
 -- ----------------------------
 CREATE TABLE IF NOT EXISTS sys_user_role (
 CREATE TABLE IF NOT EXISTS sys_user_role (
     id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '关联ID',
     id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '关联ID',
@@ -60,17 +45,7 @@ CREATE TABLE IF NOT EXISTS sys_user_role (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';
 
 
 -- ----------------------------
 -- ----------------------------
--- 5. 角色菜单关联表
--- ----------------------------
-CREATE TABLE IF NOT EXISTS sys_role_menu (
-    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '关联ID',
-    role_id BIGINT NOT NULL COMMENT '角色ID',
-    menu_id BIGINT NOT NULL COMMENT '菜单ID',
-    UNIQUE KEY uk_role_menu (role_id, menu_id) COMMENT '角色菜单唯一索引'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色菜单关联表';
-
--- ----------------------------
--- 6. 部门表
+-- 4. 部门表
 -- ----------------------------
 -- ----------------------------
 CREATE TABLE IF NOT EXISTS sys_dept (
 CREATE TABLE IF NOT EXISTS sys_dept (
     id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',
     id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '部门ID',
@@ -88,7 +63,7 @@ CREATE TABLE IF NOT EXISTS sys_dept (
 -- ----------------------------
 -- ----------------------------
 CREATE TABLE IF NOT EXISTS bpm_process_definition (
 CREATE TABLE IF NOT EXISTS bpm_process_definition (
     id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '流程定义ID',
     id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '流程定义ID',
-    process_code VARCHAR(50) NOT NULL UNIQUE COMMENT '流程编码',
+    process_code VARCHAR(50) NOT NULL COMMENT '流程编码',
     process_name VARCHAR(100) NOT NULL COMMENT '流程名称',
     process_name VARCHAR(100) NOT NULL COMMENT '流程名称',
     category VARCHAR(50) COMMENT '流程分类',
     category VARCHAR(50) COMMENT '流程分类',
     form_id BIGINT COMMENT '关联表单ID',
     form_id BIGINT COMMENT '关联表单ID',
@@ -100,7 +75,8 @@ CREATE TABLE IF NOT EXISTS bpm_process_definition (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     update_by BIGINT COMMENT '更新人ID',
     update_by BIGINT COMMENT '更新人ID',
     update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
     update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-    deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-未删除,1-已删除'
+    deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-未删除,1-已删除',
+    UNIQUE KEY uk_process_code_version (process_code, version) COMMENT '流程编码+版本唯一索引'
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程定义表';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程定义表';
 
 
 -- ----------------------------
 -- ----------------------------
@@ -115,6 +91,7 @@ CREATE TABLE IF NOT EXISTS bpm_process_instance (
     applicant_id BIGINT NOT NULL COMMENT '申请人ID',
     applicant_id BIGINT NOT NULL COMMENT '申请人ID',
     applicant_dept_id BIGINT COMMENT '申请人部门ID',
     applicant_dept_id BIGINT COMMENT '申请人部门ID',
     form_data TEXT COMMENT '表单数据JSON',
     form_data TEXT COMMENT '表单数据JSON',
+    attachment_urls TEXT COMMENT '附件URL列表JSON',
     current_node_id VARCHAR(50) COMMENT '当前节点ID',
     current_node_id VARCHAR(50) COMMENT '当前节点ID',
     status TINYINT DEFAULT 0 COMMENT '实例状态:0-进行中,1-已完成,2-已驳回,3-已撤回',
     status TINYINT DEFAULT 0 COMMENT '实例状态:0-进行中,1-已完成,2-已驳回,3-已撤回',
     result TINYINT COMMENT '审批结果:0-不通过,1-通过',
     result TINYINT COMMENT '审批结果:0-不通过,1-通过',

+ 4 - 21
src/main/resources/schema-test.sql

@@ -25,18 +25,6 @@ CREATE TABLE IF NOT EXISTS sys_role (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
-CREATE TABLE IF NOT EXISTS sys_menu (
-    id BIGINT PRIMARY KEY AUTO_INCREMENT,
-    menu_name VARCHAR(50) NOT NULL,
-    menu_type TINYINT,
-    permission VARCHAR(100),
-    parent_id BIGINT DEFAULT 0,
-    sort_order INT DEFAULT 0,
-    component VARCHAR(200),
-    icon VARCHAR(50),
-    status TINYINT DEFAULT 1
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
 CREATE TABLE IF NOT EXISTS sys_user_role (
 CREATE TABLE IF NOT EXISTS sys_user_role (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     user_id BIGINT NOT NULL,
     user_id BIGINT NOT NULL,
@@ -44,13 +32,6 @@ CREATE TABLE IF NOT EXISTS sys_user_role (
     UNIQUE KEY uk_user_role (user_id, role_id)
     UNIQUE KEY uk_user_role (user_id, role_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
-CREATE TABLE IF NOT EXISTS sys_role_menu (
-    id BIGINT PRIMARY KEY AUTO_INCREMENT,
-    role_id BIGINT NOT NULL,
-    menu_id BIGINT NOT NULL,
-    UNIQUE KEY uk_role_menu (role_id, menu_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
 CREATE TABLE IF NOT EXISTS sys_dept (
 CREATE TABLE IF NOT EXISTS sys_dept (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     dept_name VARCHAR(50) NOT NULL,
     dept_name VARCHAR(50) NOT NULL,
@@ -64,7 +45,7 @@ CREATE TABLE IF NOT EXISTS sys_dept (
 
 
 CREATE TABLE IF NOT EXISTS bpm_process_definition (
 CREATE TABLE IF NOT EXISTS bpm_process_definition (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
-    process_code VARCHAR(50) NOT NULL UNIQUE,
+    process_code VARCHAR(50) NOT NULL,
     process_name VARCHAR(100) NOT NULL,
     process_name VARCHAR(100) NOT NULL,
     category VARCHAR(50),
     category VARCHAR(50),
     form_id BIGINT,
     form_id BIGINT,
@@ -76,7 +57,8 @@ CREATE TABLE IF NOT EXISTS bpm_process_definition (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     update_by BIGINT,
     update_by BIGINT,
     update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    deleted TINYINT DEFAULT 0
+    deleted TINYINT DEFAULT 0,
+    UNIQUE KEY uk_process_code_version (process_code, version)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
 CREATE TABLE IF NOT EXISTS bpm_process_instance (
 CREATE TABLE IF NOT EXISTS bpm_process_instance (
@@ -88,6 +70,7 @@ CREATE TABLE IF NOT EXISTS bpm_process_instance (
     applicant_id BIGINT NOT NULL,
     applicant_id BIGINT NOT NULL,
     applicant_dept_id BIGINT,
     applicant_dept_id BIGINT,
     form_data TEXT,
     form_data TEXT,
+    attachment_urls TEXT,
     current_node_id VARCHAR(50),
     current_node_id VARCHAR(50),
     status TINYINT DEFAULT 0,
     status TINYINT DEFAULT 0,
     result VARCHAR(20),
     result VARCHAR(20),

+ 2 - 1
src/test/java/com/qqflow/engine/domain/flow/service/FlowEngineServiceTest.java

@@ -15,6 +15,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.context.ActiveProfiles;
 
 
 import java.util.Collections;
 import java.util.Collections;
+import java.util.List;
 
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 
@@ -36,7 +37,7 @@ class FlowEngineServiceTest {
         LoginUser loginUser = new LoginUser();
         LoginUser loginUser = new LoginUser();
         loginUser.setUserId(1L);
         loginUser.setUserId(1L);
         loginUser.setUsername("admin");
         loginUser.setUsername("admin");
-        loginUser.setPermissions(Collections.singleton("*:*:*"));
+        loginUser.setRoles(Collections.singletonList("super_admin"));
         UsernamePasswordAuthenticationToken authentication =
         UsernamePasswordAuthenticationToken authentication =
                 new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                 new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
         SecurityContextHolder.getContext().setAuthentication(authentication);
         SecurityContextHolder.getContext().setAuthentication(authentication);