增加生成后端服务代码。
This commit is contained in:
parent
e0d58311c8
commit
7826d4ec27
@ -154,7 +154,7 @@ public class DeployAppConfigServiceImpl extends BaseServiceImpl<DeployAppConfig,
|
||||
WorkflowInstanceStartRequest request = new WorkflowInstanceStartRequest();
|
||||
request.setProcessKey(workflowDefinition.getKey());
|
||||
request.setBusinessKey(environment.getEnvCode() + "_" + application.getAppCode() + "_" + System.currentTimeMillis() / 1000);
|
||||
request.setVariables(objectMapper.convertValue(dto.getBuildVariables(), new TypeReference<>() {
|
||||
request.setFormData(objectMapper.convertValue(dto.getBuildVariables(), new TypeReference<>() {
|
||||
}));
|
||||
WorkflowInstanceDTO workflowInstanceDTO = workflowInstanceService.startWorkflow(request);
|
||||
buildAndSaveDeployLog(dto, environment, application, workflowInstanceDTO);
|
||||
|
||||
@ -0,0 +1,140 @@
|
||||
package com.qqchen.deploy.backend.framework.security;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 自定义 UserDetails 实现
|
||||
* 扩展标准 UserDetails,包含用户ID、租户ID等额外信息
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-25
|
||||
*/
|
||||
@Getter
|
||||
public class CustomUserDetails implements UserDetails {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private final Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private final String password;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private final boolean enabled;
|
||||
|
||||
/**
|
||||
* 账户是否未过期
|
||||
*/
|
||||
private final boolean accountNonExpired;
|
||||
|
||||
/**
|
||||
* 账户是否未锁定
|
||||
*/
|
||||
private final boolean accountNonLocked;
|
||||
|
||||
/**
|
||||
* 凭证是否未过期
|
||||
*/
|
||||
private final boolean credentialsNonExpired;
|
||||
|
||||
/**
|
||||
* 权限列表
|
||||
*/
|
||||
private final Collection<? extends GrantedAuthority> authorities;
|
||||
|
||||
/**
|
||||
* 租户ID(可选)
|
||||
*/
|
||||
private final Long tenantId;
|
||||
|
||||
/**
|
||||
* 部门ID(可选)
|
||||
*/
|
||||
private final Long departmentId;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public CustomUserDetails(Long userId,
|
||||
String username,
|
||||
String password,
|
||||
boolean enabled,
|
||||
boolean accountNonExpired,
|
||||
boolean accountNonLocked,
|
||||
boolean credentialsNonExpired,
|
||||
Collection<? extends GrantedAuthority> authorities,
|
||||
Long tenantId,
|
||||
Long departmentId) {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.enabled = enabled;
|
||||
this.accountNonExpired = accountNonExpired;
|
||||
this.accountNonLocked = accountNonLocked;
|
||||
this.credentialsNonExpired = credentialsNonExpired;
|
||||
this.authorities = authorities;
|
||||
this.tenantId = tenantId;
|
||||
this.departmentId = departmentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化构造函数(不包含租户和部门信息)
|
||||
*/
|
||||
public CustomUserDetails(Long userId,
|
||||
String username,
|
||||
String password,
|
||||
boolean enabled,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
this(userId, username, password, enabled, true, true, true, authorities, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return accountNonExpired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return accountNonLocked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return credentialsNonExpired;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,10 +4,19 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
/**
|
||||
* Security 工具类
|
||||
* 提供获取当前用户信息、权限检查等功能
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-25
|
||||
*/
|
||||
public class SecurityUtils {
|
||||
|
||||
/**
|
||||
* 获取当前登录用户名
|
||||
*
|
||||
* @return 当前用户名,未认证时返回 "SYSTEM"
|
||||
*/
|
||||
public static String getCurrentUsername() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
@ -22,8 +31,79 @@ public class SecurityUtils {
|
||||
return principal.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID(字符串类型)
|
||||
*
|
||||
* @return 当前用户ID,未认证时返回 "system"
|
||||
*/
|
||||
public static String getCurrentUserIdAsString() {
|
||||
Long userId = getCurrentUserId();
|
||||
return userId != null ? userId.toString() : "system";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID(Long 类型)
|
||||
*
|
||||
* @return 当前用户ID,未认证时返回 null
|
||||
*/
|
||||
public static Long getCurrentUserId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof CustomUserDetails) {
|
||||
return ((CustomUserDetails) principal).getUserId();
|
||||
}
|
||||
|
||||
// 兼容标准 UserDetails(用于测试或特殊场景)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前租户ID
|
||||
*
|
||||
* @return 当前租户ID,未认证或未设置时返回 null
|
||||
*/
|
||||
public static Long getCurrentTenantId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof CustomUserDetails) {
|
||||
return ((CustomUserDetails) principal).getTenantId();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前部门ID
|
||||
*
|
||||
* @return 当前部门ID,未认证或未设置时返回 null
|
||||
*/
|
||||
public static Long getCurrentDepartmentId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof CustomUserDetails) {
|
||||
return ((CustomUserDetails) principal).getDepartmentId();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有指定权限
|
||||
*
|
||||
* @param permission 权限标识
|
||||
* @return 是否拥有该权限
|
||||
*/
|
||||
public static boolean hasPermission(String permission) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
@ -33,22 +113,10 @@ public class SecurityUtils {
|
||||
.anyMatch(auth -> auth.getAuthority().equals(permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*/
|
||||
public static Long getCurrentUserId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof UserDetails) {
|
||||
return Long.valueOf(((UserDetails) principal).getUsername());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已认证
|
||||
*
|
||||
* @return 是否已认证
|
||||
*/
|
||||
public static boolean isAuthenticated() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.qqchen.deploy.backend.system.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.security.CustomUserDetails;
|
||||
import com.qqchen.deploy.backend.system.entity.User;
|
||||
import com.qqchen.deploy.backend.system.repository.IUserRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -12,6 +13,13 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 用户详情服务实现
|
||||
* 负责从数据库加载用户信息并转换为 Spring Security 的 UserDetails
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-25
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
@ -22,15 +30,15 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
User user = userRepository.findByUsernameAndDeletedFalse(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
|
||||
return new org.springframework.security.core.userdetails.User(
|
||||
user.getUsername(),
|
||||
user.getPassword(),
|
||||
user.getEnabled(),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
|
||||
.orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
|
||||
|
||||
// ✅ 返回自定义 UserDetails,包含用户ID等扩展信息
|
||||
return new CustomUserDetails(
|
||||
user.getId(), // 用户ID
|
||||
user.getUsername(), // 用户名
|
||||
user.getPassword(), // 密码
|
||||
user.getEnabled(), // 是否启用
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) // 权限(TODO: 从数据库加载实际权限)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,19 @@
|
||||
package com.qqchen.deploy.backend.workflow.converter;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||
import com.qqchen.deploy.backend.framework.security.SecurityUtils;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataCreateFromWorkflowRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.entity.FormData;
|
||||
import com.qqchen.deploy.backend.workflow.enums.FormBusinessTypeEnums;
|
||||
import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 表单数据转换器
|
||||
@ -13,5 +23,48 @@ import org.mapstruct.Mapper;
|
||||
*/
|
||||
@Mapper(config = BaseConverter.class)
|
||||
public interface FormDataConverter extends BaseConverter<FormData, FormDataDTO> {
|
||||
|
||||
/**
|
||||
* 从工作流启动创建表单数据 DTO
|
||||
* 从 FormDefinitionDTO 和 FormDataCreateFromWorkflowRequest 映射到 FormDataDTO
|
||||
*
|
||||
* @param formDefinition 表单定义
|
||||
* @param request 工作流表单数据请求
|
||||
* @return 表单数据 DTO
|
||||
*/
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "formDefinitionId", source = "formDefinition.id")
|
||||
@Mapping(target = "formKey", source = "formDefinition.key")
|
||||
@Mapping(target = "formVersion", source = "formDefinition.formVersion")
|
||||
@Mapping(target = "categoryId", source = "formDefinition.categoryId")
|
||||
@Mapping(target = "schemaSnapshot", source = "formDefinition.schema")
|
||||
@Mapping(target = "data", source = "request.formData")
|
||||
@Mapping(target = "businessKey", source = "request.businessKey")
|
||||
@Mapping(target = "businessType", constant = "WORKFLOW")
|
||||
@Mapping(target = "status", constant = "SUBMITTED")
|
||||
@Mapping(target = "submitter", ignore = true) // 在 @AfterMapping 中设置
|
||||
@Mapping(target = "submitTime", ignore = true) // 在 @AfterMapping 中设置
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createBy", ignore = true)
|
||||
@Mapping(target = "updateBy", ignore = true)
|
||||
@Mapping(target = "version", ignore = true)
|
||||
@Mapping(target = "deleted", ignore = true)
|
||||
FormDataDTO fromWorkflowRequest(
|
||||
FormDefinitionDTO formDefinition,
|
||||
FormDataCreateFromWorkflowRequest request
|
||||
);
|
||||
|
||||
/**
|
||||
* 后处理:设置动态值
|
||||
*/
|
||||
@AfterMapping
|
||||
default void setDynamicFields(@MappingTarget FormDataDTO formDataDTO) {
|
||||
// ✅ 设置当前用户
|
||||
formDataDTO.setSubmitter(SecurityUtils.getCurrentUserIdAsString());
|
||||
|
||||
// ✅ 设置提交时间
|
||||
formDataDTO.setSubmitTime(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,22 +4,76 @@ import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowHistoricalInstancesDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
|
||||
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Named;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.mapstruct.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 工作流定义转换器
|
||||
* 工作流实例转换器
|
||||
*/
|
||||
@Mapper(config = BaseConverter.class)
|
||||
public interface WorkflowInstanceConverter extends BaseConverter<WorkflowInstance, WorkflowInstanceDTO> {
|
||||
|
||||
|
||||
@Override
|
||||
@Named("toDto")
|
||||
WorkflowInstanceDTO toDto(WorkflowInstance entity);
|
||||
|
||||
@Named("toHistoricalDto")
|
||||
WorkflowHistoricalInstancesDTO toWorkflowHistoricalInstancesDTO(WorkflowInstance workflowInstance);
|
||||
|
||||
/**
|
||||
* 从多个源对象创建 WorkflowInstance
|
||||
*
|
||||
* @param request 工作流启动请求
|
||||
* @param processInstance Flowable 流程实例
|
||||
* @param definition 工作流定义
|
||||
* @param formDataId 表单数据ID(可选)
|
||||
* @return 工作流实例
|
||||
*/
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "processInstanceId", source = "processInstance.id")
|
||||
@Mapping(target = "processDefinitionId", source = "processInstance.processDefinitionId")
|
||||
@Mapping(target = "workflowDefinitionId", source = "definition.id")
|
||||
@Mapping(target = "businessKey", source = "request.businessKey")
|
||||
@Mapping(target = "graphSnapshot", source = "definition.graph")
|
||||
@Mapping(target = "formDataId", source = "formDataId")
|
||||
@Mapping(target = "formData", ignore = true) // 在 @AfterMapping 中处理
|
||||
@Mapping(target = "status", constant = "NOT_STARTED")
|
||||
@Mapping(target = "startTime", ignore = true) // 在 @AfterMapping 中设置
|
||||
@Mapping(target = "endTime", ignore = true)
|
||||
@Mapping(target = "createTime", ignore = true)
|
||||
@Mapping(target = "updateTime", ignore = true)
|
||||
@Mapping(target = "createBy", ignore = true)
|
||||
@Mapping(target = "updateBy", ignore = true)
|
||||
@Mapping(target = "version", ignore = true)
|
||||
@Mapping(target = "deleted", ignore = true)
|
||||
WorkflowInstance toEntity(
|
||||
WorkflowInstanceStartRequest request,
|
||||
ProcessInstance processInstance,
|
||||
WorkflowDefinition definition,
|
||||
Long formDataId
|
||||
);
|
||||
|
||||
/**
|
||||
* 后处理:设置 formData 和 startTime
|
||||
*/
|
||||
@AfterMapping
|
||||
default void afterMapping(
|
||||
WorkflowInstanceStartRequest request,
|
||||
Long formDataId,
|
||||
@MappingTarget WorkflowInstance workflowInstance
|
||||
) {
|
||||
// ✅ 只有在没有 FormData ID 时才保存 JSON
|
||||
if (formDataId == null && request.getFormData() != null) {
|
||||
workflowInstance.setFormData(request.getFormData());
|
||||
}
|
||||
|
||||
// ✅ 设置开始时间
|
||||
workflowInstance.setStartTime(LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
package com.qqchen.deploy.backend.workflow.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 从工作流启动创建表单数据请求
|
||||
*
|
||||
* @author qqchen
|
||||
* @date 2025-10-25
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "从工作流启动创建表单数据请求")
|
||||
public class FormDataCreateFromWorkflowRequest {
|
||||
|
||||
@Schema(description = "表单标识", required = true, example = "APPROVAL-AND-DEPLOYMENT")
|
||||
private String formKey;
|
||||
|
||||
@Schema(description = "表单填写数据", required = true)
|
||||
private Map<String, Object> formData;
|
||||
|
||||
@Schema(description = "业务标识(工作流实例的 businessKey)", required = true, example = "DEPLOY-2023-001")
|
||||
private String businessKey;
|
||||
}
|
||||
|
||||
@ -34,20 +34,16 @@ public class WorkflowInstanceDTO extends BaseDTO {
|
||||
*/
|
||||
private Long formDataId;
|
||||
|
||||
/**
|
||||
* 启动表单数据(用于展示)
|
||||
*/
|
||||
private FormDataDTO formData;
|
||||
|
||||
/**
|
||||
* 实例状态
|
||||
*/
|
||||
private WorkflowInstanceStatusEnums status;
|
||||
|
||||
/**
|
||||
* 流程变量(JSON)
|
||||
* 表单数据(JSON)
|
||||
* 存储流程启动时传入的表单变量
|
||||
*/
|
||||
private String variables;
|
||||
private Map<String, Object> formData;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
|
||||
@ -9,10 +9,16 @@ import java.util.Map;
|
||||
@Schema(description = "工作流实例启动入参")
|
||||
public class WorkflowInstanceStartRequest {
|
||||
|
||||
@Schema(description = "流程定义标识", example = "APPROVAL-AND-DEPLOYMENT")
|
||||
private String processKey;
|
||||
|
||||
@Schema(description = "业务键", example = "DEPLOY-2023-001")
|
||||
private String businessKey;
|
||||
|
||||
private Map<String, Object> variables;
|
||||
@Schema(description = "表单标识(可选,如果有则创建 FormData 记录)", example = "APPROVAL-AND-DEPLOYMENT")
|
||||
private String formKey;
|
||||
|
||||
@Schema(description = "表单数据")
|
||||
private Map<String, Object> formData;
|
||||
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||
import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph;
|
||||
import com.qqchen.deploy.backend.workflow.entity.converter.WorkflowGraphType;
|
||||
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
|
||||
import com.vladmihalcea.hibernate.type.json.JsonType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
@ -13,6 +14,7 @@ import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工作流实例实体
|
||||
@ -58,10 +60,12 @@ public class WorkflowInstance extends Entity<Long> {
|
||||
private WorkflowInstanceStatusEnums status;
|
||||
|
||||
/**
|
||||
* 流程变量(JSON)
|
||||
* 表单数据(JSON)
|
||||
* 存储流程启动时传入的表单变量
|
||||
*/
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String variables;
|
||||
@Type(JsonType.class)
|
||||
@Column(name = "form_data", columnDefinition = "json")
|
||||
private Map<String, Object> formData;
|
||||
|
||||
/**
|
||||
* 流程图数据快照(启动时保存,用于画布还原)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.qqchen.deploy.backend.workflow.service;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataCreateFromWorkflowRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery;
|
||||
import com.qqchen.deploy.backend.workflow.entity.FormData;
|
||||
@ -28,5 +29,14 @@ public interface IFormDataService extends IBaseService<FormData, FormDataDTO, Fo
|
||||
* @return 表单数据
|
||||
*/
|
||||
FormDataDTO getByBusinessKey(String businessKey);
|
||||
|
||||
/**
|
||||
* 从工作流启动创建表单数据
|
||||
* 该方法会自动查询表单定义,组装完整的 FormData 对象
|
||||
*
|
||||
* @param request 创建请求(包含 formKey、formData、businessKey)
|
||||
* @return 创建后的表单数据
|
||||
*/
|
||||
FormDataDTO createFromWorkflowStart(FormDataCreateFromWorkflowRequest request);
|
||||
}
|
||||
|
||||
|
||||
@ -22,10 +22,12 @@ public interface IWorkflowInstanceService extends IBaseService<WorkflowInstance,
|
||||
/**
|
||||
* 创建工作流实例并关联Flowable实例
|
||||
*
|
||||
* @param request 工作流实例启动请求
|
||||
* @param processInstance Flowable流程实例
|
||||
* @param formDataId 表单数据ID(可选,如果有则关联)
|
||||
* @return 工作流实例
|
||||
*/
|
||||
WorkflowInstanceDTO createWorkflowInstance(Long workflowDefinitionId, String businessKey, ProcessInstance processInstance);
|
||||
WorkflowInstanceDTO createWorkflowInstance(WorkflowInstanceStartRequest request, ProcessInstance processInstance, Long formDataId);
|
||||
|
||||
/**
|
||||
* 更新工作流实例状态
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package com.qqchen.deploy.backend.workflow.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataCreateFromWorkflowRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery;
|
||||
import com.qqchen.deploy.backend.workflow.entity.FormData;
|
||||
import com.qqchen.deploy.backend.workflow.entity.FormDefinition;
|
||||
@ -10,6 +13,7 @@ import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IFormDataRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IFormDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.IFormDataService;
|
||||
import com.qqchen.deploy.backend.workflow.service.IFormDefinitionService;
|
||||
import com.qqchen.deploy.backend.workflow.converter.FormDataConverter;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -34,6 +38,9 @@ public class FormDataServiceImpl extends BaseServiceImpl<FormData, FormDataDTO,
|
||||
@Resource
|
||||
private IFormDefinitionRepository formDefinitionRepository;
|
||||
|
||||
@Resource
|
||||
private IFormDefinitionService formDefinitionService;
|
||||
|
||||
@Resource
|
||||
private FormDataConverter formDataConverter;
|
||||
|
||||
@ -71,5 +78,27 @@ public class FormDataServiceImpl extends BaseServiceImpl<FormData, FormDataDTO,
|
||||
|
||||
return formDataConverter.toDto(formData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public FormDataDTO createFromWorkflowStart(FormDataCreateFromWorkflowRequest request) {
|
||||
log.info("从工作流启动创建表单数据: formKey={}, businessKey={}", request.getFormKey(), request.getBusinessKey());
|
||||
|
||||
// 1. 查询表单定义(获取最新版本)
|
||||
FormDefinitionDTO formDefinition = formDefinitionService.getLatestByKey(request.getFormKey());
|
||||
if (formDefinition == null) {
|
||||
throw new BusinessException(ResponseCode.FORM_DEFINITION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 2. 使用 MapStruct Converter 创建 FormDataDTO(处理多源对象映射)
|
||||
FormDataDTO formDataDTO = formDataConverter.fromWorkflowRequest(formDefinition, request);
|
||||
|
||||
// 3. 提交表单数据
|
||||
FormDataDTO savedFormData = submit(formDataDTO);
|
||||
log.info("工作流表单数据创建成功: id={}, formKey={}, formVersion={}",
|
||||
savedFormData.getId(), formDefinition.getKey(), formDefinition.getFormVersion());
|
||||
|
||||
return savedFormData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter;
|
||||
import com.qqchen.deploy.backend.workflow.converter.WorkflowNodeInstanceConverter;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataCreateFromWorkflowRequest;
|
||||
import com.qqchen.deploy.backend.workflow.dto.FormDataDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowHistoricalInstancesDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
|
||||
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest;
|
||||
@ -21,6 +23,8 @@ import com.qqchen.deploy.backend.workflow.dto.query.WorkflowHistoricalInstancesQ
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
|
||||
import com.qqchen.deploy.backend.workflow.repository.IWorkflowNodeInstanceRepository;
|
||||
import com.qqchen.deploy.backend.workflow.service.IFormDataService;
|
||||
import com.qqchen.deploy.backend.workflow.service.IFormDefinitionService;
|
||||
import com.qqchen.deploy.backend.workflow.service.IWorkflowInstanceService;
|
||||
import com.qqchen.deploy.backend.workflow.util.FlowableUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -55,6 +59,12 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
|
||||
@Resource
|
||||
private IWorkflowInstanceRepository workflowInstanceRepository;
|
||||
|
||||
@Resource
|
||||
private IFormDefinitionService formDefinitionService;
|
||||
|
||||
@Resource
|
||||
private IFormDataService formDataService;
|
||||
|
||||
@Resource
|
||||
private RuntimeService runtimeService;
|
||||
|
||||
@ -77,24 +87,22 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
|
||||
private WorkflowNodeInstanceConverter workflowNodeInstanceConverter;
|
||||
|
||||
@Override
|
||||
public WorkflowInstanceDTO createWorkflowInstance(Long workflowDefinitionId, String businessKey, ProcessInstance processInstance) {
|
||||
public WorkflowInstanceDTO createWorkflowInstance(WorkflowInstanceStartRequest request, ProcessInstance processInstance, Long formDataId) {
|
||||
// 1. 查询流程定义,获取 graph 快照
|
||||
WorkflowDefinition definition = workflowDefinitionRepository.findById(workflowDefinitionId)
|
||||
.orElseThrow(() -> new RuntimeException("Workflow definition not found: " + workflowDefinitionId));
|
||||
WorkflowDefinition definition = workflowDefinitionRepository.findByKey(request.getProcessKey())
|
||||
.orElseThrow(() -> new RuntimeException("Workflow definition not found: " + request.getProcessKey()));
|
||||
|
||||
// 2. 创建实例并保存 graph 快照
|
||||
WorkflowInstance workflowInstance = new WorkflowInstance();
|
||||
workflowInstance.setProcessInstanceId(processInstance.getId());
|
||||
workflowInstance.setProcessDefinitionId(processInstance.getProcessDefinitionId());
|
||||
workflowInstance.setWorkflowDefinitionId(workflowDefinitionId);
|
||||
workflowInstance.setBusinessKey(businessKey);
|
||||
workflowInstance.setGraphSnapshot(definition.getGraph()); // ✅ 保存画布数据快照
|
||||
workflowInstance.setStatus(WorkflowInstanceStatusEnums.NOT_STARTED);
|
||||
workflowInstance.setStartTime(LocalDateTime.now());
|
||||
// 2. 使用 MapStruct Converter 创建实例(处理多源对象映射)
|
||||
WorkflowInstance workflowInstance = workflowInstanceConverter.toEntity(
|
||||
request,
|
||||
processInstance,
|
||||
definition,
|
||||
formDataId
|
||||
);
|
||||
workflowInstanceRepository.save(workflowInstance);
|
||||
|
||||
log.info("创建工作流实例: instanceId={}, definitionId={}, graph已保存",
|
||||
workflowInstance.getId(), workflowDefinitionId);
|
||||
log.info("创建工作流实例: instanceId={}, definitionId={}, formDataId={}, graph已保存",
|
||||
workflowInstance.getId(), definition.getId(), formDataId);
|
||||
|
||||
// 3. 返回创建结果
|
||||
return workflowInstanceConverter.toDto(workflowInstance);
|
||||
@ -170,11 +178,8 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
|
||||
WorkflowInstance instance = workflowInstanceRepository.findByProcessInstanceId(processInstanceId)
|
||||
.orElseThrow(() -> new RuntimeException("Workflow instance not found: " + processInstanceId));
|
||||
|
||||
try {
|
||||
instance.setVariables(objectMapper.writeValueAsString(variables));
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Failed to serialize variables", e);
|
||||
}
|
||||
// ✅ 不再更新 formData,因为它应该保持启动时的数据不变
|
||||
// formData 字段只在流程启动时设置一次,用于记录启动表单数据
|
||||
|
||||
instance.setStatus(WorkflowInstanceStatusEnums.COMPLETED);
|
||||
instance.setEndTime(LocalDateTime.now());
|
||||
@ -185,28 +190,59 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
|
||||
@Transactional
|
||||
public WorkflowInstanceDTO startWorkflow(WorkflowInstanceStartRequest request) {
|
||||
try {
|
||||
WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findByKey(request.getProcessKey()).orElseThrow(() -> new RuntimeException("Workflow definition process key not found: " + request.getProcessKey()));
|
||||
// ✅ 步骤1: 先创建 FormData(如果需要)
|
||||
FormDataDTO formData = null;
|
||||
if (request.getFormKey() != null && request.getFormData() != null && !request.getFormData().isEmpty()) {
|
||||
FormDataCreateFromWorkflowRequest formDataRequest = new FormDataCreateFromWorkflowRequest();
|
||||
formDataRequest.setFormKey(request.getFormKey());
|
||||
formDataRequest.setFormData(request.getFormData());
|
||||
formDataRequest.setBusinessKey(request.getBusinessKey());
|
||||
|
||||
// ✅ 将前端传入的 variables 包装到 "form" 变量中
|
||||
// 这样流程中的 ${form.xxx} 表达式就能正确解析
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
if (request.getVariables() != null && !request.getVariables().isEmpty()) {
|
||||
variables.put("form", request.getVariables());
|
||||
formData = formDataService.createFromWorkflowStart(formDataRequest);
|
||||
log.info("FormData 已创建: id={}", formData.getId());
|
||||
} else {
|
||||
log.info("FormData 创建: 跳过(无 formKey 或表单数据)");
|
||||
}
|
||||
|
||||
// ✅ 步骤2: 构建 Flowable 变量
|
||||
Map<String, Object> variables = buildFlowableVariables(request);
|
||||
|
||||
// ✅ 步骤3: 启动 Flowable 流程
|
||||
ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
|
||||
.processDefinitionKey(request.getProcessKey())
|
||||
.variables(variables) // 使用包装后的变量
|
||||
.variables(variables)
|
||||
.businessKey(request.getBusinessKey())
|
||||
.startAsync(); // 异步启动,会自动执行 shell 任务
|
||||
// .start();
|
||||
return createWorkflowInstance(workflowDefinition.getId(), request.getBusinessKey(), processInstance);
|
||||
|
||||
log.info("Flowable 流程已启动: processInstanceId={}", processInstance.getId());
|
||||
|
||||
// ✅ 步骤4: 创建 WorkflowInstance 记录(关联 FormData)
|
||||
return createWorkflowInstance(request, processInstance, formData != null ? formData.getId() : null);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to create workflow: {}", request.getProcessKey(), e);
|
||||
throw new RuntimeException("Failed to create workflow", e);
|
||||
log.error("启动工作流失败: processKey={}", request.getProcessKey(), e);
|
||||
throw new RuntimeException("启动工作流失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Flowable 流程变量
|
||||
*
|
||||
* @param request 工作流启动请求
|
||||
* @return Flowable 变量 Map
|
||||
*/
|
||||
private Map<String, Object> buildFlowableVariables(WorkflowInstanceStartRequest request) {
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
|
||||
// 将前端传入的 formData 包装到 "form" 变量中
|
||||
// 这样流程中的 ${form.xxx} 表达式就能正确解析
|
||||
Map<String, Object> formData = request.getFormData() != null ? request.getFormData() : new HashMap<>();
|
||||
if (!formData.isEmpty()) {
|
||||
variables.put("form", formData);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<WorkflowTemplateWithInstancesDTO> findTemplatesWithRecentInstances(WorkflowDefinitionQuery query) {
|
||||
Pageable pageable = PageRequest.of(query.getPageNum(), query.getPageSize());
|
||||
|
||||
@ -452,8 +452,10 @@ CREATE TABLE workflow_instance
|
||||
process_definition_id VARCHAR(64) NOT NULL COMMENT '流程定义ID',
|
||||
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
|
||||
business_key VARCHAR(64) NULL COMMENT '业务标识',
|
||||
form_data_id BIGINT NULL COMMENT '启动表单数据ID(外键关联form_data)',
|
||||
status VARCHAR(100) NOT NULL COMMENT '实例状态',
|
||||
variables TEXT NULL COMMENT '流程变量(JSON)',
|
||||
form_data JSON NULL COMMENT '表单数据(JSON)',
|
||||
graph_snapshot JSON NULL COMMENT '流程图数据快照(启动时保存,用于画布还原)',
|
||||
start_time DATETIME(6) NULL COMMENT '开始时间',
|
||||
end_time DATETIME(6) NULL COMMENT '结束时间'
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user