From 18386fde6386026c222ea8734fe277bd85d8d2e8 Mon Sep 17 00:00:00 2001 From: bruce Date: Mon, 18 May 2026 21:25:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E8=A1=A8=E7=BB=93=E6=9E=84=E4=B8=8E=E6=9C=AC=E5=9C=B0=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/sql/attachment.sql | 45 ++++++++++++ .../common/config/AttachmentProperties.java | 19 +++++ .../controller/SysAttachmentController.java | 31 ++++++++ .../bruce/common/entity/SysAttachment.java | 59 ++++++++++++++++ .../common/mapper/SysAttachmentMapper.java | 9 +++ .../common/service/ISysAttachmentService.java | 10 +++ .../impl/SysAttachmentServiceImpl.java | 70 +++++++++++++++++++ src/main/resources/application-template.yaml | 4 ++ src/main/resources/application.yaml | 3 + .../SysAttachmentComponentStructureTests.java | 48 +++++++++++++ 10 files changed, 298 insertions(+) create mode 100644 script/sql/attachment.sql create mode 100644 src/main/java/com/bruce/common/config/AttachmentProperties.java create mode 100644 src/main/java/com/bruce/common/controller/SysAttachmentController.java create mode 100644 src/main/java/com/bruce/common/entity/SysAttachment.java create mode 100644 src/main/java/com/bruce/common/mapper/SysAttachmentMapper.java create mode 100644 src/main/java/com/bruce/common/service/ISysAttachmentService.java create mode 100644 src/main/java/com/bruce/common/service/impl/SysAttachmentServiceImpl.java create mode 100644 src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java diff --git a/script/sql/attachment.sql b/script/sql/attachment.sql new file mode 100644 index 0000000..7bcdf84 --- /dev/null +++ b/script/sql/attachment.sql @@ -0,0 +1,45 @@ +DROP TABLE IF EXISTS sys_attachment; + +CREATE TABLE sys_attachment ( + id BIGSERIAL PRIMARY KEY, + source_type VARCHAR(100) NOT NULL, + source_id BIGINT, + original_name VARCHAR(255) NOT NULL, + file_name VARCHAR(255) NOT NULL, + file_suffix VARCHAR(50), + content_type VARCHAR(100), + file_size BIGINT NOT NULL DEFAULT 0, + storage_type VARCHAR(50) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_url VARCHAR(500), + version INTEGER NOT NULL DEFAULT 1, + create_time TIMESTAMP, + update_time TIMESTAMP, + remark VARCHAR(500) DEFAULT '', + create_by VARCHAR(64), + update_by VARCHAR(64) +); + +CREATE INDEX idx_sys_attachment_source_type ON sys_attachment (source_type); +CREATE INDEX idx_sys_attachment_source_id ON sys_attachment (source_id); +CREATE INDEX idx_sys_attachment_storage_type ON sys_attachment (storage_type); +CREATE INDEX idx_sys_attachment_create_time ON sys_attachment (create_time); + +COMMENT ON TABLE sys_attachment IS '系统附件表'; +COMMENT ON COLUMN sys_attachment.id IS 'ID'; +COMMENT ON COLUMN sys_attachment.source_type IS '来源业务类型'; +COMMENT ON COLUMN sys_attachment.source_id IS '来源业务ID'; +COMMENT ON COLUMN sys_attachment.original_name IS '原始文件名'; +COMMENT ON COLUMN sys_attachment.file_name IS '存储文件名'; +COMMENT ON COLUMN sys_attachment.file_suffix IS '文件后缀'; +COMMENT ON COLUMN sys_attachment.content_type IS '文件MIME类型'; +COMMENT ON COLUMN sys_attachment.file_size IS '文件大小(字节)'; +COMMENT ON COLUMN sys_attachment.storage_type IS '存储类型'; +COMMENT ON COLUMN sys_attachment.file_path IS '文件存储路径'; +COMMENT ON COLUMN sys_attachment.file_url IS '文件访问地址'; +COMMENT ON COLUMN sys_attachment.version IS '版本'; +COMMENT ON COLUMN sys_attachment.create_time IS '创建时间'; +COMMENT ON COLUMN sys_attachment.update_time IS '更新时间'; +COMMENT ON COLUMN sys_attachment.remark IS '备注'; +COMMENT ON COLUMN sys_attachment.create_by IS '创建者'; +COMMENT ON COLUMN sys_attachment.update_by IS '更新者'; diff --git a/src/main/java/com/bruce/common/config/AttachmentProperties.java b/src/main/java/com/bruce/common/config/AttachmentProperties.java new file mode 100644 index 0000000..355b15e --- /dev/null +++ b/src/main/java/com/bruce/common/config/AttachmentProperties.java @@ -0,0 +1,19 @@ +package com.bruce.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "common.attachment") +public class AttachmentProperties { + + private String basePath = "data/attachments"; + + public String getBasePath() { + return basePath; + } + + public void setBasePath(String basePath) { + this.basePath = basePath; + } +} diff --git a/src/main/java/com/bruce/common/controller/SysAttachmentController.java b/src/main/java/com/bruce/common/controller/SysAttachmentController.java new file mode 100644 index 0000000..d800ce2 --- /dev/null +++ b/src/main/java/com/bruce/common/controller/SysAttachmentController.java @@ -0,0 +1,31 @@ +package com.bruce.common.controller; + +import com.bruce.common.entity.SysAttachment; +import com.bruce.common.service.ISysAttachmentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "系统附件管理") +@RestController +@RequestMapping("/api/attachments") +public class SysAttachmentController { + + private final ISysAttachmentService sysAttachmentService; + + public SysAttachmentController(ISysAttachmentService sysAttachmentService) { + this.sysAttachmentService = sysAttachmentService; + } + + @Operation(summary = "上传附件") + @PostMapping("/upload") + public SysAttachment upload(@RequestParam("file") MultipartFile file, + @RequestParam String sourceType, + @RequestParam(required = false) Long sourceId) { + return sysAttachmentService.upload(file, sourceType, sourceId); + } +} diff --git a/src/main/java/com/bruce/common/entity/SysAttachment.java b/src/main/java/com/bruce/common/entity/SysAttachment.java new file mode 100644 index 0000000..64534b6 --- /dev/null +++ b/src/main/java/com/bruce/common/entity/SysAttachment.java @@ -0,0 +1,59 @@ +package com.bruce.common.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +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; +} diff --git a/src/main/java/com/bruce/common/mapper/SysAttachmentMapper.java b/src/main/java/com/bruce/common/mapper/SysAttachmentMapper.java new file mode 100644 index 0000000..a78cf1e --- /dev/null +++ b/src/main/java/com/bruce/common/mapper/SysAttachmentMapper.java @@ -0,0 +1,9 @@ +package com.bruce.common.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bruce.common.entity.SysAttachment; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysAttachmentMapper extends BaseMapper { +} diff --git a/src/main/java/com/bruce/common/service/ISysAttachmentService.java b/src/main/java/com/bruce/common/service/ISysAttachmentService.java new file mode 100644 index 0000000..0d226a9 --- /dev/null +++ b/src/main/java/com/bruce/common/service/ISysAttachmentService.java @@ -0,0 +1,10 @@ +package com.bruce.common.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bruce.common.entity.SysAttachment; +import org.springframework.web.multipart.MultipartFile; + +public interface ISysAttachmentService extends IService { + + SysAttachment upload(MultipartFile file, String sourceType, Long sourceId); +} diff --git a/src/main/java/com/bruce/common/service/impl/SysAttachmentServiceImpl.java b/src/main/java/com/bruce/common/service/impl/SysAttachmentServiceImpl.java new file mode 100644 index 0000000..c1b6940 --- /dev/null +++ b/src/main/java/com/bruce/common/service/impl/SysAttachmentServiceImpl.java @@ -0,0 +1,70 @@ +package com.bruce.common.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bruce.common.config.AttachmentProperties; +import com.bruce.common.entity.SysAttachment; +import com.bruce.common.mapper.SysAttachmentMapper; +import com.bruce.common.service.ISysAttachmentService; +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 implements ISysAttachmentService { + + private static final String STORAGE_TYPE_LOCAL = "LOCAL"; + + private final AttachmentProperties attachmentProperties; + + public SysAttachmentServiceImpl(AttachmentProperties attachmentProperties) { + this.attachmentProperties = attachmentProperties; + } + + @Override + public SysAttachment upload(MultipartFile file, String sourceType, Long sourceId) { + if (file == null || file.isEmpty()) { + throw new IllegalArgumentException("上传文件不能为空"); + } + 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(sourceId); + attachment.setOriginalName(originalName); + attachment.setFileName(storedFileName); + attachment.setFileSuffix(suffix); + attachment.setContentType(file.getContentType()); + attachment.setFileSize(file.getSize()); + attachment.setStorageType(STORAGE_TYPE_LOCAL); + attachment.setFilePath(dateDirectory + "/" + storedFileName); + attachment.setFileUrl(null); + attachment.setVersion(1); + save(attachment); + return attachment; + } +} diff --git a/src/main/resources/application-template.yaml b/src/main/resources/application-template.yaml index ca451bc..20873c3 100644 --- a/src/main/resources/application-template.yaml +++ b/src/main/resources/application-template.yaml @@ -11,3 +11,7 @@ mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml configuration: map-underscore-to-camel-case: true + +common: + attachment: + base-path: /data/common-agent/attachments diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index efd6f1c..4eb4e00 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -4,3 +4,6 @@ spring: profiles: active: dev +common: + attachment: + base-path: data/attachments diff --git a/src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java b/src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java new file mode 100644 index 0000000..f19e802 --- /dev/null +++ b/src/test/java/com/bruce/common/attachment/SysAttachmentComponentStructureTests.java @@ -0,0 +1,48 @@ +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.controller.SysAttachmentController; +import com.bruce.common.entity.SysAttachment; +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 org.springframework.web.multipart.MultipartFile; + +import java.lang.reflect.Method; + +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", + MultipartFile.class, + String.class, + Long.class + ); + Method controllerMethod = SysAttachmentController.class.getMethod( + "upload", + MultipartFile.class, + String.class, + Long.class + ); + + assertNotNull(serviceMethod); + assertNotNull(controllerMethod); + } +}