增加生成后端服务代码。

This commit is contained in:
dengqichen 2025-10-25 11:55:11 +08:00
parent e0d58311c8
commit 7826d4ec27
15 changed files with 507 additions and 72 deletions

View File

@ -154,7 +154,7 @@ public class DeployAppConfigServiceImpl extends BaseServiceImpl<DeployAppConfig,
WorkflowInstanceStartRequest request = new WorkflowInstanceStartRequest(); WorkflowInstanceStartRequest request = new WorkflowInstanceStartRequest();
request.setProcessKey(workflowDefinition.getKey()); request.setProcessKey(workflowDefinition.getKey());
request.setBusinessKey(environment.getEnvCode() + "_" + application.getAppCode() + "_" + System.currentTimeMillis() / 1000); 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); WorkflowInstanceDTO workflowInstanceDTO = workflowInstanceService.startWorkflow(request);
buildAndSaveDeployLog(dto, environment, application, workflowInstanceDTO); buildAndSaveDeployLog(dto, environment, application, workflowInstanceDTO);

View File

@ -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;
}
}

View File

@ -4,10 +4,19 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
/**
* Security 工具类
* 提供获取当前用户信息权限检查等功能
*
* @author qqchen
* @date 2025-10-25
*/
public class SecurityUtils { public class SecurityUtils {
/** /**
* 获取当前登录用户名 * 获取当前登录用户名
*
* @return 当前用户名未认证时返回 "SYSTEM"
*/ */
public static String getCurrentUsername() { public static String getCurrentUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
@ -22,8 +31,79 @@ public class SecurityUtils {
return principal.toString(); return principal.toString();
} }
/**
* 获取当前用户ID字符串类型
*
* @return 当前用户ID未认证时返回 "system"
*/
public static String getCurrentUserIdAsString() {
Long userId = getCurrentUserId();
return userId != null ? userId.toString() : "system";
}
/**
* 获取当前用户IDLong 类型
*
* @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) { public static boolean hasPermission(String permission) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
@ -33,22 +113,10 @@ public class SecurityUtils {
.anyMatch(auth -> auth.getAuthority().equals(permission)); .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() { public static boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.system.service.impl; 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.entity.User;
import com.qqchen.deploy.backend.system.repository.IUserRepository; import com.qqchen.deploy.backend.system.repository.IUserRepository;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -12,6 +13,13 @@ import org.springframework.stereotype.Service;
import java.util.Collections; import java.util.Collections;
/**
* 用户详情服务实现
* 负责从数据库加载用户信息并转换为 Spring Security UserDetails
*
* @author qqchen
* @date 2025-10-25
*/
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService { public class UserDetailsServiceImpl implements UserDetailsService {
@ -22,15 +30,15 @@ public class UserDetailsServiceImpl implements UserDetailsService {
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsernameAndDeletedFalse(username) User user = userRepository.findByUsernameAndDeletedFalse(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在")); .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
return new org.springframework.security.core.userdetails.User(
user.getUsername(), // 返回自定义 UserDetails包含用户ID等扩展信息
user.getPassword(), return new CustomUserDetails(
user.getEnabled(), user.getId(), // 用户ID
true, user.getUsername(), // 用户名
true, user.getPassword(), // 密码
true, user.getEnabled(), // 是否启用
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) // 权限TODO: 从数据库加载实际权限
); );
} }
} }

View File

@ -1,9 +1,19 @@
package com.qqchen.deploy.backend.workflow.converter; package com.qqchen.deploy.backend.workflow.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter; 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.FormDataDTO;
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
import com.qqchen.deploy.backend.workflow.entity.FormData; 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.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) @Mapper(config = BaseConverter.class)
public interface FormDataConverter extends BaseConverter<FormData, FormDataDTO> { 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());
}
} }

View File

@ -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.WorkflowDefinitionDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowHistoricalInstancesDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowHistoricalInstancesDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; 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.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance; import com.qqchen.deploy.backend.workflow.entity.WorkflowInstance;
import org.mapstruct.Mapper; import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
import org.mapstruct.Named; import org.flowable.engine.runtime.ProcessInstance;
import org.mapstruct.*;
import java.time.LocalDateTime;
/** /**
* 工作流定义转换器 * 工作流实例转换器
*/ */
@Mapper(config = BaseConverter.class) @Mapper(config = BaseConverter.class)
public interface WorkflowInstanceConverter extends BaseConverter<WorkflowInstance, WorkflowInstanceDTO> { public interface WorkflowInstanceConverter extends BaseConverter<WorkflowInstance, WorkflowInstanceDTO> {
@Override @Override
@Named("toDto") @Named("toDto")
WorkflowInstanceDTO toDto(WorkflowInstance entity); WorkflowInstanceDTO toDto(WorkflowInstance entity);
@Named("toHistoricalDto") @Named("toHistoricalDto")
WorkflowHistoricalInstancesDTO toWorkflowHistoricalInstancesDTO(WorkflowInstance workflowInstance); 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());
}
} }

