增加form表单

This commit is contained in:
dengqichen 2025-10-24 10:31:40 +08:00
parent 03d4d21424
commit 96296270bb
24 changed files with 1373 additions and 1 deletions

View File

@ -149,7 +149,11 @@ public enum ResponseCode {
/** /**
* 工作流变量反序列化错误 * 工作流变量反序列化错误
*/ */
WORKFLOW_VARIABLE_DESERIALIZE_ERROR(50302, "workflow.variable.deserialize.error"); WORKFLOW_VARIABLE_DESERIALIZE_ERROR(50302, "workflow.variable.deserialize.error"),
// 表单管理相关错误码 (2800-2899)
FORM_DEFINITION_NOT_FOUND(2800, "form.definition.not.found"),
FORM_DATA_NOT_FOUND(2801, "form.data.not.found");
private final int code; private final int code;
private final String messageKey; // 国际化消息key private final String messageKey; // 国际化消息key

View File

@ -0,0 +1,52 @@
package com.qqchen.deploy.backend.workflow.api;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
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;
import com.qqchen.deploy.backend.workflow.service.IFormDataService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 表单数据API控制器
*
* @author qqchen
* @date 2025-10-24
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/workflow/form-data")
@Tag(name = "表单数据管理", description = "表单数据管理相关接口")
public class FormDataApiController extends BaseController<FormData, FormDataDTO, Long, FormDataQuery> {
@Resource
private IFormDataService formDataService;
@Operation(summary = "提交表单数据")
@PostMapping("/submit")
public Response<FormDataDTO> submit(@RequestBody FormDataDTO dto) {
FormDataDTO result = formDataService.submit(dto);
return Response.success(result);
}
@Operation(summary = "根据业务标识查询表单数据")
@GetMapping("/business/{businessKey}")
public Response<FormDataDTO> getByBusinessKey(
@Parameter(description = "业务标识", required = true) @PathVariable String businessKey
) {
FormDataDTO result = formDataService.getByBusinessKey(businessKey);
return Response.success(result);
}
@Override
protected void exportData(jakarta.servlet.http.HttpServletResponse response, java.util.List<FormDataDTO> data) {
// 暂不支持导出
}
}

View File

@ -0,0 +1,54 @@
package com.qqchen.deploy.backend.workflow.api;
import com.qqchen.deploy.backend.framework.api.Response;
import com.qqchen.deploy.backend.framework.controller.BaseController;
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
import com.qqchen.deploy.backend.workflow.dto.query.FormDefinitionQuery;
import com.qqchen.deploy.backend.workflow.entity.FormDefinition;
import com.qqchen.deploy.backend.workflow.service.IFormDefinitionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 表单定义API控制器
*
* @author qqchen
* @date 2025-10-24
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/workflow/form-definition")
@Tag(name = "表单定义管理", description = "表单定义管理相关接口")
public class FormDefinitionApiController extends BaseController<FormDefinition, FormDefinitionDTO, Long, FormDefinitionQuery> {
@Resource
private IFormDefinitionService formDefinitionService;
@Operation(summary = "发布表单")
@PostMapping("/{id}/publish")
public Response<FormDefinitionDTO> publish(
@Parameter(description = "表单定义ID", required = true) @PathVariable Long id
) {
FormDefinitionDTO result = formDefinitionService.publish(id);
return Response.success(result);
}
@Operation(summary = "根据key获取最新版本表单")
@GetMapping("/latest/{key}")
public Response<FormDefinitionDTO> getLatestByKey(
@Parameter(description = "表单标识", required = true) @PathVariable String key
) {
FormDefinitionDTO result = formDefinitionService.getLatestByKey(key);
return Response.success(result);
}
@Override
protected void exportData(jakarta.servlet.http.HttpServletResponse response, java.util.List<FormDefinitionDTO> data) {
// 暂不支持导出
}
}

View File

@ -0,0 +1,17 @@
package com.qqchen.deploy.backend.workflow.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.workflow.dto.FormDataDTO;
import com.qqchen.deploy.backend.workflow.entity.FormData;
import org.mapstruct.Mapper;
/**
* 表单数据转换器
*
* @author qqchen
* @date 2025-10-24
*/
@Mapper(config = BaseConverter.class)
public interface FormDataConverter extends BaseConverter<FormData, FormDataDTO> {
}

View File

@ -0,0 +1,17 @@
package com.qqchen.deploy.backend.workflow.converter;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
import com.qqchen.deploy.backend.workflow.entity.FormDefinition;
import org.mapstruct.Mapper;
/**
* 表单定义转换器
*
* @author qqchen
* @date 2025-10-24
*/
@Mapper(config = BaseConverter.class)
public interface FormDefinitionConverter extends BaseConverter<FormDefinition, FormDefinitionDTO> {
}

View File

@ -0,0 +1,85 @@
package com.qqchen.deploy.backend.workflow.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import com.qqchen.deploy.backend.workflow.dto.form.FormSchema;
import com.qqchen.deploy.backend.workflow.enums.FormBusinessTypeEnums;
import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 表单数据DTO
*
* @author qqchen
* @date 2025-10-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "表单数据DTO")
public class FormDataDTO extends BaseDTO {
/**
* 表单定义ID
*/
@Schema(description = "表单定义ID", example = "1")
private Long formDefinitionId;
/**
* 表单标识冗余存储避免JOIN
*/
@Schema(description = "表单标识", example = "employee_info_form")
private String formKey;
/**
* 表单版本冗余存储用于历史追溯
*/
@Schema(description = "表单版本", example = "1")
private Integer formVersion;
/**
* 业务标识如工作流实例ID订单号等
*/
@Schema(description = "业务标识", example = "workflow_instance_123")
private String businessKey;
/**
* 业务类型WORKFLOW-工作流ORDER-订单STANDALONE-独立表单
*/
@Schema(description = "业务类型")
private FormBusinessTypeEnums businessType;
/**
* 表单填写数据用户提交的实际数据
*/
@Schema(description = "表单填写数据")
private Map<String, Object> data;
/**
* 表单Schema快照用于历史追溯确保数据可还原
*/
@Schema(description = "表单Schema快照")
private FormSchema schemaSnapshot;
/**
* 提交人
*/
@Schema(description = "提交人", example = "admin")
private String submitter;
/**
* 提交时间
*/
@Schema(description = "提交时间", example = "2025-10-24T12:00:00")
private LocalDateTime submitTime;
/**
* 状态DRAFT-草稿SUBMITTED-已提交COMPLETED-已完成REJECTED-已拒绝
*/
@Schema(description = "状态")
private FormDataStatusEnums status;
}

