mirror of
https://github.com/KwiTsukasa/kt-template-online-api.git
synced 2026-05-27 15:44:54 +08:00
feat: 补全接口文档注释, 实现minio接口
This commit is contained in:
parent
9240968c82
commit
f236d86ff3
3
.env
3
.env
@ -5,7 +5,8 @@ DB_PASSWORD=qwqvqaqeq2333KT
|
||||
DB_DATABASE=shy_template
|
||||
DB_SYNC=true
|
||||
|
||||
MINIO_ENDPOINT=192.168.1.206
|
||||
MINIO_ENDPOINT=localhost
|
||||
MINIO_PORT=9000
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
MINIO_BUCKET=kt-template-online
|
||||
|
||||
@ -9,3 +9,4 @@ MINIO_ENDPOINT=192.168.1.206
|
||||
MINIO_PORT=9000
|
||||
MINIO_ACCESS_KEY=minioadmin
|
||||
MINIO_SECRET_KEY=minioadmin
|
||||
MINIO_BUCKET=kt-template-online
|
||||
|
||||
295
API.md
Normal file
295
API.md
Normal file
@ -0,0 +1,295 @@
|
||||
# KT Template Online API
|
||||
|
||||
后端服务默认监听 `48085`,Swagger 地址为 `/api`,OpenAPI JSON 地址为 `/api-json`。接口除文件下载外,统一返回 `{ code, msg, data }`。
|
||||
|
||||
## 通用响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
失败时仍使用相同结构,常见为:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"msg": "操作失败",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## Root
|
||||
|
||||
### GET `/`
|
||||
|
||||
重定向到 Swagger 文档页 `/api#/`,HTTP 状态码为 `301`。
|
||||
|
||||
## 数据结构
|
||||
|
||||
### Component
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ---------------- | ------- | ---------------------------- |
|
||||
| id | string | 组件 ID,新增时由后端生成 |
|
||||
| name | string | 组件名称 |
|
||||
| type | number | 一级类型,`1` 图表,`2` 组件 |
|
||||
| componentType | number | 二级类型 |
|
||||
| typeMsg | string | 一级类型文本,列表接口返回 |
|
||||
| componentTypeMsg | string | 二级类型文本,列表接口返回 |
|
||||
| image | string | 封面图 |
|
||||
| template | string | playground 序列化模板内容 |
|
||||
| createTime | string | 创建时间 |
|
||||
| updateTime | string | 更新时间 |
|
||||
| is_deleted | boolean | 逻辑删除标记 |
|
||||
|
||||
### 字典
|
||||
|
||||
`COMPONENT_TYPE`:
|
||||
|
||||
| label | value |
|
||||
| ----- | ----- |
|
||||
| 图表 | 1 |
|
||||
| 组件 | 2 |
|
||||
|
||||
`CHART`:`未分类(-1)`、`折线图(1)`、`柱状图(2)`、`饼图(3)`、`散点图(4)`、`地图(5)`、`K线图(6)`、`雷达图(7)`、`盒须图(8)`、`热力图(9)`、`关系图(10)`、`路径图(11)`、`树图(12)`、`矩树图(13)`、`旭日图(14)`、`平行坐标系(15)`、`桑基图(16)`、`漏斗图(17)`、`仪表盘(18)`、`象形图(19)`、`河流图(20)`、`水球(21)`、`词云(22)`。
|
||||
|
||||
`COMPONENT`:`未分类(-1)`、`表格(23)`、`表单(24)`、`容器(25)`。
|
||||
|
||||
## Component
|
||||
|
||||
### GET `/component/allList`
|
||||
|
||||
获取全部组件。
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"id": "1d8d3dd2-99f0-4d10-9a44-0cf9566b37c9",
|
||||
"name": "基础折线图",
|
||||
"type": 1,
|
||||
"componentType": 1,
|
||||
"typeMsg": "图表",
|
||||
"componentTypeMsg": "折线图",
|
||||
"image": "",
|
||||
"template": "%7B%22version%22%3A%221.0%22%7D",
|
||||
"createTime": "2026-05-13T02:30:00.000Z",
|
||||
"updateTime": "2026-05-13T02:30:00.000Z",
|
||||
"is_deleted": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/component/list`
|
||||
|
||||
分页获取组件列表。
|
||||
|
||||
Query:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ------------- | ------ | ---- | ------------ |
|
||||
| pageNo | number | 是 | 页码 |
|
||||
| pageSize | number | 是 | 每页条数 |
|
||||
| name | string | 否 | 名称模糊搜索 |
|
||||
| type | number | 否 | 一级类型 |
|
||||
| componentType | number | 否 | 二级类型 |
|
||||
|
||||
响应 `data`:`{ list: Component[], total: number }`。
|
||||
|
||||
### GET `/component/detail`
|
||||
|
||||
获取组件详情。
|
||||
|
||||
Query:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---- | ------ | ---- | ------- |
|
||||
| id | string | 是 | 组件 ID |
|
||||
|
||||
响应 `data`:`Component`。
|
||||
|
||||
### POST `/component/save`
|
||||
|
||||
新增组件。`SaveMiddleware` 会删除 body 中的 `id`,新增时不需要传 `id`。
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "基础折线图",
|
||||
"type": 1,
|
||||
"componentType": 1,
|
||||
"image": "",
|
||||
"template": "%7B%22version%22%3A%221.0%22%7D"
|
||||
}
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": "1d8d3dd2-99f0-4d10-9a44-0cf9566b37c9"
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/component/update`
|
||||
|
||||
编辑组件。
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1d8d3dd2-99f0-4d10-9a44-0cf9566b37c9",
|
||||
"name": "基础折线图",
|
||||
"type": 1,
|
||||
"componentType": 1,
|
||||
"image": "",
|
||||
"template": "%7B%22version%22%3A%221.0%22%7D"
|
||||
}
|
||||
```
|
||||
|
||||
响应 `data`:`true` 表示更新成功。
|
||||
|
||||
### POST `/component/remove`
|
||||
|
||||
逻辑删除组件。
|
||||
|
||||
Query:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---- | ------ | ---- | ------- |
|
||||
| id | string | 是 | 组件 ID |
|
||||
|
||||
响应 `data`:`true` 表示删除成功。
|
||||
|
||||
## Dict
|
||||
|
||||
### GET `/dict/getDictByKey`
|
||||
|
||||
根据字典 key 获取字典。
|
||||
|
||||
Query:
|
||||
|
||||
| 参数 | 类型 | 必填 | 可选值 |
|
||||
| ------- | ------ | ---- | -------------------------------------- |
|
||||
| dictKey | string | 是 | `COMPONENT_TYPE`、`CHART`、`COMPONENT` |
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"label": "图表",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"label": "组件",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/dict/getComponentDictByType`
|
||||
|
||||
根据一级类型获取二级类型字典。
|
||||
|
||||
Query:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---- | ------ | ---- | ------------------ |
|
||||
| type | number | 是 | `1` 图表,`2` 组件 |
|
||||
|
||||
响应 `data`:`Array<{ label: string; value: number }>`。
|
||||
|
||||
## MinIO
|
||||
|
||||
### GET `/minio/check`
|
||||
|
||||
检查 MinIO 连接和 bucket 状态。
|
||||
|
||||
Query:`bucketName?: string`
|
||||
|
||||
响应 `data`:`{ bucketName: string; exists: boolean }`。
|
||||
|
||||
### POST `/minio/bucket`
|
||||
|
||||
创建 bucket,已存在时跳过。
|
||||
|
||||
Query:`bucketName?: string`
|
||||
|
||||
响应 `data`:bucket 名称。
|
||||
|
||||
### POST `/minio/upload`
|
||||
|
||||
上传文件,请求类型为 `multipart/form-data`。
|
||||
|
||||
Body:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
| ---------- | ------ | ---- | ---------------------- |
|
||||
| file | File | 是 | 文件 |
|
||||
| bucketName | string | 否 | bucket 名称 |
|
||||
| objectName | string | 否 | 对象名,不传时自动生成 |
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"bucketName": "kt-template-online",
|
||||
"objectName": "uploads/1715580000000-a1b2c3-demo.png",
|
||||
"etag": "9b2cf535f27731c974343645a3985328",
|
||||
"size": 2048,
|
||||
"mimeType": "image/png",
|
||||
"url": "http://127.0.0.1:9000/kt-template-online/uploads/demo.png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/minio/list`
|
||||
|
||||
获取文件列表。
|
||||
|
||||
Query:`bucketName?: string`、`prefix?: string`、`recursive?: string`
|
||||
|
||||
响应 `data`:MinIO 对象数组,常见字段为 `name`、`size`、`etag`、`lastModified`。
|
||||
|
||||
### GET `/minio/url`
|
||||
|
||||
获取文件临时访问地址。
|
||||
|
||||
Query:`objectName: string`、`bucketName?: string`、`expiry?: string`
|
||||
|
||||
响应 `data`:临时访问 URL。
|
||||
|
||||
### GET `/minio/download`
|
||||
|
||||
下载文件,直接返回文件流。
|
||||
|
||||
Query:`objectName: string`、`bucketName?: string`
|
||||
|
||||
### DELETE `/minio/remove`
|
||||
|
||||
删除文件。
|
||||
|
||||
Query:`objectName: string`、`bucketName?: string`
|
||||
|
||||
响应 `data`:`true` 表示删除成功。
|
||||
32
package.json
32
package.json
@ -1,21 +1,24 @@
|
||||
{
|
||||
"name": "kt-template-online-api",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"description": "kt-template-online server API",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "nest build && NODE_ENV=prod node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"start:prod": "nest build && cross-env NODE_ENV=prod node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
|
||||
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:ci": "jest --runInBand --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
@ -23,29 +26,14 @@
|
||||
"@nestjs/common": "^9.4.3",
|
||||
"@nestjs/config": "^2.3.4",
|
||||
"@nestjs/core": "^9.4.3",
|
||||
"@nestjs/jwt": "^10.2.0",
|
||||
"@nestjs/mapped-types": "^2.1.1",
|
||||
"@nestjs/passport": "^9.0.3",
|
||||
"@nestjs/platform-express": "^9.4.3",
|
||||
"@nestjs/swagger": "^7.4.2",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@types/dotenv": "^8.2.3",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/multer": "^1.4.13",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.6.1",
|
||||
"express-session": "^1.19.0",
|
||||
"lodash": "^4.18.1",
|
||||
"minio": "^8.0.7",
|
||||
"mssql": "^9.3.2",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mysql2": "^3.22.3",
|
||||
"nestjs-knife4j-plus": "^1.0.7",
|
||||
"nestjs-knife4j-plus": "^1.0.8",
|
||||
"nestjs-minio-client": "^2.2.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"rxjs": "^7.8.2",
|
||||
"svg-captcha": "^1.4.0",
|
||||
@ -56,11 +44,9 @@
|
||||
"@nestjs/schematics": "^9.2.0",
|
||||
"@nestjs/testing": "^9.4.3",
|
||||
"@types/express": "^4.17.25",
|
||||
"@types/express-session": "^1.19.0",
|
||||
"@types/jest": "29.2.4",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/passport-jwt": "^3.0.13",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/supertest": "^2.0.16",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
|
||||
595
pnpm-lock.yaml
595
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
import { Controller, Get, Redirect } from '@nestjs/common';
|
||||
import { ApiMovedPermanentlyResponse, ApiOperation } from '@nestjs/swagger';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
@ -7,6 +8,10 @@ export class AppController {
|
||||
|
||||
@Get()
|
||||
@Redirect('/api#/', 301)
|
||||
@ApiOperation({ summary: '重定向到Swagger文档' })
|
||||
@ApiMovedPermanentlyResponse({
|
||||
description: '重定向到 /api#/',
|
||||
})
|
||||
getHome() {
|
||||
return { url: '/api#/' };
|
||||
}
|
||||
|
||||
@ -6,11 +6,13 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { MinioModule } from 'nestjs-minio-client';
|
||||
import { ComponentModule } from './component/component.module';
|
||||
import { DictModule } from './dict/dict.module';
|
||||
import { MinioClientModule } from './minio/minio.module';
|
||||
import { SaveMiddleware } from './middleware/save.middleware';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: `.env${
|
||||
process.env.NODE_ENV ? `.${process.env.NODE_ENV}` : ''
|
||||
}`,
|
||||
@ -47,6 +49,7 @@ import { SaveMiddleware } from './middleware/save.middleware';
|
||||
}),
|
||||
ComponentModule,
|
||||
DictModule,
|
||||
MinioClientModule,
|
||||
],
|
||||
providers: [AppService, ConfigService],
|
||||
})
|
||||
|
||||
181
src/common/swagger-response.ts
Normal file
181
src/common/swagger-response.ts
Normal file
@ -0,0 +1,181 @@
|
||||
import { applyDecorators, Type } from '@nestjs/common';
|
||||
import { ApiExtraModels, ApiOkResponse, ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
type SwaggerSchema = Record<string, any>;
|
||||
|
||||
type ApiResponseOptions = {
|
||||
description?: string;
|
||||
schema?: SwaggerSchema;
|
||||
example: any;
|
||||
};
|
||||
|
||||
const primitiveTypeMap = {
|
||||
string: String,
|
||||
number: Number,
|
||||
boolean: Boolean,
|
||||
};
|
||||
|
||||
const setClassName = (target: Type<any>, name: string) => {
|
||||
Object.defineProperty(target, 'name', {
|
||||
value: name,
|
||||
});
|
||||
|
||||
return target;
|
||||
};
|
||||
|
||||
export class PaginatedDto<TData> {
|
||||
@ApiProperty()
|
||||
total: number;
|
||||
|
||||
@ApiProperty({
|
||||
type: Array,
|
||||
})
|
||||
list: TData[];
|
||||
}
|
||||
|
||||
export class ApiResponseDto<TData> {
|
||||
@ApiProperty({
|
||||
example: 200,
|
||||
})
|
||||
code: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: '操作成功',
|
||||
})
|
||||
msg: string;
|
||||
|
||||
@ApiProperty()
|
||||
data: TData;
|
||||
}
|
||||
|
||||
const getResponseExample = (example: any) => ({
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: example,
|
||||
});
|
||||
|
||||
export const ApiSuccessResponse = ({
|
||||
description = '操作成功',
|
||||
schema,
|
||||
example,
|
||||
}: ApiResponseOptions) => {
|
||||
const primitiveType = primitiveTypeMap[schema?.type] || Object;
|
||||
|
||||
class ApiSuccessResponseDto extends ApiResponseDto<any> {
|
||||
@ApiProperty({
|
||||
type: primitiveType,
|
||||
description: schema?.description,
|
||||
})
|
||||
declare data: any;
|
||||
}
|
||||
|
||||
setClassName(ApiSuccessResponseDto, `ApiResponseOf${primitiveType.name}`);
|
||||
|
||||
return applyDecorators(
|
||||
ApiExtraModels(ApiSuccessResponseDto),
|
||||
ApiOkResponse({
|
||||
description,
|
||||
type: ApiSuccessResponseDto,
|
||||
example: getResponseExample(example),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const ApiModelResponse = <TModel extends Type<any>>(
|
||||
model: TModel,
|
||||
example: any,
|
||||
description?: string,
|
||||
) => {
|
||||
class ApiModelResponseDto extends ApiResponseDto<TModel> {
|
||||
@ApiProperty({
|
||||
type: model,
|
||||
})
|
||||
declare data: TModel;
|
||||
}
|
||||
|
||||
setClassName(ApiModelResponseDto, `ApiResponseOf${model.name}`);
|
||||
|
||||
return applyDecorators(
|
||||
ApiExtraModels(ApiModelResponseDto, model),
|
||||
ApiOkResponse({
|
||||
description: description || '操作成功',
|
||||
type: ApiModelResponseDto,
|
||||
example: getResponseExample(example),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const ApiArrayResponse = <TModel extends Type<any>>(
|
||||
model: TModel,
|
||||
example: any[],
|
||||
description?: string,
|
||||
) => {
|
||||
class ApiArrayResponseDto extends ApiResponseDto<TModel[]> {
|
||||
@ApiProperty({
|
||||
type: [model],
|
||||
})
|
||||
declare data: TModel[];
|
||||
}
|
||||
|
||||
setClassName(ApiArrayResponseDto, `ApiResponseOf${model.name}Array`);
|
||||
|
||||
return applyDecorators(
|
||||
ApiExtraModels(ApiArrayResponseDto, model),
|
||||
ApiOkResponse({
|
||||
description: description || '操作成功',
|
||||
type: ApiArrayResponseDto,
|
||||
example: getResponseExample(example),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const ApiPageResponse = <TModel extends Type<any>>(
|
||||
model: TModel,
|
||||
example: any[],
|
||||
total = 1,
|
||||
description?: string,
|
||||
) => {
|
||||
class PageResponseDto extends PaginatedDto<TModel> {
|
||||
@ApiProperty({
|
||||
type: [model],
|
||||
})
|
||||
declare list: TModel[];
|
||||
}
|
||||
|
||||
class ApiPageResponseDto extends ApiResponseDto<PageResponseDto> {
|
||||
@ApiProperty({
|
||||
type: PageResponseDto,
|
||||
})
|
||||
declare data: PageResponseDto;
|
||||
}
|
||||
|
||||
setClassName(PageResponseDto, `PaginatedResponseOf${model.name}`);
|
||||
setClassName(ApiPageResponseDto, `ApiResponseOfPaginated${model.name}`);
|
||||
|
||||
return applyDecorators(
|
||||
ApiExtraModels(ApiPageResponseDto, PageResponseDto, PaginatedDto, model),
|
||||
ApiOkResponse({
|
||||
description: description || '操作成功',
|
||||
type: ApiPageResponseDto,
|
||||
example: getResponseExample({
|
||||
list: example,
|
||||
total,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const ApiFileDownloadResponse = (description = '文件下载成功') =>
|
||||
applyDecorators(
|
||||
ApiOkResponse({
|
||||
description,
|
||||
content: {
|
||||
'application/octet-stream': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
format: 'binary',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
@ -10,7 +10,6 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiOkResponse,
|
||||
ApiOperation,
|
||||
ApiProperty,
|
||||
ApiQuery,
|
||||
@ -20,8 +19,28 @@ import {
|
||||
import { ToolsService } from '@/utils/tool.service';
|
||||
import { ComponentService } from './component.service';
|
||||
import { Component } from './component.entity';
|
||||
import { PaginatedDto } from '@/utils/constant';
|
||||
import { ComponentDto } from './component.dto';
|
||||
import {
|
||||
PaginatedDto,
|
||||
ApiArrayResponse,
|
||||
ApiModelResponse,
|
||||
ApiPageResponse,
|
||||
ApiSuccessResponse,
|
||||
} from '@/common/swagger-response';
|
||||
|
||||
const componentExample = {
|
||||
id: '1d8d3dd2-99f0-4d10-9a44-0cf9566b37c9',
|
||||
name: '基础折线图',
|
||||
type: 1,
|
||||
componentType: 1,
|
||||
typeMsg: '图表',
|
||||
componentTypeMsg: '折线图',
|
||||
image: '',
|
||||
template: '%7B%22version%22%3A%221.0%22%7D',
|
||||
createTime: '2026-05-13T02:30:00.000Z',
|
||||
updateTime: '2026-05-13T02:30:00.000Z',
|
||||
is_deleted: false,
|
||||
};
|
||||
|
||||
class CompPageDto
|
||||
extends PartialType(Component)
|
||||
@ -39,16 +58,9 @@ class CompPageDto
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
class CompPageResDto extends PaginatedDto {
|
||||
@ApiProperty({
|
||||
type: [ComponentDto],
|
||||
})
|
||||
list: ComponentDto[];
|
||||
}
|
||||
|
||||
@Controller('component')
|
||||
@ApiTags('component')
|
||||
@ApiExtraModels(PaginatedDto, ComponentDto)
|
||||
@ApiExtraModels(PaginatedDto)
|
||||
export class ComponentController {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
@ -57,7 +69,7 @@ export class ComponentController {
|
||||
|
||||
@Get('allList')
|
||||
@ApiOperation({ summary: '获取组件列表' })
|
||||
@ApiOkResponse({ type: [ComponentDto] })
|
||||
@ApiArrayResponse(ComponentDto, [componentExample])
|
||||
async getAllList(@Res() res) {
|
||||
const list = await this.componentService.all();
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', list));
|
||||
@ -66,13 +78,11 @@ export class ComponentController {
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取组件列表分页' })
|
||||
@ApiQuery({ type: [CompPageDto] })
|
||||
@ApiOkResponse({
|
||||
type: CompPageResDto,
|
||||
})
|
||||
@ApiPageResponse(ComponentDto, [componentExample], 1)
|
||||
async getList(
|
||||
@Res() res,
|
||||
@Query() { pageNo, pageSize, ...args }: PageParams<ComponentDto>,
|
||||
): Promise<CompPageResDto> {
|
||||
): Promise<PaginatedDto<ComponentDto>> {
|
||||
const list = await this.componentService.page({
|
||||
pageNo,
|
||||
pageSize,
|
||||
@ -85,6 +95,13 @@ export class ComponentController {
|
||||
@Post('save')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '保存组件' })
|
||||
@ApiSuccessResponse({
|
||||
schema: {
|
||||
type: 'string',
|
||||
description: '新增组件ID',
|
||||
},
|
||||
example: '1d8d3dd2-99f0-4d10-9a44-0cf9566b37c9',
|
||||
})
|
||||
async save(@Res() res, @Body() component: Component) {
|
||||
const save = await this.componentService.save(component);
|
||||
|
||||
@ -101,6 +118,12 @@ export class ComponentController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '删除组件' })
|
||||
@ApiQuery({ name: 'id', type: String })
|
||||
@ApiSuccessResponse({
|
||||
schema: {
|
||||
type: 'boolean',
|
||||
},
|
||||
example: true,
|
||||
})
|
||||
async remove(@Res() res, @Query('id') id) {
|
||||
const remove = await this.componentService.remove(id);
|
||||
|
||||
@ -118,6 +141,12 @@ export class ComponentController {
|
||||
@Post('update')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: '编辑组件' })
|
||||
@ApiSuccessResponse({
|
||||
schema: {
|
||||
type: 'boolean',
|
||||
},
|
||||
example: true,
|
||||
})
|
||||
async update(@Res() res, @Body() component: Component) {
|
||||
const update = await this.componentService.update(component);
|
||||
|
||||
@ -135,7 +164,7 @@ export class ComponentController {
|
||||
@Get('detail')
|
||||
@ApiOperation({ summary: '组件详情' })
|
||||
@ApiQuery({ name: 'id', type: String })
|
||||
@ApiOkResponse({ type: ComponentDto })
|
||||
@ApiModelResponse(ComponentDto, componentExample)
|
||||
async detail(@Res() res, @Query('id') id) {
|
||||
const detail = await this.componentService.find(id);
|
||||
|
||||
|
||||
@ -1,8 +1,39 @@
|
||||
import { Controller, Get, HttpStatus, ParseIntPipe, Query, Res } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
HttpStatus,
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import { ToolsService } from '@/utils/tool.service';
|
||||
import { DictService } from './dict.service';
|
||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||
import { ComponentTypeEnum, DictKeyEnum, DictKeyType } from '@/utils/constant';
|
||||
import { ApiArrayResponse } from '@/common/swagger-response';
|
||||
import { DictDto } from './dict.dto';
|
||||
|
||||
const componentTypeDictExample = [
|
||||
{
|
||||
label: '图表',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '组件',
|
||||
value: 2,
|
||||
},
|
||||
];
|
||||
|
||||
const chartDictExample = [
|
||||
{
|
||||
label: '未分类',
|
||||
value: -1,
|
||||
},
|
||||
{
|
||||
label: '折线图',
|
||||
value: 1,
|
||||
},
|
||||
];
|
||||
|
||||
@ApiTags('dict')
|
||||
@Controller('dict')
|
||||
@ -14,31 +45,21 @@ export class DictController {
|
||||
|
||||
@ApiOperation({ summary: '根据key获取字典' })
|
||||
@ApiQuery({ name: 'dictKey', enum: DictKeyEnum })
|
||||
@ApiArrayResponse(DictDto, componentTypeDictExample)
|
||||
@Get('getDictByKey')
|
||||
async getDictByKey(@Res() res, @Query('dictKey') dictKey: DictKeyType) {
|
||||
const dict = this.toolsService.getDictByKey(dictKey)
|
||||
const dict = this.toolsService.getDictByKey(dictKey);
|
||||
|
||||
return res.send(
|
||||
this.toolsService.res(
|
||||
HttpStatus.OK,
|
||||
'操作成功',
|
||||
dict,
|
||||
),
|
||||
);
|
||||
return res.send(this.toolsService.res(HttpStatus.OK, '操作成功', dict));
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: '根据组件类型获取组件字典' })
|
||||
@ApiQuery({ name: 'type', enum: ComponentTypeEnum })
|
||||
@ApiArrayResponse(DictDto, chartDictExample)
|
||||
@Get('getComponentDictByType')
|
||||
async getComponentDictByType(@Res() res, @Query('type', ParseIntPipe) type) {
|
||||
const dict = await this.dictService.getComponentDictByType(type)
|
||||
const dict = await this.dictService.getComponentDictByType(type);
|
||||
|
||||
return res.send(
|
||||
this.toolsService.res(
|
||||
HttpStatus.OK,
|
||||
'操作成功',
|
||||
dict,
|
||||
),
|
||||
);
|
||||
return res.send(this.toolsService.res(HttpStatus.OK, '操作成功', dict));
|
||||
}
|
||||
}
|
||||
|
||||
13
src/dict/dict.dto.ts
Normal file
13
src/dict/dict.dto.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class DictDto {
|
||||
@ApiProperty({
|
||||
example: '图表',
|
||||
})
|
||||
label: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
})
|
||||
value: number;
|
||||
}
|
||||
@ -1,16 +1,226 @@
|
||||
import {
|
||||
Controller,
|
||||
|
||||
Body,
|
||||
Delete,
|
||||
Get,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Query,
|
||||
Res,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import {
|
||||
ApiBody,
|
||||
ApiConsumes,
|
||||
ApiOperation,
|
||||
ApiQuery,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { ToolsService } from '@/utils/tool.service';
|
||||
import { MinioClientService } from './minio.service';
|
||||
import type { MinioUploadFile } from './minio.service';
|
||||
import {
|
||||
ApiFileDownloadResponse,
|
||||
ApiArrayResponse,
|
||||
ApiModelResponse,
|
||||
ApiSuccessResponse,
|
||||
} from '@/common/swagger-response';
|
||||
import {
|
||||
MinioBucketStatusDto,
|
||||
MinioObjectDto,
|
||||
MinioUploadResultDto,
|
||||
} from './minio.dto';
|
||||
|
||||
@Controller('minio')
|
||||
@ApiTags('minio')
|
||||
export class MinioClientController {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly minioClientService: MinioClientService,
|
||||
) {} //注入服务
|
||||
|
||||
@Get('check')
|
||||
@ApiOperation({ summary: '检查MinIO连接和Bucket状态' })
|
||||
@ApiQuery({ name: 'bucketName', required: false })
|
||||
@ApiModelResponse(MinioBucketStatusDto, {
|
||||
bucketName: 'kt-template-online',
|
||||
exists: true,
|
||||
})
|
||||
async check(@Res() res, @Query('bucketName') bucketName?: string) {
|
||||
const result = await this.minioClientService.checkConnection(bucketName);
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', result));
|
||||
}
|
||||
|
||||
@Post('bucket')
|
||||
@ApiOperation({ summary: '创建Bucket(存在则跳过)' })
|
||||
@ApiQuery({ name: 'bucketName', required: false })
|
||||
@ApiSuccessResponse({
|
||||
schema: {
|
||||
type: 'string',
|
||||
description: 'Bucket名称',
|
||||
},
|
||||
example: 'kt-template-online',
|
||||
})
|
||||
async createBucket(@Res() res, @Query('bucketName') bucketName?: string) {
|
||||
const result = await this.minioClientService.ensureBucket(bucketName);
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', result));
|
||||
}
|
||||
|
||||
@Post('upload')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@ApiOperation({ summary: '上传文件到MinIO' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiModelResponse(MinioUploadResultDto, {
|
||||
bucketName: 'kt-template-online',
|
||||
objectName: 'uploads/1715580000000-a1b2c3-demo.png',
|
||||
etag: '9b2cf535f27731c974343645a3985328',
|
||||
size: 2048,
|
||||
mimeType: 'image/png',
|
||||
url: 'http://127.0.0.1:9000/kt-template-online/uploads/demo.png',
|
||||
})
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
file: {
|
||||
type: 'string',
|
||||
format: 'binary',
|
||||
},
|
||||
bucketName: {
|
||||
type: 'string',
|
||||
},
|
||||
objectName: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['file'],
|
||||
},
|
||||
})
|
||||
async upload(
|
||||
@Res() res,
|
||||
@UploadedFile() file: MinioUploadFile,
|
||||
@Body('bucketName') bucketName?: string,
|
||||
@Body('objectName') objectName?: string,
|
||||
) {
|
||||
const result = await this.minioClientService.uploadObject({
|
||||
bucketName,
|
||||
objectName,
|
||||
file,
|
||||
});
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', result));
|
||||
}
|
||||
|
||||
@Get('list')
|
||||
@ApiOperation({ summary: '获取MinIO文件列表' })
|
||||
@ApiQuery({ name: 'bucketName', required: false })
|
||||
@ApiQuery({ name: 'prefix', required: false })
|
||||
@ApiQuery({ name: 'recursive', required: false })
|
||||
@ApiArrayResponse(MinioObjectDto, [
|
||||
{
|
||||
name: 'uploads/demo.png',
|
||||
size: 2048,
|
||||
etag: '9b2cf535f27731c974343645a3985328',
|
||||
lastModified: '2026-05-13T02:30:00.000Z',
|
||||
},
|
||||
])
|
||||
async list(
|
||||
@Res() res,
|
||||
@Query('bucketName') bucketName?: string,
|
||||
@Query('prefix') prefix?: string,
|
||||
@Query('recursive') recursive?: string,
|
||||
) {
|
||||
const result = await this.minioClientService.listObjects({
|
||||
bucketName,
|
||||
prefix,
|
||||
recursive: recursive !== 'false',
|
||||
});
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', result));
|
||||
}
|
||||
|
||||
@Get('url')
|
||||
@ApiOperation({ summary: '获取文件临时访问地址' })
|
||||
@ApiQuery({ name: 'objectName' })
|
||||
@ApiQuery({ name: 'bucketName', required: false })
|
||||
@ApiQuery({ name: 'expiry', required: false })
|
||||
@ApiSuccessResponse({
|
||||
schema: {
|
||||
type: 'string',
|
||||
description: '文件临时访问地址',
|
||||
},
|
||||
example:
|
||||
'http://127.0.0.1:9000/kt-template-online/uploads/demo.png?X-Amz-Algorithm=AWS4-HMAC-SHA256',
|
||||
})
|
||||
async getUrl(
|
||||
@Res() res,
|
||||
@Query('objectName') objectName: string,
|
||||
@Query('bucketName') bucketName?: string,
|
||||
@Query('expiry') expiry?: string,
|
||||
) {
|
||||
const result = await this.minioClientService.getPresignedUrl(
|
||||
objectName,
|
||||
bucketName,
|
||||
expiry ? Number(expiry) : undefined,
|
||||
);
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', result));
|
||||
}
|
||||
|
||||
@Get('download')
|
||||
@ApiOperation({ summary: '下载MinIO文件' })
|
||||
@ApiQuery({ name: 'objectName' })
|
||||
@ApiQuery({ name: 'bucketName', required: false })
|
||||
@ApiFileDownloadResponse()
|
||||
async download(
|
||||
@Res() res: Response,
|
||||
@Query('objectName') objectName: string,
|
||||
@Query('bucketName') bucketName?: string,
|
||||
) {
|
||||
const { stream, stat } = await this.minioClientService.getObject(
|
||||
objectName,
|
||||
bucketName,
|
||||
);
|
||||
|
||||
res.setHeader(
|
||||
'Content-Type',
|
||||
stat.metaData?.['content-type'] || 'application/octet-stream',
|
||||
);
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="${encodeURIComponent(
|
||||
objectName.split('/').pop(),
|
||||
)}"`,
|
||||
);
|
||||
|
||||
stream.pipe(res);
|
||||
}
|
||||
|
||||
@Delete('remove')
|
||||
@ApiOperation({ summary: '删除MinIO文件' })
|
||||
@ApiQuery({ name: 'objectName' })
|
||||
@ApiQuery({ name: 'bucketName', required: false })
|
||||
@ApiSuccessResponse({
|
||||
schema: {
|
||||
type: 'boolean',
|
||||
},
|
||||
example: true,
|
||||
})
|
||||
async remove(
|
||||
@Res() res,
|
||||
@Query('objectName') objectName: string,
|
||||
@Query('bucketName') bucketName?: string,
|
||||
) {
|
||||
const result = await this.minioClientService.removeObject(
|
||||
objectName,
|
||||
bucketName,
|
||||
);
|
||||
|
||||
res.send(this.toolsService.res(HttpStatus.OK, '操作成功', result));
|
||||
}
|
||||
}
|
||||
|
||||
67
src/minio/minio.dto.ts
Normal file
67
src/minio/minio.dto.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class MinioBucketStatusDto {
|
||||
@ApiProperty({
|
||||
example: 'kt-template-online',
|
||||
})
|
||||
bucketName: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
})
|
||||
exists: boolean;
|
||||
}
|
||||
|
||||
export class MinioUploadResultDto {
|
||||
@ApiProperty({
|
||||
example: 'kt-template-online',
|
||||
})
|
||||
bucketName: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'uploads/1715580000000-a1b2c3-demo.png',
|
||||
})
|
||||
objectName: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '9b2cf535f27731c974343645a3985328',
|
||||
})
|
||||
etag: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 2048,
|
||||
})
|
||||
size: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'image/png',
|
||||
})
|
||||
mimeType: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'http://127.0.0.1:9000/kt-template-online/uploads/demo.png',
|
||||
})
|
||||
url: string;
|
||||
}
|
||||
|
||||
export class MinioObjectDto {
|
||||
@ApiProperty({
|
||||
example: 'uploads/demo.png',
|
||||
})
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 2048,
|
||||
})
|
||||
size: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: '9b2cf535f27731c974343645a3985328',
|
||||
})
|
||||
etag: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2026-05-13T02:30:00.000Z',
|
||||
})
|
||||
lastModified: string;
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { MinioClientController } from './minio.controller';
|
||||
import { MinioClientService } from './minio.service';
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
import { MinioModule } from 'nestjs-minio-client';
|
||||
import { ToolsService } from '@/utils/tool.service';
|
||||
|
||||
@Module({
|
||||
imports: [MinioModule],
|
||||
imports: [ConfigModule],
|
||||
controllers: [MinioClientController],
|
||||
providers: [MinioClientService, ToolsService],
|
||||
exports: [MinioClientService],
|
||||
|
||||
@ -1,11 +1,179 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { MinioService } from 'nestjs-minio-client';
|
||||
import type { Readable } from 'stream';
|
||||
|
||||
export type MinioUploadFile = {
|
||||
originalname: string;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
buffer: Buffer;
|
||||
};
|
||||
|
||||
type UploadObjectOptions = {
|
||||
bucketName?: string;
|
||||
objectName?: string;
|
||||
file: MinioUploadFile;
|
||||
};
|
||||
|
||||
type ListObjectOptions = {
|
||||
bucketName?: string;
|
||||
prefix?: string;
|
||||
recursive?: boolean;
|
||||
};
|
||||
|
||||
type MinioObjectResult = {
|
||||
stream: Readable;
|
||||
stat: {
|
||||
size: number;
|
||||
etag: string;
|
||||
lastModified: Date;
|
||||
metaData: Record<string, any>;
|
||||
versionId?: string | null;
|
||||
};
|
||||
bucketName: string;
|
||||
objectName: string;
|
||||
};
|
||||
|
||||
import { ToolsService } from 'src/utils/tool.service';
|
||||
@Injectable()
|
||||
export class MinioClientService {
|
||||
constructor(
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly minioService: MinioService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
private get client() {
|
||||
return this.minioService.client;
|
||||
}
|
||||
|
||||
getDefaultBucket(): string {
|
||||
return this.configService.get('MINIO_BUCKET') || 'kt-template-online';
|
||||
}
|
||||
|
||||
getBucketName(bucketName?: string): string {
|
||||
return bucketName || this.getDefaultBucket();
|
||||
}
|
||||
|
||||
async checkConnection(bucketName?: string) {
|
||||
const targetBucket = this.getBucketName(bucketName);
|
||||
const exists = await this.client.bucketExists(targetBucket);
|
||||
|
||||
return {
|
||||
bucketName: targetBucket,
|
||||
exists,
|
||||
};
|
||||
}
|
||||
|
||||
async ensureBucket(bucketName?: string): Promise<string> {
|
||||
const targetBucket = this.getBucketName(bucketName);
|
||||
const exists = await this.client.bucketExists(targetBucket);
|
||||
|
||||
if (!exists) {
|
||||
await this.client.makeBucket(targetBucket, 'us-east-1');
|
||||
}
|
||||
|
||||
return targetBucket;
|
||||
}
|
||||
|
||||
async uploadObject({ bucketName, objectName, file }: UploadObjectOptions) {
|
||||
if (!file) {
|
||||
throw new BadRequestException('请选择要上传的文件');
|
||||
}
|
||||
|
||||
const targetBucket = await this.ensureBucket(bucketName);
|
||||
const targetObjectName = objectName || this.createObjectName(file.originalname);
|
||||
|
||||
const result = await this.client.putObject(
|
||||
targetBucket,
|
||||
targetObjectName,
|
||||
file.buffer,
|
||||
file.size,
|
||||
{
|
||||
'Content-Type': file.mimetype,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
bucketName: targetBucket,
|
||||
objectName: targetObjectName,
|
||||
etag: result.etag,
|
||||
size: file.size,
|
||||
mimeType: file.mimetype,
|
||||
url: await this.getPresignedUrl(targetObjectName, targetBucket),
|
||||
};
|
||||
}
|
||||
|
||||
async listObjects({
|
||||
bucketName,
|
||||
prefix = '',
|
||||
recursive = true,
|
||||
}: ListObjectOptions) {
|
||||
const targetBucket = this.getBucketName(bucketName);
|
||||
const exists = await this.client.bucketExists(targetBucket);
|
||||
|
||||
if (!exists) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const objects = [];
|
||||
const stream = this.client.listObjectsV2(targetBucket, prefix, recursive);
|
||||
|
||||
stream.on('data', (object) => objects.push(object));
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => resolve(objects));
|
||||
});
|
||||
}
|
||||
|
||||
async getObject(
|
||||
objectName: string,
|
||||
bucketName?: string,
|
||||
): Promise<MinioObjectResult> {
|
||||
if (!objectName) {
|
||||
throw new BadRequestException('objectName不能为空');
|
||||
}
|
||||
|
||||
const targetBucket = this.getBucketName(bucketName);
|
||||
const objectStat = await this.client.statObject(targetBucket, objectName);
|
||||
const stream = await this.client.getObject(targetBucket, objectName);
|
||||
|
||||
return {
|
||||
stream,
|
||||
stat: objectStat,
|
||||
bucketName: targetBucket,
|
||||
objectName,
|
||||
};
|
||||
}
|
||||
|
||||
async getPresignedUrl(
|
||||
objectName: string,
|
||||
bucketName?: string,
|
||||
expiry = 24 * 60 * 60,
|
||||
): Promise<string> {
|
||||
if (!objectName) {
|
||||
throw new BadRequestException('objectName不能为空');
|
||||
}
|
||||
|
||||
return this.client.presignedGetObject(
|
||||
this.getBucketName(bucketName),
|
||||
objectName,
|
||||
expiry,
|
||||
);
|
||||
}
|
||||
|
||||
async removeObject(objectName: string, bucketName?: string): Promise<boolean> {
|
||||
if (!objectName) {
|
||||
throw new BadRequestException('objectName不能为空');
|
||||
}
|
||||
|
||||
await this.client.removeObject(this.getBucketName(bucketName), objectName);
|
||||
return true;
|
||||
}
|
||||
|
||||
private createObjectName(originalName: string): string {
|
||||
const safeName = originalName.replace(/[\\/]/g, '_');
|
||||
const random = Math.random().toString(36).slice(2, 8);
|
||||
|
||||
return `uploads/${Date.now()}-${random}-${safeName}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,3 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class PaginatedDto {
|
||||
@ApiProperty()
|
||||
total: number;
|
||||
}
|
||||
|
||||
export enum ComponentTypeEnum {
|
||||
CHART = 1,
|
||||
COMPONENT = 2,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user