View File

@ -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;
}

View File

@ -34,20 +34,16 @@ public class WorkflowInstanceDTO extends BaseDTO {
*/ */
private Long formDataId; private Long formDataId;
/**
* 启动表单数据用于展示
*/
private FormDataDTO formData;
/** /**
* 实例状态 * 实例状态
*/ */
private WorkflowInstanceStatusEnums status; private WorkflowInstanceStatusEnums status;
/** /**
* 流程变量(JSON) * 表单数据(JSON)
* 存储流程启动时传入的表单变量
*/ */
private String variables; private Map<String, Object> formData;
/** /**
* 开始时间 * 开始时间

View File

@ -9,10 +9,16 @@ import java.util.Map;
@Schema(description = "工作流实例启动入参") @Schema(description = "工作流实例启动入参")
public class WorkflowInstanceStartRequest { public class WorkflowInstanceStartRequest {
@Schema(description = "流程定义标识", example = "APPROVAL-AND-DEPLOYMENT")
private String processKey; private String processKey;
@Schema(description = "业务键", example = "DEPLOY-2023-001")
private String businessKey; 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;
} }

View File

@ -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.dto.definition.workflow.WorkflowDefinitionGraph;
import com.qqchen.deploy.backend.workflow.entity.converter.WorkflowGraphType; import com.qqchen.deploy.backend.workflow.entity.converter.WorkflowGraphType;
import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums; import com.qqchen.deploy.backend.workflow.enums.WorkflowInstanceStatusEnums;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated; import jakarta.persistence.Enumerated;
@ -13,6 +14,7 @@ import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map;
/** /**
* 工作流实例实体 * 工作流实例实体
@ -58,10 +60,12 @@ public class WorkflowInstance extends Entity<Long> {
private WorkflowInstanceStatusEnums status; private WorkflowInstanceStatusEnums status;
/** /**
* 流程变量(JSON) * 表单数据(JSON)
* 存储流程启动时传入的表单变量
*/ */
@Column(columnDefinition = "TEXT") @Type(JsonType.class)
private String variables; @Column(name = "form_data", columnDefinition = "json")
private Map<String, Object> formData;
/** /**
* 流程图数据快照启动时保存用于画布还原 * 流程图数据快照启动时保存用于画布还原

View File

@ -1,6 +1,7 @@
package com.qqchen.deploy.backend.workflow.service; package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService; 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.FormDataDTO;
import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery; import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery;
import com.qqchen.deploy.backend.workflow.entity.FormData; import com.qqchen.deploy.backend.workflow.entity.FormData;
@ -28,5 +29,14 @@ public interface IFormDataService extends IBaseService<FormData, FormDataDTO, Fo
* @return 表单数据 * @return 表单数据
*/ */
FormDataDTO getByBusinessKey(String businessKey); FormDataDTO getByBusinessKey(String businessKey);
/**
* 从工作流启动创建表单数据
* 该方法会自动查询表单定义组装完整的 FormData 对象
*
* @param request 创建请求包含 formKeyformDatabusinessKey
* @return 创建后的表单数据
*/
FormDataDTO createFromWorkflowStart(FormDataCreateFromWorkflowRequest request);
} }

View File