View File

@ -0,0 +1,78 @@
package com.qqchen.deploy.backend.workflow.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import com.qqchen.deploy.backend.workflow.dto.form.FormSchema;
import com.qqchen.deploy.backend.workflow.enums.FormCategoryEnums;
import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 表单定义DTO
*
* @author qqchen
* @date 2025-10-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "表单定义DTO")
public class FormDefinitionDTO extends BaseDTO {
/**
* 表单名称
*/
@Schema(description = "表单名称", example = "员工信息登记表")
private String name;
/**
* 表单标识业务唯一
*/
@Schema(description = "表单标识", example = "employee_info_form")
private String key;
/**
* 表单版本号
*/
@Schema(description = "表单版本号", example = "1")
private Integer formVersion;
/**
* 表单分类
*/
@Schema(description = "表单分类")
private FormCategoryEnums category;
/**
* 表单描述
*/
@Schema(description = "表单描述", example = "用于采集员工基本信息")
private String description;
/**
* 表单Schema前端设计器导出的JSON结构
*/
@Schema(description = "表单Schema")
private FormSchema schema;
/**
* 标签用于分类和搜索
*/
@Schema(description = "标签列表", example = "[\"员工管理\", \"信息采集\"]")
private List<String> tags;
/**
* 状态DRAFT-草稿PUBLISHED-已发布DISABLED-已禁用
*/
@Schema(description = "状态")
private FormDefinitionStatusEnums status;
/**
* 是否为模板
*/
@Schema(description = "是否为模板", example = "false")
private Boolean isTemplate;
}

View File

