定义 vscode 代码片段

编辑器中 command+shift+p 打开命令面板,搜索 snip,然后新建全局或项目的代码片段,给文件起个名字,然后配置代码片段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"Express handler / middleware":{
"prefix": "eh", //代码片段快捷键
"body":[
"",
"/**",
" * $1", //第一个编辑点
" */",
"export const ${2:NAME} = async (", //第二个编辑点,如果没有默认显示NAME
" request: Request,",
" response: Response,",
" next: NextFunction",
" ) => {",
" $3", //第三个编辑点
" };"
]
},
"Async function":{
"prefix": "asf",
"body":[
"",
"/**",
" * $1",
" */",
"export const ${2:NAME} = async (",
" $3",
" ) => {",
" $4",
" };"
]
}
}

在项目中输入 eh 或 asf 就会出现相应的代码片段了。


创建模块

file.controller.ts:

1
import { Request, Response, NextFunction } from "express";

file.middleware.ts:

1
import { Request, Response, NextFunction } from "express";

file.router.ts:

1
2
3
4
5
6
7
8
9
import express from "express";
import * as fileController from "./file.controller";

const router = express.Router();

/**
* 导出路由
*/
export default router;

file.service.ts:

1
import { connection } from "../app/database/mysql";

在 app/index.ts 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import express from "express";
import postRouter from "../post/post.router";
import userRouter from "../user/user.router";
import authRouter from "../auth/auth.router";
import fileRouter from "../file/file.router";
import { defaultErrorHandler } from "./app.middleware";

/**
* 创建应用
*/
const app = express();

/**
* 导入处理json使用的中间件
*/
app.use(express.json());

/**
* 路由
*/
app.use(postRouter, userRouter, authRouter, fileRouter);

/**
* 默认异常处理器
*/
app.use(defaultErrorHandler);

/**
* 导出应用
*/
export default app;

定义上传文件的功能

借助 multer 这个包来完成

1
npm install multer

如果使用了 typescript,需要安装类型定义

1
npm install @types/multer

file.middleware.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Request, Response, NextFunction } from "express";
import multer from 'multer';

/**
* 创建一个multer
*/
const fileUpload = multer({
dest:'uploads/',
})

/**
* 文件拦截器
*/
export const fileInterceptor = fileUpload.single('file');

在 gitignore 中忽略,/uploads


定义上传文件接口

file.controller.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Request, Response, NextFunction } from "express";

/**
* 上传文件
*/
export const store = async (
request: Request,
response: Response,
next: NextFunction
) => {
console.log(request.file);
response.sendStatus(200);
};

file.router.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import express from "express";
import { authGuard } from "src/auth/auth.middleware";
import * as fileController from "./file.controller";
import { fileInterceptor } from "./file.middleware";

const router = express.Router();

/**
* 上传文件
*/
router.post("/files", authGuard, fileInterceptor, fileController.store);

/**
* 导出路由
*/
export default router;

创建存储文件信息数据表

sql 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE TABLE `file` (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`originalname` VARCHAR(255) NOT NULL,
`mimetype` VARCHAR(255) NOT NULL,
`filename` VARCHAR(255) NOT NULL,
`size` INT(11) NOT NULL,
`postId` INT(11) NOT NULL,
`userId` INT(11) NOT NULL,

FOREIGN KEY (`postId`) REFERENCES `post`(`id`)
ON DELETE NO ACTION ON UPDATE NO ACTION,

FOREIGN KEY (`userId`) REFERENCES `user`(`id`)
ON DELETE NO ACTION ON UPDATE NO ACTION
) DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci;

定义存储文件信息功能

新建文件 file.model.ts:

1
2
3
4
5
6
7
8
9
export class FileModel {
id?: string;
originalname: string;
mimetype: string;
filename: string;
size: number;
userId: number;
postId?: number;
}

file.service.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { connection } from "../app/database/mysql";
import { FileModel } from "./file.model";

/**
* 存储文件信息
*/
export const createFile = async (file: FileModel) => {
//准备查询
const statement = `
INSERT INTO file
SET ?
`;

// 执行查询
const [data] = await connection.promise().query(statement, file);

//提供数据
return data;
};

存储上传文件信息

file.controller.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { Request, Response, NextFunction } from "express";
import _ from "lodash";
import { createFile } from "./file.service";

/**
* 上传文件
*/
export const store = async (
request: Request,
response: Response,
next: NextFunction
) => {
//当前用户
const { id: userId } = request.user;

//所属内容
const { post: postId } = request.query;

//文件信息
const fileInfo = _.pick(request.file, [
"originalname",
"mimetype",
"filename",
"size",
]);

try {
//保存文件信息
const data = await createFile({
...fileInfo,
userId,
postId,
});

// 做出响应
response.sendStatus(201).send(data);
} catch (error) {
next(error);
}
};

定义文件上传接口

file.service.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 按ID 查找文件
*/
export const findFileById = async (fileId: number) => {
//准备查询
const statement = `
SELECT * FROM file
WHERE id = ?
`;

// 执行查询
const [data] = await connection.promise().query(statement, fileId);

//提供数据
return data[0];
};

file.controller.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 文件服务
*/
export const serve = async (
request: Request,
response: Response,
next: NextFunction
) => {
//从地址参数里得到文件 ID
const { fileId } = request.params;

try {
//查找文件信息
const file = await findFileById(parseInt(fileId, 10));

//做出响应
response.sendFile(file.filename, {
root: "uploads",
headers: {
"Content-Type": file.mimetype,
},
});
} catch (error) {
next(error);
}
};

file.router.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import express from "express";
import { authGuard } from "../auth/auth.middleware";
import * as fileController from "./file.controller";
import { fileInterceptor } from "./file.middleware";

const router = express.Router();

/**
* 上传文件
*/
router.post("/files", authGuard, fileInterceptor, fileController.store);

/**
* 文件服务
*/
router.get("/files/:fileId/serve", fileController.serve);

/**
* 导出路由
*/
export default router;