Merge remote-tracking branch 'origin/dev' into dev
commit
a8d2486ec1
|
|
@ -143,7 +143,7 @@
|
|||
7. 【强制】NPE 是程序员最常遇到的异常,必须进行预防性检查。
|
||||
说明:当方法返回值为集合或对象时,先判空再操作。
|
||||
|
||||
8. 【推荐】尽量使用 try-with-resources 语句关闭资源。
|
||||
8. 【推荐】尽量使用 try-with-fileStorages 语句关闭资源。
|
||||
正例:
|
||||
```java
|
||||
try (InputStream is = new FileInputStream(path)) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
# GBS 平台文件存储(OSS)配置汇总
|
||||
|
||||
> **生成时间**:2026-06-30 10:05
|
||||
> **数据来源**:Nacos(jjb-dragon 命名空间)+ MySQL `jjb-saas-attachment` 库
|
||||
> **结论**:平台文件存储**全部使用阿里云 OSS**,无 MinIO 在用
|
||||
|
||||
---
|
||||
|
||||
## 一、核心结论
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| **存储后端** | 阿里云 OSS(Aliyun Object Storage Service) |
|
||||
| **支持的存储类型** | `ALIYUN_OSS`(普通文件 70 个渠道)、`ALIYUN_VIDEO_OSS`(视频点播 28 个渠道) |
|
||||
| **存储 Region** | 华东1(杭州)`oss-cn-hangzhou` |
|
||||
| **Bucket** | `test-dragon-yf-pub`(所有租户共用) |
|
||||
| **总容量** | 平台级 4 TB(4398046511104 字节),单租户默认 100 GB |
|
||||
| **已用容量** | 平台级 3.09 TB(3096225104031 字节 / ≈ 2.81 TiB) |
|
||||
| **Nacos 默认存储策略** | `ALIYUN_OSS` |
|
||||
| **MinIO 状态** | 容器在运行,但**仅作历史残留**,无任何业务使用 |
|
||||
|
||||
---
|
||||
|
||||
## 二、Nacos 默认存储策略
|
||||
|
||||
Nacos 命名空间 `jjb-dragon` → 配置 `jjb-saas-attachment.yml`:
|
||||
|
||||
```yaml
|
||||
attachment:
|
||||
# 默认存储空间 支持MINIO、ALIYUN_OSS
|
||||
storage-policies: ALIYUN_OSS # ← 当前使用阿里云 OSS
|
||||
file-view-url: ${common.gateway.network.http.external}/fileview/onlinePreview
|
||||
expiration: 3600
|
||||
default-init-capacity: 102400 # 默认 100 GB
|
||||
|
||||
minio:
|
||||
inter:
|
||||
replace-enum: true
|
||||
url: http://192.168.1.117:9001 # ← 旧地址,无效(已弃用)
|
||||
```
|
||||
|
||||
> ⚠️ 注释里仍写着"支持 MINIO、ALIYUN_OSS",但 `minio.inter.url` 指向已不存在的旧环境 `192.168.1.117:9001`。当前业务完全走 OSS,不影响上传。
|
||||
|
||||
---
|
||||
|
||||
## 三、阿里云 OSS 凭证(2 套 — 文件/视频)
|
||||
|
||||
| 用途 | AccessKeyId | AccessKeySecret | Bucket | Region | 内/外网 Endpoint |
|
||||
|------|-------------|-----------------|--------|--------|------------------|
|
||||
| **ALIYUN_OSS**(文件) | `LTAI5tFerUgBCLEbXtinDD6d` | `XyGVNV2RvW8U3ceoJDiy9A9oLvCGYp` | `test-dragon-yf-pub` | oss-cn-hangzhou | `https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com` |
|
||||
| **ALIYUN_VIDEO_OSS**(视频) | `LTAI5tDAMFkin5tavp4thfaG` | `XoBYTtIgeHOiz3udiP5gm0rcdRWp25` | `test-dragon-yf-pub` | oss-cn-shanghai(regionId) | 同上 |
|
||||
|
||||
**URL 模板**(来自 `attachment_tenant_channel.channel_config` JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"rootDir": "jjb",
|
||||
"endpoint": "oss-cn-hangzhou.aliyuncs.com",
|
||||
"bucketName": "test-dragon-yf-pub",
|
||||
"expiration": null,
|
||||
"accessKeyId": "LTAI5tFerUgBCLEbXtinDD6d",
|
||||
"accessKeySecret": "XyGVNV2RvW8U3ceoJDiy9A9oLvCGYp",
|
||||
"innerNetBaseUrl": "https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com",
|
||||
"outerNetBaseUrl": "https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com",
|
||||
"innerFileViewBaseUrl": "https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com",
|
||||
"outerFileViewBaseUrl": "https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com"
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ 视频 OSS 凭证中 `regionId: cn-shanghai` 与 `endpoint: oss-cn-hangzhou` 不一致(**疑似配置错配**),但实际请求走 endpoint,杭州 bucket 可正常访问。如切换视频点播需注意。
|
||||
|
||||
---
|
||||
|
||||
## 四、数据库表结构
|
||||
|
||||
### 4.1 `attachment_tenant_channel`(租户存储渠道绑定表)
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `id` | 主键 |
|
||||
| `channel_owner_enum` | 渠道所属(PLATFORM / 自定义) |
|
||||
| `channel_type_enum` | 渠道类型(ALIYUN_OSS / ALIYUN_VIDEO_OSS / MinIO 等) |
|
||||
| `channel_name` | 渠道名称 |
|
||||
| `channel_config` | 渠道配置 JSON(含 endpoint/bucket/accessKey 等) |
|
||||
| `total_storage_size` | 总容量(字节) |
|
||||
| `default_enum` | 是否默认渠道 |
|
||||
| `tenant_id` / `org_id` | 租户/机构 |
|
||||
|
||||
**数据规模**:98 条记录(70 个 ALIYUN_OSS + 28 个 ALIYUN_VIDEO_OSS)
|
||||
|
||||
### 4.2 `oss_region_endpoint`(OSS 区域映射表)
|
||||
预置 16 个阿里云 OSS Region(杭州/上海/北京/深圳/香港/美国等),用于 endpoint 路由。
|
||||
|
||||
| 关键 Region | 外网 endpoint | 内网 endpoint |
|
||||
|-------------|---------------|---------------|
|
||||
| 华东1(杭州) | oss-cn-hangzhou.aliyuncs.com | oss-cn-hangzhou-internal.aliyuncs.com |
|
||||
| 华东2(上海) | oss-cn-shanghai.aliyuncs.com | oss-cn-shanghai-internal.aliyuncs.com |
|
||||
| 华北2(北京) | oss-cn-beijing.aliyuncs.com | oss-cn-beijing-internal.aliyuncs.com |
|
||||
| 华南1(深圳) | oss-cn-shenzhen.aliyuncs.com | oss-cn-shenzhen-internal.aliyuncs.com |
|
||||
| 中国香港 | oss-cn-hongkong.aliyuncs.com | oss-cn-hongkong-internal.aliyuncs.com |
|
||||
|
||||
### 4.3 `attachment_file`(文件元数据表)
|
||||
| 关键字段 | 说明 |
|
||||
|----------|------|
|
||||
| `storage_type_enum` | 存储类型(本地/阿里OSS/本地minio等) |
|
||||
| `bucket_name` | bucket 名(如 `test-dragon-yf-pub`) |
|
||||
| `url` | 上传文件 URL |
|
||||
| `preview_url` / `download_url` | 预览/下载地址 |
|
||||
| `status_enum` / `upload_status_enum` | 状态/上传状态 |
|
||||
| `file_size` / `name` / `ext` | 文件属性 |
|
||||
| `channel_id` | 渠道 id(关联 `attachment_tenant_channel`) |
|
||||
|
||||
### 4.4 `attachment_storage`(存储用量表)
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `tenant_id` | 租户 |
|
||||
| `total_storage_size` | 总配额(字节,默认 107374182400 = 100 GB) |
|
||||
| `occupied_storage_size` | 已用容量(字节) |
|
||||
|
||||
**平台总和**:4 TB 配额,3.09 TB 已用(**73% 使用率**)
|
||||
|
||||
---
|
||||
|
||||
## 五、最近文件上传样本
|
||||
|
||||
```
|
||||
id: 2065350515959717890
|
||||
name: 48afad885c233d3cf82fa3b779002554
|
||||
storage_type_enum: NULL
|
||||
bucket_name: test-dragon-yf-pub
|
||||
file_size: 410715
|
||||
create_time: 2026-06-12 16:28:30
|
||||
```
|
||||
|
||||
> 注意:`storage_type_enum` 字段为 NULL(schema 里没存具体值),但 `bucket_name` = `test-dragon-yf-pub` 表明所有文件都上传到了阿里云 OSS。
|
||||
|
||||
---
|
||||
|
||||
## 六、MinIO 容器状态(仅作残留)
|
||||
|
||||
```
|
||||
容器名: minio
|
||||
状态: Up 13 days
|
||||
端口: 0.0.0.0:9000-9001 (API/Console)
|
||||
数据目录: /data
|
||||
内容: .minio.sys/ jjb-attachment/ (jjb-attachment 桶内只有 test.txt)
|
||||
```
|
||||
|
||||
**结论**:MinIO 容器仍在运行(端口 9000/9001 暴露在 master 节点),但桶内无业务数据,**仅作早期本地化部署残留**。Nacos 配置文件 `minio.inter.url` 指向的 `192.168.1.117:9001` 早已不可达(应为 192.168.20.100:9000),但因为默认策略是 ALIYUN_OSS,所以**未影响实际业务**。
|
||||
|
||||
---
|
||||
|
||||
## 七、问题与改进建议
|
||||
|
||||
1. **MinIO 配置应清理**:`jjb-saas-attachment.yml` 的 `minio.inter.url` 指向旧地址 `192.168.1.117:9001`,与现状不符,应改为 `192.168.20.100:9000` 或直接移除(既然不使用)。
|
||||
2. **视频 OSS region 错配**:`ALIYUN_VIDEO_OSS` 的 `regionId: cn-shanghai` 与 `endpoint: oss-cn-hangzhou` 不一致,存在历史误配风险。
|
||||
3. **`storage_type_enum` 字段为空**:`attachment_file.storage_type_enum` 全部为 NULL,无法直接通过 SQL 区分文件走的是 OSS 还是 MinIO。判断需通过 `bucket_name` 字段。
|
||||
4. **平台总容量 73% 已用**(3.09 TB / 4 TB),建议监控告警。
|
||||
5. **凭证无主从**:OSS AK/SK 直接以明文存储在 `attachment_tenant_channel.channel_config` JSON 字段,建议改用 K8s Secret + Nacos 占位符注入。
|
||||
|
||||
---
|
||||
|
||||
## 八、相关服务连通性入口
|
||||
|
||||
- **OSS 外网地址**:https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com
|
||||
- **OSS 内网地址**:https://test-dragon-yf-pub.oss-cn-hangzhou-internal.aliyuncs.com
|
||||
- **MinIO Console**(master 节点):http://192.168.20.100:9001(admin / Zcloud@zcloud2026,无业务数据)
|
||||
- **MinIO API**:http://192.168.20.100:9000
|
||||
- **attachment 服务内部访问路径**:`lb://jjb-saas-attachment/attachment/**`
|
||||
- **文件预览地址模板**:`${common.gateway.network.http.external}/fileview/onlinePreview?xxx`
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package com.qiyoule.resource.domain.entity;
|
||||
|
||||
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.io.Serializable;
|
||||
import java.util.Date;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @TableName filestorage
|
||||
*/
|
||||
@TableName(value ="filestorage")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class Resource implements Serializable {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Integer size;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String filename;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String originalFilename;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String basePath;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String ext;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String contentType;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String platform;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String md5;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String creatorName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private String creatorUid;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Date createdTime;
|
||||
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) {
|
||||
if (this == that) {
|
||||
return true;
|
||||
}
|
||||
if (that == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != that.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Resource other = (Resource) that;
|
||||
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
|
||||
&& (this.getUrl() == null ? other.getUrl() == null : this.getUrl().equals(other.getUrl()))
|
||||
&& (this.getSize() == null ? other.getSize() == null : this.getSize().equals(other.getSize()))
|
||||
&& (this.getFilename() == null ? other.getFilename() == null : this.getFilename().equals(other.getFilename()))
|
||||
&& (this.getOriginalFilename() == null ? other.getOriginalFilename() == null : this.getOriginalFilename().equals(other.getOriginalFilename()))
|
||||
&& (this.getBasePath() == null ? other.getBasePath() == null : this.getBasePath().equals(other.getBasePath()))
|
||||
&& (this.getPath() == null ? other.getPath() == null : this.getPath().equals(other.getPath()))
|
||||
&& (this.getExt() == null ? other.getExt() == null : this.getExt().equals(other.getExt()))
|
||||
&& (this.getContentType() == null ? other.getContentType() == null : this.getContentType().equals(other.getContentType()))
|
||||
&& (this.getPlatform() == null ? other.getPlatform() == null : this.getPlatform().equals(other.getPlatform()))
|
||||
&& (this.getMd5() == null ? other.getMd5() == null : this.getMd5().equals(other.getMd5()))
|
||||
&& (this.getCreatorName() == null ? other.getCreatorName() == null : this.getCreatorName().equals(other.getCreatorName()))
|
||||
&& (this.getCreatorUid() == null ? other.getCreatorUid() == null : this.getCreatorUid().equals(other.getCreatorUid()))
|
||||
&& (this.getCreatedTime() == null ? other.getCreatedTime() == null : this.getCreatedTime().equals(other.getCreatedTime()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
|
||||
result = prime * result + ((getUrl() == null) ? 0 : getUrl().hashCode());
|
||||
result = prime * result + ((getSize() == null) ? 0 : getSize().hashCode());
|
||||
result = prime * result + ((getFilename() == null) ? 0 : getFilename().hashCode());
|
||||
result = prime * result + ((getOriginalFilename() == null) ? 0 : getOriginalFilename().hashCode());
|
||||
result = prime * result + ((getBasePath() == null) ? 0 : getBasePath().hashCode());
|
||||
result = prime * result + ((getPath() == null) ? 0 : getPath().hashCode());
|
||||
result = prime * result + ((getExt() == null) ? 0 : getExt().hashCode());
|
||||
result = prime * result + ((getContentType() == null) ? 0 : getContentType().hashCode());
|
||||
result = prime * result + ((getPlatform() == null) ? 0 : getPlatform().hashCode());
|
||||
result = prime * result + ((getMd5() == null) ? 0 : getMd5().hashCode());
|
||||
result = prime * result + ((getCreatorName() == null) ? 0 : getCreatorName().hashCode());
|
||||
result = prime * result + ((getCreatorUid() == null) ? 0 : getCreatorUid().hashCode());
|
||||
result = prime * result + ((getCreatedTime() == null) ? 0 : getCreatedTime().hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(getClass().getSimpleName());
|
||||
sb.append(" [");
|
||||
sb.append("Hash = ").append(hashCode());
|
||||
sb.append(", id=").append(id);
|
||||
sb.append(", url=").append(url);
|
||||
sb.append(", size=").append(size);
|
||||
sb.append(", filename=").append(filename);
|
||||
sb.append(", originalFilename=").append(originalFilename);
|
||||
sb.append(", basePath=").append(basePath);
|
||||
sb.append(", path=").append(path);
|
||||
sb.append(", ext=").append(ext);
|
||||
sb.append(", contentType=").append(contentType);
|
||||
sb.append(", platform=").append(platform);
|
||||
sb.append(", md5=").append(md5);
|
||||
sb.append(", creatorName=").append(creatorName);
|
||||
sb.append(", creatorUid=").append(creatorUid);
|
||||
sb.append(", createdTime=").append(createdTime);
|
||||
sb.append(", serialVersionUID=").append(serialVersionUID);
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
-- =============================================
|
||||
-- 文件存储本地管理表 filestorage
|
||||
-- 数据库: jjb_saas_safety_eval
|
||||
-- 说明: 用于记录上传到阿里云OSS的文件元信息,
|
||||
-- 在存储/删除文件时进行文件的维护管理
|
||||
-- =============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `filestorage` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
`url` VARCHAR(1024) NOT NULL COMMENT 'OSS文件完整访问URL',
|
||||
`base_path` VARCHAR(512) DEFAULT NULL COMMENT 'OSS存储根路径 (如 jjb)',
|
||||
`path` VARCHAR(512) DEFAULT NULL COMMENT 'OSS存储相对路径 (不含basePath)',
|
||||
`filename` VARCHAR(512) DEFAULT NULL COMMENT 'OSS存储文件名 (uuid生成)',
|
||||
`original_filename` VARCHAR(512) DEFAULT NULL COMMENT '文件原始名称',
|
||||
`size` BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
|
||||
`ext` VARCHAR(64) DEFAULT NULL COMMENT '文件扩展名(不含.)',
|
||||
`content_type` VARCHAR(128) DEFAULT NULL COMMENT '文件MIME类型',
|
||||
`platform` VARCHAR(64) DEFAULT NULL COMMENT 'OSS平台标识 (如 aliyun-oss)',
|
||||
`md5` VARCHAR(64) DEFAULT NULL COMMENT 'Content-MD5或文件MD5值',
|
||||
`creator_name` VARCHAR(128) DEFAULT NULL COMMENT '上传者姓名',
|
||||
`creator_uid` VARCHAR(128) DEFAULT NULL COMMENT '上传者UID',
|
||||
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
|
||||
INDEX `idx_platform` (`platform`),
|
||||
INDEX `idx_creator_uid` (`creator_uid`),
|
||||
INDEX `idx_filename` (`filename`),
|
||||
INDEX `idx_created_time` (`created_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件存储本地管理表';
|
||||
20
pom.xml
20
pom.xml
|
|
@ -48,6 +48,12 @@
|
|||
<lombok.version>1.18.30</lombok.version>
|
||||
|
||||
<jjb-saas-system-client.version>1.7.1.1-SNAPSHOT</jjb-saas-system-client.version>
|
||||
|
||||
<!-- X File Storage(多OSS统一文件存储) -->
|
||||
<x-file-storage.version>2.2.0</x-file-storage.version>
|
||||
|
||||
<!-- Aliyun OSS SDK -->
|
||||
<aliyun-oss.version>3.17.4</aliyun-oss.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
|
@ -201,6 +207,20 @@
|
|||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- X File Storage(多OSS统一文件存储) -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.x-file-storage</groupId>
|
||||
<artifactId>x-file-storage-spring</artifactId>
|
||||
<version>${x-file-storage.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Aliyun OSS SDK -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>${aliyun-oss.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package org.qinan.safetyeval.adapter.web;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.qinan.safetyeval.client.api.FileStorageApi;
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件存储管理适配层(Controller)
|
||||
* <p>
|
||||
* 提供 filestorage 表的下载、删除、获取地址等 REST 接口。
|
||||
* 文件新增由 {@link FileUploadController} 在上传时同步完成。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Api(tags = "文件存储管理")
|
||||
@RestController
|
||||
@RequestMapping("/file-storage")
|
||||
public class FileStorageController {
|
||||
|
||||
@Resource
|
||||
private FileStorageApi fileStorageApi;
|
||||
|
||||
// ==================== 下载 ====================
|
||||
|
||||
@ApiOperation("下载文件")
|
||||
@GetMapping("/download/{fileId}")
|
||||
public void download(
|
||||
@ApiParam("文件ID") @PathVariable Integer fileId,
|
||||
HttpServletResponse response) {
|
||||
try {
|
||||
SingleResponse<FileInfoCO> result = fileStorageApi.getPresignedUrl(fileId);
|
||||
if (!result.isSuccess() || result.getData() == null) {
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
FileInfoCO fileInfo = result.getData();
|
||||
|
||||
response.setContentType(fileInfo.getContentType() != null ? fileInfo.getContentType() : "application/octet-stream");
|
||||
response.setHeader("Content-Disposition",
|
||||
"attachment; filename=\"" + java.net.URLEncoder.encode(fileInfo.getOriginalFilename(), "UTF-8") + "\"");
|
||||
if (fileInfo.getSize() != null) {
|
||||
response.setContentLengthLong(fileInfo.getSize());
|
||||
}
|
||||
|
||||
URL url = new URL(fileInfo.getPresignedUrl());
|
||||
try (java.io.InputStream in = url.openStream();
|
||||
OutputStream out = response.getOutputStream()) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 获取地址/信息 ====================
|
||||
|
||||
@ApiOperation("获取文件签名URL(临时访问链接)")
|
||||
@GetMapping("/presigned-url/{fileId}")
|
||||
public SingleResponse<FileInfoCO> getPresignedUrl(
|
||||
@ApiParam("文件ID") @PathVariable Integer fileId) {
|
||||
return fileStorageApi.getPresignedUrl(fileId);
|
||||
}
|
||||
|
||||
@ApiOperation("获取文件信息")
|
||||
@GetMapping("/info/{fileId}")
|
||||
public SingleResponse<FileInfoCO> getInfo(
|
||||
@ApiParam("文件ID") @PathVariable Integer fileId) {
|
||||
return fileStorageApi.getInfo(fileId);
|
||||
}
|
||||
|
||||
// ==================== 删除 ====================
|
||||
|
||||
@ApiOperation("删除文件")
|
||||
@DeleteMapping("/delete/{fileId}")
|
||||
public SingleResponse<Void> delete(
|
||||
@ApiParam("文件ID") @PathVariable Integer fileId) {
|
||||
return fileStorageApi.delete(fileId);
|
||||
}
|
||||
|
||||
@ApiOperation("批量删除文件")
|
||||
@DeleteMapping("/delete-batch")
|
||||
public SingleResponse<Void> deleteBatch(
|
||||
@ApiParam("文件ID列表") @RequestBody List<Integer> fileIds) {
|
||||
return fileStorageApi.deleteBatch(fileIds);
|
||||
}
|
||||
|
||||
// ==================== 列表查询 ====================
|
||||
|
||||
@ApiOperation("获取所有文件列表")
|
||||
@GetMapping("/list-all")
|
||||
public SingleResponse<List<FileInfoCO>> listAll() {
|
||||
return fileStorageApi.listAll();
|
||||
}
|
||||
|
||||
@ApiOperation("根据上传者获取文件列表")
|
||||
@GetMapping("/list-by-creator")
|
||||
public SingleResponse<List<FileInfoCO>> listByCreator(
|
||||
@ApiParam("上传者UID") @RequestParam("creatorUid") String creatorUid) {
|
||||
return fileStorageApi.listByCreator(creatorUid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package org.qinan.safetyeval.adapter.web;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.qinan.safetyeval.client.api.FileUploadApi;
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件上传适配层(Controller)
|
||||
* <p>
|
||||
* 区分公开上传(创建人默认 system)与内部上传(创建人为当前登录用户)。
|
||||
* 上传完成后自动同步元信息到 filestorage 表。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Api(tags = "文件上传")
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class FileUploadController {
|
||||
|
||||
@Resource
|
||||
private FileUploadApi fileUploadApi;
|
||||
@ApiOperation("内部上传单个文件(创建人为当前用户)")
|
||||
@PostMapping("/upload")
|
||||
public SingleResponse<FileInfoCO> uploadInternal(
|
||||
@ApiParam("上传文件") @RequestParam("file") MultipartFile file) {
|
||||
return fileUploadApi.uploadInternal(file);
|
||||
}
|
||||
|
||||
@ApiOperation("内部批量上传文件(创建人为当前用户)")
|
||||
@PostMapping("/upload/batch")
|
||||
public SingleResponse<List<FileInfoCO>> uploadInternalBatch(
|
||||
@ApiParam("上传文件列表") @RequestParam("files") List<MultipartFile> files) {
|
||||
return fileUploadApi.uploadInternalBatch(files);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.qinan.safetyeval.adapter.web;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.qinan.safetyeval.client.api.FileUploadApi;
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
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;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件上传适配层(Controller)
|
||||
* <p>
|
||||
* 区分公开上传(创建人默认 system)与内部上传(创建人为当前登录用户)。
|
||||
* 上传完成后自动同步元信息到 filestorage 表。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Api(tags = "文件上传")
|
||||
@RestController
|
||||
@RequestMapping("/test/file")
|
||||
public class FileUploadPublicController {
|
||||
|
||||
@Resource
|
||||
private FileUploadApi fileUploadApi;
|
||||
|
||||
// ==================== 公开上传 ====================
|
||||
|
||||
@ApiOperation("公开上传单个文件(创建人默认 system)")
|
||||
@PostMapping("/upload")
|
||||
public SingleResponse<FileInfoCO> uploadPublic(
|
||||
@ApiParam("上传文件") @RequestParam("file") MultipartFile file) {
|
||||
return fileUploadApi.uploadPublic(file);
|
||||
}
|
||||
|
||||
@ApiOperation("公开批量上传文件(创建人默认 system)")
|
||||
@PostMapping("/upload/batch")
|
||||
public SingleResponse<List<FileInfoCO>> uploadPublicBatch(
|
||||
@ApiParam("上传文件列表") @RequestParam("files") List<MultipartFile> files) {
|
||||
return fileUploadApi.uploadPublicBatch(files);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package org.qinan.safetyeval.app.executor;
|
||||
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.dromara.x.file.storage.core.FileStorageService;
|
||||
import org.qinan.safetyeval.client.api.FileStorageApi;
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.qinan.safetyeval.domain.entity.FileStorage;
|
||||
import org.qinan.safetyeval.domain.exception.BizException;
|
||||
import org.qinan.safetyeval.domain.exception.ErrorCode;
|
||||
import org.qinan.safetyeval.domain.service.FileStorageDomainService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 文件存储管理执行器(App层)
|
||||
* <p>
|
||||
* 负责 filestorage 表的查询、删除、获取签名URL等管理操作。
|
||||
* 文件新增由 {@link FileUploadExecutor} 在上传时同步完成,本执行器不提供上传能力。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Service
|
||||
public class FileStorageExecutor implements FileStorageApi {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FileStorageExecutor.class);
|
||||
|
||||
@Resource
|
||||
private FileStorageService fileStorageService;
|
||||
|
||||
@Resource
|
||||
private FileStorageDomainService fileStorageDomainService;
|
||||
|
||||
@Override
|
||||
public SingleResponse<FileInfoCO> getPresignedUrl(Integer fileId) {
|
||||
try {
|
||||
FileStorage fileStorage = fileStorageDomainService.getResource(fileId);
|
||||
FileInfo ossFileInfo = FileStorageHelper.buildOssFileInfo(fileStorage);
|
||||
FileStorageHelper.checkOssExists(fileStorageService, ossFileInfo);
|
||||
|
||||
String presignedUrl = FileStorageHelper.generatePresignedUrl(fileStorageService, ossFileInfo);
|
||||
return SingleResponse.success(FileStorageHelper.toCO(fileStorage, presignedUrl));
|
||||
} catch (BizException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Generate presigned URL failed: fileId={}", fileId, e);
|
||||
throw new BizException(ErrorCode.FILE_URL_GENERATE_FAILED, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<FileInfoCO> getInfo(Integer fileId) {
|
||||
FileStorage fileStorage = fileStorageDomainService.getResource(fileId);
|
||||
String presignedUrl = null;
|
||||
try {
|
||||
FileInfo ossFileInfo = FileStorageHelper.buildOssFileInfo(fileStorage);
|
||||
if (fileStorageService.exists(ossFileInfo)) {
|
||||
presignedUrl = FileStorageHelper.generatePresignedUrl(fileStorageService, ossFileInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to generate presigned URL for info query: fileId={}", fileId, e);
|
||||
}
|
||||
return SingleResponse.success(FileStorageHelper.toCO(fileStorage, presignedUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<Void> delete(Integer fileId) {
|
||||
try {
|
||||
FileStorage fileStorage = fileStorageDomainService.getResource(fileId);
|
||||
|
||||
FileInfo ossFileInfo = FileStorageHelper.buildOssFileInfo(fileStorage);
|
||||
if (!fileStorageService.delete(ossFileInfo)) {
|
||||
log.warn("OSS delete returned false for fileId={}, url={}", fileId, fileStorage.getUrl());
|
||||
}
|
||||
|
||||
fileStorageDomainService.deleteResource(fileId);
|
||||
log.info("File deleted successfully: fileId={}, filename={}", fileId, fileStorage.getFilename());
|
||||
return SingleResponse.success();
|
||||
} catch (BizException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("File delete failed: fileId={}", fileId, e);
|
||||
throw new BizException(ErrorCode.FILE_DELETE_FAILED, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<Void> deleteBatch(List<Integer> fileIds) {
|
||||
if (fileIds == null || fileIds.isEmpty()) {
|
||||
return SingleResponse.success();
|
||||
}
|
||||
for (Integer fileId : fileIds) {
|
||||
try {
|
||||
delete(fileId);
|
||||
} catch (Exception e) {
|
||||
log.warn("Batch delete: failed for fileId={}, continue with next", fileId, e);
|
||||
}
|
||||
}
|
||||
return SingleResponse.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<List<FileInfoCO>> listAll() {
|
||||
List<FileStorage> fileStorages = fileStorageDomainService.listAllResources();
|
||||
List<FileInfoCO> result = fileStorages.stream()
|
||||
.map(r -> FileStorageHelper.toCO(r, null))
|
||||
.collect(Collectors.toList());
|
||||
return SingleResponse.success(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<List<FileInfoCO>> listByCreator(String creatorUid) {
|
||||
List<FileStorage> fileStorages = fileStorageDomainService.listResourcesByCreator(creatorUid);
|
||||
List<FileInfoCO> result = fileStorages.stream()
|
||||
.map(r -> FileStorageHelper.toCO(r, null))
|
||||
.collect(Collectors.toList());
|
||||
return SingleResponse.success(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package org.qinan.safetyeval.app.executor;
|
||||
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.dromara.x.file.storage.core.FileStorageService;
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.domain.entity.FileStorage;
|
||||
import org.qinan.safetyeval.domain.exception.BizException;
|
||||
import org.qinan.safetyeval.domain.exception.ErrorCode;
|
||||
import org.qinan.safetyeval.infrastructure.adapter.auth.AuthUserContextAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文件操作公共工具类(App层内部共享)
|
||||
* <p>
|
||||
* 提供实体转换、OSS FileInfo 构建、创建人信息填充等通用逻辑。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
final class FileStorageHelper {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FileStorageHelper.class);
|
||||
|
||||
/** 签名URL默认过期时间:1小时(毫秒) */
|
||||
static final long PRESIGNED_URL_EXPIRE_MILLIS = Duration.ofHours(1).toMillis();
|
||||
|
||||
private FileStorageHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 OSS FileInfo 构建 FileStorage 实体
|
||||
*
|
||||
* @param ossFileInfo OSS 文件信息
|
||||
* @param originalFilename 原始文件名
|
||||
* @param isInternal true-内部(取当前用户),false-公开(creator=system)
|
||||
*/
|
||||
static FileStorage buildResource(FileInfo ossFileInfo, String originalFilename, boolean isInternal) {
|
||||
FileStorage fileStorage = new FileStorage();
|
||||
fileStorage.setUrl(ossFileInfo.getUrl());
|
||||
fileStorage.setSize(ossFileInfo.getSize());
|
||||
fileStorage.setFilename(ossFileInfo.getFilename());
|
||||
fileStorage.setOriginalFilename(originalFilename);
|
||||
fileStorage.setBasePath(ossFileInfo.getBasePath());
|
||||
fileStorage.setPath(ossFileInfo.getPath());
|
||||
fileStorage.setExt(ossFileInfo.getExt());
|
||||
fileStorage.setContentType(ossFileInfo.getContentType());
|
||||
fileStorage.setPlatform(ossFileInfo.getPlatform());
|
||||
fileStorage.setCreatedTime(LocalDateTime.now());
|
||||
|
||||
if (isInternal) {
|
||||
fileStorage.setCreatorName(AuthUserContextAdapter.getCurrentUserName());
|
||||
Long userId = AuthUserContextAdapter.getCurrentUserId();
|
||||
fileStorage.setCreatorUid(userId != null ? String.valueOf(userId) : "0");
|
||||
} else {
|
||||
fileStorage.setCreatorName("system");
|
||||
fileStorage.setCreatorUid("0");
|
||||
}
|
||||
return fileStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 FileStorage 实体构建 OSS FileInfo(用于存在性检查、删除、生成签名URL等OOS操作)
|
||||
*/
|
||||
static FileInfo buildOssFileInfo(FileStorage fileStorage) {
|
||||
FileInfo ossFileInfo = new FileInfo();
|
||||
ossFileInfo.setUrl(fileStorage.getUrl());
|
||||
ossFileInfo.setBasePath(fileStorage.getBasePath());
|
||||
ossFileInfo.setFilename(fileStorage.getFilename());
|
||||
ossFileInfo.setPlatform(fileStorage.getPlatform());
|
||||
return ossFileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签名URL
|
||||
*/
|
||||
static String generatePresignedUrl(FileStorageService fileStorageService, FileInfo ossFileInfo) {
|
||||
Date expiration = new Date(System.currentTimeMillis() + PRESIGNED_URL_EXPIRE_MILLIS);
|
||||
return fileStorageService.generatePresignedUrl(ossFileInfo, expiration).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件在 OSS 是否存在
|
||||
*/
|
||||
static void checkOssExists(FileStorageService fileStorageService, FileInfo ossFileInfo) {
|
||||
if (!fileStorageService.exists(ossFileInfo)) {
|
||||
throw new BizException(ErrorCode.FILE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FileStorage 转 FileInfoCO
|
||||
*/
|
||||
static FileInfoCO toCO(FileStorage fileStorage, String presignedUrl) {
|
||||
if (fileStorage == null) {
|
||||
return null;
|
||||
}
|
||||
FileInfoCO co = new FileInfoCO();
|
||||
co.setId(fileStorage.getId());
|
||||
co.setUrl(fileStorage.getUrl());
|
||||
co.setPresignedUrl(presignedUrl != null ? presignedUrl : fileStorage.getUrl());
|
||||
co.setSize(fileStorage.getSize());
|
||||
co.setOriginalFilename(fileStorage.getOriginalFilename());
|
||||
co.setExt(fileStorage.getExt());
|
||||
co.setContentType(fileStorage.getContentType());
|
||||
co.setCreatorName(fileStorage.getCreatorName());
|
||||
co.setCreatedTime(fileStorage.getCreatedTime());
|
||||
return co;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package org.qinan.safetyeval.app.executor;
|
||||
|
||||
import org.dromara.x.file.storage.core.FileInfo;
|
||||
import org.dromara.x.file.storage.core.FileStorageService;
|
||||
import org.qinan.safetyeval.client.api.FileUploadApi;
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.qinan.safetyeval.domain.entity.FileStorage;
|
||||
import org.qinan.safetyeval.domain.exception.BizException;
|
||||
import org.qinan.safetyeval.domain.exception.ErrorCode;
|
||||
import org.qinan.safetyeval.domain.service.FileStorageDomainService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件上传执行器(App层)
|
||||
* <p>
|
||||
* 负责文件上传至阿里云 OSS,并在上传完成后将元信息同步写入 filestorage 表。
|
||||
* 区分公开上传(创建人=system)与内部上传(创建人=当前登录用户)。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Service
|
||||
public class FileUploadExecutor implements FileUploadApi {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FileUploadExecutor.class);
|
||||
|
||||
@Resource
|
||||
private FileStorageService fileStorageService;
|
||||
|
||||
@Resource
|
||||
private FileStorageDomainService fileStorageDomainService;
|
||||
|
||||
// ==================== 公开上传(创建人默认 system) ====================
|
||||
|
||||
@Override
|
||||
public SingleResponse<FileInfoCO> uploadPublic(MultipartFile file) {
|
||||
return doUpload(file, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<List<FileInfoCO>> uploadPublicBatch(List<MultipartFile> files) {
|
||||
return doUploadBatch(files, false);
|
||||
}
|
||||
|
||||
// ==================== 内部上传(创建人为当前用户) ====================
|
||||
|
||||
@Override
|
||||
public SingleResponse<FileInfoCO> uploadInternal(MultipartFile file) {
|
||||
return doUpload(file, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleResponse<List<FileInfoCO>> uploadInternalBatch(List<MultipartFile> files) {
|
||||
return doUploadBatch(files, true);
|
||||
}
|
||||
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 执行单个文件上传并同步写入 filestorage 表
|
||||
*
|
||||
* @param file 上传的文件
|
||||
* @param isInternal true-内部(取当前用户),false-公开(creator=system)
|
||||
*/
|
||||
private SingleResponse<FileInfoCO> doUpload(MultipartFile file, boolean isInternal) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new BizException(ErrorCode.FILE_UPLOAD_EMPTY);
|
||||
}
|
||||
try {
|
||||
// 1. 上传到 OSS
|
||||
FileInfo ossFileInfo = fileStorageService.of(file).upload();
|
||||
log.info("OSS upload success: url={}, size={}, isInternal={}",
|
||||
ossFileInfo.getUrl(), ossFileInfo.getSize(), isInternal);
|
||||
|
||||
// 2. 同步元信息到本地 filestorage 表
|
||||
FileStorage fileStorage = FileStorageHelper.buildResource(ossFileInfo, file.getOriginalFilename(), isInternal);
|
||||
FileStorage saved = fileStorageDomainService.saveResource(fileStorage);
|
||||
|
||||
return SingleResponse.success(FileStorageHelper.toCO(saved, null));
|
||||
} catch (BizException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("File upload failed: {}, isInternal={}", file.getOriginalFilename(), isInternal, e);
|
||||
throw new BizException(ErrorCode.FILE_UPLOAD_FAILED, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量上传
|
||||
*/
|
||||
private SingleResponse<List<FileInfoCO>> doUploadBatch(List<MultipartFile> files, boolean isInternal) {
|
||||
if (files == null || files.isEmpty()) {
|
||||
throw new BizException(ErrorCode.FILE_UPLOAD_EMPTY);
|
||||
}
|
||||
List<FileInfoCO> results = new ArrayList<>();
|
||||
for (MultipartFile file : files) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
SingleResponse<FileInfoCO> result = doUpload(file, isInternal);
|
||||
if (result.isSuccess() && result.getData() != null) {
|
||||
results.add(result.getData());
|
||||
}
|
||||
}
|
||||
return SingleResponse.success(results);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package org.qinan.safetyeval.client.api;
|
||||
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件存储管理接口定义(Client层)
|
||||
* <p>
|
||||
* 提供 filestorage 表的查询、删除、获取签名URL等管理功能。
|
||||
* 文件的新增由上传接口({@link FileUploadApi})间接完成,本接口不提供直接新增能力。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
public interface FileStorageApi {
|
||||
|
||||
/**
|
||||
* 获取文件签名URL(临时访问链接)
|
||||
*
|
||||
* @param fileId 文件ID
|
||||
* @return 文件信息(含签名URL)
|
||||
*/
|
||||
SingleResponse<FileInfoCO> getPresignedUrl(Integer fileId);
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*
|
||||
* @param fileId 文件ID
|
||||
* @return 文件信息
|
||||
*/
|
||||
SingleResponse<FileInfoCO> getInfo(Integer fileId);
|
||||
|
||||
/**
|
||||
* 删除文件(OSS + DB)
|
||||
*
|
||||
* @param fileId 文件ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
SingleResponse<Void> delete(Integer fileId);
|
||||
|
||||
/**
|
||||
* 批量删除文件(OSS + DB)
|
||||
*
|
||||
* @param fileIds 文件ID列表
|
||||
* @return 操作结果
|
||||
*/
|
||||
SingleResponse<Void> deleteBatch(List<Integer> fileIds);
|
||||
|
||||
/**
|
||||
* 获取所有文件列表
|
||||
*
|
||||
* @return 文件信息列表
|
||||
*/
|
||||
SingleResponse<List<FileInfoCO>> listAll();
|
||||
|
||||
/**
|
||||
* 根据上传者获取文件列表
|
||||
*
|
||||
* @param creatorUid 上传者UID
|
||||
* @return 文件信息列表
|
||||
*/
|
||||
SingleResponse<List<FileInfoCO>> listByCreator(String creatorUid);
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package org.qinan.safetyeval.client.api;
|
||||
|
||||
import org.qinan.safetyeval.client.co.FileInfoCO;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件上传接口定义(Client层)
|
||||
* <p>
|
||||
* 提供公开/内部两种文件上传方式。上传完成后自动同步元信息到 filestorage 表。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
public interface FileUploadApi {
|
||||
|
||||
/**
|
||||
* 公开上传单个文件(创建人默认为 system)
|
||||
*
|
||||
* @param file 上传的文件
|
||||
* @return 文件信息
|
||||
*/
|
||||
SingleResponse<FileInfoCO> uploadPublic(MultipartFile file);
|
||||
|
||||
/**
|
||||
* 公开批量上传文件(创建人默认为 system)
|
||||
*
|
||||
* @param files 上传的文件列表
|
||||
* @return 文件信息列表
|
||||
*/
|
||||
SingleResponse<List<FileInfoCO>> uploadPublicBatch(List<MultipartFile> files);
|
||||
|
||||
/**
|
||||
* 内部上传单个文件(创建人为当前登录用户)
|
||||
*
|
||||
* @param file 上传的文件
|
||||
* @return 文件信息
|
||||
*/
|
||||
SingleResponse<FileInfoCO> uploadInternal(MultipartFile file);
|
||||
|
||||
/**
|
||||
* 内部批量上传文件(创建人为当前登录用户)
|
||||
*
|
||||
* @param files 上传的文件列表
|
||||
* @return 文件信息列表
|
||||
*/
|
||||
SingleResponse<List<FileInfoCO>> uploadInternalBatch(List<MultipartFile> files);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.qinan.safetyeval.client.co;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 文件信息 CO(Client Object - 返回给前端)
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Data
|
||||
public class FileInfoCO {
|
||||
|
||||
/** 文件ID */
|
||||
private Integer id;
|
||||
|
||||
/** OSS文件访问URL */
|
||||
private String url;
|
||||
|
||||
/** 文件签名URL(带过期时间的临时访问链接) */
|
||||
private String presignedUrl;
|
||||
|
||||
/** 文件大小(字节) */
|
||||
private Long size;
|
||||
|
||||
/** 文件原始名称 */
|
||||
private String originalFilename;
|
||||
|
||||
/** 文件扩展名 */
|
||||
private String ext;
|
||||
|
||||
/** 文件MIME类型 */
|
||||
private String contentType;
|
||||
|
||||
/** 上传者姓名 */
|
||||
private String creatorName;
|
||||
|
||||
/** 上传时间 */
|
||||
private LocalDateTime createdTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package org.qinan.safetyeval.domain.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 文件资源领域实体
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Data
|
||||
public class FileStorage {
|
||||
|
||||
/** 主键ID */
|
||||
private Integer id;
|
||||
|
||||
/** OSS文件完整访问URL */
|
||||
private String url;
|
||||
|
||||
/** 文件大小(字节) */
|
||||
private Long size;
|
||||
|
||||
/** OSS存储文件名 */
|
||||
private String filename;
|
||||
|
||||
/** 文件原始名称 */
|
||||
private String originalFilename;
|
||||
|
||||
/** OSS存储根路径 */
|
||||
private String basePath;
|
||||
|
||||
/** OSS存储相对路径 */
|
||||
private String path;
|
||||
|
||||
/** 文件扩展名 */
|
||||
private String ext;
|
||||
|
||||
/** 文件MIME类型 */
|
||||
private String contentType;
|
||||
|
||||
/** OSS平台标识 */
|
||||
private String platform;
|
||||
|
||||
/** Content-MD5值 */
|
||||
private String md5;
|
||||
|
||||
/** 上传者姓名 */
|
||||
private String creatorName;
|
||||
|
||||
/** 上传者UID */
|
||||
private String creatorUid;
|
||||
|
||||
/** 上传时间 */
|
||||
private LocalDateTime createdTime;
|
||||
}
|
||||
|
|
@ -70,6 +70,16 @@ public enum ErrorCode {
|
|||
QUAL_FILING_CHANGE_NO_DIFF("02-02-101", "备案信息无变更,无需提交"),
|
||||
QUAL_FILING_CHANGE_IN_PROGRESS("02-02-102", "存在进行中的备案变更"),
|
||||
|
||||
// ---- 文件存储 ----
|
||||
FILE_UPLOAD_FAILED("03-01-001", "文件上传失败"),
|
||||
FILE_UPLOAD_EMPTY("03-01-002", "上传文件为空"),
|
||||
FILE_NOT_FOUND("03-01-003", "文件不存在"),
|
||||
FILE_DELETE_FAILED("03-01-004", "文件删除失败"),
|
||||
FILE_DOWNLOAD_FAILED("03-01-005", "文件下载失败"),
|
||||
FILE_OSS_OPERATION_FAILED("03-01-006", "OSS操作异常"),
|
||||
FILE_URL_GENERATE_FAILED("03-01-007", "文件地址生成失败"),
|
||||
FILE_SIZE_EXCEEDED("03-01-008", "文件大小超出限制"),
|
||||
|
||||
/** 未知错误 */
|
||||
UNKNOWN_ERROR("99-99-999", "系统异常,请稍后重试");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package org.qinan.safetyeval.domain.gateway;
|
||||
|
||||
import org.qinan.safetyeval.domain.entity.FileStorage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件资源Gateway接口(Domain层定义)
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
public interface ResourceGateway {
|
||||
|
||||
/**
|
||||
* 保存文件资源元信息
|
||||
*/
|
||||
FileStorage save(FileStorage fileStorage);
|
||||
|
||||
/**
|
||||
* 批量保存文件资源元信息
|
||||
*/
|
||||
List<FileStorage> saveBatch(List<FileStorage> fileStorages);
|
||||
|
||||
/**
|
||||
* 根据ID查询
|
||||
*/
|
||||
FileStorage get(Integer id);
|
||||
|
||||
/**
|
||||
* 根据文件名查询
|
||||
*/
|
||||
FileStorage getByFilename(String filename);
|
||||
|
||||
/**
|
||||
* 根据URL查询
|
||||
*/
|
||||
FileStorage getByUrl(String url);
|
||||
|
||||
/**
|
||||
* 根据ID删除
|
||||
*/
|
||||
void delete(Integer id);
|
||||
|
||||
/**
|
||||
* 批量根据ID删除
|
||||
*/
|
||||
void deleteBatch(List<Integer> ids);
|
||||
|
||||
/**
|
||||
* 更新资源信息
|
||||
*/
|
||||
FileStorage update(FileStorage fileStorage);
|
||||
|
||||
/**
|
||||
* 查询所有资源列表
|
||||
*/
|
||||
List<FileStorage> listAll();
|
||||
|
||||
/**
|
||||
* 根据创建者查询
|
||||
*/
|
||||
List<FileStorage> listByCreator(String creatorUid);
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package org.qinan.safetyeval.domain.service;
|
||||
|
||||
import org.qinan.safetyeval.domain.entity.FileStorage;
|
||||
import org.qinan.safetyeval.domain.exception.BizException;
|
||||
import org.qinan.safetyeval.domain.exception.ErrorCode;
|
||||
import org.qinan.safetyeval.domain.gateway.ResourceGateway;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件存储领域服务
|
||||
* <p>
|
||||
* 协调 OSS 存储操作与本地数据库维护管理,
|
||||
* 确保文件存储/删除时同步更新 filestorage 表。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Service
|
||||
public class FileStorageDomainService {
|
||||
|
||||
@javax.annotation.Resource
|
||||
private ResourceGateway resourceGateway;
|
||||
|
||||
/**
|
||||
* 保存文件元信息到数据库
|
||||
*/
|
||||
public FileStorage saveResource(FileStorage fileStorage) {
|
||||
if (fileStorage == null) {
|
||||
throw new BizException(ErrorCode.FILE_UPLOAD_EMPTY);
|
||||
}
|
||||
return resourceGateway.save(fileStorage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存文件元信息
|
||||
*/
|
||||
public List<FileStorage> saveResourceBatch(List<FileStorage> fileStorages) {
|
||||
if (fileStorages == null || fileStorages.isEmpty()) {
|
||||
throw new BizException(ErrorCode.FILE_UPLOAD_EMPTY);
|
||||
}
|
||||
return resourceGateway.saveBatch(fileStorages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取文件元信息
|
||||
*/
|
||||
public FileStorage getResource(Integer id) {
|
||||
FileStorage fileStorage = resourceGateway.get(id);
|
||||
if (fileStorage == null) {
|
||||
throw new BizException(ErrorCode.FILE_NOT_FOUND);
|
||||
}
|
||||
return fileStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件名获取文件元信息
|
||||
*/
|
||||
public FileStorage getResourceByFilename(String filename) {
|
||||
FileStorage fileStorage = resourceGateway.getByFilename(filename);
|
||||
if (fileStorage == null) {
|
||||
throw new BizException(ErrorCode.FILE_NOT_FOUND);
|
||||
}
|
||||
return fileStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件元信息
|
||||
*/
|
||||
public void deleteResource(Integer id) {
|
||||
FileStorage fileStorage = resourceGateway.get(id);
|
||||
if (fileStorage == null) {
|
||||
throw new BizException(ErrorCode.FILE_NOT_FOUND);
|
||||
}
|
||||
resourceGateway.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除文件元信息
|
||||
*/
|
||||
public void deleteResourceBatch(List<Integer> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
resourceGateway.deleteBatch(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有文件列表
|
||||
*/
|
||||
public List<FileStorage> listAllResources() {
|
||||
return resourceGateway.listAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据创建者获取文件列表
|
||||
*/
|
||||
public List<FileStorage> listResourcesByCreator(String creatorUid) {
|
||||
return resourceGateway.listByCreator(creatorUid);
|
||||
}
|
||||
}
|
||||
|
|
@ -79,5 +79,17 @@
|
|||
<artifactId>zcloud_gbscommon</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- X File Storage(多OSS统一文件存储) -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.x-file-storage</groupId>
|
||||
<artifactId>x-file-storage-spring</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Aliyun OSS SDK -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package org.qinan.safetyeval.infrastructure.config;
|
||||
|
||||
import org.dromara.x.file.storage.spring.EnableFileStorage;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 文件存储自动配置
|
||||
* <p>
|
||||
* 启用 x-file-storage 的 Spring 集成,
|
||||
* OSS 具体参数通过 application.yml 注入。
|
||||
* </p>
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Configuration
|
||||
@EnableFileStorage
|
||||
public class FileStorageAutoConfiguration {
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package org.qinan.safetyeval.infrastructure.dataobject;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 文件资源数据对象(Infrastructure层 - 对应 filestorage 表)
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Data
|
||||
@TableName("filestorage")
|
||||
public class ResourceDO {
|
||||
|
||||
/** 主键ID */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
/** OSS文件完整访问URL */
|
||||
private String url;
|
||||
|
||||
/** 文件大小(字节) */
|
||||
private Long size;
|
||||
|
||||
/** OSS存储文件名 */
|
||||
private String filename;
|
||||
|
||||
/** 文件原始名称 */
|
||||
private String originalFilename;
|
||||
|
||||
/** OSS存储根路径 */
|
||||
private String basePath;
|
||||
|
||||
/** OSS存储相对路径 */
|
||||
private String path;
|
||||
|
||||
/** 文件扩展名 */
|
||||
private String ext;
|
||||
|
||||
/** 文件MIME类型 */
|
||||
private String contentType;
|
||||
|
||||
/** OSS平台标识 */
|
||||
private String platform;
|
||||
|
||||
/** Content-MD5值 */
|
||||
private String md5;
|
||||
|
||||
/** 上传者姓名 */
|
||||
private String creatorName;
|
||||
|
||||
/** 上传者UID */
|
||||
private String creatorUid;
|
||||
|
||||
/** 上传时间 */
|
||||
private LocalDateTime createdTime;
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
package org.qinan.safetyeval.infrastructure.gatewayimpl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.qinan.safetyeval.domain.entity.FileStorage;
|
||||
import org.qinan.safetyeval.domain.gateway.ResourceGateway;
|
||||
import org.qinan.safetyeval.infrastructure.dataobject.ResourceDO;
|
||||
import org.qinan.safetyeval.infrastructure.mapper.ResourceMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 文件资源Gateway实现(Infrastructure层)
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Component
|
||||
public class ResourceGatewayImpl implements ResourceGateway {
|
||||
|
||||
@Resource
|
||||
private ResourceMapper resourceMapper;
|
||||
|
||||
@Override
|
||||
public FileStorage save(FileStorage fileStorage) {
|
||||
ResourceDO dataObject = toDO(fileStorage);
|
||||
resourceMapper.insert(dataObject);
|
||||
fileStorage.setId(dataObject.getId());
|
||||
return fileStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileStorage> saveBatch(List<FileStorage> fileStorages) {
|
||||
List<ResourceDO> dataObjects = fileStorages.stream()
|
||||
.map(this::toDO)
|
||||
.collect(Collectors.toList());
|
||||
for (ResourceDO dataObject : dataObjects) {
|
||||
resourceMapper.insert(dataObject);
|
||||
}
|
||||
for (int i = 0; i < fileStorages.size(); i++) {
|
||||
fileStorages.get(i).setId(dataObjects.get(i).getId());
|
||||
}
|
||||
return fileStorages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileStorage get(Integer id) {
|
||||
ResourceDO dataObject = resourceMapper.selectById(id);
|
||||
return toEntity(dataObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileStorage getByFilename(String filename) {
|
||||
LambdaQueryWrapper<ResourceDO> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(ResourceDO::getFilename, filename);
|
||||
ResourceDO dataObject = resourceMapper.selectOne(wrapper);
|
||||
return toEntity(dataObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileStorage getByUrl(String url) {
|
||||
LambdaQueryWrapper<ResourceDO> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(ResourceDO::getUrl, url);
|
||||
ResourceDO dataObject = resourceMapper.selectOne(wrapper);
|
||||
return toEntity(dataObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(Integer id) {
|
||||
resourceMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBatch(List<Integer> ids) {
|
||||
resourceMapper.deleteBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileStorage update(FileStorage fileStorage) {
|
||||
ResourceDO dataObject = toDO(fileStorage);
|
||||
dataObject.setId(fileStorage.getId());
|
||||
resourceMapper.updateById(dataObject);
|
||||
return get(fileStorage.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileStorage> listAll() {
|
||||
List<ResourceDO> dataObjects = resourceMapper.selectList(null);
|
||||
return dataObjects.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileStorage> listByCreator(String creatorUid) {
|
||||
LambdaQueryWrapper<ResourceDO> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(ResourceDO::getCreatorUid, creatorUid);
|
||||
List<ResourceDO> dataObjects = resourceMapper.selectList(wrapper);
|
||||
return dataObjects.stream()
|
||||
.map(this::toEntity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ---------- 实体转换 ----------
|
||||
|
||||
private ResourceDO toDO(FileStorage entity) {
|
||||
if (entity == null) return null;
|
||||
ResourceDO dataObject = new ResourceDO();
|
||||
dataObject.setUrl(entity.getUrl());
|
||||
dataObject.setSize(entity.getSize());
|
||||
dataObject.setFilename(entity.getFilename());
|
||||
dataObject.setOriginalFilename(entity.getOriginalFilename());
|
||||
dataObject.setBasePath(entity.getBasePath());
|
||||
dataObject.setPath(entity.getPath());
|
||||
dataObject.setExt(entity.getExt());
|
||||
dataObject.setContentType(entity.getContentType());
|
||||
dataObject.setPlatform(entity.getPlatform());
|
||||
dataObject.setMd5(entity.getMd5());
|
||||
dataObject.setCreatorName(entity.getCreatorName());
|
||||
dataObject.setCreatorUid(entity.getCreatorUid());
|
||||
dataObject.setCreatedTime(entity.getCreatedTime());
|
||||
return dataObject;
|
||||
}
|
||||
|
||||
private FileStorage toEntity(ResourceDO dataObject) {
|
||||
if (dataObject == null) return null;
|
||||
FileStorage entity = new FileStorage();
|
||||
entity.setId(dataObject.getId());
|
||||
entity.setUrl(dataObject.getUrl());
|
||||
entity.setSize(dataObject.getSize());
|
||||
entity.setFilename(dataObject.getFilename());
|
||||
entity.setOriginalFilename(dataObject.getOriginalFilename());
|
||||
entity.setBasePath(dataObject.getBasePath());
|
||||
entity.setPath(dataObject.getPath());
|
||||
entity.setExt(dataObject.getExt());
|
||||
entity.setContentType(dataObject.getContentType());
|
||||
entity.setPlatform(dataObject.getPlatform());
|
||||
entity.setMd5(dataObject.getMd5());
|
||||
entity.setCreatorName(dataObject.getCreatorName());
|
||||
entity.setCreatorUid(dataObject.getCreatorUid());
|
||||
entity.setCreatedTime(dataObject.getCreatedTime());
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package org.qinan.safetyeval.infrastructure.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.qinan.safetyeval.infrastructure.dataobject.ResourceDO;
|
||||
|
||||
/**
|
||||
* 文件资源 Mapper
|
||||
*
|
||||
* @author safety-eval
|
||||
*/
|
||||
@Mapper
|
||||
public interface ResourceMapper extends BaseMapper<ResourceDO> {
|
||||
}
|
||||
|
|
@ -122,6 +122,8 @@ safety-eval:
|
|||
- /qual-filing-material
|
||||
- /qual-filing-personnel
|
||||
- /qual-filing-personnel-cert
|
||||
- /file-storage
|
||||
- /file
|
||||
# 本地联调默认用户上下文(与 init-v2.sql 测试数据 @org_id=1 @tenant_id=1001 一致)
|
||||
public-api:
|
||||
system-user-id: 1
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ spring:
|
|||
config:
|
||||
import:
|
||||
- classpath:sdk-prod2.yml
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 100MB
|
||||
max-request-size: 500MB
|
||||
|
||||
|
||||
# ---------- MyBatis Plus ----------
|
||||
|
|
@ -38,6 +43,21 @@ mybatis-plus:
|
|||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
|
||||
# ---------- 文件存储(阿里云 OSS) ----------
|
||||
dromara:
|
||||
x-file-storage:
|
||||
default-platform: aliyun-oss-1
|
||||
thumbnail-suffix: "" # 不使用缩略图
|
||||
aliyun-oss:
|
||||
- platform: aliyun-oss-1
|
||||
enable-storage: true
|
||||
access-key: LTAI5tFerUgBCLEbXtinDD6d
|
||||
secret-key: XyGVNV2RvW8U3ceoJDiy9A9oLvCGYp
|
||||
end-point: oss-cn-hangzhou.aliyuncs.com
|
||||
bucket-name: test-dragon-yf-pub
|
||||
domain: https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/
|
||||
base-path: jjb/
|
||||
|
||||
# ---------- Dubbo(环境无关的公共部分,各profile覆盖地址/端口) ----------
|
||||
dubbo:
|
||||
application:
|
||||
|
|
@ -71,7 +91,7 @@ safety-eval:
|
|||
- /swagger-ui
|
||||
- /swagger-resources
|
||||
- /webjars
|
||||
- /public/org-personnel
|
||||
- /public
|
||||
- /test
|
||||
- /tmp
|
||||
- /mock
|
||||
|
|
|
|||
Loading…
Reference in New Issue