From 96296270bbbd74b0da49ce9d047e4dbb4ce9296b Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 24 Oct 2025 10:31:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0form=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/framework/enums/ResponseCode.java | 6 +- .../workflow/api/FormDataApiController.java | 52 +++++++ .../api/FormDefinitionApiController.java | 54 ++++++++ .../workflow/converter/FormDataConverter.java | 17 +++ .../converter/FormDefinitionConverter.java | 17 +++ .../backend/workflow/dto/FormDataDTO.java | 85 ++++++++++++ .../workflow/dto/FormDefinitionDTO.java | 78 +++++++++++ .../backend/workflow/dto/form/FormSchema.java | 79 +++++++++++ .../workflow/dto/query/FormDataQuery.java | 57 ++++++++ .../dto/query/FormDefinitionQuery.java | 57 ++++++++ .../backend/workflow/entity/FormData.java | 95 +++++++++++++ .../workflow/entity/FormDefinition.java | 88 ++++++++++++ .../entity/converter/FormSchemaType.java | 131 ++++++++++++++++++ .../workflow/enums/FormBusinessTypeEnums.java | 28 ++++ .../workflow/enums/FormCategoryEnums.java | 29 ++++ .../workflow/enums/FormDataStatusEnums.java | 29 ++++ .../enums/FormDefinitionStatusEnums.java | 28 ++++ .../repository/IFormDataRepository.java | 65 +++++++++ .../repository/IFormDefinitionRepository.java | 64 +++++++++ .../workflow/service/IFormDataService.java | 32 +++++ .../service/IFormDefinitionService.java | 33 +++++ .../service/impl/FormDataServiceImpl.java | 74 ++++++++++ .../impl/FormDefinitionServiceImpl.java | 88 ++++++++++++ .../db/changelog/changes/v1.0.0-schema.sql | 88 ++++++++++++ 24 files changed, 1373 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDataApiController.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDefinitionApiController.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDataConverter.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDefinitionConverter.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDataDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDefinitionDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/form/FormSchema.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDataQuery.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDefinitionQuery.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormData.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormDefinition.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/converter/FormSchemaType.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormBusinessTypeEnums.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormCategoryEnums.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDataStatusEnums.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDefinitionStatusEnums.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDataRepository.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDefinitionRepository.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDataService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDefinitionService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDataServiceImpl.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDefinitionServiceImpl.java diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java index 19d12afd..bbdd0b38 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java @@ -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 String messageKey; // 国际化消息key diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDataApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDataApiController.java new file mode 100644 index 00000000..7adcabb8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDataApiController.java @@ -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 { + + @Resource + private IFormDataService formDataService; + + @Operation(summary = "提交表单数据") + @PostMapping("/submit") + public Response submit(@RequestBody FormDataDTO dto) { + FormDataDTO result = formDataService.submit(dto); + return Response.success(result); + } + + @Operation(summary = "根据业务标识查询表单数据") + @GetMapping("/business/{businessKey}") + public Response 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 data) { + // 暂不支持导出 + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDefinitionApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDefinitionApiController.java new file mode 100644 index 00000000..81f44ee5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDefinitionApiController.java @@ -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 { + + @Resource + private IFormDefinitionService formDefinitionService; + + @Operation(summary = "发布表单") + @PostMapping("/{id}/publish") + public Response 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 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 data) { + // 暂不支持导出 + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDataConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDataConverter.java new file mode 100644 index 00000000..8e341bfb --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDataConverter.java @@ -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 { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDefinitionConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDefinitionConverter.java new file mode 100644 index 00000000..e991f0d7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDefinitionConverter.java @@ -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 { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDataDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDataDTO.java new file mode 100644 index 00000000..c5d68ad8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDataDTO.java @@ -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 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; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDefinitionDTO.java new file mode 100644 index 00000000..47948755 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDefinitionDTO.java @@ -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 tags; + + /** + * 状态(DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用) + */ + @Schema(description = "状态") + private FormDefinitionStatusEnums status; + + /** + * 是否为模板 + */ + @Schema(description = "是否为模板", example = "false") + private Boolean isTemplate; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/form/FormSchema.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/form/FormSchema.java new file mode 100644 index 00000000..acbcb5dc --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/form/FormSchema.java @@ -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 fields; + + /** + * 表单配置 + */ + private Map config; + + /** + * 表单字段定义 + */ + @Data + public static class FormField { + /** + * 字段名称 + */ + private String name; + + /** + * 字段标签 + */ + private String label; + + /** + * 字段类型(input、select、textarea、date、number等) + */ + private String type; + + /** + * 是否必填 + */ + private Boolean required; + + /** + * 默认值 + */ + private Object defaultValue; + + /** + * 字段配置(不同类型字段有不同配置) + */ + private Map config; + + /** + * 验证规则 + */ + private List> validators; + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDataQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDataQuery.java new file mode 100644 index 00000000..5df5abfe --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDataQuery.java @@ -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; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDefinitionQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDefinitionQuery.java new file mode 100644 index 00000000..7e0a808d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDefinitionQuery.java @@ -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; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormData.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormData.java new file mode 100644 index 00000000..92b7da68 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormData.java @@ -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 { + + /** + * 表单定义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 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; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormDefinition.java new file mode 100644 index 00000000..7ce04baa --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormDefinition.java @@ -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 { + + /** + * 表单名称 + */ + @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 tags; + + /** + * 状态(DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用) + */ + @Column(name = "status", nullable = false) + @Enumerated(EnumType.STRING) + private FormDefinitionStatusEnums status; + + /** + * 是否为模板 + */ + @Column(name = "is_template", nullable = false) + private Boolean isTemplate; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/converter/FormSchemaType.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/converter/FormSchemaType.java new file mode 100644 index 00000000..0e000d88 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/converter/FormSchemaType.java @@ -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 { + 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 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); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormBusinessTypeEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormBusinessTypeEnums.java new file mode 100644 index 00000000..aa8460e6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormBusinessTypeEnums.java @@ -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; + } + +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormCategoryEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormCategoryEnums.java new file mode 100644 index 00000000..1817121e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormCategoryEnums.java @@ -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; + } + +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDataStatusEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDataStatusEnums.java new file mode 100644 index 00000000..ae4481ee --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDataStatusEnums.java @@ -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; + } + +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDefinitionStatusEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDefinitionStatusEnums.java new file mode 100644 index 00000000..1777a733 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDefinitionStatusEnums.java @@ -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; + } + +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDataRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDataRepository.java new file mode 100644 index 00000000..4e32245c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDataRepository.java @@ -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 { + + /** + * 根据业务标识查询 + * + * @param businessKey 业务标识 + * @return 表单数据 + */ + Optional findByBusinessKeyAndDeletedFalse(String businessKey); + + /** + * 根据表单标识分页查询 + * + * @param formKey 表单标识 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findByFormKeyAndDeletedFalse(String formKey, Pageable pageable); + + /** + * 根据提交人分页查询 + * + * @param submitter 提交人 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findBySubmitterAndDeletedFalse(String submitter, Pageable pageable); + + /** + * 根据状态分页查询 + * + * @param status 状态 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findByStatusAndDeletedFalse(FormDataStatusEnums status, Pageable pageable); + + /** + * 根据表单定义ID查询列表 + * + * @param formDefinitionId 表单定义ID + * @return 表单数据列表 + */ + List findByFormDefinitionIdAndDeletedFalse(Long formDefinitionId); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDefinitionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDefinitionRepository.java new file mode 100644 index 00000000..2d74c3e5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDefinitionRepository.java @@ -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 { + + /** + * 根据表单标识和版本查询 + * + * @param key 表单标识 + * @param formVersion 表单版本 + * @return 表单定义 + */ + Optional findByKeyAndFormVersionAndDeletedFalse(String key, Integer formVersion); + + /** + * 根据表单标识查询最新版本 + * + * @param key 表单标识 + * @return 表单定义 + */ + Optional findFirstByKeyAndDeletedFalseOrderByFormVersionDesc(String key); + + /** + * 检查表单标识是否存在 + * + * @param key 表单标识 + * @return 是否存在 + */ + boolean existsByKeyAndDeletedFalse(String key); + + /** + * 根据状态分页查询 + * + * @param status 状态 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findByStatusAndDeletedFalse(FormDefinitionStatusEnums status, Pageable pageable); + + /** + * 根据状态查询列表 + * + * @param status 状态 + * @return 表单定义列表 + */ + List findByStatusAndDeletedFalse(FormDefinitionStatusEnums status); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDataService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDataService.java new file mode 100644 index 00000000..59a8a0a5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDataService.java @@ -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 { + + /** + * 提交表单数据 + * + * @param dto 表单数据DTO + * @return 提交后的表单数据 + */ + FormDataDTO submit(FormDataDTO dto); + + /** + * 根据业务标识查询表单数据 + * + * @param businessKey 业务标识 + * @return 表单数据 + */ + FormDataDTO getByBusinessKey(String businessKey); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDefinitionService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDefinitionService.java new file mode 100644 index 00000000..863fb2e8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDefinitionService.java @@ -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 { + + /** + * 发布表单(版本不可变) + * 如果当前表单已发布,则创建新版本;否则直接发布 + * + * @param id 表单定义ID + * @return 发布后的表单定义 + */ + FormDefinitionDTO publish(Long id); + + /** + * 根据表单标识获取最新版本 + * + * @param key 表单标识 + * @return 表单定义 + */ + FormDefinitionDTO getLatestByKey(String key); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDataServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDataServiceImpl.java new file mode 100644 index 00000000..6b8dcbb8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDataServiceImpl.java @@ -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 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); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDefinitionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDefinitionServiceImpl.java new file mode 100644 index 00000000..ac989144 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDefinitionServiceImpl.java @@ -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 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); + } +} + diff --git a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql index c5ac9400..574d6758 100644 --- a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql +++ b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql @@ -689,3 +689,91 @@ CREATE TABLE sys_notification_channel ( INDEX idx_deleted (deleted) ) 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 = '表单数据表'; +