@ -0,0 +1,79 @@
package com.qqchen.deploy.backend.workflow.dto.form;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* 表单Schema定义
* 用于存储前端表单设计器导出的JSON结构
*
* @author qqchen
* @date 2025-10-24
*/
@Data
public class FormSchema {
/**
* 表单标题
*/
private String title;
/**
* 表单描述
*/
private String description;
/**
* 表单字段列表
*/
private List<FormField> fields;
/**
* 表单配置
*/
private Map<String, Object> config;
/**
* 表单字段定义
*/
@Data
public static class FormField {
/**
* 字段名称
*/
private String name;
/**
* 字段标签
*/
private String label;
/**
* 字段类型inputselecttextareadatenumber等
*/
private String type;
/**
* 是否必填
*/
private Boolean required;
/**
* 默认值
*/
private Object defaultValue;
/**
* 字段配置不同类型字段有不同配置
*/
private Map<String, Object> config;
/**
* 验证规则
*/
private List<Map<String, Object>> validators;
}
}

View File

@ -0,0 +1,57 @@
package com.qqchen.deploy.backend.workflow.dto.query;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.enums.QueryType;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import com.qqchen.deploy.backend.workflow.enums.FormBusinessTypeEnums;
import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 表单数据查询对象
*
* @author qqchen
* @date 2025-10-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class FormDataQuery extends BaseQuery {
/**
* 表单定义ID精确查询
*/
@QueryField(field = "formDefinitionId", type = QueryType.EQUAL)
private Long formDefinitionId;
/**
* 表单标识精确查询
*/
@QueryField(field = "formKey", type = QueryType.EQUAL)
private String formKey;
/**
* 业务标识精确查询
*/
@QueryField(field = "businessKey", type = QueryType.EQUAL)
private String businessKey;
/**
* 业务类型精确查询
*/
@QueryField(field = "businessType", type = QueryType.EQUAL)
private FormBusinessTypeEnums businessType;
/**
* 提交人精确查询
*/
@QueryField(field = "submitter", type = QueryType.EQUAL)
private String submitter;
/**
* 状态精确查询
*/
@QueryField(field = "status", type = QueryType.EQUAL)
private FormDataStatusEnums status;
}

View File

@ -0,0 +1,57 @@
package com.qqchen.deploy.backend.workflow.dto.query;
import com.qqchen.deploy.backend.framework.annotation.QueryField;
import com.qqchen.deploy.backend.framework.enums.QueryType;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import com.qqchen.deploy.backend.workflow.enums.FormCategoryEnums;
import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 表单定义查询对象
*
* @author qqchen
* @date 2025-10-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class FormDefinitionQuery extends BaseQuery {
/**
* 表单名称模糊查询
*/
@QueryField(field = "name", type = QueryType.LIKE)
private String name;
/**
* 表单标识精确查询
*/
@QueryField(field = "key", type = QueryType.EQUAL)
private String key;
/**
* 表单版本精确查询
*/
@QueryField(field = "formVersion", type = QueryType.EQUAL)
private Integer formVersion;
/**
* 表单分类精确查询
*/
@QueryField(field = "category", type = QueryType.EQUAL)
private FormCategoryEnums category;
/**
* 状态精确查询
*/
@QueryField(field = "status", type = QueryType.EQUAL)
private FormDefinitionStatusEnums status;
/**
* 是否为模板精确查询
*/
@QueryField(field = "isTemplate", type = QueryType.EQUAL)
private Boolean isTemplate;
}

View File

