yuanjiajun commited on
Commit
ca63dd4
·
1 Parent(s): 96a676d

feat: 文件服务

Browse files
.eslintignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ node_modules/
2
+ build/
3
+ .eslintrc.js
.eslintrc.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ env: {
5
+ node: true,
6
+ es6: true,
7
+ },
8
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:import/typescript', 'plugin:prettier/recommended'],
9
+ parserOptions: {
10
+ ecmaVersion: 2018,
11
+ sourceType: 'module',
12
+ ecmaFeatures: {
13
+ jsx: true,
14
+ },
15
+ project: './tsconfig.json',
16
+ tsconfigRootDir: __dirname,
17
+ },
18
+ plugins: ['simple-import-sort', 'filenames', 'folders', 'promise', 'eslint-comments', 'prettier', 'eslint-plugin-import'],
19
+ rules: {
20
+ 'linebreak-style': ['error', 'unix'],
21
+ quotes: ['warn', 'single'],
22
+ // 文件命名规范 使用小写中划线连接
23
+ // 'filenames/match-regex': [2, '^[a-z]+(-[a-z0-9]+)*(.test)?$',
24
+ // true
25
+ // ], // 所有文件中划线小写
26
+ // 文件导出规范
27
+ // 'filenames/match-exported': ['error', ['kebab', 'camel', 'pascal']],
28
+ 'filenames/no-index': 'off',
29
+ 'import/no-dynamic-require': 'off',
30
+ // 开启 prettier
31
+ 'prettier/prettier': 'error',
32
+ // 未使用的变量
33
+ '@typescript-eslint/no-unused-vars': 'error',
34
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
35
+ '@typescript-eslint/explicit-function-return-type': 'off',
36
+ '@typescript-eslint/comma-dangle': 'off',
37
+ // any 禁止使用
38
+ '@typescript-eslint/no-explicit-any': 'off',
39
+ '@typescript-eslint/no-unsafe-call': 'off',
40
+ '@typescript-eslint/no-unsafe-argument': 'off',
41
+ '@typescript-eslint/no-unsafe-return': 'off',
42
+ // '@typescript-eslint/no-unsafe-assignment': 'off',
43
+ // '@typescript-eslint/no-unsafe-declaration-merging': 'warn',
44
+ // '@typescript-eslint/no-unsafe-enum-comparison': 'warn',
45
+ // '@typescript-eslint/no-unsafe-member-access': 'warn',
46
+ // 定义 enmu 和 interface 命名规范
47
+ '@typescript-eslint/naming-convention': [
48
+ 'error',
49
+ {
50
+ selector: 'enumMember',
51
+ format: ['camelCase'],
52
+ },
53
+ {
54
+ selector: 'enum',
55
+ format: ['PascalCase'],
56
+ // prefix: ['ENUM_'],
57
+ },
58
+ // {
59
+ // selector: 'interface',
60
+ // format: ['PascalCase'],
61
+ // prefix: ['I', '1'],
62
+ // },
63
+ ],
64
+ 'max-lines-per-function': 0,
65
+ 'no-console': 'warn',
66
+ 'simple-import-sort/exports': 'warn',
67
+ 'import/no-cycle': 'error',
68
+ // 文件夹命名规范
69
+ // "folders/match-regex": [2, "^[a-z]+(-[a-z0-9]+)*$", "src"],
70
+ 'promise/catch-or-return': [
71
+ 'error',
72
+ {
73
+ allowFinally: true,
74
+ },
75
+ ],
76
+ "promise/prefer-await-to-then": "error",
77
+ complexity: ['error', 20],
78
+ 'max-depth': ['warn', 6],
79
+ 'max-lines': [
80
+ 'warn',
81
+ {
82
+ max: 800,
83
+ skipBlankLines: true,
84
+ skipComments: true,
85
+ },
86
+ ],
87
+ 'max-len': 'off',
88
+ '@typescript-eslint/ban-types': 'warn',
89
+ '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',
90
+ '@typescript-eslint/prefer-nullish-coalescing': 'off',
91
+ '@typescript-eslint/no-misused-promises': [
92
+ 'error',
93
+ {
94
+ checksVoidReturn: false,
95
+ },
96
+ ],
97
+ '@typescript-eslint/return-await': 'off', // 这个暂时关了,autofix 可能会导致问题
98
+ '@typescript-eslint/no-loss-of-precision': 'off',
99
+ 'import/no-cycle': 'warn', // 先关掉,后续解决
100
+ },
101
+ };
102
+
103
+
.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .DS_Store
2
+
3
+ .pnp
4
+ .pnp.js
5
+ .env.local
6
+ .env.*.local
7
+ .history
8
+ .rts*
9
+ *.log*
10
+ *.pid
11
+ *.pid.*
12
+ *.report
13
+ *.lcov
14
+ lib-cov
15
+
16
+ node_modules/
17
+ .npm
18
+ .lock-wscript
19
+ .yarn-integrity
20
+ .node_repl_history
21
+ .nyc_output
22
+ *.tsbuildinfo
23
+ .eslintcache
24
+ .sonarlint
25
+
26
+ dist/
27
+ coverage/
28
+ release/
29
+ output/
30
+ output_resource/
31
+
32
+ .vscode/**/*
33
+ !.vscode/settings.json
34
+ !.vscode/extensions.json
35
+ .idea/
36
+
37
+ **/*/typings/auto-generated
38
+
39
+ .changeset/pre.json
40
+
41
+ .pnpm-store/
.prettierrc ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "printWidth": 150,
3
+ "tabWidth": 2,
4
+ "singleQuote": true
5
+ }
.vscode/extensions.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "recommendations": ["dbaeumer.vscode-eslint"]
3
+ }
.vscode/settings.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "prettier.enable": true,
3
+ "eslint.enable": true,
4
+ "editor.codeActionsOnSave": {
5
+ "source.fixAll.eslint": "explicit"
6
+ },
7
+ "eslint.workingDirectories": [
8
+ {
9
+ "mode": "auto"
10
+ }
11
+ ]
12
+ }
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Node 镜像作为基础镜像
2
+ FROM node:14
3
+
4
+ VOLUME /app/static
5
+
6
+ # 设置工作目录
7
+ WORKDIR /usr/src/app
8
+
9
+ # 创建上传文件夹
10
+ RUN mkdir -p /usr/src/app/uploads && chown -R node:node /usr/src/app/uploads
11
+
12
+ # 复制 package.json 和 package-lock.json
13
+ COPY package*.json ./
14
+
15
+ # 复制项目文件到工作目录
16
+ COPY . .
17
+
18
+ # 安装项目依赖
19
+ RUN npm install
20
+
21
+ RUN npm run build && [ $? -eq 0 ]
22
+
23
+ # 暴露应用运行端口
24
+ EXPOSE 3000
25
+
26
+ # 定义环境变量
27
+ ENV PORT=3000
28
+
29
+ # 启动应用
30
+ CMD ["npm", "start"]
nodemon.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "watch": ["src"],
3
+ "ext": "ts",
4
+ "ignore": ["src/**/*.spec.ts"],
5
+ "exec": "ts-node -r tsconfig-paths/register ./src/app.ts"
6
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "stt",
3
+ "version": "1.0.0",
4
+ "description": "--- title: Stt emoji: 🦀 colorFrom: purple colorTo: pink sdk: docker pinned: false license: mit ---",
5
+ "main": ".eslintrc.js",
6
+ "scripts": {
7
+ "dev": "nodemon",
8
+ "build": "tsc && tsc-alias",
9
+ "start": "node ./dist/app.js",
10
+ "lint": "eslint --ext .ts src --fix"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://huggingface.co/spaces/Joey7938/stt"
15
+ },
16
+ "author": "",
17
+ "license": "ISC",
18
+ "dependencies": {
19
+ "@types/koa": "^2.15.0",
20
+ "@types/koa-static": "^4.0.4",
21
+ "eslint-plugin-prettier": "^5.2.1",
22
+ "koa": "^2.15.3",
23
+ "koa-body": "^6.0.1",
24
+ "koa-bodyparser": "^4.4.1",
25
+ "koa-mount": "^4.0.0",
26
+ "koa-router": "^13.0.1",
27
+ "koa-static": "^5.0.0",
28
+ "nodemon": "^3.1.7"
29
+ },
30
+ "devDependencies": {
31
+ "@eslint/js": "^9.14.0",
32
+ "@types/html-docx-js": "^0.3.4",
33
+ "@types/koa-bodyparser": "^4.3.12",
34
+ "@types/koa-mount": "^4.0.5",
35
+ "@types/koa-router": "^7.4.8",
36
+ "@types/node": "^17.0.45",
37
+ "@typescript-eslint/parser": "^5.62.0",
38
+ "eslint": "^8.57.1",
39
+ "eslint-plugin-import": "^2.31.0",
40
+ "prettier": "^3.3.3",
41
+ "ts-node": "^10.9.2",
42
+ "tsconfig-paths": "^4.2.0",
43
+ "typescript": "^5.6.3",
44
+ "typescript-eslint": "^7.18.0"
45
+ }
46
+ }
src/app.ts ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Koa from 'koa';
2
+ import bodyParser from 'koa-body';
3
+ import mount from 'koa-mount';
4
+ import staticMiddleware from 'koa-static';
5
+ import fileRoutes from './routes/fileRoutes';
6
+ import { errorMiddleware } from './middleware/errorMiddleware';
7
+ import bodyparser from 'koa-bodyparser';
8
+ import path from 'path';
9
+
10
+ const app = new Koa();
11
+
12
+ // 自定义中间件解析二进制数据
13
+ app.use(async (ctx, next) => {
14
+ if (ctx.request.header['content-type'] === 'application/octet-stream') {
15
+ const chunks = [];
16
+ for await (const chunk of ctx.req) {
17
+ chunks.push(chunk);
18
+ }
19
+ ctx.request.body = Buffer.concat(chunks);
20
+ }
21
+ await next();
22
+ });
23
+
24
+
25
+ // 配置 bodyparser
26
+ app.use(bodyparser());
27
+
28
+ // 日志中间件
29
+ app.use(async (ctx, next) => {
30
+ const start = Date.now();
31
+ await next();
32
+ const ms = Date.now() - start;
33
+ console.log(`${ctx.method} ${ctx.url} - ${ctx.status} ${ms}ms`);
34
+ });
35
+
36
+ app.use(errorMiddleware);
37
+ app.use(bodyParser({ multipart: true }));
38
+ app.use(mount('/uploads', staticMiddleware(path.join(__dirname, '../../uploads'))));
39
+ app.use(fileRoutes.routes());
40
+ app.use(fileRoutes.allowedMethods());
41
+
42
+ // const PORT = process.env.PORT || 3000;
43
+ const PORT = process.env.PORT || 3001;
44
+
45
+ app.listen(PORT, () => {
46
+ console.log(`Server is running on port ${PORT}`);
47
+ });
src/controllers/fileController.ts ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Context } from 'koa';
4
+
5
+ const UPLOAD_DIR = path.join(__dirname, '../../uploads');
6
+
7
+ if (!fs.existsSync(UPLOAD_DIR)) {
8
+ fs.mkdirSync(UPLOAD_DIR, { recursive: true });
9
+ }
10
+
11
+ export const getFileNameList = async (ctx: Context) => {
12
+ try {
13
+ const files = fs.readdirSync(UPLOAD_DIR);
14
+ ctx.body = { files };
15
+ } catch (error: any) {
16
+ ctx.status = 500;
17
+ ctx.body = { error: error.message };
18
+ }
19
+ };
20
+
21
+ export const uploadFile = async (ctx: Context) => {
22
+ try {
23
+ if (!ctx.request.body || !ctx.request.header['content-disposition']) {
24
+ ctx.status = 400;
25
+ ctx.body = { error: 'No file or file name uploaded' };
26
+ return;
27
+ }
28
+
29
+ const fileBuffer = ctx.request.body;
30
+ const contentDisposition = ctx.request.header['content-disposition'];
31
+ const fileNameMatch = contentDisposition.match(/filename=(.+)/);
32
+
33
+ if (!fileNameMatch) {
34
+ ctx.status = 400;
35
+ ctx.body = { error: 'File name not found in Content-Disposition header' };
36
+ return;
37
+ }
38
+
39
+ const fileName = fileNameMatch[1];
40
+ const targetPath = path.join(UPLOAD_DIR, fileName);
41
+
42
+ console.log( fileBuffer);
43
+ fs.writeFileSync(targetPath, fileBuffer);
44
+ ctx.body = { message: 'File uploaded successfully' };
45
+ } catch (error: any) {
46
+ ctx.status = 500;
47
+ ctx.body = { error: error.message };
48
+ }
49
+ };
50
+
51
+ export const getFile = async (ctx: Context) => {
52
+ try {
53
+ const fileName = ctx.params.fileName;
54
+ const filePath = path.join(UPLOAD_DIR, fileName);
55
+
56
+ if (!fs.existsSync(filePath)) {
57
+ ctx.status = 404;
58
+ ctx.body = { error: 'File not found' };
59
+ return;
60
+ }
61
+
62
+ ctx.attachment(fileName);
63
+ ctx.body = fs.createReadStream(filePath);
64
+ } catch (error: any) {
65
+ ctx.status = 500;
66
+ ctx.body = { error: error.message };
67
+ }
68
+ };
69
+
70
+ export const deleteFile = async (ctx: Context) => {
71
+ try {
72
+ console.log('1231321');
73
+ const { fileName } = ctx.request.body;
74
+
75
+ if (!fileName) {
76
+ ctx.status = 400;
77
+ ctx.body = { error: 'File name is required' };
78
+ return;
79
+ }
80
+
81
+ const filePath = path.join(UPLOAD_DIR, fileName);
82
+ console.log('filePathdsadsadsa');
83
+ console.log(filePath);
84
+ if (!fs.existsSync(filePath)) {
85
+ ctx.status = 404;
86
+ ctx.body = { error: 'File not found' };
87
+ return;
88
+ }
89
+
90
+ fs.unlinkSync(filePath);
91
+ ctx.body = { message: 'File deleted successfully' };
92
+ } catch (error: any) {
93
+ ctx.status = 500;
94
+ ctx.body = { error: error.message };
95
+ }
96
+ };
src/middleware/errorMiddleware.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Middleware } from 'koa';
2
+
3
+ export const errorMiddleware: Middleware = async (ctx, next) => {
4
+ try {
5
+ await next();
6
+ } catch (error) {
7
+ ctx.status = 500;
8
+ ctx.body = { error: error };
9
+ }
10
+ };
src/routes/fileRoutes.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Router from 'koa-router';
2
+ import { getFileNameList, uploadFile, getFile, deleteFile } from '../controllers/fileController';
3
+
4
+ const router = new Router();
5
+
6
+ router.get('/file/getFileNameList', getFileNameList);
7
+ router.post('/file/uploadFile', uploadFile);
8
+ router.get('/file/getFile/:fileName', getFile);
9
+ router.post('/file/deleteFile', deleteFile);
10
+
11
+ export default router;
tsconfig.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "moduleResolution": "node",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "baseUrl": ".", // 对应文件系统中的根路径
14
+ "paths": {
15
+ "@/*": ["src/*"] // 将 @/ 映射到 src/ 目录
16
+ },
17
+ },
18
+ "include": ["src"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }