feat(m2): add contract attachment upload and listing

This commit is contained in:
2026-05-25 01:31:01 +08:00
parent cc7fef8ae9
commit 88c4e22d36
6 changed files with 171 additions and 4 deletions
@@ -1,5 +1,7 @@
package cn.craftlabs.platform.api.contracts;
import cn.craftlabs.platform.api.persistence.attachment.PlatformContractAttachment;
import cn.craftlabs.platform.api.persistence.attachment.PlatformContractAttachmentMapper;
import cn.craftlabs.platform.api.service.ContractService;
import cn.craftlabs.platform.api.web.dto.ContractCreateRequest;
import cn.craftlabs.platform.api.web.dto.ContractLineRequest;
@@ -8,10 +10,16 @@ import cn.craftlabs.platform.api.web.dto.ContractResponse;
import cn.craftlabs.platform.api.web.dto.ContractStatusPatchRequest;
import cn.craftlabs.platform.api.web.dto.ContractUpdateRequest;
import cn.craftlabs.platform.api.web.dto.PageResponse;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.io.File;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
@@ -24,19 +32,19 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/** 合同 API:头信息与行挂在同一资源树下(嵌套路由)。 */
@RestController
@RequestMapping("/api/v1/contracts")
@Validated
public class ContractController {
private final ContractService contractService;
private final PlatformContractAttachmentMapper attachmentMapper;
public ContractController(ContractService contractService) {
public ContractController(ContractService contractService, PlatformContractAttachmentMapper attachmentMapper) {
this.contractService = contractService;
this.attachmentMapper = attachmentMapper;
}
@GetMapping
@@ -97,4 +105,40 @@ public class ContractController {
public void deleteLine(@PathVariable("id") long contractId, @PathVariable("lineId") long lineId) {
contractService.deleteLine(contractId, lineId);
}
@PostMapping("/{id}/attachments")
public ResponseEntity<Map<String, Object>> uploadAttachment(
@PathVariable Long id,
@RequestParam("file") MultipartFile file) {
try {
String uploadDir = System.getProperty("user.dir") + "/uploads/contracts/" + id + "/";
File dir = new File(uploadDir);
if (!dir.exists()) dir.mkdirs();
String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
File dest = new File(uploadDir + fileName);
file.transferTo(dest);
PlatformContractAttachment attachment = new PlatformContractAttachment();
attachment.setContractId(id);
attachment.setFileName(file.getOriginalFilename());
attachment.setFilePath(dest.getAbsolutePath());
attachment.setFileSize(file.getSize());
attachment.setContentType(file.getContentType());
attachment.setCreatedAt(OffsetDateTime.now());
attachmentMapper.insert(attachment);
return ResponseEntity.ok(Map.of("id", attachment.getId(), "fileName", attachment.getFileName()));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
}
}
@GetMapping("/{id}/attachments")
public ResponseEntity<List<PlatformContractAttachment>> getAttachments(@PathVariable Long id) {
var query = Wrappers.lambdaQuery(PlatformContractAttachment.class)
.eq(PlatformContractAttachment::getContractId, id)
.orderByDesc(PlatformContractAttachment::getCreatedAt);
return ResponseEntity.ok(attachmentMapper.selectList(query));
}
}
@@ -0,0 +1,44 @@
package cn.craftlabs.platform.api.persistence.attachment;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.OffsetDateTime;
@TableName("platform_contract_attachment")
public class PlatformContractAttachment {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("contract_id")
private Long contractId;
@TableField("file_name")
private String fileName;
@TableField("file_path")
private String filePath;
@TableField("file_size")
private Long fileSize;
@TableField("content_type")
private String contentType;
@TableField("uploaded_by")
private String uploadedBy;
@TableField("created_at")
private OffsetDateTime createdAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getContractId() { return contractId; }
public void setContractId(Long contractId) { this.contractId = contractId; }
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public String getFilePath() { return filePath; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public Long getFileSize() { return fileSize; }
public void setFileSize(Long fileSize) { this.fileSize = fileSize; }
public String getContentType() { return contentType; }
public void setContentType(String contentType) { this.contentType = contentType; }
public String getUploadedBy() { return uploadedBy; }
public void setUploadedBy(String uploadedBy) { this.uploadedBy = uploadedBy; }
public OffsetDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
}
@@ -0,0 +1,7 @@
package cn.craftlabs.platform.api.persistence.attachment;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PlatformContractAttachmentMapper extends BaseMapper<PlatformContractAttachment> {}
@@ -0,0 +1,13 @@
-- V10__contract_attachments.sql
CREATE TABLE platform_contract_attachment (
id BIGSERIAL PRIMARY KEY,
contract_id BIGINT NOT NULL REFERENCES platform_contract(id),
file_name VARCHAR(256) NOT NULL,
file_path VARCHAR(1024) NOT NULL,
file_size BIGINT,
content_type VARCHAR(128),
uploaded_by VARCHAR(256),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_attachment_contract ON platform_contract_attachment(contract_id);