@ -0,0 +1,95 @@
package com.qqchen.deploy.backend.workflow.entity;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.workflow.dto.form.FormSchema;
import com.qqchen.deploy.backend.workflow.entity.converter.FormSchemaType;
import com.qqchen.deploy.backend.workflow.enums.FormBusinessTypeEnums;
import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 表单数据实体
*
* @author qqchen
* @date 2025-10-24
*/
@Data
@Table(name = "form_data")
@jakarta.persistence.Entity
@EqualsAndHashCode(callSuper = true)
@LogicDelete
public class FormData extends Entity<Long> {
/**
* 表单定义ID
*/
@Column(name = "form_definition_id", nullable = false)
private Long formDefinitionId;
/**
* 表单标识冗余存储避免JOIN
*/
@Column(name = "form_key", nullable = false)
private String formKey;
/**
* 表单版本冗余存储用于历史追溯
*/
@Column(name = "form_version", nullable = false)
private Integer formVersion;
/**
* 业务标识如工作流实例ID订单号等
*/
@Column(name = "business_key")
private String businessKey;
/**
* 业务类型WORKFLOW-工作流ORDER-订单STANDALONE-独立表单
*/
@Column(name = "business_type")
@Enumerated(EnumType.STRING)
private FormBusinessTypeEnums businessType;
/**
* 表单填写数据用户提交的实际数据
*/
@Type(JsonType.class)
@Column(name = "data", nullable = false, columnDefinition = "json")
private Map<String, Object> data;
/**
* 表单Schema快照用于历史追溯确保数据可还原
*/
@Type(FormSchemaType.class)
@Column(name = "schema_snapshot", nullable = false, columnDefinition = "json")
private FormSchema schemaSnapshot;
/**
* 提交人
*/
@Column(name = "submitter")
private String submitter;
/**
* 提交时间
*/
@Column(name = "submit_time")
private LocalDateTime submitTime;
/**
* 状态DRAFT-草稿SUBMITTED-已提交COMPLETED-已完成REJECTED-已拒绝
*/
@Column(name = "status", nullable = false)
@Enumerated(EnumType.STRING)
private FormDataStatusEnums status;
}

View File

@ -0,0 +1,88 @@
package com.qqchen.deploy.backend.workflow.entity;
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
import com.qqchen.deploy.backend.framework.domain.Entity;
import com.qqchen.deploy.backend.workflow.dto.form.FormSchema;
import com.qqchen.deploy.backend.workflow.entity.converter.FormSchemaType;
import com.qqchen.deploy.backend.workflow.enums.FormCategoryEnums;
import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums;
import com.vladmihalcea.hibernate.type.json.JsonType;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.util.List;
/**
* 表单定义实体
*
* @author qqchen
* @date 2025-10-24
*/
@Data
@Table(name = "form_definition")
@jakarta.persistence.Entity
@EqualsAndHashCode(callSuper = true)
@LogicDelete
public class FormDefinition extends Entity<Long> {
/**
* 表单名称
*/
@Column(nullable = false)
private String name;
/**
* 表单标识业务唯一
*/
@Column(name = "`key`", nullable = false)
private String key;
/**
* 表单版本号
*/
@Column(name = "form_version", nullable = false)
private Integer formVersion;
/**
* 表单分类
*/
@Column(name = "category")
@Enumerated(EnumType.STRING)
private FormCategoryEnums category;
/**
* 表单描述
*/
@Column(name = "description", columnDefinition = "TEXT")
private String description;
/**
* 表单Schema前端设计器导出的JSON结构
*/
@Type(FormSchemaType.class)
@Column(name = "schema", nullable = false, columnDefinition = "json")
private FormSchema schema;
/**
* 标签用于分类和搜索
*/
@Type(JsonType.class)
@Column(name = "tags", columnDefinition = "json")
private List<String> tags;
/**
* 状态DRAFT-草稿PUBLISHED-已发布DISABLED-已禁用
*/
@Column(name = "status", nullable = false)
@Enumerated(EnumType.STRING)
private FormDefinitionStatusEnums status;
/**
* 是否为模板
*/
@Column(name = "is_template", nullable = false)
private Boolean isTemplate;
}

View File

