refactor(modules): 拆分多模块工程并收口common基础模块
This commit is contained in:
61
common-agent-common/pom.xml
Normal file
61
common-agent-common/pom.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.bruce</groupId>
|
||||
<artifactId>common-agent-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-agent-common</artifactId>
|
||||
<name>common-agent-common</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-parsers-standard-package</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.bruce.common.config;
|
||||
|
||||
import com.bruce.common.constant.CommonConsts;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@ConfigurationProperties(prefix = "common.attachment")
|
||||
public class AttachmentProperties {
|
||||
|
||||
private String basePath = CommonConsts.DEFAULT_ATTACHMENT_BASE_PATH;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bruce.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
public class EntityAuditMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
private static final String SYSTEM_USER = "system";
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
Date now = new Date();
|
||||
fillIfNull(metaObject, "createTime", now);
|
||||
fillIfNull(metaObject, "updateTime", now);
|
||||
fillIfNull(metaObject, "createBy", SYSTEM_USER);
|
||||
fillIfNull(metaObject, "updateBy", SYSTEM_USER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
setIfWritable(metaObject, "updateTime", new Date());
|
||||
setIfWritable(metaObject, "updateBy", SYSTEM_USER);
|
||||
}
|
||||
|
||||
private void fillIfNull(MetaObject metaObject, String fieldName, Object fieldVal) {
|
||||
if (!metaObject.hasSetter(fieldName) || metaObject.getValue(fieldName) != null) {
|
||||
return;
|
||||
}
|
||||
metaObject.setValue(fieldName, fieldVal);
|
||||
}
|
||||
|
||||
private void setIfWritable(MetaObject metaObject, String fieldName, Object fieldVal) {
|
||||
if (metaObject.hasSetter(fieldName)) {
|
||||
metaObject.setValue(fieldName, fieldVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.bruce.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bruce.common.constant;
|
||||
|
||||
public final class CommonConsts {
|
||||
|
||||
public static final String DATE_FORMAT_LONG_STR = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public static final String DATE_FORMAT_MILLIS_STR = "yyyy-MM-dd HH:mm:ss.SSS";
|
||||
|
||||
public static final String TIME_ZONE_GMT8 = "GMT+8";
|
||||
|
||||
public static final String DEFAULT_ATTACHMENT_BASE_PATH = "data/attachments";
|
||||
|
||||
public static final String STORAGE_TYPE_LOCAL = "LOCAL";
|
||||
|
||||
public static final String REQUEST_RESULT_SUCCESS_CODE = "0";
|
||||
|
||||
public static final String REQUEST_RESULT_FAIL_CODE = "-1";
|
||||
|
||||
private CommonConsts() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bruce.common.controller;
|
||||
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
|
||||
import com.bruce.common.dto.response.SysAttachmentResponse;
|
||||
import com.bruce.common.factory.SysAttachmentFactory;
|
||||
import com.bruce.common.service.ISysAttachmentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "系统附件管理")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/attachments")
|
||||
@RequiredArgsConstructor
|
||||
public class SysAttachmentController {
|
||||
|
||||
private final ISysAttachmentService sysAttachmentService;
|
||||
|
||||
private final SysAttachmentFactory sysAttachmentFactory;
|
||||
|
||||
@Operation(summary = "上传附件")
|
||||
@PostMapping("/upload")
|
||||
public RequestResult<SysAttachmentResponse> upload(@ModelAttribute SysAttachmentUploadRequest request) {
|
||||
log.info("上传附件开始,sourceType={}, sourceId={}",
|
||||
request == null ? null : request.getSourceType(),
|
||||
request == null ? null : request.getSourceId());
|
||||
SysAttachmentResponse response = sysAttachmentFactory.toResponse(sysAttachmentService.upload(request));
|
||||
log.info("上传附件结束,attachmentId={}, sourceType={}, sourceId={}",
|
||||
response == null ? null : response.getId(),
|
||||
request == null ? null : request.getSourceType(),
|
||||
request == null ? null : request.getSourceId());
|
||||
return RequestResult.success(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.bruce.common.controller;
|
||||
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import com.bruce.common.dto.request.SysEnumBatchSaveRequest;
|
||||
import com.bruce.common.dto.request.SysEnumManageQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumSaveRequest;
|
||||
import com.bruce.common.dto.response.SysEnumResponse;
|
||||
import com.bruce.common.service.ISysEnumService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "系统枚举管理")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/sys-enum")
|
||||
public class SysEnumController {
|
||||
|
||||
@Autowired
|
||||
private ISysEnumService sysEnumService;
|
||||
|
||||
@Operation(summary = "查询全部系统枚举")
|
||||
@PostMapping("/list")
|
||||
public RequestResult<List<SysEnumResponse>> list() {
|
||||
log.info("SysEnumController.list start");
|
||||
List<SysEnumResponse> responses = sysEnumService.listResponses();
|
||||
log.info("SysEnumController.list success, count={}", responses.size());
|
||||
return RequestResult.success(responses);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据模块和类型查询系统枚举")
|
||||
@PostMapping("/query")
|
||||
public RequestResult<List<SysEnumResponse>> queryByCatalogAndType(@RequestBody SysEnumQueryRequest request) {
|
||||
log.info("SysEnumController.queryByCatalogAndType start, request={}", request);
|
||||
List<SysEnumResponse> responses = sysEnumService.listByCatalogAndTypeResponses(request);
|
||||
log.info("SysEnumController.queryByCatalogAndType success, count={}", responses.size());
|
||||
return RequestResult.success(responses);
|
||||
}
|
||||
|
||||
@Operation(summary = "管理端查询系统枚举")
|
||||
@PostMapping("/queryForManagement")
|
||||
public RequestResult<List<SysEnumResponse>> queryForManagement(@RequestBody(required = false) SysEnumManageQueryRequest request) {
|
||||
log.info("SysEnumController.queryForManagement start, request={}", request);
|
||||
List<SysEnumResponse> responses = sysEnumService.listForManagement(request);
|
||||
log.info("SysEnumController.queryForManagement success, count={}", responses.size());
|
||||
return RequestResult.success(responses);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询系统枚举详情")
|
||||
@GetMapping("/detail")
|
||||
public RequestResult<SysEnumResponse> getById(@RequestParam("id") Long id) {
|
||||
log.info("SysEnumController.getById start, id={}", id);
|
||||
SysEnumResponse response = sysEnumService.getResponseById(id);
|
||||
log.info("SysEnumController.getById success, id={}, found={}", id, response != null);
|
||||
return RequestResult.success(response);
|
||||
}
|
||||
|
||||
@Operation(summary = "新增或修改系统枚举")
|
||||
@PostMapping("/save")
|
||||
public RequestResult<Boolean> saveOrUpdate(@RequestBody SysEnumSaveRequest request) {
|
||||
log.info("SysEnumController.saveOrUpdate start, request={}", request);
|
||||
Boolean result = sysEnumService.saveOrUpdate(request);
|
||||
log.info("SysEnumController.saveOrUpdate success, id={}, catalog={}, type={}, value={}, result={}",
|
||||
request.getId(), request.getCatalog(), request.getType(), request.getValue(), result);
|
||||
return RequestResult.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "批量新增系统枚举")
|
||||
@PostMapping("/batchSave")
|
||||
public RequestResult<Boolean> batchSave(@RequestBody SysEnumBatchSaveRequest request) {
|
||||
log.info("SysEnumController.batchSave start, request={}", request);
|
||||
Boolean result = sysEnumService.batchSave(request);
|
||||
log.info("SysEnumController.batchSave success, catalog={}, type={}, itemCount={}, result={}",
|
||||
request.getCatalog(), request.getType(), request.getItems() == null ? 0 : request.getItems().size(), result);
|
||||
return RequestResult.success(result);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除系统枚举")
|
||||
@PostMapping("/delete")
|
||||
public RequestResult<Boolean> deleteById(@RequestParam("id") Long id) {
|
||||
log.info("SysEnumController.deleteById start, id={}", id);
|
||||
Boolean result = sysEnumService.removeById(id);
|
||||
log.info("SysEnumController.deleteById success, id={}, result={}", id, result);
|
||||
return RequestResult.success(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bruce.common.document.parse;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Data
|
||||
public class DocumentParseContext {
|
||||
|
||||
private Long documentId;
|
||||
|
||||
private Long attachmentId;
|
||||
|
||||
private String originalName;
|
||||
|
||||
private String suffix;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private Path filePath;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.bruce.common.document.parse;
|
||||
|
||||
public class DocumentParseException extends RuntimeException {
|
||||
|
||||
public DocumentParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DocumentParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.bruce.common.document.parse;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class DocumentParseResult {
|
||||
|
||||
private String text;
|
||||
|
||||
private Integer textLength;
|
||||
|
||||
private Integer pageCount;
|
||||
|
||||
private Integer sheetCount;
|
||||
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.bruce.common.document.parse;
|
||||
|
||||
public interface DocumentParser {
|
||||
|
||||
boolean supports(DocumentParseContext context);
|
||||
|
||||
DocumentParseResult parse(DocumentParseContext context);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bruce.common.document.parse;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Component
|
||||
public class DocumentParserFactory {
|
||||
|
||||
private final List<DocumentParser> parsers;
|
||||
|
||||
public DocumentParserFactory(List<DocumentParser> parsers) {
|
||||
this.parsers = parsers;
|
||||
}
|
||||
|
||||
public DocumentParser resolve(DocumentParseContext context) {
|
||||
return parsers.stream()
|
||||
.filter(parser -> parser.supports(context))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new DocumentParseException("不支持的文档类型: " + resolveType(context)));
|
||||
}
|
||||
|
||||
private String resolveType(DocumentParseContext context) {
|
||||
if (context == null) {
|
||||
return "unknown";
|
||||
}
|
||||
if (StringUtils.hasText(context.getSuffix())) {
|
||||
return context.getSuffix().trim().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
if (StringUtils.hasText(context.getContentType())) {
|
||||
return context.getContentType().trim();
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.bruce.common.document.parse.impl;
|
||||
|
||||
import com.bruce.common.document.parse.DocumentParseContext;
|
||||
import com.bruce.common.document.parse.DocumentParseException;
|
||||
import com.bruce.common.document.parse.DocumentParseResult;
|
||||
import org.apache.tika.Tika;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
import org.apache.tika.metadata.Metadata;
|
||||
import org.apache.tika.metadata.TikaCoreProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
abstract class AbstractTikaDocumentParser {
|
||||
|
||||
private static final int MAX_TEXT_LENGTH = -1;
|
||||
|
||||
private final Tika tika = new Tika();
|
||||
|
||||
boolean supportsSuffix(DocumentParseContext context, Set<String> suffixes) {
|
||||
return context != null
|
||||
&& StringUtils.hasText(context.getSuffix())
|
||||
&& suffixes.contains(context.getSuffix().trim().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
boolean supportsContentType(DocumentParseContext context, String prefix) {
|
||||
return context != null
|
||||
&& StringUtils.hasText(context.getContentType())
|
||||
&& context.getContentType().trim().toLowerCase(Locale.ROOT).startsWith(prefix);
|
||||
}
|
||||
|
||||
DocumentParseResult parseWithTika(DocumentParseContext context) {
|
||||
if (context == null || context.getFilePath() == null) {
|
||||
throw new DocumentParseException("解析文件不能为空");
|
||||
}
|
||||
try {
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, context.getOriginalName());
|
||||
if (StringUtils.hasText(context.getContentType())) {
|
||||
metadata.set(Metadata.CONTENT_TYPE, context.getContentType());
|
||||
}
|
||||
String text;
|
||||
try (InputStream inputStream = Files.newInputStream(context.getFilePath())) {
|
||||
text = tika.parseToString(inputStream, metadata, MAX_TEXT_LENGTH);
|
||||
}
|
||||
DocumentParseResult result = new DocumentParseResult();
|
||||
result.setText(text == null ? "" : text.trim());
|
||||
result.setTextLength(result.getText().length());
|
||||
result.getMetadata().put("contentType", firstNonBlank(metadata.get(Metadata.CONTENT_TYPE), context.getContentType()));
|
||||
result.getMetadata().put("resourceName", firstNonBlank(metadata.get(TikaCoreProperties.RESOURCE_NAME_KEY), context.getOriginalName()));
|
||||
return result;
|
||||
} catch (IOException | TikaException e) {
|
||||
throw new DocumentParseException("文档解析失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String firstNonBlank(String first, String fallback) {
|
||||
return StringUtils.hasText(first) ? first : fallback;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bruce.common.document.parse.impl;
|
||||
|
||||
import com.bruce.common.document.parse.DocumentParseContext;
|
||||
import com.bruce.common.document.parse.DocumentParser;
|
||||
import com.bruce.common.document.parse.DocumentParseResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class ExcelDocumentParser extends AbstractTikaDocumentParser implements DocumentParser {
|
||||
|
||||
private static final Set<String> SUFFIXES = Set.of("xls", "xlsx");
|
||||
|
||||
@Override
|
||||
public boolean supports(DocumentParseContext context) {
|
||||
return supportsSuffix(context, SUFFIXES)
|
||||
|| supportsContentType(context, "application/vnd.ms-excel")
|
||||
|| supportsContentType(context, "application/vnd.openxmlformats-officedocument.spreadsheetml");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentParseResult parse(DocumentParseContext context) {
|
||||
return parseWithTika(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bruce.common.document.parse.impl;
|
||||
|
||||
import com.bruce.common.document.parse.DocumentParseContext;
|
||||
import com.bruce.common.document.parse.DocumentParser;
|
||||
import com.bruce.common.document.parse.DocumentParseResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class PdfDocumentParser extends AbstractTikaDocumentParser implements DocumentParser {
|
||||
|
||||
private static final Set<String> SUFFIXES = Set.of("pdf");
|
||||
|
||||
@Override
|
||||
public boolean supports(DocumentParseContext context) {
|
||||
return supportsSuffix(context, SUFFIXES) || supportsContentType(context, "application/pdf");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentParseResult parse(DocumentParseContext context) {
|
||||
return parseWithTika(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.bruce.common.document.parse.impl;
|
||||
|
||||
import com.bruce.common.document.parse.DocumentParseContext;
|
||||
import com.bruce.common.document.parse.DocumentParser;
|
||||
import com.bruce.common.document.parse.DocumentParseResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class TxtDocumentParser extends AbstractTikaDocumentParser implements DocumentParser {
|
||||
|
||||
private static final Set<String> SUFFIXES = Set.of("txt", "md", "log");
|
||||
|
||||
@Override
|
||||
public boolean supports(DocumentParseContext context) {
|
||||
return supportsSuffix(context, SUFFIXES) || supportsContentType(context, "text/");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentParseResult parse(DocumentParseContext context) {
|
||||
return parseWithTika(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bruce.common.document.parse.impl;
|
||||
|
||||
import com.bruce.common.document.parse.DocumentParseContext;
|
||||
import com.bruce.common.document.parse.DocumentParser;
|
||||
import com.bruce.common.document.parse.DocumentParseResult;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class WordDocumentParser extends AbstractTikaDocumentParser implements DocumentParser {
|
||||
|
||||
private static final Set<String> SUFFIXES = Set.of("doc", "docx");
|
||||
|
||||
@Override
|
||||
public boolean supports(DocumentParseContext context) {
|
||||
return supportsSuffix(context, SUFFIXES)
|
||||
|| supportsContentType(context, "application/msword")
|
||||
|| supportsContentType(context, "application/vnd.openxmlformats-officedocument.wordprocessingml");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentParseResult parse(DocumentParseContext context) {
|
||||
return parseWithTika(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bruce.common.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.bruce.common.domain.model.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_attachment")
|
||||
@Schema(description = "系统附件")
|
||||
public class SysAttachment extends BaseEntity {
|
||||
|
||||
@Schema(description = "来源业务类型")
|
||||
@TableField("source_type")
|
||||
private String sourceType;
|
||||
|
||||
@Schema(description = "来源业务ID")
|
||||
@TableField("source_id")
|
||||
private Long sourceId;
|
||||
|
||||
@Schema(description = "原始文件名")
|
||||
@TableField("original_name")
|
||||
private String originalName;
|
||||
|
||||
@Schema(description = "存储文件名")
|
||||
@TableField("file_name")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "文件后缀")
|
||||
@TableField("file_suffix")
|
||||
private String fileSuffix;
|
||||
|
||||
@Schema(description = "文件MIME类型")
|
||||
@TableField("content_type")
|
||||
private String contentType;
|
||||
|
||||
@Schema(description = "文件大小(字节)")
|
||||
@TableField("file_size")
|
||||
private Long fileSize;
|
||||
|
||||
@Schema(description = "存储类型")
|
||||
@TableField("storage_type")
|
||||
private String storageType;
|
||||
|
||||
@Schema(description = "文件存储路径")
|
||||
@TableField("file_path")
|
||||
private String filePath;
|
||||
|
||||
@Schema(description = "文件访问地址")
|
||||
@TableField("file_url")
|
||||
private String fileUrl;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bruce.common.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.bruce.common.domain.model.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_enum")
|
||||
@Schema(description = "系统枚举配置")
|
||||
public class SysEnum extends BaseEntity {
|
||||
|
||||
@Schema(description = "模块")
|
||||
private String catalog;
|
||||
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "值")
|
||||
private Integer value;
|
||||
|
||||
@Schema(description = "字符串值")
|
||||
private String strvalue;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.bruce.common.domain.model;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.bruce.common.constant.CommonConsts;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Schema(description = "实体公共基类")
|
||||
public class BaseEntity {
|
||||
|
||||
@Schema(description = "主键ID")
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
@TableField(value = "create_by", fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@Schema(description = "创建时间", example = "2026-05-18 20:00:00")
|
||||
@JsonFormat(pattern = CommonConsts.DATE_FORMAT_LONG_STR, timezone = CommonConsts.TIME_ZONE_GMT8)
|
||||
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@Schema(description = "更新时间", example = "2026-05-18 20:00:00")
|
||||
@JsonFormat(pattern = CommonConsts.DATE_FORMAT_LONG_STR, timezone = CommonConsts.TIME_ZONE_GMT8)
|
||||
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description = "版本")
|
||||
@Version
|
||||
private Integer version;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.bruce.common.domain.model;
|
||||
|
||||
import com.bruce.common.constant.CommonConsts;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RequestResult<T> {
|
||||
|
||||
public static final String FAIL_CODE = CommonConsts.REQUEST_RESULT_FAIL_CODE;
|
||||
|
||||
public static final String SUCCESS_CODE = CommonConsts.REQUEST_RESULT_SUCCESS_CODE;
|
||||
|
||||
@Schema(description = "错误消息")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "消息代码")
|
||||
private String resultcode;
|
||||
|
||||
@Schema(description = "数据")
|
||||
private T data;
|
||||
|
||||
public RequestResult(T data) {
|
||||
this.resultcode = SUCCESS_CODE;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> RequestResult<T> success() {
|
||||
return build(SUCCESS_CODE, null, null);
|
||||
}
|
||||
|
||||
public static <T> RequestResult<T> success(T data) {
|
||||
return build(SUCCESS_CODE, null, data);
|
||||
}
|
||||
|
||||
public static <T> RequestResult<T> success(String message, T data) {
|
||||
return build(SUCCESS_CODE, message, data);
|
||||
}
|
||||
|
||||
public static <T> RequestResult<T> fail(String message) {
|
||||
return build(FAIL_CODE, message, null);
|
||||
}
|
||||
|
||||
public static <T> RequestResult<T> fail(String resultcode, String message) {
|
||||
return build(resultcode, message, null);
|
||||
}
|
||||
|
||||
private static <T> RequestResult<T> build(String resultcode, String message, T data) {
|
||||
RequestResult<T> result = new RequestResult<>();
|
||||
result.setResultcode(resultcode);
|
||||
result.setMessage(message);
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bruce.common.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Data
|
||||
@Schema(description = "附件上传请求")
|
||||
public class SysAttachmentUploadRequest {
|
||||
|
||||
@Schema(description = "上传文件")
|
||||
private MultipartFile file;
|
||||
|
||||
@Schema(description = "来源类型")
|
||||
private String sourceType;
|
||||
|
||||
@Schema(description = "来源业务ID")
|
||||
private Long sourceId;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.bruce.common.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统枚举批量保存请求")
|
||||
public class SysEnumBatchSaveRequest {
|
||||
|
||||
@Schema(description = "模块目录")
|
||||
private String catalog;
|
||||
|
||||
@Schema(description = "枚举类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "枚举项")
|
||||
private List<Item> items;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统枚举批量保存项")
|
||||
public static class Item {
|
||||
|
||||
@Schema(description = "枚举名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "整型值")
|
||||
private Integer value;
|
||||
|
||||
@Schema(description = "字符串值")
|
||||
private String strvalue;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.bruce.common.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统枚举管理查询请求")
|
||||
public class SysEnumManageQueryRequest {
|
||||
|
||||
@Schema(description = "模块目录")
|
||||
private String catalog;
|
||||
|
||||
@Schema(description = "枚举类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "关键字,匹配目录、类型、名称、字符串值或备注")
|
||||
private String keyword;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.bruce.common.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统枚举查询请求")
|
||||
public class SysEnumQueryRequest {
|
||||
|
||||
@Schema(description = "模块目录")
|
||||
private String catalog;
|
||||
|
||||
@Schema(description = "枚举类型")
|
||||
private String type;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bruce.common.dto.request;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统枚举保存请求")
|
||||
public class SysEnumSaveRequest {
|
||||
|
||||
@Schema(description = "主键ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "模块目录")
|
||||
private String catalog;
|
||||
|
||||
@Schema(description = "枚举类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "枚举名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "整型值")
|
||||
private Integer value;
|
||||
|
||||
@Schema(description = "字符串值")
|
||||
private String strvalue;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.bruce.common.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统附件响应")
|
||||
public class SysAttachmentResponse {
|
||||
|
||||
@Schema(description = "主键ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "来源业务类型")
|
||||
private String sourceType;
|
||||
|
||||
@Schema(description = "来源业务ID")
|
||||
private Long sourceId;
|
||||
|
||||
@Schema(description = "原始文件名")
|
||||
private String originalName;
|
||||
|
||||
@Schema(description = "存储文件名")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "文件后缀")
|
||||
private String fileSuffix;
|
||||
|
||||
@Schema(description = "文件MIME类型")
|
||||
private String contentType;
|
||||
|
||||
@Schema(description = "文件大小(字节)")
|
||||
private Long fileSize;
|
||||
|
||||
@Schema(description = "存储类型")
|
||||
private String storageType;
|
||||
|
||||
@Schema(description = "文件访问地址")
|
||||
private String fileUrl;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bruce.common.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "系统枚举响应")
|
||||
public class SysEnumResponse {
|
||||
|
||||
@Schema(description = "主键ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "模块目录")
|
||||
private String catalog;
|
||||
|
||||
@Schema(description = "枚举类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "枚举名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "整型值")
|
||||
private Integer value;
|
||||
|
||||
@Schema(description = "字符串值")
|
||||
private String strvalue;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.bruce.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CommonStatusEnum implements PersistableSysEnumDefinition {
|
||||
|
||||
DISABLED(0, "禁用"),
|
||||
ENABLED(1, "启用"),
|
||||
DRAFT(2, "草稿"),
|
||||
PROCESSING(3, "处理中"),
|
||||
COMPLETED(4, "已完成"),
|
||||
FAILED(5, "失败");
|
||||
|
||||
private static final String CATALOG = "common";
|
||||
|
||||
private static final String TYPE = "common_status";
|
||||
|
||||
private static final String REMARK = "通用状态";
|
||||
|
||||
private final Integer value;
|
||||
|
||||
private final String label;
|
||||
|
||||
@Override
|
||||
public String getCatalog() {
|
||||
return CATALOG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemark() {
|
||||
return REMARK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bruce.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum EnableStatusEnum implements PersistableSysEnumDefinition {
|
||||
|
||||
DISABLED(0, "禁用"),
|
||||
ENABLED(1, "启用");
|
||||
|
||||
private static final String CATALOG = "common";
|
||||
|
||||
private static final String TYPE = "enable_status";
|
||||
|
||||
private static final String REMARK = "通用启用状态";
|
||||
|
||||
private final Integer value;
|
||||
|
||||
private final String label;
|
||||
|
||||
@Override
|
||||
public String getCatalog() {
|
||||
return CATALOG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemark() {
|
||||
return REMARK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bruce.common.enums;
|
||||
|
||||
/**
|
||||
* 可同步到 sys_enum 的枚举定义契约。
|
||||
* <p>
|
||||
* 长期固定的结构化文本字段统一通过该契约描述,
|
||||
* 便于前后端传值、数据库初始化和后续协作保持同一事实来源。
|
||||
*/
|
||||
public interface PersistableSysEnumDefinition {
|
||||
|
||||
/**
|
||||
* 枚举所属模块目录,例如 common、rag。
|
||||
*/
|
||||
String getCatalog();
|
||||
|
||||
/**
|
||||
* 枚举所属类型,同一 catalog/type 视为同一个枚举组。
|
||||
*/
|
||||
String getType();
|
||||
|
||||
/**
|
||||
* 落库到 sys_enum.name 的展示名称。
|
||||
*/
|
||||
default String getName() {
|
||||
return getLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举整型值,前后端协议统一传该值。
|
||||
*/
|
||||
Integer getValue();
|
||||
|
||||
/**
|
||||
* 可选的字符串值,当前大多数业务枚举不使用。
|
||||
*/
|
||||
default String getStrvalue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序值,默认与枚举值保持一致。
|
||||
*/
|
||||
default Integer getSort() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* sys_enum.remark 中的说明文本。
|
||||
*/
|
||||
String getRemark();
|
||||
|
||||
/**
|
||||
* 枚举显示文案,通常与 name 保持一致。
|
||||
*/
|
||||
String getLabel();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.bruce.common.factory;
|
||||
|
||||
import com.bruce.common.domain.entity.SysAttachment;
|
||||
import com.bruce.common.dto.response.SysAttachmentResponse;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 系统附件工厂,集中处理附件元数据出参转换。
|
||||
*/
|
||||
@Component
|
||||
public class SysAttachmentFactory {
|
||||
|
||||
public SysAttachmentResponse toResponse(SysAttachment entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysAttachmentResponse response = new SysAttachmentResponse();
|
||||
BeanUtils.copyProperties(entity, response);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.bruce.common.factory;
|
||||
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import com.bruce.common.dto.request.SysEnumBatchSaveRequest;
|
||||
import com.bruce.common.dto.request.SysEnumSaveRequest;
|
||||
import com.bruce.common.dto.response.SysEnumResponse;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统枚举工厂,统一负责请求对象、实体对象和响应对象之间的转换。
|
||||
*/
|
||||
@Component
|
||||
public class SysEnumFactory {
|
||||
|
||||
/**
|
||||
* 将保存请求转换为实体,避免在服务层散落字段拷贝逻辑。
|
||||
*/
|
||||
public SysEnum toEntity(SysEnumSaveRequest request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
SysEnum entity = new SysEnum();
|
||||
BeanUtils.copyProperties(request, entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将批量请求中的单个枚举项转换为实体,catalog/type 由外层分组统一提供。
|
||||
*/
|
||||
public SysEnum toEntity(String catalog, String type, SysEnumBatchSaveRequest.Item item) {
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
SysEnum entity = new SysEnum();
|
||||
entity.setCatalog(catalog);
|
||||
entity.setType(type);
|
||||
entity.setName(item.getName());
|
||||
entity.setValue(item.getValue());
|
||||
entity.setStrvalue(item.getStrvalue());
|
||||
entity.setSort(item.getSort());
|
||||
entity.setRemark(item.getRemark());
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将实体转换为返回对象,保持接口层不直接暴露实体。
|
||||
*/
|
||||
public SysEnumResponse toResponse(SysEnum entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
SysEnumResponse response = new SysEnumResponse();
|
||||
BeanUtils.copyProperties(entity, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
public List<SysEnumResponse> toResponses(List<SysEnum> entities) {
|
||||
return entities == null ? List.of() : entities.stream().map(this::toResponse).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.bruce.common.handler;
|
||||
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<RequestResult<Void>> handleIllegalArgumentException(IllegalArgumentException exception) {
|
||||
log.warn("GlobalExceptionHandler.handleIllegalArgumentException, message={}", exception.getMessage(), exception);
|
||||
return buildResponse(HttpStatus.BAD_REQUEST, exception.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<RequestResult<Void>> handleException(Exception exception) {
|
||||
log.error("GlobalExceptionHandler.handleException", exception);
|
||||
return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, "系统内部错误,请稍后重试");
|
||||
}
|
||||
|
||||
private ResponseEntity<RequestResult<Void>> buildResponse(HttpStatus status, String message) {
|
||||
return ResponseEntity.status(status)
|
||||
.body(RequestResult.fail(String.valueOf(status.value()), message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.bruce.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.bruce.common.domain.entity.SysAttachment;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SysAttachmentMapper extends BaseMapper<SysAttachment> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.bruce.common.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface SysEnumMapper extends BaseMapper<SysEnum> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.bruce.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bruce.common.domain.entity.SysAttachment;
|
||||
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
|
||||
|
||||
public interface ISysAttachmentService extends IService<SysAttachment> {
|
||||
|
||||
SysAttachment upload(SysAttachmentUploadRequest request);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.bruce.common.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import com.bruce.common.dto.request.SysEnumBatchSaveRequest;
|
||||
import com.bruce.common.dto.request.SysEnumManageQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumSaveRequest;
|
||||
import com.bruce.common.dto.response.SysEnumResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISysEnumService extends IService<SysEnum> {
|
||||
|
||||
List<SysEnum> listByCatalogAndType(SysEnumQueryRequest request);
|
||||
|
||||
List<SysEnumResponse> listResponses();
|
||||
|
||||
List<SysEnumResponse> listByCatalogAndTypeResponses(SysEnumQueryRequest request);
|
||||
|
||||
List<SysEnumResponse> listForManagement(SysEnumManageQueryRequest request);
|
||||
|
||||
SysEnumResponse getResponseById(Long id);
|
||||
|
||||
boolean saveOrUpdate(SysEnumSaveRequest request);
|
||||
|
||||
boolean batchSave(SysEnumBatchSaveRequest request);
|
||||
|
||||
boolean replaceByCatalogAndType(String catalog, String type, List<SysEnum> items);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.bruce.common.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bruce.common.config.AttachmentProperties;
|
||||
import com.bruce.common.constant.CommonConsts;
|
||||
import com.bruce.common.domain.entity.SysAttachment;
|
||||
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
|
||||
import com.bruce.common.mapper.SysAttachmentMapper;
|
||||
import com.bruce.common.service.ISysAttachmentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class SysAttachmentServiceImpl extends ServiceImpl<SysAttachmentMapper, SysAttachment> implements ISysAttachmentService {
|
||||
|
||||
@Autowired
|
||||
private AttachmentProperties attachmentProperties;
|
||||
|
||||
@Override
|
||||
public SysAttachment upload(SysAttachmentUploadRequest request) {
|
||||
// 这里先完成上传校验和本地落盘,后续如接入对象存储也只需替换该流程内部实现。
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("上传请求不能为空");
|
||||
}
|
||||
MultipartFile file = request.getFile();
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new IllegalArgumentException("上传文件不能为空");
|
||||
}
|
||||
String sourceType = request.getSourceType();
|
||||
if (!StringUtils.hasText(sourceType)) {
|
||||
throw new IllegalArgumentException("sourceType不能为空");
|
||||
}
|
||||
|
||||
String originalName = file.getOriginalFilename();
|
||||
String suffix = StringUtils.getFilenameExtension(originalName);
|
||||
String storedFileName = UUID.randomUUID() + (StringUtils.hasText(suffix) ? "." + suffix : "");
|
||||
String dateDirectory = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
Path rootPath = Paths.get(attachmentProperties.getBasePath()).toAbsolutePath().normalize();
|
||||
Path targetDirectory = rootPath.resolve(dateDirectory);
|
||||
Path targetPath = targetDirectory.resolve(storedFileName);
|
||||
|
||||
try {
|
||||
Files.createDirectories(targetDirectory);
|
||||
file.transferTo(targetPath);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("文件上传失败", e);
|
||||
}
|
||||
|
||||
SysAttachment attachment = new SysAttachment();
|
||||
attachment.setSourceType(sourceType);
|
||||
attachment.setSourceId(request.getSourceId());
|
||||
attachment.setOriginalName(originalName);
|
||||
attachment.setFileName(storedFileName);
|
||||
attachment.setFileSuffix(suffix);
|
||||
attachment.setContentType(file.getContentType());
|
||||
attachment.setFileSize(file.getSize());
|
||||
attachment.setStorageType(CommonConsts.STORAGE_TYPE_LOCAL);
|
||||
attachment.setFilePath(dateDirectory + "/" + storedFileName);
|
||||
attachment.setFileUrl(null);
|
||||
attachment.setVersion(1);
|
||||
save(attachment);
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package com.bruce.common.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import com.bruce.common.dto.request.SysEnumBatchSaveRequest;
|
||||
import com.bruce.common.dto.request.SysEnumManageQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumSaveRequest;
|
||||
import com.bruce.common.dto.response.SysEnumResponse;
|
||||
import com.bruce.common.factory.SysEnumFactory;
|
||||
import com.bruce.common.mapper.SysEnumMapper;
|
||||
import com.bruce.common.service.ISysEnumService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SysEnumServiceImpl extends ServiceImpl<SysEnumMapper, SysEnum> implements ISysEnumService {
|
||||
|
||||
private final SysEnumFactory sysEnumFactory;
|
||||
|
||||
@Override
|
||||
public List<SysEnum> listByCatalogAndType(SysEnumQueryRequest request) {
|
||||
log.info("SysEnumServiceImpl.listByCatalogAndType start, request={}", request);
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("查询请求不能为空");
|
||||
}
|
||||
List<SysEnum> result = lambdaQuery()
|
||||
.eq(StringUtils.hasText(request.getCatalog()), SysEnum::getCatalog, request.getCatalog())
|
||||
.eq(StringUtils.hasText(request.getType()), SysEnum::getType, request.getType())
|
||||
.orderByAsc(SysEnum::getSort)
|
||||
.list();
|
||||
log.info("SysEnumServiceImpl.listByCatalogAndType success, count={}", result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysEnumResponse> listResponses() {
|
||||
log.info("查询系统枚举列表开始");
|
||||
List<SysEnumResponse> responses = sysEnumFactory.toResponses(list());
|
||||
log.info("查询系统枚举列表结束,count={}", responses.size());
|
||||
return responses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysEnumResponse> listByCatalogAndTypeResponses(SysEnumQueryRequest request) {
|
||||
log.info("按模块和类型查询系统枚举开始,catalog={}, type={}",
|
||||
request == null ? null : request.getCatalog(),
|
||||
request == null ? null : request.getType());
|
||||
List<SysEnumResponse> responses = sysEnumFactory.toResponses(listByCatalogAndType(request));
|
||||
log.info("按模块和类型查询系统枚举结束,count={}", responses.size());
|
||||
return responses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysEnumResponse> listForManagement(SysEnumManageQueryRequest request) {
|
||||
log.info("SysEnumServiceImpl.listForManagement start, request={}", request);
|
||||
SysEnumManageQueryRequest queryRequest = request == null ? new SysEnumManageQueryRequest() : request;
|
||||
String keyword = queryRequest.getKeyword();
|
||||
List<SysEnumResponse> responses = sysEnumFactory.toResponses(lambdaQuery()
|
||||
.eq(StringUtils.hasText(queryRequest.getCatalog()), SysEnum::getCatalog, queryRequest.getCatalog())
|
||||
.eq(StringUtils.hasText(queryRequest.getType()), SysEnum::getType, queryRequest.getType())
|
||||
.and(StringUtils.hasText(keyword), wrapper -> wrapper
|
||||
.like(SysEnum::getCatalog, keyword)
|
||||
.or()
|
||||
.like(SysEnum::getType, keyword)
|
||||
.or()
|
||||
.like(SysEnum::getName, keyword)
|
||||
.or()
|
||||
.like(SysEnum::getStrvalue, keyword)
|
||||
.or()
|
||||
.like(SysEnum::getRemark, keyword))
|
||||
.orderByAsc(SysEnum::getCatalog)
|
||||
.orderByAsc(SysEnum::getType)
|
||||
.orderByAsc(SysEnum::getSort)
|
||||
.orderByAsc(SysEnum::getId)
|
||||
.list());
|
||||
log.info("SysEnumServiceImpl.listForManagement success, count={}", responses.size());
|
||||
return responses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysEnumResponse getResponseById(Long id) {
|
||||
log.info("查询系统枚举详情开始,id={}", id);
|
||||
SysEnumResponse response = sysEnumFactory.toResponse(getById(id));
|
||||
log.info("查询系统枚举详情结束,id={}, found={}", id, response != null);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveOrUpdate(SysEnumSaveRequest request) {
|
||||
log.info("保存系统枚举开始,id={}, catalog={}, type={}, value={}",
|
||||
request == null ? null : request.getId(),
|
||||
request == null ? null : request.getCatalog(),
|
||||
request == null ? null : request.getType(),
|
||||
request == null ? null : request.getValue());
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("保存请求不能为空");
|
||||
}
|
||||
|
||||
SysEnum sysEnum = sysEnumFactory.toEntity(request);
|
||||
boolean result = super.saveOrUpdate(sysEnum);
|
||||
log.info("保存系统枚举结束,id={}, catalog={}, type={}, value={}, result={}",
|
||||
request.getId(), request.getCatalog(), request.getType(), request.getValue(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean batchSave(SysEnumBatchSaveRequest request) {
|
||||
log.info("批量保存系统枚举开始,catalog={}, type={}",
|
||||
request == null ? null : request.getCatalog(),
|
||||
request == null ? null : request.getType());
|
||||
List<SysEnum> existingEnums = lambdaQuery()
|
||||
.eq(request != null && StringUtils.hasText(request.getCatalog()), SysEnum::getCatalog, request == null ? null : request.getCatalog())
|
||||
.eq(request != null && StringUtils.hasText(request.getType()), SysEnum::getType, request == null ? null : request.getType())
|
||||
.list();
|
||||
validateBatchSaveRequest(request, existingEnums);
|
||||
|
||||
List<SysEnum> enums = request.getItems().stream()
|
||||
.map(item -> sysEnumFactory.toEntity(request.getCatalog(), request.getType(), item))
|
||||
.toList();
|
||||
boolean result = saveBatch(enums);
|
||||
log.info("批量保存系统枚举结束,catalog={}, type={}, itemCount={}, result={}",
|
||||
request.getCatalog(), request.getType(), enums.size(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean replaceByCatalogAndType(String catalog, String type, List<SysEnum> items) {
|
||||
log.info("SysEnumServiceImpl.replaceByCatalogAndType start, catalog={}, type={}, itemCount={}",
|
||||
catalog, type, items == null ? 0 : items.size());
|
||||
validateReplaceByCatalogAndTypeRequest(catalog, type, items);
|
||||
|
||||
boolean removed = lambdaUpdate()
|
||||
.eq(SysEnum::getCatalog, catalog)
|
||||
.eq(SysEnum::getType, type)
|
||||
.remove();
|
||||
boolean saved = saveBatch(items);
|
||||
boolean result = removed || saved;
|
||||
log.info("SysEnumServiceImpl.replaceByCatalogAndType success, catalog={}, type={}, removed={}, saved={}, result={}",
|
||||
catalog, type, removed, saved, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void validateBatchSaveRequest(SysEnumBatchSaveRequest request, List<SysEnum> existingEnums) {
|
||||
log.info("SysEnumServiceImpl.validateBatchSaveRequest start");
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("批量保存请求不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(request.getCatalog())) {
|
||||
throw new IllegalArgumentException("模块目录不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(request.getType())) {
|
||||
throw new IllegalArgumentException("枚举类型不能为空");
|
||||
}
|
||||
if (request.getItems() == null || request.getItems().isEmpty()) {
|
||||
throw new IllegalArgumentException("枚举项不能为空");
|
||||
}
|
||||
|
||||
Set<Integer> requestValues = new HashSet<>();
|
||||
for (SysEnumBatchSaveRequest.Item item : request.getItems()) {
|
||||
if (item == null) {
|
||||
throw new IllegalArgumentException("枚举项不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(item.getName())) {
|
||||
throw new IllegalArgumentException("枚举名称不能为空");
|
||||
}
|
||||
if (item.getValue() == null) {
|
||||
throw new IllegalArgumentException("枚举整型值不能为空");
|
||||
}
|
||||
if (!requestValues.add(item.getValue())) {
|
||||
throw new IllegalArgumentException("批量保存请求中存在重复枚举值: " + item.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
Set<Integer> existingValues = new HashSet<>();
|
||||
if (existingEnums != null) {
|
||||
existingEnums.stream()
|
||||
.map(SysEnum::getValue)
|
||||
.forEach(existingValues::add);
|
||||
}
|
||||
for (Integer value : requestValues) {
|
||||
if (existingValues.contains(value)) {
|
||||
throw new IllegalArgumentException("枚举值已存在: " + value);
|
||||
}
|
||||
}
|
||||
log.info("SysEnumServiceImpl.validateBatchSaveRequest success, catalog={}, type={}, requestValueCount={}, existingValueCount={}",
|
||||
request.getCatalog(), request.getType(), requestValues.size(), existingValues.size());
|
||||
}
|
||||
|
||||
private void validateReplaceByCatalogAndTypeRequest(String catalog, String type, List<SysEnum> items) {
|
||||
if (!StringUtils.hasText(catalog)) {
|
||||
throw new IllegalArgumentException("模块目录不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(type)) {
|
||||
throw new IllegalArgumentException("枚举类型不能为空");
|
||||
}
|
||||
if (items == null || items.isEmpty()) {
|
||||
throw new IllegalArgumentException("枚举项不能为空");
|
||||
}
|
||||
|
||||
Set<Integer> values = new HashSet<>();
|
||||
Set<Integer> sorts = new HashSet<>();
|
||||
for (SysEnum item : items) {
|
||||
if (item == null) {
|
||||
throw new IllegalArgumentException("枚举项不能为空");
|
||||
}
|
||||
if (!catalog.equals(item.getCatalog()) || !type.equals(item.getType())) {
|
||||
throw new IllegalArgumentException("替换的枚举项 catalog/type 必须与目标分组一致");
|
||||
}
|
||||
if (!StringUtils.hasText(item.getName())) {
|
||||
throw new IllegalArgumentException("枚举名称不能为空");
|
||||
}
|
||||
if (item.getValue() == null) {
|
||||
throw new IllegalArgumentException("枚举整型值不能为空");
|
||||
}
|
||||
if (!values.add(item.getValue())) {
|
||||
throw new IllegalArgumentException("枚举值重复: " + item.getValue());
|
||||
}
|
||||
if (item.getSort() == null) {
|
||||
throw new IllegalArgumentException("枚举排序不能为空");
|
||||
}
|
||||
if (!sorts.add(item.getSort())) {
|
||||
throw new IllegalArgumentException("枚举排序重复: " + item.getSort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.bruce.common.typehandler;
|
||||
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
public class PgJsonbStringTypeHandler extends BaseTypeHandler<String> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
|
||||
ps.setObject(i, parameter, Types.OTHER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
return rs.getString(columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
return rs.getString(columnIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
return cs.getString(columnIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.bruce.common.attachment;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bruce.common.config.AttachmentProperties;
|
||||
import com.bruce.common.controller.SysAttachmentController;
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import com.bruce.common.dto.request.SysAttachmentUploadRequest;
|
||||
import com.bruce.common.domain.entity.SysAttachment;
|
||||
import com.bruce.common.dto.response.SysAttachmentResponse;
|
||||
import com.bruce.common.factory.SysAttachmentFactory;
|
||||
import com.bruce.common.mapper.SysAttachmentMapper;
|
||||
import com.bruce.common.service.ISysAttachmentService;
|
||||
import com.bruce.common.service.impl.SysAttachmentServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SysAttachmentComponentStructureTests {
|
||||
|
||||
@Test
|
||||
void sysAttachmentComponentsShouldReuseMybatisPlusBaseTypes() {
|
||||
assertTrue(BaseMapper.class.isAssignableFrom(SysAttachmentMapper.class));
|
||||
assertTrue(IService.class.isAssignableFrom(ISysAttachmentService.class));
|
||||
assertTrue(ServiceImpl.class.isAssignableFrom(SysAttachmentServiceImpl.class));
|
||||
assertTrue(ISysAttachmentService.class.isAssignableFrom(SysAttachmentServiceImpl.class));
|
||||
assertTrue(SysAttachment.class.isAssignableFrom(SysAttachment.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sysAttachmentShouldExposeUploadMethods() throws NoSuchMethodException {
|
||||
Method serviceMethod = ISysAttachmentService.class.getMethod(
|
||||
"upload",
|
||||
SysAttachmentUploadRequest.class
|
||||
);
|
||||
Method controllerMethod = SysAttachmentController.class.getMethod(
|
||||
"upload",
|
||||
SysAttachmentUploadRequest.class
|
||||
);
|
||||
|
||||
assertNotNull(serviceMethod);
|
||||
assertNotNull(controllerMethod);
|
||||
assertEquals(SysAttachment.class, serviceMethod.getReturnType());
|
||||
assertEquals(RequestResult.class, controllerMethod.getReturnType());
|
||||
assertTrue(controllerMethod.getGenericReturnType().getTypeName().contains("SysAttachmentResponse"));
|
||||
assertEquals(SysAttachmentResponse.class, SysAttachmentFactory.class.getMethod("toResponse", SysAttachment.class).getReturnType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void attachmentPropertiesShouldExposeBindableBasePath() {
|
||||
AttachmentProperties properties = new AttachmentProperties();
|
||||
|
||||
assertEquals("data/attachments", properties.getBasePath());
|
||||
|
||||
properties.setBasePath("custom/attachments");
|
||||
|
||||
assertEquals("custom/attachments", properties.getBasePath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bruce.common.config;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class MybatisPlusConfigTests {
|
||||
|
||||
@Test
|
||||
void mybatisPlusInterceptorShouldRegisterOptimisticLocker() {
|
||||
MybatisPlusConfig config = new MybatisPlusConfig();
|
||||
|
||||
var interceptor = config.mybatisPlusInterceptor();
|
||||
|
||||
assertTrue(interceptor.getInterceptors().stream()
|
||||
.anyMatch(OptimisticLockerInnerInterceptor.class::isInstance));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.bruce.common.constant;
|
||||
|
||||
import com.bruce.common.config.AttachmentProperties;
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class CommonConstsStructureTests {
|
||||
|
||||
@Test
|
||||
void commonConstsShouldExposeReusableInfrastructureConstants() {
|
||||
assertEquals("yyyy-MM-dd HH:mm:ss", CommonConsts.DATE_FORMAT_LONG_STR);
|
||||
assertEquals("yyyy-MM-dd HH:mm:ss.SSS", CommonConsts.DATE_FORMAT_MILLIS_STR);
|
||||
assertEquals("GMT+8", CommonConsts.TIME_ZONE_GMT8);
|
||||
assertEquals("data/attachments", CommonConsts.DEFAULT_ATTACHMENT_BASE_PATH);
|
||||
assertEquals("LOCAL", CommonConsts.STORAGE_TYPE_LOCAL);
|
||||
assertEquals("0", CommonConsts.REQUEST_RESULT_SUCCESS_CODE);
|
||||
assertEquals("-1", CommonConsts.REQUEST_RESULT_FAIL_CODE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sharedDefaultConstantsShouldBeReusedByCommonComponents() {
|
||||
AttachmentProperties properties = new AttachmentProperties();
|
||||
|
||||
assertEquals(CommonConsts.DEFAULT_ATTACHMENT_BASE_PATH, properties.getBasePath());
|
||||
assertEquals(CommonConsts.REQUEST_RESULT_SUCCESS_CODE, RequestResult.SUCCESS_CODE);
|
||||
assertEquals(CommonConsts.REQUEST_RESULT_FAIL_CODE, RequestResult.FAIL_CODE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.bruce.common.document.parse;
|
||||
|
||||
import com.bruce.common.document.parse.impl.ExcelDocumentParser;
|
||||
import com.bruce.common.document.parse.impl.PdfDocumentParser;
|
||||
import com.bruce.common.document.parse.impl.TxtDocumentParser;
|
||||
import com.bruce.common.document.parse.impl.WordDocumentParser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class DocumentParserFactoryTests {
|
||||
|
||||
@Test
|
||||
void resolveShouldChooseParserByFileSuffix() {
|
||||
DocumentParserFactory factory = new DocumentParserFactory(List.of(
|
||||
new TxtDocumentParser(),
|
||||
new WordDocumentParser(),
|
||||
new PdfDocumentParser(),
|
||||
new ExcelDocumentParser()
|
||||
));
|
||||
|
||||
assertEquals(TxtDocumentParser.class, factory.resolve(context("txt")).getClass());
|
||||
assertEquals(WordDocumentParser.class, factory.resolve(context("docx")).getClass());
|
||||
assertEquals(PdfDocumentParser.class, factory.resolve(context("pdf")).getClass());
|
||||
assertEquals(ExcelDocumentParser.class, factory.resolve(context("xlsx")).getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveShouldRejectUnsupportedSuffix() {
|
||||
DocumentParserFactory factory = new DocumentParserFactory(List.of(new TxtDocumentParser()));
|
||||
|
||||
DocumentParseException exception = assertThrows(
|
||||
DocumentParseException.class,
|
||||
() -> factory.resolve(context("zip"))
|
||||
);
|
||||
|
||||
assertEquals("不支持的文档类型: zip", exception.getMessage());
|
||||
}
|
||||
|
||||
private DocumentParseContext context(String suffix) {
|
||||
DocumentParseContext context = new DocumentParseContext();
|
||||
context.setSuffix(suffix);
|
||||
context.setFilePath(Path.of("sample." + suffix));
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.bruce.common.document.parse;
|
||||
|
||||
import com.bruce.common.document.parse.impl.TxtDocumentParser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class TxtDocumentParserTests {
|
||||
|
||||
@TempDir
|
||||
private Path tempDir;
|
||||
|
||||
@Test
|
||||
void parseShouldReadPlainTextContent() throws Exception {
|
||||
Path file = tempDir.resolve("people.txt");
|
||||
Files.writeString(file, "张三 是 产品经理\n李四 是 后端工程师", StandardCharsets.UTF_8);
|
||||
DocumentParseContext context = new DocumentParseContext();
|
||||
context.setOriginalName("people.txt");
|
||||
context.setSuffix("txt");
|
||||
context.setContentType("text/plain");
|
||||
context.setFilePath(file);
|
||||
|
||||
DocumentParseResult result = new TxtDocumentParser().parse(context);
|
||||
|
||||
assertEquals("张三 是 产品经理\n李四 是 后端工程师", result.getText());
|
||||
assertEquals(result.getText().length(), result.getTextLength());
|
||||
assertTrue(result.getMetadata().get("contentType").toString().startsWith("text/plain"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsShouldAcceptTextSuffixAndContentType() {
|
||||
TxtDocumentParser parser = new TxtDocumentParser();
|
||||
DocumentParseContext suffixContext = new DocumentParseContext();
|
||||
suffixContext.setSuffix("TXT");
|
||||
DocumentParseContext contentTypeContext = new DocumentParseContext();
|
||||
contentTypeContext.setContentType("text/plain");
|
||||
|
||||
assertTrue(parser.supports(suffixContext));
|
||||
assertTrue(parser.supports(contentTypeContext));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.bruce.common.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.bruce.common.domain.model.BaseEntity;
|
||||
import com.bruce.common.domain.entity.SysAttachment;
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class EntityStructureTests {
|
||||
|
||||
@Test
|
||||
void baseEntityShouldUseMybatisPlusAssignedIdAndSysEnumShouldContainBusinessFields() throws NoSuchFieldException {
|
||||
Field idField = BaseEntity.class.getDeclaredField("id");
|
||||
TableId tableId = idField.getAnnotation(TableId.class);
|
||||
|
||||
assertNotNull(tableId);
|
||||
assertEquals(IdType.ASSIGN_ID, tableId.type());
|
||||
|
||||
Set<String> fieldNames = Arrays.stream(SysEnum.class.getDeclaredFields())
|
||||
.map(Field::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
assertTrue(fieldNames.contains("catalog"));
|
||||
assertTrue(fieldNames.contains("type"));
|
||||
assertTrue(fieldNames.contains("name"));
|
||||
assertTrue(fieldNames.contains("value"));
|
||||
assertTrue(fieldNames.contains("strvalue"));
|
||||
assertTrue(fieldNames.contains("sort"));
|
||||
assertTrue(fieldNames.contains("remark"));
|
||||
assertTrue(BaseEntity.class.isAssignableFrom(SysEnum.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entitiesShouldExposeOpenApiSchemaAnnotations() throws NoSuchFieldException {
|
||||
Schema baseSchema = BaseEntity.class.getAnnotation(Schema.class);
|
||||
Schema sysEnumSchema = SysEnum.class.getAnnotation(Schema.class);
|
||||
Schema createTimeSchema = BaseEntity.class.getDeclaredField("createTime").getAnnotation(Schema.class);
|
||||
Schema catalogSchema = SysEnum.class.getDeclaredField("catalog").getAnnotation(Schema.class);
|
||||
|
||||
assertNotNull(baseSchema);
|
||||
assertNotNull(sysEnumSchema);
|
||||
assertNotNull(createTimeSchema);
|
||||
assertNotNull(catalogSchema);
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionShouldBeDefinedOnBaseEntityOnly() throws NoSuchFieldException {
|
||||
Field versionField = BaseEntity.class.getDeclaredField("version");
|
||||
Version version = versionField.getAnnotation(Version.class);
|
||||
|
||||
assertNotNull(versionField);
|
||||
assertNotNull(version);
|
||||
assertTrue(Arrays.stream(SysEnum.class.getDeclaredFields()).noneMatch(field -> "version".equals(field.getName())));
|
||||
assertTrue(Arrays.stream(SysAttachment.class.getDeclaredFields()).noneMatch(field -> "version".equals(field.getName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void auditFieldsShouldUseMybatisPlusAutoFill() throws NoSuchFieldException {
|
||||
TableField createBy = BaseEntity.class.getDeclaredField("createBy").getAnnotation(TableField.class);
|
||||
TableField createTime = BaseEntity.class.getDeclaredField("createTime").getAnnotation(TableField.class);
|
||||
TableField updateBy = BaseEntity.class.getDeclaredField("updateBy").getAnnotation(TableField.class);
|
||||
TableField updateTime = BaseEntity.class.getDeclaredField("updateTime").getAnnotation(TableField.class);
|
||||
|
||||
assertNotNull(createBy);
|
||||
assertNotNull(createTime);
|
||||
assertNotNull(updateBy);
|
||||
assertNotNull(updateTime);
|
||||
assertEquals(FieldFill.INSERT, createBy.fill());
|
||||
assertEquals(FieldFill.INSERT, createTime.fill());
|
||||
assertEquals(FieldFill.INSERT_UPDATE, updateBy.fill());
|
||||
assertEquals(FieldFill.INSERT_UPDATE, updateTime.fill());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.bruce.common.enumconfig;
|
||||
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import com.bruce.common.dto.request.SysEnumBatchSaveRequest;
|
||||
import com.bruce.common.factory.SysEnumFactory;
|
||||
import com.bruce.common.service.impl.SysEnumServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class SysEnumBatchSaveValidationTests {
|
||||
|
||||
private final SysEnumFactory sysEnumFactory = new SysEnumFactory();
|
||||
|
||||
@Test
|
||||
void batchSaveShouldRejectDuplicateValuesInsideRequest() {
|
||||
SysEnumServiceImpl service = new SysEnumServiceImpl(sysEnumFactory);
|
||||
SysEnumBatchSaveRequest request = new SysEnumBatchSaveRequest();
|
||||
request.setCatalog("common");
|
||||
request.setType("enable_status");
|
||||
request.setItems(List.of(
|
||||
item("启用", 1),
|
||||
item("可用", 1)
|
||||
));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> service.validateBatchSaveRequest(request, List.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchSaveShouldRejectDuplicateValuesFromExistingEnums() {
|
||||
SysEnumServiceImpl service = new SysEnumServiceImpl(sysEnumFactory);
|
||||
SysEnum existing = new SysEnum();
|
||||
existing.setCatalog("common");
|
||||
existing.setType("enable_status");
|
||||
existing.setValue(1);
|
||||
|
||||
SysEnumBatchSaveRequest request = new SysEnumBatchSaveRequest();
|
||||
request.setCatalog("common");
|
||||
request.setType("enable_status");
|
||||
request.setItems(List.of(item("启用", 1)));
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> service.validateBatchSaveRequest(request, List.of(existing))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchSaveShouldAcceptUniqueValues() {
|
||||
SysEnumServiceImpl service = new SysEnumServiceImpl(sysEnumFactory);
|
||||
SysEnumBatchSaveRequest request = new SysEnumBatchSaveRequest();
|
||||
request.setCatalog("common");
|
||||
request.setType("enable_status");
|
||||
request.setItems(List.of(
|
||||
item("禁用", 0),
|
||||
item("启用", 1)
|
||||
));
|
||||
|
||||
assertDoesNotThrow(() -> service.validateBatchSaveRequest(request, List.of()));
|
||||
}
|
||||
|
||||
private SysEnumBatchSaveRequest.Item item(String name, Integer value) {
|
||||
SysEnumBatchSaveRequest.Item item = new SysEnumBatchSaveRequest.Item();
|
||||
item.setName(name);
|
||||
item.setValue(value);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.bruce.common.enumconfig;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bruce.common.controller.SysEnumController;
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import com.bruce.common.dto.request.SysEnumBatchSaveRequest;
|
||||
import com.bruce.common.dto.request.SysEnumManageQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumQueryRequest;
|
||||
import com.bruce.common.dto.request.SysEnumSaveRequest;
|
||||
import com.bruce.common.dto.response.SysEnumResponse;
|
||||
import com.bruce.common.factory.SysEnumFactory;
|
||||
import com.bruce.common.mapper.SysEnumMapper;
|
||||
import com.bruce.common.service.ISysEnumService;
|
||||
import com.bruce.common.service.impl.SysEnumServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SysEnumComponentStructureTests {
|
||||
|
||||
@Test
|
||||
void sysEnumComponentsShouldReuseMybatisPlusBaseTypes() {
|
||||
assertTrue(BaseMapper.class.isAssignableFrom(SysEnumMapper.class));
|
||||
assertTrue(IService.class.isAssignableFrom(ISysEnumService.class));
|
||||
assertTrue(ServiceImpl.class.isAssignableFrom(SysEnumServiceImpl.class));
|
||||
assertTrue(ISysEnumService.class.isAssignableFrom(SysEnumServiceImpl.class));
|
||||
assertTrue(SysEnum.class.isAssignableFrom(SysEnum.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sysEnumShouldExposeQueryMethodForCatalogAndType() throws NoSuchMethodException {
|
||||
Method serviceMethod = ISysEnumService.class.getMethod("listByCatalogAndType", SysEnumQueryRequest.class);
|
||||
Method controllerMethod = SysEnumController.class.getMethod("queryByCatalogAndType", SysEnumQueryRequest.class);
|
||||
|
||||
assertNotNull(serviceMethod);
|
||||
assertNotNull(controllerMethod);
|
||||
assertEquals(List.class, serviceMethod.getReturnType());
|
||||
assertEquals(RequestResult.class, controllerMethod.getReturnType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sysEnumShouldExposeSaveOrUpdateAndDeleteInterfaces() throws NoSuchMethodException {
|
||||
Method saveOrUpdateMethod = ISysEnumService.class.getMethod("saveOrUpdate", SysEnumSaveRequest.class);
|
||||
Method batchSaveMethod = ISysEnumService.class.getMethod("batchSave", SysEnumBatchSaveRequest.class);
|
||||
Method controllerSaveOrUpdateMethod = SysEnumController.class.getMethod("saveOrUpdate", SysEnumSaveRequest.class);
|
||||
Method controllerBatchSaveMethod = SysEnumController.class.getMethod("batchSave", SysEnumBatchSaveRequest.class);
|
||||
Method deleteMethod = SysEnumController.class.getMethod("deleteById", Long.class);
|
||||
Method listMethod = SysEnumController.class.getMethod("list");
|
||||
|
||||
assertNotNull(saveOrUpdateMethod);
|
||||
assertNotNull(batchSaveMethod);
|
||||
assertNotNull(controllerSaveOrUpdateMethod);
|
||||
assertNotNull(controllerBatchSaveMethod);
|
||||
assertNotNull(deleteMethod);
|
||||
assertNotNull(listMethod);
|
||||
assertEquals(boolean.class, saveOrUpdateMethod.getReturnType());
|
||||
assertEquals(boolean.class, batchSaveMethod.getReturnType());
|
||||
assertEquals(RequestResult.class, controllerSaveOrUpdateMethod.getReturnType());
|
||||
assertEquals(RequestResult.class, controllerBatchSaveMethod.getReturnType());
|
||||
assertEquals(RequestResult.class, deleteMethod.getReturnType());
|
||||
assertEquals(RequestResult.class, listMethod.getReturnType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sysEnumShouldExposeManagementQueryWithoutChangingCatalogTypeQuery() throws NoSuchMethodException {
|
||||
Method serviceMethod = ISysEnumService.class.getMethod("listForManagement", SysEnumManageQueryRequest.class);
|
||||
Method controllerMethod = SysEnumController.class.getMethod("queryForManagement", SysEnumManageQueryRequest.class);
|
||||
Method catalogTypeMethod = ISysEnumService.class.getMethod("listByCatalogAndType", SysEnumQueryRequest.class);
|
||||
|
||||
assertNotNull(serviceMethod);
|
||||
assertNotNull(controllerMethod);
|
||||
assertNotNull(catalogTypeMethod);
|
||||
assertEquals(List.class, serviceMethod.getReturnType());
|
||||
assertEquals(RequestResult.class, controllerMethod.getReturnType());
|
||||
assertEquals(List.class, catalogTypeMethod.getReturnType());
|
||||
assertTrue(serviceMethod.getGenericReturnType().getTypeName().contains("SysEnumResponse"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sysEnumControllerShouldReturnDtoContracts() throws NoSuchMethodException {
|
||||
Method listMethod = SysEnumController.class.getMethod("list");
|
||||
Method manageQueryMethod = SysEnumController.class.getMethod("queryForManagement", SysEnumManageQueryRequest.class);
|
||||
Method detailMethod = SysEnumController.class.getMethod("getById", Long.class);
|
||||
Method serviceListMethod = ISysEnumService.class.getMethod("listResponses");
|
||||
Method responseFactory = SysEnumFactory.class.getMethod("toResponse", SysEnum.class);
|
||||
|
||||
assertTrue(serviceListMethod.getGenericReturnType().getTypeName().contains("SysEnumResponse"));
|
||||
assertTrue(listMethod.getGenericReturnType().getTypeName().contains("SysEnumResponse"));
|
||||
assertTrue(manageQueryMethod.getGenericReturnType().getTypeName().contains("SysEnumResponse"));
|
||||
assertTrue(detailMethod.getGenericReturnType().getTypeName().contains("SysEnumResponse"));
|
||||
assertEquals(SysEnumResponse.class, responseFactory.getReturnType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sysEnumShouldProvideDefinitionSyncSupportEntry() throws NoSuchMethodException {
|
||||
Method groupMethod = SysEnumDefinitionSyncSupport.class.getDeclaredMethod("groupOf", List.class);
|
||||
Method uniqueKeyMethod = SysEnumDefinitionSyncSupport.class.getDeclaredMethod("validateUniqueGroupKeys", List.class);
|
||||
|
||||
assertNotNull(groupMethod);
|
||||
assertNotNull(uniqueKeyMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sysEnumServiceShouldExposeReplaceByCatalogAndType() throws NoSuchMethodException {
|
||||
Method replaceMethod = ISysEnumService.class.getMethod("replaceByCatalogAndType", String.class, String.class, List.class);
|
||||
|
||||
assertNotNull(replaceMethod);
|
||||
assertEquals(boolean.class, replaceMethod.getReturnType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.bruce.common.enumconfig;
|
||||
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import com.bruce.common.enums.PersistableSysEnumDefinition;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* sys_enum 初始化测试的辅助工具。
|
||||
* <p>
|
||||
* 该类只服务于测试入口,用于把代码里的枚举定义组装成可落库的数据结构,
|
||||
* 并在真正写库前完成组级唯一性校验。
|
||||
*/
|
||||
final class SysEnumDefinitionSyncSupport {
|
||||
|
||||
private SysEnumDefinitionSyncSupport() {
|
||||
}
|
||||
|
||||
static EnumGroup groupOf(List<? extends PersistableSysEnumDefinition> definitions) {
|
||||
if (definitions == null || definitions.isEmpty()) {
|
||||
throw new IllegalArgumentException("枚举定义不能为空");
|
||||
}
|
||||
PersistableSysEnumDefinition first = definitions.getFirst();
|
||||
validateGroupMembers(first, definitions);
|
||||
validateUniqueValuesAndSorts(first, definitions);
|
||||
return new EnumGroup(first.getCatalog(), first.getType(), List.copyOf(definitions));
|
||||
}
|
||||
|
||||
static void validateUniqueGroupKeys(List<EnumGroup> groups) {
|
||||
Set<String> keys = new HashSet<>();
|
||||
for (EnumGroup group : groups) {
|
||||
String key = group.catalog() + "/" + group.type();
|
||||
if (!keys.add(key)) {
|
||||
throw new IllegalArgumentException("存在重复的枚举分组: " + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static List<SysEnum> toEntities(EnumGroup group) {
|
||||
return group.definitions().stream()
|
||||
.map(item -> {
|
||||
SysEnum sysEnum = new SysEnum();
|
||||
sysEnum.setCatalog(group.catalog());
|
||||
sysEnum.setType(group.type());
|
||||
sysEnum.setName(item.getName());
|
||||
sysEnum.setValue(item.getValue());
|
||||
sysEnum.setStrvalue(item.getStrvalue());
|
||||
sysEnum.setSort(item.getSort());
|
||||
sysEnum.setRemark(item.getRemark());
|
||||
return sysEnum;
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static void validateGroupMembers(
|
||||
PersistableSysEnumDefinition first,
|
||||
List<? extends PersistableSysEnumDefinition> definitions
|
||||
) {
|
||||
for (PersistableSysEnumDefinition item : definitions) {
|
||||
if (!first.getCatalog().equals(item.getCatalog()) || !first.getType().equals(item.getType())) {
|
||||
throw new IllegalArgumentException("同一枚举组中的 catalog/type 必须一致");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateUniqueValuesAndSorts(
|
||||
PersistableSysEnumDefinition first,
|
||||
List<? extends PersistableSysEnumDefinition> definitions
|
||||
) {
|
||||
Set<Integer> values = new HashSet<>();
|
||||
Set<Integer> sorts = new HashSet<>();
|
||||
for (PersistableSysEnumDefinition item : definitions) {
|
||||
if (!values.add(item.getValue())) {
|
||||
throw new IllegalArgumentException("枚举值重复: " + first.getCatalog() + "/" + first.getType() + "/" + item.getValue());
|
||||
}
|
||||
if (!sorts.add(item.getSort())) {
|
||||
throw new IllegalArgumentException("枚举排序重复: " + first.getCatalog() + "/" + first.getType() + "/" + item.getSort());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record EnumGroup(
|
||||
String catalog,
|
||||
String type,
|
||||
List<? extends PersistableSysEnumDefinition> definitions
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.bruce.common.factory;
|
||||
|
||||
import com.bruce.common.domain.entity.SysAttachment;
|
||||
import com.bruce.common.dto.response.SysAttachmentResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class SysAttachmentFactoryTests {
|
||||
|
||||
@Test
|
||||
void shouldConvertEntityToResponse() {
|
||||
SysAttachment entity = new SysAttachment();
|
||||
entity.setId(3003L);
|
||||
entity.setSourceType("RAG");
|
||||
entity.setSourceId(1001L);
|
||||
entity.setOriginalName("知识库说明.pdf");
|
||||
entity.setFileName("uuid.pdf");
|
||||
entity.setFileSuffix("pdf");
|
||||
entity.setContentType("application/pdf");
|
||||
entity.setFileSize(1024L);
|
||||
entity.setStorageType("LOCAL");
|
||||
entity.setFilePath("20260601/uuid.pdf");
|
||||
entity.setFileUrl(null);
|
||||
entity.setRemark("上传附件");
|
||||
|
||||
SysAttachmentFactory factory = new SysAttachmentFactory();
|
||||
SysAttachmentResponse response = factory.toResponse(entity);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(3003L, response.getId());
|
||||
assertEquals("RAG", response.getSourceType());
|
||||
assertEquals(1001L, response.getSourceId());
|
||||
assertEquals("知识库说明.pdf", response.getOriginalName());
|
||||
assertEquals("uuid.pdf", response.getFileName());
|
||||
assertEquals("pdf", response.getFileSuffix());
|
||||
assertEquals("application/pdf", response.getContentType());
|
||||
assertEquals(1024L, response.getFileSize());
|
||||
assertEquals("LOCAL", response.getStorageType());
|
||||
assertEquals("上传附件", response.getRemark());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.bruce.common.factory;
|
||||
|
||||
import com.bruce.common.domain.entity.SysEnum;
|
||||
import com.bruce.common.dto.request.SysEnumSaveRequest;
|
||||
import com.bruce.common.dto.response.SysEnumResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class SysEnumFactoryTests {
|
||||
|
||||
@Test
|
||||
void shouldConvertSaveRequestToEntity() {
|
||||
SysEnumSaveRequest request = new SysEnumSaveRequest();
|
||||
request.setId(1001L);
|
||||
request.setCatalog("common");
|
||||
request.setType("enable_status");
|
||||
request.setName("启用");
|
||||
request.setValue(1);
|
||||
request.setStrvalue("ENABLED");
|
||||
request.setSort(1);
|
||||
request.setRemark("启停状态");
|
||||
|
||||
SysEnumFactory factory = new SysEnumFactory();
|
||||
SysEnum entity = factory.toEntity(request);
|
||||
|
||||
assertNotNull(entity);
|
||||
assertEquals(1001L, entity.getId());
|
||||
assertEquals("common", entity.getCatalog());
|
||||
assertEquals("enable_status", entity.getType());
|
||||
assertEquals("启用", entity.getName());
|
||||
assertEquals(1, entity.getValue());
|
||||
assertEquals("ENABLED", entity.getStrvalue());
|
||||
assertEquals(1, entity.getSort());
|
||||
assertEquals("启停状态", entity.getRemark());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertEntityToResponse() {
|
||||
SysEnum entity = new SysEnum();
|
||||
entity.setId(2002L);
|
||||
entity.setCatalog("rag");
|
||||
entity.setType("parse_status");
|
||||
entity.setName("已解析");
|
||||
entity.setValue(2);
|
||||
entity.setStrvalue("PARSED");
|
||||
entity.setSort(2);
|
||||
entity.setRemark("解析状态");
|
||||
|
||||
SysEnumFactory factory = new SysEnumFactory();
|
||||
SysEnumResponse response = factory.toResponse(entity);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(2002L, response.getId());
|
||||
assertEquals("rag", response.getCatalog());
|
||||
assertEquals("parse_status", response.getType());
|
||||
assertEquals("已解析", response.getName());
|
||||
assertEquals(2, response.getValue());
|
||||
assertEquals("PARSED", response.getStrvalue());
|
||||
assertEquals(2, response.getSort());
|
||||
assertEquals("解析状态", response.getRemark());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bruce.common.handler;
|
||||
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class GlobalExceptionHandlerTests {
|
||||
|
||||
@Test
|
||||
void shouldReturnStructuredBadRequestForIllegalArgumentException() {
|
||||
GlobalExceptionHandler handler = new GlobalExceptionHandler();
|
||||
ResponseEntity<RequestResult<Void>> response = handler.handleIllegalArgumentException(
|
||||
new IllegalArgumentException("知识库编码已存在: TEST-1"));
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals("400", response.getBody().getResultcode());
|
||||
assertEquals("知识库编码已存在: TEST-1", response.getBody().getMessage());
|
||||
assertEquals(null, response.getBody().getData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.bruce.common.model;
|
||||
|
||||
import com.bruce.common.domain.model.RequestResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
class RequestResultStructureTests {
|
||||
|
||||
@Test
|
||||
void dataConstructorShouldDefaultToSuccessCode() {
|
||||
RequestResult<String> result = new RequestResult<>("payload");
|
||||
|
||||
assertEquals(RequestResult.SUCCESS_CODE, result.getResultcode());
|
||||
assertNull(result.getMessage());
|
||||
assertEquals("payload", result.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void successFactoryShouldBuildSuccessfulResult() {
|
||||
RequestResult<String> result = RequestResult.success("payload");
|
||||
|
||||
assertEquals(RequestResult.SUCCESS_CODE, result.getResultcode());
|
||||
assertNull(result.getMessage());
|
||||
assertEquals("payload", result.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void successFactoryWithMessageShouldKeepCustomMessage() {
|
||||
RequestResult<String> result = RequestResult.success("操作成功", "payload");
|
||||
|
||||
assertEquals(RequestResult.SUCCESS_CODE, result.getResultcode());
|
||||
assertEquals("操作成功", result.getMessage());
|
||||
assertEquals("payload", result.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void failFactoryShouldBuildFailureResult() {
|
||||
RequestResult<Void> result = RequestResult.fail("校验失败");
|
||||
|
||||
assertEquals(RequestResult.FAIL_CODE, result.getResultcode());
|
||||
assertEquals("校验失败", result.getMessage());
|
||||
assertNull(result.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void failFactoryWithCustomCodeShouldKeepCustomCode() {
|
||||
RequestResult<Void> result = RequestResult.fail("VALIDATE_ERROR", "校验失败");
|
||||
|
||||
assertEquals("VALIDATE_ERROR", result.getResultcode());
|
||||
assertEquals("校验失败", result.getMessage());
|
||||
assertNull(result.getData());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user