@ -22,10 +22,12 @@ public interface IWorkflowInstanceService extends IBaseService<WorkflowInstance,
/** /**
* 创建工作流实例并关联Flowable实例 * 创建工作流实例并关联Flowable实例
* *
* @param request 工作流实例启动请求
* @param processInstance Flowable流程实例 * @param processInstance Flowable流程实例
* @param formDataId 表单数据ID可选如果有则关联
* @return 工作流实例 * @return 工作流实例
*/ */
WorkflowInstanceDTO createWorkflowInstance(Long workflowDefinitionId, String businessKey, ProcessInstance processInstance); WorkflowInstanceDTO createWorkflowInstance(WorkflowInstanceStartRequest request, ProcessInstance processInstance, Long formDataId);
/** /**
* 更新工作流实例状态 * 更新工作流实例状态

View File

@ -1,8 +1,11 @@
package com.qqchen.deploy.backend.workflow.service.impl; 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.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; 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.FormDataDTO;
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery; import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery;
import com.qqchen.deploy.backend.workflow.entity.FormData; import com.qqchen.deploy.backend.workflow.entity.FormData;
import com.qqchen.deploy.backend.workflow.entity.FormDefinition; 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.IFormDataRepository;
import com.qqchen.deploy.backend.workflow.repository.IFormDefinitionRepository; import com.qqchen.deploy.backend.workflow.repository.IFormDefinitionRepository;
import com.qqchen.deploy.backend.workflow.service.IFormDataService; 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 com.qqchen.deploy.backend.workflow.converter.FormDataConverter;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -34,6 +38,9 @@ public class FormDataServiceImpl extends BaseServiceImpl<FormData, FormDataDTO,
@Resource @Resource
private IFormDefinitionRepository formDefinitionRepository; private IFormDefinitionRepository formDefinitionRepository;
@Resource
private IFormDefinitionService formDefinitionService;
@Resource @Resource
private FormDataConverter formDataConverter; private FormDataConverter formDataConverter;
@ -71,5 +78,27 @@ public class FormDataServiceImpl extends BaseServiceImpl<FormData, FormDataDTO,
return formDataConverter.toDto(formData); 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;
}
} }

View File

@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter; import com.qqchen.deploy.backend.workflow.converter.WorkflowInstanceConverter;
import com.qqchen.deploy.backend.workflow.converter.WorkflowNodeInstanceConverter; 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.WorkflowHistoricalInstancesDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO;
import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceStartRequest; 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.IWorkflowDefinitionRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository; import com.qqchen.deploy.backend.workflow.repository.IWorkflowInstanceRepository;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowNodeInstanceRepository; 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.service.IWorkflowInstanceService;
import com.qqchen.deploy.backend.workflow.util.FlowableUtils; import com.qqchen.deploy.backend.workflow.util.FlowableUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -55,6 +59,12 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
@Resource @Resource
private IWorkflowInstanceRepository workflowInstanceRepository; private IWorkflowInstanceRepository workflowInstanceRepository;
@Resource
private IFormDefinitionService formDefinitionService;
@Resource
private IFormDataService formDataService;
@Resource @Resource
private RuntimeService runtimeService; private RuntimeService runtimeService;
@ -77,24 +87,22 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
private WorkflowNodeInstanceConverter workflowNodeInstanceConverter; private WorkflowNodeInstanceConverter workflowNodeInstanceConverter;
@Override @Override
public WorkflowInstanceDTO createWorkflowInstance(Long workflowDefinitionId, String businessKey, ProcessInstance processInstance) { public WorkflowInstanceDTO createWorkflowInstance(WorkflowInstanceStartRequest request, ProcessInstance processInstance, Long formDataId) {
// 1. 查询流程定义获取 graph 快照 // 1. 查询流程定义获取 graph 快照
WorkflowDefinition definition = workflowDefinitionRepository.findById(workflowDefinitionId) WorkflowDefinition definition = workflowDefinitionRepository.findByKey(request.getProcessKey())
.orElseThrow(() -> new RuntimeException("Workflow definition not found: " + workflowDefinitionId)); .orElseThrow(() -> new RuntimeException("Workflow definition not found: " + request.getProcessKey()));
// 2. 创建实例并保存 graph 快照 // 2. 使用 MapStruct Converter 创建实例处理多源对象映射
WorkflowInstance workflowInstance = new WorkflowInstance(); WorkflowInstance workflowInstance = workflowInstanceConverter.toEntity(
workflowInstance.setProcessInstanceId(processInstance.getId()); request,
workflowInstance.setProcessDefinitionId(processInstance.getProcessDefinitionId()); processInstance,
workflowInstance.setWorkflowDefinitionId(workflowDefinitionId); definition,
workflowInstance.setBusinessKey(businessKey); formDataId
workflowInstance.setGraphSnapshot(definition.getGraph()); // 保存画布数据快照 );
workflowInstance.setStatus(WorkflowInstanceStatusEnums.NOT_STARTED);
workflowInstance.setStartTime(LocalDateTime.now());
workflowInstanceRepository.save(workflowInstance); workflowInstanceRepository.save(workflowInstance);
log.info("创建工作流实例: instanceId={}, definitionId={}, graph已保存", log.info("创建工作流实例: instanceId={}, definitionId={}, formDataId={}, graph已保存",
workflowInstance.getId(), workflowDefinitionId); workflowInstance.getId(), definition.getId(), formDataId);
// 3. 返回创建结果 // 3. 返回创建结果
return workflowInstanceConverter.toDto(workflowInstance); return workflowInstanceConverter.toDto(workflowInstance);
@ -170,11 +178,8 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
WorkflowInstance instance = workflowInstanceRepository.findByProcessInstanceId(processInstanceId) WorkflowInstance instance = workflowInstanceRepository.findByProcessInstanceId(processInstanceId)
.orElseThrow(() -> new RuntimeException("Workflow instance not found: " + processInstanceId)); .orElseThrow(() -> new RuntimeException("Workflow instance not found: " + processInstanceId));
try { // 不再更新 formData因为它应该保持启动时的数据不变
instance.setVariables(objectMapper.writeValueAsString(variables)); // formData 字段只在流程启动时设置一次用于记录启动表单数据
} catch (JsonProcessingException e) {
log.error("Failed to serialize variables", e);
}
instance.setStatus(WorkflowInstanceStatusEnums.COMPLETED); instance.setStatus(WorkflowInstanceStatusEnums.COMPLETED);
instance.setEndTime(LocalDateTime.now()); instance.setEndTime(LocalDateTime.now());
@ -185,28 +190,59 @@ public class WorkflowInstanceServiceImpl extends BaseServiceImpl<WorkflowInstanc
@Transactional @Transactional
public WorkflowInstanceDTO startWorkflow(WorkflowInstanceStartRequest request) { public WorkflowInstanceDTO startWorkflow(WorkflowInstanceStartRequest request) {
try { try {
WorkflowDefinition workflowDefinition = workflowDefinitionRepository.findByKey(request.getProcessKey()).orElseThrow(() -> new RuntimeException("Workflow definition process key not found: " + request.getProcessKey())); // 步骤1: 先创建 FormData如果需要
FormDataDTO formData = null;
// 将前端传入的 variables 包装到 "form" 变量中 if (request.getFormKey() != null && request.getFormData() != null && !request.getFormData().isEmpty()) {
// 这样流程中的 ${form.xxx} 表达式就能正确解析 FormDataCreateFromWorkflowRequest formDataRequest = new FormDataCreateFromWorkflowRequest();
Map<String, Object> variables = new HashMap<>(); formDataRequest.setFormKey(request.getFormKey());
if (request.getVariables() != null && !request.getVariables().isEmpty()) { formDataRequest.setFormData(request.getFormData());
variables.put("form", request.getVariables()); formDataRequest.setBusinessKey(request.getBusinessKey());
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() ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
.processDefinitionKey(request.getProcessKey()) .processDefinitionKey(request.getProcessKey())
.variables(variables) // 使用包装后的变量 .variables(variables)
.businessKey(request.getBusinessKey()) .businessKey(request.getBusinessKey())
.startAsync(); // 异步启动会自动执行 shell 任务 .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) { } catch (Exception e) {
log.error("Failed to create workflow: {}", request.getProcessKey(), e); log.error("启动工作流失败: processKey={}", request.getProcessKey(), e);
throw new RuntimeException("Failed to create workflow", 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 @Override
public Page<WorkflowTemplateWithInstancesDTO> findTemplatesWithRecentInstances(WorkflowDefinitionQuery query) { public Page<WorkflowTemplateWithInstancesDTO> findTemplatesWithRecentInstances(WorkflowDefinitionQuery query) {
Pageable pageable = PageRequest.of(query.getPageNum(), query.getPageSize()); Pageable pageable = PageRequest.of(query.getPageNum(), query.getPageSize());

View File

@ -452,8 +452,10 @@ CREATE TABLE workflow_instance
process_definition_id VARCHAR(64) NOT NULL COMMENT '流程定义ID', process_definition_id VARCHAR(64) NOT NULL COMMENT '流程定义ID',
workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID', workflow_definition_id BIGINT NOT NULL COMMENT '工作流定义ID',
business_key VARCHAR(64) NULL COMMENT '业务标识', business_key VARCHAR(64) NULL COMMENT '业务标识',
form_data_id BIGINT NULL COMMENT '启动表单数据ID外键关联form_data',
status VARCHAR(100) NOT NULL COMMENT '实例状态', 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 '开始时间', start_time DATETIME(6) NULL COMMENT '开始时间',
end_time DATETIME(6) NULL COMMENT '结束时间' end_time DATETIME(6) NULL COMMENT '结束时间'