@ -0,0 +1,131 @@
package com.qqchen.deploy.backend.workflow.entity.converter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qqchen.deploy.backend.workflow.dto.form.FormSchema;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
/**
* 自定义 Hibernate 类型用于处理 FormSchema 的序列化和反序列化
*
* @author qqchen
* @date 2025-10-24
*/
public class FormSchemaType implements UserType<FormSchema> {
private static final Logger log = LoggerFactory.getLogger(FormSchemaType.class);
private final ObjectMapper objectMapper;
public FormSchemaType() {
this.objectMapper = new ObjectMapper();
// 配置 ObjectMapper忽略未知属性
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public int getSqlType() {
return Types.VARCHAR;
}
@Override
public Class<FormSchema> returnedClass() {
return FormSchema.class;
}
@Override
public boolean equals(FormSchema x, FormSchema y) {
if (x == y) {
return true;
}
if (x == null || y == null) {
return false;
}
return x.equals(y);
}
@Override
public int hashCode(FormSchema x) {
return x == null ? 0 : x.hashCode();
}
@Override
public FormSchema nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor session, Object owner)
throws SQLException {
String value = rs.getString(position);
if (value == null) {
return null;
}
try {
return objectMapper.readValue(value, FormSchema.class);
} catch (JsonProcessingException e) {
// 记录错误日志但不抛出异常返回 null 使得单条记录的错误不影响整体查询
log.error("Failed to convert String to FormSchema: " + value, e);
return null;
}
}
@Override
public void nullSafeSet(PreparedStatement st, FormSchema value, int index, SharedSessionContractImplementor session)
throws SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
return;
}
try {
st.setString(index, objectMapper.writeValueAsString(value));
} catch (JsonProcessingException e) {
throw new HibernateException("Failed to convert FormSchema to String: " + value, e);
}
}
@Override
public FormSchema deepCopy(FormSchema value) {
if (value == null) {
return null;
}
try {
return objectMapper.readValue(objectMapper.writeValueAsString(value), FormSchema.class);
} catch (JsonProcessingException e) {
throw new HibernateException("Failed to deep copy FormSchema", e);
}
}
@Override
public boolean isMutable() {
return true;
}
@Override
public Serializable disassemble(FormSchema value) {
try {
return value == null ? null : objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new HibernateException("Failed to disassemble FormSchema", e);
}
}
@Override
public FormSchema assemble(Serializable cached, Object owner) {
try {
return cached == null ? null : objectMapper.readValue(cached.toString(), FormSchema.class);
} catch (JsonProcessingException e) {
throw new HibernateException("Failed to assemble FormSchema", e);
}
}
@Override
public FormSchema replace(FormSchema detached, FormSchema managed, Object owner) {
return deepCopy(detached);
}
}

View File

