@svton/cli 1.2.2 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6 -13
- package/dist/index.mjs +6 -13
- package/package.json +1 -3
- package/templates/apps/admin/next-env.d.ts +0 -2
- package/templates/apps/admin/next.config.js +0 -15
- package/templates/apps/admin/package.json.tpl +0 -54
- package/templates/apps/admin/postcss.config.js +0 -6
- package/templates/apps/admin/src/app/globals.css +0 -37
- package/templates/apps/admin/src/app/layout.tsx +0 -19
- package/templates/apps/admin/src/app/login/page.tsx +0 -96
- package/templates/apps/admin/src/app/page.tsx +0 -8
- package/templates/apps/admin/src/app/users/page.tsx +0 -165
- package/templates/apps/admin/src/components/ui/switch.tsx +0 -29
- package/templates/apps/admin/src/hooks/useAPI.ts +0 -130
- package/templates/apps/admin/src/lib/api-client.ts +0 -112
- package/templates/apps/admin/src/lib/api-server.ts +0 -95
- package/templates/apps/admin/tailwind.config.js +0 -54
- package/templates/apps/admin/tsconfig.json +0 -22
- package/templates/apps/backend/.env.example +0 -29
- package/templates/apps/backend/nest-cli.json +0 -8
- package/templates/apps/backend/package.json.tpl +0 -57
- package/templates/apps/backend/prisma/schema.prisma +0 -72
- package/templates/apps/backend/prisma/seed.ts +0 -32
- package/templates/apps/backend/src/app.controller.ts +0 -15
- package/templates/apps/backend/src/app.module.ts +0 -85
- package/templates/apps/backend/src/app.service.ts +0 -12
- package/templates/apps/backend/src/auth/auth.controller.ts +0 -31
- package/templates/apps/backend/src/auth/auth.module.ts +0 -27
- package/templates/apps/backend/src/auth/auth.service.ts +0 -89
- package/templates/apps/backend/src/auth/jwt-auth.guard.ts +0 -5
- package/templates/apps/backend/src/auth/jwt.strategy.ts +0 -27
- package/templates/apps/backend/src/config/env.schema.ts +0 -35
- package/templates/apps/backend/src/main.ts +0 -51
- package/templates/apps/backend/src/object-storage/object-storage.controller.ts +0 -114
- package/templates/apps/backend/src/object-storage/object-storage.module.ts +0 -7
- package/templates/apps/backend/src/prisma/prisma.module.ts +0 -9
- package/templates/apps/backend/src/prisma/prisma.service.ts +0 -13
- package/templates/apps/backend/src/user/user.controller.ts +0 -50
- package/templates/apps/backend/src/user/user.module.ts +0 -12
- package/templates/apps/backend/src/user/user.service.ts +0 -117
- package/templates/apps/backend/tsconfig.json +0 -23
- package/templates/apps/mobile/babel.config.js +0 -8
- package/templates/apps/mobile/config/index.ts +0 -65
- package/templates/apps/mobile/package.json.tpl +0 -48
- package/templates/apps/mobile/project.config.json.tpl +0 -17
- package/templates/apps/mobile/src/app.config.ts +0 -9
- package/templates/apps/mobile/src/app.scss +0 -4
- package/templates/apps/mobile/src/app.ts +0 -8
- package/templates/apps/mobile/src/hooks/useAPI.ts +0 -285
- package/templates/apps/mobile/src/pages/index/index.scss +0 -7
- package/templates/apps/mobile/src/pages/index/index.tsx +0 -49
- package/templates/apps/mobile/src/services/api.ts +0 -155
- package/templates/apps/mobile/src/services/upload.service.ts +0 -41
- package/templates/apps/mobile/tsconfig.json +0 -21
- package/templates/configs/authz.config.ts +0 -10
- package/templates/configs/cache.config.ts +0 -14
- package/templates/configs/oauth.config.ts +0 -20
- package/templates/configs/payment.config.ts +0 -44
- package/templates/configs/queue.config.ts +0 -21
- package/templates/configs/rate-limit.config.ts +0 -16
- package/templates/configs/sms.config.ts +0 -11
- package/templates/configs/storage.config.ts +0 -14
- package/templates/examples/README.md +0 -258
- package/templates/examples/authz/README.md +0 -273
- package/templates/examples/authz/roles.guard.ts +0 -37
- package/templates/examples/authz/user.controller.ts +0 -116
- package/templates/examples/cache/README.md +0 -82
- package/templates/examples/cache/user.controller.ts +0 -42
- package/templates/examples/cache/user.service.ts +0 -78
- package/templates/examples/oauth/README.md +0 -192
- package/templates/examples/oauth/auth.controller.ts +0 -99
- package/templates/examples/oauth/auth.service.ts +0 -97
- package/templates/examples/payment/README.md +0 -151
- package/templates/examples/payment/order.controller.ts +0 -56
- package/templates/examples/payment/order.service.ts +0 -132
- package/templates/examples/payment/webhook.controller.ts +0 -73
- package/templates/examples/queue/README.md +0 -134
- package/templates/examples/queue/email.controller.ts +0 -34
- package/templates/examples/queue/email.processor.ts +0 -68
- package/templates/examples/queue/email.service.ts +0 -64
- package/templates/examples/rate-limit/README.md +0 -249
- package/templates/examples/rate-limit/api.controller.ts +0 -113
- package/templates/examples/sms/README.md +0 -121
- package/templates/examples/sms/sms.service.ts +0 -69
- package/templates/examples/sms/verification.controller.ts +0 -100
- package/templates/examples/storage/README.md +0 -224
- package/templates/examples/storage/upload.controller.ts +0 -117
- package/templates/examples/storage/upload.service.ts +0 -123
- package/templates/packages/types/package.json.tpl +0 -16
- package/templates/packages/types/src/api.ts +0 -88
- package/templates/packages/types/src/common.ts +0 -89
- package/templates/packages/types/src/index.ts +0 -3
- package/templates/packages/types/tsconfig.json +0 -16
- package/templates/skills/authz.skill.md +0 -42
- package/templates/skills/base.skill.md +0 -57
- package/templates/skills/cache.skill.md +0 -88
- package/templates/skills/oauth.skill.md +0 -41
- package/templates/skills/payment.skill.md +0 -129
- package/templates/skills/queue.skill.md +0 -140
- package/templates/skills/rate-limit.skill.md +0 -38
- package/templates/skills/sms.skill.md +0 -39
- package/templates/skills/storage.skill.md +0 -42
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
# 对象存储示例
|
|
2
|
-
|
|
3
|
-
本示例展示如何使用 `@svton/nestjs-object-storage` 模块实现文件上传。
|
|
4
|
-
|
|
5
|
-
## 文件说明
|
|
6
|
-
|
|
7
|
-
- `upload.service.ts` - 上传服务,封装存储操作
|
|
8
|
-
- `upload.controller.ts` - 上传控制器,提供上传接口
|
|
9
|
-
|
|
10
|
-
## 核心功能
|
|
11
|
-
|
|
12
|
-
### 服务端上传
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
const result = await this.uploadService.uploadFile(file);
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### 客户端直传
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
// 1. 获取上传凭证
|
|
22
|
-
const token = await this.uploadService.getUploadToken();
|
|
23
|
-
|
|
24
|
-
// 2. 客户端使用凭证直接上传到七牛云
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 删除文件
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
await this.uploadService.deleteFile(key);
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### 获取私有文件 URL
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
const url = await this.uploadService.getPrivateUrl(key, 3600);
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## 测试接口
|
|
40
|
-
|
|
41
|
-
### 获取上传凭证
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
curl http://localhost:3000/examples/upload/token
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### 服务端上传文件
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
curl -X POST http://localhost:3000/examples/upload/file \
|
|
51
|
-
-F "file=@/path/to/file.jpg"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### 上传图片
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
curl -X POST http://localhost:3000/examples/upload/image \
|
|
58
|
-
-F "file=@/path/to/image.jpg"
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 删除文件
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
curl -X DELETE http://localhost:3000/examples/upload/uploads/1234567890-abc.jpg
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 获取文件信息
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
curl http://localhost:3000/examples/upload/info/uploads/1234567890-abc.jpg
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### 获取私有文件 URL
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
curl "http://localhost:3000/examples/upload/private-url?key=uploads/1234567890-abc.jpg&expires=3600"
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## 环境变量配置
|
|
80
|
-
|
|
81
|
-
在 `.env` 文件中配置:
|
|
82
|
-
|
|
83
|
-
```env
|
|
84
|
-
STORAGE_PROVIDER=qiniu
|
|
85
|
-
QINIU_ACCESS_KEY=your_access_key
|
|
86
|
-
QINIU_SECRET_KEY=your_secret_key
|
|
87
|
-
QINIU_BUCKET=your_bucket_name
|
|
88
|
-
QINIU_DOMAIN=https://cdn.example.com
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## 客户端直传示例
|
|
92
|
-
|
|
93
|
-
### Web 端(使用 qiniu-js)
|
|
94
|
-
|
|
95
|
-
```javascript
|
|
96
|
-
// 1. 获取上传凭证
|
|
97
|
-
const { token, uploadUrl, domain } = await fetch('/examples/upload/token').then(r => r.json());
|
|
98
|
-
|
|
99
|
-
// 2. 使用 qiniu-js 上传
|
|
100
|
-
import * as qiniu from 'qiniu-js';
|
|
101
|
-
|
|
102
|
-
const observable = qiniu.upload(file, key, token, {}, {
|
|
103
|
-
useCdnDomain: true,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
observable.subscribe({
|
|
107
|
-
next: (result) => {
|
|
108
|
-
console.log('Progress:', result.total.percent);
|
|
109
|
-
},
|
|
110
|
-
error: (err) => {
|
|
111
|
-
console.error('Upload failed:', err);
|
|
112
|
-
},
|
|
113
|
-
complete: (result) => {
|
|
114
|
-
console.log('Upload complete:', result);
|
|
115
|
-
const fileUrl = `${domain}/${result.key}`;
|
|
116
|
-
},
|
|
117
|
-
});
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### 小程序端
|
|
121
|
-
|
|
122
|
-
```javascript
|
|
123
|
-
// 1. 获取上传凭证
|
|
124
|
-
const { token, uploadUrl } = await wx.request({
|
|
125
|
-
url: 'https://api.example.com/examples/upload/token',
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// 2. 使用 wx.uploadFile 上传
|
|
129
|
-
wx.chooseImage({
|
|
130
|
-
success: (res) => {
|
|
131
|
-
wx.uploadFile({
|
|
132
|
-
url: uploadUrl,
|
|
133
|
-
filePath: res.tempFilePaths[0],
|
|
134
|
-
name: 'file',
|
|
135
|
-
formData: {
|
|
136
|
-
token: token,
|
|
137
|
-
key: `uploads/${Date.now()}.jpg`,
|
|
138
|
-
},
|
|
139
|
-
success: (uploadRes) => {
|
|
140
|
-
console.log('Upload success:', uploadRes);
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## 图片处理
|
|
148
|
-
|
|
149
|
-
七牛云支持强大的图片处理功能:
|
|
150
|
-
|
|
151
|
-
### 缩略图
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
https://cdn.example.com/image.jpg?imageView2/1/w/200/h/200
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### 裁剪
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
https://cdn.example.com/image.jpg?imageMogr2/crop/!300x300a0a0
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 水印
|
|
164
|
-
|
|
165
|
-
```
|
|
166
|
-
https://cdn.example.com/image.jpg?watermark/2/text/SGVsbG8gV29ybGQ=
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
### 格式转换
|
|
170
|
-
|
|
171
|
-
```
|
|
172
|
-
https://cdn.example.com/image.jpg?imageMogr2/format/webp
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## 最佳实践
|
|
176
|
-
|
|
177
|
-
1. **文件命名**:使用时间戳 + 随机字符串,避免重名
|
|
178
|
-
2. **目录结构**:按类型或日期分目录存储
|
|
179
|
-
3. **客户端直传**:大文件使用客户端直传,减轻服务器压力
|
|
180
|
-
4. **CDN 加速**:配置 CDN 域名,加速文件访问
|
|
181
|
-
5. **安全性**:私有文件使用签名 URL,设置合理的过期时间
|
|
182
|
-
6. **文件验证**:上传前验证文件类型和大小
|
|
183
|
-
7. **错误处理**:妥善处理上传失败、网络异常等情况
|
|
184
|
-
|
|
185
|
-
## 常见场景
|
|
186
|
-
|
|
187
|
-
### 用户头像上传
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
// 1. 上传图片
|
|
191
|
-
const result = await this.uploadService.uploadImage(file);
|
|
192
|
-
|
|
193
|
-
// 2. 更新用户头像
|
|
194
|
-
await this.userService.updateAvatar(userId, result.url);
|
|
195
|
-
|
|
196
|
-
// 3. 删除旧头像
|
|
197
|
-
if (oldAvatarKey) {
|
|
198
|
-
await this.uploadService.deleteFile(oldAvatarKey);
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### 文章图片上传
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
// 富文本编辑器上传图片
|
|
206
|
-
const result = await this.uploadService.uploadImage(file);
|
|
207
|
-
|
|
208
|
-
// 返回图片 URL 给编辑器
|
|
209
|
-
return { url: result.url };
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### 文件下载
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
// 生成临时下载链接
|
|
216
|
-
const url = await this.uploadService.getPrivateUrl(key, 3600);
|
|
217
|
-
|
|
218
|
-
// 重定向到下载链接
|
|
219
|
-
return { url };
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## 更多信息
|
|
223
|
-
|
|
224
|
-
查看官方文档:https://751848178.github.io/svton/packages/nestjs-object-storage
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Controller,
|
|
3
|
-
Post,
|
|
4
|
-
Get,
|
|
5
|
-
Delete,
|
|
6
|
-
UseInterceptors,
|
|
7
|
-
UploadedFile,
|
|
8
|
-
Body,
|
|
9
|
-
Query,
|
|
10
|
-
Param,
|
|
11
|
-
} from '@nestjs/common';
|
|
12
|
-
import { FileInterceptor } from '@nestjs/platform-express';
|
|
13
|
-
import { UploadService } from './upload.service';
|
|
14
|
-
|
|
15
|
-
@Controller('examples/upload')
|
|
16
|
-
export class UploadController {
|
|
17
|
-
constructor(private readonly uploadService: UploadService) {}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 获取上传凭证(客户端直传)
|
|
21
|
-
*/
|
|
22
|
-
@Get('token')
|
|
23
|
-
async getUploadToken(@Query('key') key?: string) {
|
|
24
|
-
const token = await this.uploadService.getUploadToken(key);
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
token,
|
|
28
|
-
uploadUrl: 'https://upload.qiniup.com', // 七牛云上传地址
|
|
29
|
-
domain: process.env.QINIU_DOMAIN,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 服务端上传文件
|
|
35
|
-
*/
|
|
36
|
-
@Post('file')
|
|
37
|
-
@UseInterceptors(FileInterceptor('file'))
|
|
38
|
-
async uploadFile(@UploadedFile() file: Express.Multer.File) {
|
|
39
|
-
const result = await this.uploadService.uploadFile(file);
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
message: 'File uploaded successfully',
|
|
43
|
-
...result,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 上传图片(带压缩)
|
|
49
|
-
*/
|
|
50
|
-
@Post('image')
|
|
51
|
-
@UseInterceptors(FileInterceptor('file'))
|
|
52
|
-
async uploadImage(@UploadedFile() file: Express.Multer.File) {
|
|
53
|
-
const result = await this.uploadService.uploadImage(file);
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
message: 'Image uploaded successfully',
|
|
57
|
-
...result,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 批量上传
|
|
63
|
-
*/
|
|
64
|
-
@Post('batch')
|
|
65
|
-
@UseInterceptors(FileInterceptor('files'))
|
|
66
|
-
async uploadBatch(@UploadedFile() files: Express.Multer.File[]) {
|
|
67
|
-
const results = await this.uploadService.uploadBatch(files);
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
message: 'Files uploaded successfully',
|
|
71
|
-
files: results,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* 删除文件
|
|
77
|
-
*/
|
|
78
|
-
@Delete(':key')
|
|
79
|
-
async deleteFile(@Param('key') key: string) {
|
|
80
|
-
await this.uploadService.deleteFile(key);
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
message: 'File deleted successfully',
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 获取文件信息
|
|
89
|
-
*/
|
|
90
|
-
@Get('info/:key')
|
|
91
|
-
async getFileInfo(@Param('key') key: string) {
|
|
92
|
-
const info = await this.uploadService.getFileInfo(key);
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
...info,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 获取私有文件访问 URL
|
|
101
|
-
*/
|
|
102
|
-
@Get('private-url')
|
|
103
|
-
async getPrivateUrl(
|
|
104
|
-
@Query('key') key: string,
|
|
105
|
-
@Query('expires') expires?: string,
|
|
106
|
-
) {
|
|
107
|
-
const url = await this.uploadService.getPrivateUrl(
|
|
108
|
-
key,
|
|
109
|
-
expires ? parseInt(expires) : 3600,
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
url,
|
|
114
|
-
expiresIn: expires || 3600,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
2
|
-
import { ObjectStorageService } from '@svton/nestjs-object-storage';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as crypto from 'crypto';
|
|
5
|
-
|
|
6
|
-
@Injectable()
|
|
7
|
-
export class UploadService {
|
|
8
|
-
constructor(private readonly storageService: ObjectStorageService) {}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 获取上传凭证(用于客户端直传)
|
|
12
|
-
*/
|
|
13
|
-
async getUploadToken(key?: string): Promise<string> {
|
|
14
|
-
return this.storageService.getUploadToken(key);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 上传文件
|
|
19
|
-
*/
|
|
20
|
-
async uploadFile(file: Express.Multer.File) {
|
|
21
|
-
// 生成唯一文件名
|
|
22
|
-
const ext = path.extname(file.originalname);
|
|
23
|
-
const filename = `${Date.now()}-${crypto.randomBytes(8).toString('hex')}${ext}`;
|
|
24
|
-
const key = `uploads/${filename}`;
|
|
25
|
-
|
|
26
|
-
// 上传文件
|
|
27
|
-
const result = await this.storageService.upload(file.buffer, key);
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
key,
|
|
31
|
-
url: result.url,
|
|
32
|
-
size: file.size,
|
|
33
|
-
mimeType: file.mimetype,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 上传图片(带压缩)
|
|
39
|
-
*/
|
|
40
|
-
async uploadImage(file: Express.Multer.File) {
|
|
41
|
-
// 验证是否为图片
|
|
42
|
-
if (!file.mimetype.startsWith('image/')) {
|
|
43
|
-
throw new Error('File must be an image');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const ext = path.extname(file.originalname);
|
|
47
|
-
const filename = `${Date.now()}-${crypto.randomBytes(8).toString('hex')}${ext}`;
|
|
48
|
-
const key = `images/${filename}`;
|
|
49
|
-
|
|
50
|
-
// 上传原图
|
|
51
|
-
const result = await this.storageService.upload(file.buffer, key);
|
|
52
|
-
|
|
53
|
-
// 生成缩略图 URL(七牛云图片处理)
|
|
54
|
-
const thumbnailUrl = `${result.url}?imageView2/1/w/200/h/200`;
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
key,
|
|
58
|
-
url: result.url,
|
|
59
|
-
thumbnailUrl,
|
|
60
|
-
size: file.size,
|
|
61
|
-
mimeType: file.mimetype,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 批量上传
|
|
67
|
-
*/
|
|
68
|
-
async uploadBatch(files: Express.Multer.File[]) {
|
|
69
|
-
const results = await Promise.all(
|
|
70
|
-
files.map((file) => this.uploadFile(file)),
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return results;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* 删除文件
|
|
78
|
-
*/
|
|
79
|
-
async deleteFile(key: string): Promise<void> {
|
|
80
|
-
await this.storageService.delete(key);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 获取文件信息
|
|
85
|
-
*/
|
|
86
|
-
async getFileInfo(key: string) {
|
|
87
|
-
const info = await this.storageService.getFileInfo(key);
|
|
88
|
-
|
|
89
|
-
// 兼容不同存储提供商的返回格式
|
|
90
|
-
return {
|
|
91
|
-
key,
|
|
92
|
-
size: info.fsize || info.size || 0,
|
|
93
|
-
mimeType: info.mimeType || info.type || 'application/octet-stream',
|
|
94
|
-
hash: info.hash || info.etag || '',
|
|
95
|
-
putTime: info.putTime
|
|
96
|
-
? new Date(info.putTime / 10000) // 七牛云格式
|
|
97
|
-
: info.lastModified
|
|
98
|
-
? new Date(info.lastModified) // 其他格式
|
|
99
|
-
: new Date(),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 获取私有文件访问 URL
|
|
105
|
-
*/
|
|
106
|
-
async getPrivateUrl(key: string, expires: number = 3600): Promise<string> {
|
|
107
|
-
return this.storageService.getPrivateUrl(key, expires);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 移动文件
|
|
112
|
-
*/
|
|
113
|
-
async moveFile(sourceKey: string, destKey: string): Promise<void> {
|
|
114
|
-
await this.storageService.move(sourceKey, destKey);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* 复制文件
|
|
119
|
-
*/
|
|
120
|
-
async copyFile(sourceKey: string, destKey: string): Promise<void> {
|
|
121
|
-
await this.storageService.copy(sourceKey, destKey);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{ORG_NAME}}/types",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "{{PROJECT_NAME}} 共享类型定义",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"types": "./dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"dev": "tsc --watch",
|
|
10
|
-
"clean": "rm -rf dist",
|
|
11
|
-
"type-check": "tsc --noEmit"
|
|
12
|
-
},
|
|
13
|
-
"devDependencies": {
|
|
14
|
-
"typescript": "^5.3.0"
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// API 请求/响应类型定义
|
|
3
|
-
// ============================================================
|
|
4
|
-
|
|
5
|
-
import type { UserVo, UserRole, PaginationParams, ContentStatus } from './common';
|
|
6
|
-
|
|
7
|
-
// ============================================================
|
|
8
|
-
// 认证相关
|
|
9
|
-
// ============================================================
|
|
10
|
-
|
|
11
|
-
// 登录请求
|
|
12
|
-
export interface LoginDto {
|
|
13
|
-
phone: string;
|
|
14
|
-
password: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// 登录响应
|
|
18
|
-
export interface LoginVo {
|
|
19
|
-
accessToken: string;
|
|
20
|
-
refreshToken: string;
|
|
21
|
-
user: UserVo;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// 注册请求
|
|
25
|
-
export interface RegisterDto {
|
|
26
|
-
phone: string;
|
|
27
|
-
password: string;
|
|
28
|
-
nickname: string;
|
|
29
|
-
code?: string; // 验证码
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// 刷新 Token
|
|
33
|
-
export interface RefreshTokenDto {
|
|
34
|
-
refreshToken: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ============================================================
|
|
38
|
-
// 用户管理
|
|
39
|
-
// ============================================================
|
|
40
|
-
|
|
41
|
-
// 用户列表查询
|
|
42
|
-
export interface UserListParams extends PaginationParams {
|
|
43
|
-
keyword?: string;
|
|
44
|
-
role?: UserRole;
|
|
45
|
-
status?: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// 创建用户
|
|
49
|
-
export interface CreateUserDto {
|
|
50
|
-
phone: string;
|
|
51
|
-
password: string;
|
|
52
|
-
nickname: string;
|
|
53
|
-
role?: UserRole;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 更新用户
|
|
57
|
-
export interface UpdateUserDto {
|
|
58
|
-
nickname?: string;
|
|
59
|
-
avatar?: string;
|
|
60
|
-
role?: UserRole;
|
|
61
|
-
status?: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ============================================================
|
|
65
|
-
// 内容管理
|
|
66
|
-
// ============================================================
|
|
67
|
-
|
|
68
|
-
// 内容列表查询
|
|
69
|
-
export interface ContentListParams extends PaginationParams {
|
|
70
|
-
keyword?: string;
|
|
71
|
-
status?: ContentStatus;
|
|
72
|
-
authorId?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 创建内容
|
|
76
|
-
export interface CreateContentDto {
|
|
77
|
-
title: string;
|
|
78
|
-
content: string;
|
|
79
|
-
images?: string[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 更新内容
|
|
83
|
-
export interface UpdateContentDto {
|
|
84
|
-
title?: string;
|
|
85
|
-
content?: string;
|
|
86
|
-
images?: string[];
|
|
87
|
-
status?: ContentStatus;
|
|
88
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// 通用类型定义
|
|
3
|
-
// ============================================================
|
|
4
|
-
|
|
5
|
-
// 分页请求参数
|
|
6
|
-
export interface PaginationParams {
|
|
7
|
-
page?: number;
|
|
8
|
-
pageSize?: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// 分页响应
|
|
12
|
-
export interface PaginatedResponse<T> {
|
|
13
|
-
list: T[];
|
|
14
|
-
total: number;
|
|
15
|
-
page: number;
|
|
16
|
-
pageSize: number;
|
|
17
|
-
totalPages: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// 通用 API 响应
|
|
21
|
-
export interface ApiResponse<T = unknown> {
|
|
22
|
-
code: number;
|
|
23
|
-
message: string;
|
|
24
|
-
data: T;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ============================================================
|
|
28
|
-
// 用户相关类型
|
|
29
|
-
// ============================================================
|
|
30
|
-
|
|
31
|
-
// 用户角色
|
|
32
|
-
export type UserRole = 'user' | 'admin' | 'super_admin';
|
|
33
|
-
|
|
34
|
-
// 用户状态
|
|
35
|
-
export type UserStatus = 0 | 1; // 0: 禁用, 1: 启用
|
|
36
|
-
|
|
37
|
-
// 用户基础信息
|
|
38
|
-
export interface UserVo {
|
|
39
|
-
id: number;
|
|
40
|
-
phone: string;
|
|
41
|
-
nickname: string;
|
|
42
|
-
avatar?: string;
|
|
43
|
-
role: UserRole;
|
|
44
|
-
status: UserStatus;
|
|
45
|
-
createdAt: string;
|
|
46
|
-
updatedAt: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 用户详情(包含更多字段)
|
|
50
|
-
export interface UserDetailVo extends UserVo {
|
|
51
|
-
email?: string;
|
|
52
|
-
bio?: string;
|
|
53
|
-
lastLoginAt?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ============================================================
|
|
57
|
-
// 内容相关类型
|
|
58
|
-
// ============================================================
|
|
59
|
-
|
|
60
|
-
// 内容状态
|
|
61
|
-
export type ContentStatus = 'draft' | 'pending' | 'published' | 'rejected';
|
|
62
|
-
|
|
63
|
-
// 内容基础信息
|
|
64
|
-
export interface ContentVo {
|
|
65
|
-
id: number;
|
|
66
|
-
title: string;
|
|
67
|
-
content: string;
|
|
68
|
-
images: string[];
|
|
69
|
-
status: ContentStatus;
|
|
70
|
-
viewCount: number;
|
|
71
|
-
likeCount: number;
|
|
72
|
-
commentCount: number;
|
|
73
|
-
author: UserVo;
|
|
74
|
-
createdAt: string;
|
|
75
|
-
updatedAt: string;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ============================================================
|
|
79
|
-
// 通用工具类型
|
|
80
|
-
// ============================================================
|
|
81
|
-
|
|
82
|
-
// 可选的 ID
|
|
83
|
-
export type WithOptionalId<T> = Omit<T, 'id'> & { id?: number };
|
|
84
|
-
|
|
85
|
-
// 创建 DTO(移除系统字段)
|
|
86
|
-
export type CreateDto<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>;
|
|
87
|
-
|
|
88
|
-
// 更新 DTO
|
|
89
|
-
export type UpdateDto<T> = Partial<CreateDto<T>> & { id: number };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"declarationMap": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"outDir": "./dist",
|
|
12
|
-
"rootDir": "./src"
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"],
|
|
15
|
-
"exclude": ["node_modules", "dist"]
|
|
16
|
-
}
|