Jelajahi Sumber

feat: v0.1.0

ye-zhaojia 1 Minggu lalu
induk
melakukan
58e87bb403
47 mengubah file dengan 650 tambahan dan 673 penghapusan
  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>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
-            <optional>true</optional>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -126,6 +126,24 @@
                     </excludes>
                 </configuration>
             </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>
     </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.util.Date;
 import java.util.List;
-import java.util.Set;
 import java.util.UUID;
 
 @Component
@@ -39,7 +38,6 @@ public class JwtUtils {
                 .claim("deptId", loginUser.getDeptId())
                 .claim("employeeType", loginUser.getEmployeeType())
                 .claim("userType", loginUser.getUserType())
-                .claim("permissions", loginUser.getPermissions())
                 .claim("roles", loginUser.getRoles())
                 .issuedAt(now)
                 .expiration(expiry)
@@ -73,8 +71,6 @@ public class JwtUtils {
         user.setDeptId(claims.get("deptId", Long.class));
         user.setEmployeeType(claims.get("employeeType", 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);
         user.setRoles(roles != null ? roles : null);
         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.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 @Configuration
@@ -16,4 +17,10 @@ public class WebConfig implements WebMvcConfigurer {
                 .allowCredentials(true)
                 .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.List;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 @Data
@@ -20,7 +19,6 @@ public class LoginUser implements UserDetails {
     private Long deptId;
     private String employeeType;
     private String userType; // SYSTEM / ROLE
-    private Set<String> permissions;
     private List<String> roles;
 
     @JsonIgnore
@@ -29,8 +27,8 @@ public class LoginUser implements UserDetails {
     @Override
     @JsonIgnore
     public Collection<? extends GrantedAuthority> getAuthorities() {
-        return permissions.stream()
-                .map(SimpleGrantedAuthority::new)
+        return roles.stream()
+                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                 .collect(Collectors.toList());
     }
 
@@ -59,6 +57,6 @@ public class LoginUser implements UserDetails {
     }
 
     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,
                                      Integer version, String title, Long applicantId,
                                      Long applicantDeptId, String formData,
-                                     String currentNodeId, Integer status) {
+                                     String attachmentUrls, Integer status) {
         ProcessInstance po = new ProcessInstance();
         po.setInstanceNo(instanceNo);
         po.setProcessDefinitionId(processDefinitionId);
@@ -20,7 +20,7 @@ public class ProcessInstanceAssembler {
         po.setApplicantId(applicantId);
         po.setApplicantDeptId(applicantDeptId);
         po.setFormData(formData);
-        po.setCurrentNodeId(currentNodeId);
+        po.setAttachmentUrls(attachmentUrls);
         po.setStatus(status);
         po.setStartTime(LocalDateTime.now());
         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();
         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")
     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")
     private String formData;
 
+    @Schema(description = "附件URL列表JSON")
+    private String attachmentUrls;
+
     @Schema(description = "当前节点ID")
     private String currentNodeId;
 
@@ -77,6 +80,7 @@ public class ProcessInstanceDTO {
         dto.setApplicantId(po.getApplicantId());
         dto.setApplicantDeptId(po.getApplicantDeptId());
         dto.setFormData(po.getFormData());
+        dto.setAttachmentUrls(po.getAttachmentUrls());
         dto.setCurrentNodeId(po.getCurrentNodeId());
         dto.setStatus(po.getStatus());
         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")
     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("assigneeType") String assigneeType,
             @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")
     private String formData;
 
+    @TableField("attachment_urls")
+    @Schema(description = "附件URL列表JSON")
+    private String attachmentUrls;
+
     @TableField("current_node_id")
     @Schema(description = "当前节点ID")
     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);
 
     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, ProcessInstance instance);
+
     List<Long> calculateAssignees(FlowNode node, ProcessInstance instance);
 
     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, String targetNodeId);
+
     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.service.ApprovalTaskService;
 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 org.springframework.context.ApplicationEventPublisher;
 import org.springframework.stereotype.Service;
@@ -43,6 +45,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     private final ApprovalTaskAssembler approvalTaskAssembler;
     private final FlowEngineService flowEngineService;
     private final ApplicationEventPublisher eventPublisher;
+    private final SysUserRoleMapper sysUserRoleMapper;
 
     @Override
     public PageResult<ApprovalTaskDTO> todoList(Long assigneeId, String assigneeType, String processName, Integer pageNum, Integer pageSize) {
@@ -68,6 +71,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     public void approve(ApproveTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         this.saveRecord(task, instance, operatorId, ApprovalAction.APPROVE, ApprovalResult.PASS.getCode(), dto.getComment(), dto.getAttachmentUrls());
@@ -79,6 +83,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     public void reject(ApproveTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         this.saveRecord(task, instance, operatorId, ApprovalAction.REJECT, ApprovalResult.REJECT.getCode(), dto.getComment(), dto.getAttachmentUrls());
@@ -90,10 +95,11 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     public void returnTask(ApproveTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         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());
     }
 
@@ -101,6 +107,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     public void transfer(TransferTaskDTO dto) {
         ApprovalTask task = this.getPendingTask(dto.getTaskId());
+        this.checkTaskPermission(task);
         ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
         Long operatorId = SecurityUtils.getUserId();
         Long transferToUserId = dto.getTransferToUserId() != null ? dto.getTransferToUserId() : dto.getTransferTo();
@@ -114,6 +121,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     @Transactional
     public void addSign(Long taskId, Long assigneeId) {
         ApprovalTask task = this.getPendingTask(taskId);
+        this.checkTaskPermission(task);
         ApprovalTask newTask = this.approvalTaskAssembler.buildNew(
                 task.getInstanceId(), task.getNodeId(), task.getNodeName(),
                 task.getNodeType(), assigneeId, task.getAssigneeType(), TaskStatus.PENDING.getCode()
@@ -132,6 +140,37 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     }
 
     @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) {
         com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalTask> wrapper =
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ApprovalTask>();
@@ -173,6 +212,30 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
                 || 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,
                             ApprovalAction action, String result, String comment, String attachmentUrls) {
         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
     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))
-                .map(FlowEdge::getTargetNodeId)
                 .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()
                 .filter(n -> targetIds.contains(n.getId()))
                 .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
     public List<Long> calculateAssignees(FlowNode node, ProcessInstance instance) {
         Map<String, Object> props = node.getProperties();
@@ -122,10 +182,15 @@ public class FlowEngineServiceImpl implements FlowEngineService {
 
     @Override
     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) {
             case APPROVE -> this.handleApprove(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("不支持的操作类型");
         }
     }
@@ -134,7 +199,7 @@ public class FlowEngineServiceImpl implements FlowEngineService {
     public void startInstance(ProcessInstance instance, ProcessDefinition definition) {
         FlowModel model = this.parseModel(definition.getModelJson());
         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.updateInstanceNode(instance, nextNodes);
     }
@@ -155,8 +220,14 @@ public class FlowEngineServiceImpl implements FlowEngineService {
             if (role == null) {
                 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)) {
             SysUser applicant = this.sysUserMapper.selectById(instance.getApplicantId());
@@ -204,7 +275,7 @@ public class FlowEngineServiceImpl implements FlowEngineService {
                             .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)) {
             this.completeInstance(instance);
             return;
@@ -218,14 +289,23 @@ public class FlowEngineServiceImpl implements FlowEngineService {
         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);
         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("无法找到回退目标节点");
         }
-        List<FlowNode> returnTargets = Collections.singletonList(prevNode);
+        List<FlowNode> returnTargets = Collections.singletonList(targetNode);
         this.createTasksForNodes(instance, returnTargets, model);
         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())) {
                 continue;
             }
-            // CC节点:不创建审批任务,仅记录(后续可扩展抄送通知)
+            // CC节点:创建抄送任务并自动标记为已阅,同时继续推进下游
             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;
             }
             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.domain.flow.dto.ProcessDefinitionDTO;
 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.ProcessInstanceMapper;
 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 lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -21,9 +25,19 @@ import java.util.stream.Collectors;
 public class ProcessDefinitionServiceImpl implements ProcessDefinitionService {
 
     private final ProcessDefinitionMapper processDefinitionMapper;
+    private final ProcessInstanceMapper processInstanceMapper;
 
     @Override
     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);
         return po.getId();
     }
@@ -65,7 +79,24 @@ public class ProcessDefinitionServiceImpl implements ProcessDefinitionService {
     @Override
     public void deleteDefinition(Long 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);
     }
 

+ 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) {
         ProcessDefinition definition = this.getEnabledDefinition(dto.getProcessDefinitionId());
         ProcessInstance instance = this.buildInstance(dto, definition);
+        instance.setAttachmentUrls(dto.getAttachmentUrls());
         this.processInstanceMapper.insert(instance);
         this.flowEngineService.startInstance(instance, definition);
         return instance.getId();
@@ -85,6 +86,10 @@ public class ProcessInstanceServiceImpl implements ProcessInstanceService {
     public void revoke(Long id) {
         ProcessInstance instance = this.processInstanceMapper.selectById(id);
         this.validateRevocable(instance);
+        Long operatorId = SecurityUtils.getUserId();
+        if (instance != null && !instance.getApplicantId().equals(operatorId)) {
+            throw new BusinessException("无权撤回他人发起的流程");
+        }
         this.processInstanceMapper.update(null, Wrappers.<ProcessInstance>lambdaUpdate()
                 .eq(ProcessInstance::getId, id)
                 .set(ProcessInstance::getStatus, ProcessStatus.REVOKED.getCode())
@@ -202,7 +207,7 @@ public class ProcessInstanceServiceImpl implements ProcessInstanceService {
                 userId,
                 deptId,
                 dto.getFormData(),
-                null,
+                dto.getAttachmentUrls(),
                 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.TableId;
 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 lombok.Data;
 
@@ -36,7 +36,7 @@ public class SysRole {
     @Schema(description = "登录账号")
     private String username;
 
-    @JsonIgnore
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
     @Schema(description = "密码")
     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> 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 java.util.List;
-import java.util.Set;
 
 @Service
 public class RoleAuthService {
@@ -36,13 +35,6 @@ public class RoleAuthService {
         loginUser.setUsername(role.getUsername());
         loginUser.setRealName(role.getRoleName());
         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()));
         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 java.util.List;
-import java.util.Set;
 
 public interface SysUserService extends IService<SysUser> {
 
@@ -23,8 +22,6 @@ public interface SysUserService extends IService<SysUser> {
 
     SysUser getByUsername(String username);
 
-    Set<String> loadUserPermissions(Long userId);
-
     List<String> loadUserRoles(Long userId);
 
     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.qqflow.engine.domain.system.entity.SysDept;
 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.mapper.SysDeptMapper;
 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.service.SysRoleService;
 import jakarta.annotation.Resource;
@@ -31,9 +29,6 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole>
     private SysUserRoleMapper sysUserRoleMapper;
 
     @Resource
-    private SysRoleMenuMapper sysRoleMenuMapper;
-
-    @Resource
     private SysDeptMapper sysDeptMapper;
 
     @Resource
@@ -65,10 +60,6 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole>
                 new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysUserRole>()
                         .eq(SysUserRole::getRoleId, id)
         );
-        sysRoleMenuMapper.delete(
-                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysRoleMenu>()
-                        .eq(SysRoleMenu::getRoleId, 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.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 @Service
@@ -194,12 +193,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
     }
 
     @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) {
         return this.baseMapper.selectRoleCodesByUserId(userId);
     }
@@ -238,6 +231,9 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
         if (user == null) {
             throw new UsernameNotFoundException("用户不存在");
         }
+        if (user.getStatus() != null && user.getStatus() != 0) {
+            throw new UsernameNotFoundException("用户已被禁用");
+        }
         LoginUser loginUser = new LoginUser();
         loginUser.setUserId(user.getId());
         loginUser.setUsername(user.getUsername());
@@ -246,7 +242,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser>
         loginUser.setEmployeeType(user.getEmployeeType());
         loginUser.setUserType("SYSTEM");
         loginUser.setPassword(user.getPassword());
-        loginUser.setPermissions(this.loadUserPermissions(user.getId()));
         loginUser.setRoles(this.loadUserRoles(user.getId()));
         return loginUser;
     }

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

@@ -3,15 +3,21 @@ server:
 
 spring:
   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:
     init:
       mode: always
       schema-locations: classpath:schema-dev.sql
       data-locations: classpath:data-dev.sql
+  h2:
+    console:
+      enabled: true
+      path: /h2-console
+      settings:
+        web-allow-others: true
   data:
     redis:
       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),
 (2, '技术部', 'TECH', 0, 2, 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),
 (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),
 (2, 2, 4),
 (3, 3, 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),
 (2, '技术部', 'TECH', 0, 2, 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),
 (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),
 (2, 2, 4),
 (3, 3, 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
     </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>

+ 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
     </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>

+ 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
 ) 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 (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     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)
 ) 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 (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     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 (
     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,
     category VARCHAR(50),
     form_id BIGINT,
@@ -76,7 +57,8 @@ CREATE TABLE IF NOT EXISTS bpm_process_definition (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     update_by BIGINT,
     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;
 
 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_dept_id BIGINT,
     form_data TEXT,
+    attachment_urls TEXT,
     current_node_id VARCHAR(50),
     status TINYINT DEFAULT 0,
     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='系统角色表';
 
 -- ----------------------------
--- 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 (
     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='用户角色关联表';
 
 -- ----------------------------
--- 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 (
     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 (
     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 '流程名称',
     category VARCHAR(50) COMMENT '流程分类',
     form_id BIGINT COMMENT '关联表单ID',
@@ -100,7 +75,8 @@ CREATE TABLE IF NOT EXISTS bpm_process_definition (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     update_by BIGINT COMMENT '更新人ID',
     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='流程定义表';
 
 -- ----------------------------
@@ -115,6 +91,7 @@ CREATE TABLE IF NOT EXISTS bpm_process_instance (
     applicant_id BIGINT NOT NULL COMMENT '申请人ID',
     applicant_dept_id BIGINT COMMENT '申请人部门ID',
     form_data TEXT COMMENT '表单数据JSON',
+    attachment_urls TEXT COMMENT '附件URL列表JSON',
     current_node_id VARCHAR(50) COMMENT '当前节点ID',
     status TINYINT DEFAULT 0 COMMENT '实例状态:0-进行中,1-已完成,2-已驳回,3-已撤回',
     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
 ) 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 (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     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)
 ) 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 (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     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 (
     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,
     category VARCHAR(50),
     form_id BIGINT,
@@ -76,7 +57,8 @@ CREATE TABLE IF NOT EXISTS bpm_process_definition (
     create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     update_by BIGINT,
     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;
 
 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_dept_id BIGINT,
     form_data TEXT,
+    attachment_urls TEXT,
     current_node_id VARCHAR(50),
     status TINYINT DEFAULT 0,
     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 java.util.Collections;
+import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -36,7 +37,7 @@ class FlowEngineServiceTest {
         LoginUser loginUser = new LoginUser();
         loginUser.setUserId(1L);
         loginUser.setUsername("admin");
-        loginUser.setPermissions(Collections.singleton("*:*:*"));
+        loginUser.setRoles(Collections.singletonList("super_admin"));
         UsernamePasswordAuthenticationToken authentication =
                 new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
         SecurityContextHolder.getContext().setAuthentication(authentication);