@ -0,0 +1,28 @@
package com.qqchen.deploy.backend.workflow.enums;
import lombok.Getter;
/**
* 表单业务类型枚举
*
* @author qqchen
* @date 2025-10-24
*/
@Getter
public enum FormBusinessTypeEnums {
WORKFLOW("WORKFLOW", "工作流"),
ORDER("ORDER", "订单"),
STANDALONE("STANDALONE", "独立表单");
private final String code;
private final String description;
FormBusinessTypeEnums(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@ -0,0 +1,29 @@
package com.qqchen.deploy.backend.workflow.enums;
import lombok.Getter;
/**
* 表单分类枚举
*
* @author qqchen
* @date 2025-10-24
*/
@Getter
public enum FormCategoryEnums {
APPROVAL("APPROVAL", "审批"),
DATA_COLLECTION("DATA_COLLECTION", "数据采集"),
SURVEY("SURVEY", "问卷调查"),
OTHER("OTHER", "其他");
private final String code;
private final String description;
FormCategoryEnums(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@ -0,0 +1,29 @@
package com.qqchen.deploy.backend.workflow.enums;
import lombok.Getter;
/**
* 表单数据状态枚举
*
* @author qqchen
* @date 2025-10-24
*/
@Getter
public enum FormDataStatusEnums {
DRAFT("DRAFT", "草稿"),
SUBMITTED("SUBMITTED", "已提交"),
COMPLETED("COMPLETED", "已完成"),
REJECTED("REJECTED", "已拒绝");
private final String code;
private final String description;
FormDataStatusEnums(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@ -0,0 +1,28 @@
package com.qqchen.deploy.backend.workflow.enums;
import lombok.Getter;
/**
* 表单定义状态枚举
*
* @author qqchen
* @date 2025-10-24
*/
@Getter
public enum FormDefinitionStatusEnums {
DRAFT("DRAFT", "草稿"),
PUBLISHED("PUBLISHED", "已发布"),
DISABLED("DISABLED", "已禁用");
private final String code;
private final String description;
FormDefinitionStatusEnums(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@ -0,0 +1,65 @@
package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.FormData;
import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* 表单数据Repository接口
*
* @author qqchen
* @date 2025-10-24
*/
@Repository
public interface IFormDataRepository extends IBaseRepository<FormData, Long> {
/**
* 根据业务标识查询
*
* @param businessKey 业务标识
* @return 表单数据
*/
Optional<FormData> findByBusinessKeyAndDeletedFalse(String businessKey);
/**
* 根据表单标识分页查询
*
* @param formKey 表单标识
* @param pageable 分页参数
* @return 分页结果
*/
Page<FormData> findByFormKeyAndDeletedFalse(String formKey, Pageable pageable);
/**
* 根据提交人分页查询
*
* @param submitter 提交人
* @param pageable 分页参数
* @return 分页结果
*/
Page<FormData> findBySubmitterAndDeletedFalse(String submitter, Pageable pageable);
/**
* 根据状态分页查询
*
* @param status 状态
* @param pageable 分页参数
* @return 分页结果
*/
Page<FormData> findByStatusAndDeletedFalse(FormDataStatusEnums status, Pageable pageable);
/**
* 根据表单定义ID查询列表
*
* @param formDefinitionId 表单定义ID
* @return 表单数据列表
*/
List<FormData> findByFormDefinitionIdAndDeletedFalse(Long formDefinitionId);
}

View File

@ -0,0 +1,64 @@
package com.qqchen.deploy.backend.workflow.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.workflow.entity.FormDefinition;
import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* 表单定义Repository接口
*
* @author qqchen
* @date 2025-10-24
*/
@Repository
public interface IFormDefinitionRepository extends IBaseRepository<FormDefinition, Long> {
/**
* 根据表单标识和版本查询
*
* @param key 表单标识
* @param formVersion 表单版本
* @return 表单定义
*/
Optional<FormDefinition> findByKeyAndFormVersionAndDeletedFalse(String key, Integer formVersion);
/**
* 根据表单标识查询最新版本
*
* @param key 表单标识
* @return 表单定义
*/
Optional<FormDefinition> findFirstByKeyAndDeletedFalseOrderByFormVersionDesc(String key);
/**
* 检查表单标识是否存在
*
* @param key 表单标识
* @return 是否存在
*/
boolean existsByKeyAndDeletedFalse(String key);
/**
* 根据状态分页查询
*
* @param status 状态
* @param pageable 分页参数
* @return 分页结果
*/
Page<FormDefinition> findByStatusAndDeletedFalse(FormDefinitionStatusEnums status, Pageable pageable);
/**
* 根据状态查询列表
*
* @param status 状态
* @return 表单定义列表
*/
List<FormDefinition> findByStatusAndDeletedFalse(FormDefinitionStatusEnums status);
}

View File

@ -0,0 +1,32 @@
package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService;
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;
/**
* 表单数据服务接口
*
* @author qqchen
* @date 2025-10-24
*/
public interface IFormDataService extends IBaseService<FormData, FormDataDTO, FormDataQuery, Long> {
/**
* 提交表单数据
*
* @param dto 表单数据DTO
* @return 提交后的表单数据
*/
FormDataDTO submit(FormDataDTO dto);
/**
* 根据业务标识查询表单数据
*
* @param businessKey 业务标识
* @return 表单数据
*/
FormDataDTO getByBusinessKey(String businessKey);
}

View File

@ -0,0 +1,33 @@
package com.qqchen.deploy.backend.workflow.service;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
import com.qqchen.deploy.backend.workflow.dto.query.FormDefinitionQuery;
import com.qqchen.deploy.backend.workflow.entity.FormDefinition;
/**
* 表单定义服务接口
*
* @author qqchen
* @date 2025-10-24
*/
public interface IFormDefinitionService extends IBaseService<FormDefinition, FormDefinitionDTO, FormDefinitionQuery, Long> {
/**
* 发布表单版本不可变
* 如果当前表单已发布则创建新版本否则直接发布
*
* @param id 表单定义ID
* @return 发布后的表单定义
*/
FormDefinitionDTO publish(Long id);
/**
* 根据表单标识获取最新版本
*
* @param key 表单标识
* @return 表单定义
*/
FormDefinitionDTO getLatestByKey(String key);
}

View File

@ -0,0 +1,74 @@
package com.qqchen.deploy.backend.workflow.service.impl;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
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;
import com.qqchen.deploy.backend.workflow.entity.FormDefinition;
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.converter.FormDataConverter;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
/**
* 表单数据服务实现
*
* @author qqchen
* @date 2025-10-24
*/
@Slf4j
@Service
public class FormDataServiceImpl extends BaseServiceImpl<FormData, FormDataDTO, FormDataQuery, Long> implements IFormDataService {
@Resource
private IFormDataRepository formDataRepository;
@Resource
private IFormDefinitionRepository formDefinitionRepository;
@Resource
private FormDataConverter formDataConverter;
@Override
@Transactional
public FormDataDTO submit(FormDataDTO dto) {
log.info("提交表单数据: formKey={}, submitter={}", dto.getFormKey(), dto.getSubmitter());
// 1. 查询表单定义获取schema快照
FormDefinition formDefinition = formDefinitionRepository.findById(dto.getFormDefinitionId())
.orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DEFINITION_NOT_FOUND));
// 2. 创建表单数据
FormData formData = formDataConverter.toEntity(dto);
formData.setFormKey(formDefinition.getKey());
formData.setFormVersion(formDefinition.getFormVersion());
formData.setSchemaSnapshot(formDefinition.getSchema()); // 保存schema快照
formData.setSubmitTime(LocalDateTime.now());
formData.setStatus(FormDataStatusEnums.SUBMITTED);
// 3. 保存
FormData savedFormData = formDataRepository.save(formData);
log.info("表单数据提交成功: id={}, formKey={}", savedFormData.getId(), savedFormData.getFormKey());
return formDataConverter.toDto(savedFormData);
}
@Override
public FormDataDTO getByBusinessKey(String businessKey) {
log.info("根据业务标识查询表单数据: businessKey={}", businessKey);
FormData formData = formDataRepository.findByBusinessKeyAndDeletedFalse(businessKey)
.orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DATA_NOT_FOUND));
return formDataConverter.toDto(formData);
}
}

View File

@ -0,0 +1,88 @@
package com.qqchen.deploy.backend.workflow.service.impl;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO;
import com.qqchen.deploy.backend.workflow.dto.query.FormDefinitionQuery;
import com.qqchen.deploy.backend.workflow.entity.FormDefinition;
import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums;
import com.qqchen.deploy.backend.workflow.repository.IFormDefinitionRepository;
import com.qqchen.deploy.backend.workflow.service.IFormDefinitionService;
import com.qqchen.deploy.backend.workflow.converter.FormDefinitionConverter;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 表单定义服务实现
*
* @author qqchen
* @date 2025-10-24
*/
@Slf4j
@Service
public class FormDefinitionServiceImpl extends BaseServiceImpl<FormDefinition, FormDefinitionDTO, FormDefinitionQuery, Long> implements IFormDefinitionService {
@Resource
private IFormDefinitionRepository formDefinitionRepository;
@Resource
private FormDefinitionConverter formDefinitionConverter;
@Override
@Transactional
public FormDefinitionDTO publish(Long id) {
log.info("发布表单定义ID: {}", id);
// 1. 查询当前表单定义
FormDefinition currentForm = formDefinitionRepository.findById(id)
.orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DEFINITION_NOT_FOUND));
// 2. 检查是否已发布
if (FormDefinitionStatusEnums.PUBLISHED.equals(currentForm.getStatus())) {
log.info("表单已发布,创建新版本: key={}, currentVersion={}", currentForm.getKey(), currentForm.getFormVersion());
// 创建新版本
FormDefinition newVersion = new FormDefinition();
newVersion.setName(currentForm.getName());
newVersion.setKey(currentForm.getKey());
newVersion.setFormVersion(currentForm.getFormVersion() + 1);
newVersion.setCategory(currentForm.getCategory());
newVersion.setDescription(currentForm.getDescription());
newVersion.setSchema(currentForm.getSchema());
newVersion.setTags(currentForm.getTags());
newVersion.setStatus(FormDefinitionStatusEnums.PUBLISHED);
newVersion.setIsTemplate(currentForm.getIsTemplate());
FormDefinition savedNewVersion = formDefinitionRepository.save(newVersion);
log.info("新版本创建成功: id={}, version={}", savedNewVersion.getId(), savedNewVersion.getFormVersion());
return formDefinitionConverter.toDto(savedNewVersion);
} else {
// 3. 首次发布直接更新状态
log.info("首次发布表单: key={}, version={}", currentForm.getKey(), currentForm.getFormVersion());
currentForm.setStatus(FormDefinitionStatusEnums.PUBLISHED);
if (currentForm.getFormVersion() == null) {
currentForm.setFormVersion(1);
}
FormDefinition savedForm = formDefinitionRepository.save(currentForm);
log.info("表单发布成功: id={}, version={}", savedForm.getId(), savedForm.getFormVersion());
return formDefinitionConverter.toDto(savedForm);
}
}
@Override
public FormDefinitionDTO getLatestByKey(String key) {
log.info("根据key获取最新版本表单: key={}", key);
FormDefinition formDefinition = formDefinitionRepository
.findFirstByKeyAndDeletedFalseOrderByFormVersionDesc(key)
.orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DEFINITION_NOT_FOUND));
return formDefinitionConverter.toDto(formDefinition);
}
}

View File

@ -689,3 +689,91 @@ CREATE TABLE sys_notification_channel (
INDEX idx_deleted (deleted) INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知渠道配置表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知渠道配置表';
-- =====================================================================================================================
-- 表单管理相关表
-- =====================================================================================================================
-- 表单定义表
CREATE TABLE form_definition
(
-- 主键
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
-- 基础信息
name VARCHAR(255) NOT NULL COMMENT '表单名称',
`key` VARCHAR(255) NOT NULL COMMENT '表单标识(业务唯一)',
form_version INT NOT NULL DEFAULT 1 COMMENT '表单版本号',
category VARCHAR(100) NULL COMMENT '表单分类APPROVAL-审批、DATA_COLLECTION-数据采集、SURVEY-问卷调查、OTHER-其他)',
description TEXT NULL COMMENT '表单描述',
-- 表单配置
schema JSON NOT NULL COMMENT '表单Schema前端设计器导出的JSON结构',
tags JSON NULL COMMENT '标签(用于分类和搜索)',
-- 表单属性
status VARCHAR(50) NOT NULL DEFAULT 'DRAFT' COMMENT '状态DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用)',
is_template BIT NOT NULL DEFAULT 0 COMMENT '是否为模板',
-- 审计字段
create_by VARCHAR(255) NULL COMMENT '创建人',
create_time DATETIME(6) NULL COMMENT '创建时间',
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
update_by VARCHAR(255) NULL COMMENT '更新人',
update_time DATETIME(6) NULL COMMENT '更新时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
-- 约束和索引
UNIQUE KEY uk_key_version (`key`, form_version),
INDEX idx_category (category),
INDEX idx_status (status),
INDEX idx_is_template (is_template),
INDEX idx_deleted (deleted)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = '表单定义表';
-- 表单数据表
CREATE TABLE form_data
(
-- 主键
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
-- 关联表单定义
form_definition_id BIGINT NOT NULL COMMENT '表单定义ID',
form_key VARCHAR(255) NOT NULL COMMENT '表单标识冗余存储避免JOIN',
form_version INT NOT NULL COMMENT '表单版本(冗余存储,用于历史追溯)',
-- 业务关联(松耦合)
business_key VARCHAR(255) NULL COMMENT '业务标识如工作流实例ID、订单号等',
business_type VARCHAR(50) NULL COMMENT '业务类型WORKFLOW-工作流、ORDER-订单、STANDALONE-独立表单)',
-- 表单数据
data JSON NOT NULL COMMENT '表单填写数据(用户提交的实际数据)',
schema_snapshot JSON NOT NULL COMMENT '表单Schema快照用于历史追溯确保数据可还原',
-- 提交信息
submitter VARCHAR(255) NULL COMMENT '提交人',
submit_time DATETIME(6) NULL COMMENT '提交时间',
-- 状态
status VARCHAR(50) NOT NULL DEFAULT 'SUBMITTED' COMMENT '状态DRAFT-草稿、SUBMITTED-已提交、COMPLETED-已完成、REJECTED-已拒绝)',
-- 审计字段
create_by VARCHAR(255) NULL COMMENT '创建人',
create_time DATETIME(6) NULL COMMENT '创建时间',
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除',
update_by VARCHAR(255) NULL COMMENT '更新人',
update_time DATETIME(6) NULL COMMENT '更新时间',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
-- 索引
INDEX idx_form_definition_id (form_definition_id),
INDEX idx_form_key (form_key),
INDEX idx_business_key (business_key),
INDEX idx_business_type (business_type),
INDEX idx_submitter (submitter),
INDEX idx_status (status),
INDEX idx_submit_time (submit_time),
INDEX idx_deleted (deleted)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci
COMMENT = '表单数据表';