文章目录
- 一、开发环境
- 二、数据库准备files表【视项目需求不同可略过,一般小项目可忽略】
- 1.数据库准备files表的目的
- 2.表结构
- 三、后端接口准备
- 1.配置config
- 2.router.ts中配置路由
- 3.创建controller
- 4.创建service
- ①为什么做这步
- ②创建service/common文件
- ③service/common.ts文件内容
- 四、前端调接口上传图片
- 五、前后端联调
一、开发环境
- 前端 React.js + Antd。你用其他的vue什么的或者其他的UI组件库都是一样的。
- 后端 Eggjs
- 数据库 MySQL
二、数据库准备files表【视项目需求不同可略过,一般小项目可忽略】
1.数据库准备files表的目的
有同学可能会有疑问,为啥会涉及到新建一个表?
因为我们需要把file的原始信息存储起来,比如file name、mime type、服务器上的url等。【可能后续产品会要求展示图片的原始信息,上传人等等】
2.表结构
PS:可根据自己项目需要自行拓展字段
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for files
-- ----------------------------
DROP TABLE IF EXISTS `files`;
CREATE TABLE `files` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`file_name` varchar(255) DEFAULT NULL COMMENT '文件名',
`mime_type` varchar(20) DEFAULT NULL COMMENT '文件类型',
`url` varchar(255) NOT NULL COMMENT '文件的服务器地址',
`upload_user_id` bigint(20) DEFAULT NULL COMMENT '上传人id',
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '上传时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SET FOREIGN_KEY_CHECKS = 1;
三、后端接口准备
PS:依赖的安装包笔者这里没写,笔者是直接生成的项目,都是自带的,如果你报错缺哪个包就安哪个包即可。
1.配置config
找到 config/config.default.ts 文件追加如下配置:
// 上传文件相关
uploadImgDir: 'app/public/img',
multipart: {
mode: 'file',
fileSize: 1048576000,
whitelist: ['.txt', '.png', '.jpg', '.jpeg', '.webp'], // 配置允许哪些文件上传
},
static: {
prefix: '/app/public', //访问前缀
dir: path.join(appInfo.baseDir, 'app/public'),
dynamic: true,
preload: false,
maxAge: 31536000,
buffer: true,
},
// END-上传文件相关
2.router.ts中配置路由
3.创建controller
PS: 注意这里controller文件的命名是和上面router.ts中配置的第二个参数是关联的
由于我们上面注册的是controller.common.upload
那么我们就在app/controller 目录下新建common.ts文件。里面追加一个upload接口。
common.ts全部代码:
import { Controller } from 'egg';
const fs = require('fs');
const moment = require('moment');
const mkdirp = require('mkdirp');
const path = require('path');
module.exports = app => {
/**
* @Controller 公共接口
*/
return class CommonController extends Controller {
/**
* @summary 上传图片
* @description 公共的上传图片的接口
* @router post /api/common/upload
* @request header string token
* @request formData file *file
* @response 200 uploadResponse 上传成功
*/
public async upload(ctx) {
// 需要前往 config/config.default.js 设置 config.multipart 的 mode 属性为 file
const files = ctx.request.files;
// 存放最终资源的路径集合
const uploadDirs: string[] = [];
// 存放最终的id集合,判断是否上传成功的依据
const ids: number[] = [];
// 将所有的文件都放到params里面,然后一起存数据库
const params: any[] = [];
// 1.获取当前日期-用于创建目录
const day = moment(new Date()).format('YYYYMMDD');
// 2.创建图片保存的路径
const dir = path.join(this.config.uploadImgDir, day);
await mkdirp(dir); // 不存在就创建目录
// 从token中获取操作人的id - 你如果不需要存upload_user_id那么就忽略这步
const userInfo = await app.jwt.verify(ctx?.headers?.token || '', app.config.jwt.secret);
try {
files.forEach(file => {
const fileContent = fs.readFileSync(file.filepath);
// 生成图片最终在服务器的名称以及返回在服务器上的地址
const uploadDir = path.join(dir, Date.now() + path.extname(file.filename));
uploadDirs.push(uploadDir);
params.push({
url: uploadDir,
filename: file.filename,
mimeType: file.mimeType,
uploadUserId: userInfo.id,
});
// 写入文件夹
fs.writeFileSync(uploadDir, fileContent);
});
// 存到数据库
const res = await ctx.service.common.upload(params);
ids.push(res?.insertId);
if (ids.every(ite => !ite) || ids.length === 0) {
ctx.failure('上传失败', { ids });
} else {
ctx.success('上传成功', { ids, uploadDirs });
}
} catch (e) {
console.log('error', e);
// 清除临时文件
ctx.cleanupRequestFiles();
}
}
};
};
4.创建service
①为什么做这步
因为我们在上一步的controller里面是把数据的操作放到了service里面进行处理的,所以我们在创建一下service文件然后写存数据库的sql。
②创建service/common文件
③service/common.ts文件内容
import { Service } from 'egg';
type UploadParams = {
url: string; // 图片的服务器地址 url
filename: string; // 图片的原始name file_name
mimeType: string; // 图片的类型 mime_ype
uploadUserId: number; // 上传人id upload_user_id
};
/**
* Common Service
*/
export default class User extends Service {
/**
* 上传文件
*/
public async upload(uploadParams: UploadParams[]) {
try {
let sqlValue = '';
uploadParams.forEach((param, index) => {
if (index !== 0) {
sqlValue += ',';
}
sqlValue += `('${param.url}', '${param.filename}', '${param.mimeType}', ${param.uploadUserId})`;
});
const sql = `INSERT INTO files(url, file_name, mime_type, upload_user_id) VALUES${sqlValue}`;
const insertFileres = await this.app.mysql.query(sql);
return insertFileres;
} catch (err) {
console.log('存文件到数据库失败:', err);
}
}
}
至此服务端的任务都完成了,就差写前端组件调接口联调了。
四、前端调接口上传图片
PS: 前端笔者这里就把大部分代码都省略了,如果后续真的很多朋友需要的话笔者在补充上来。笔者只会在下面写调接口的关键代码。
笔者用的是antd的Upload组件,然后用提供的api:customRequest 调用接口写后续的逻辑。
antd-upload组件的API链接:https://ant.design/components/upload-cn#api
// 前端主要是把文件放到formData里面
// 然后options配置'Content-type': 'multipart/form-data'
// 这样传给后端就OK了
// 这个customRequest是放到Upload组件里面的一个props哈
// 如果你不是用的antd的Upload组件你只需要把后面的function留着用即可
customRequest: async ({ file }) => {
let param = new FormData();
param.append('files', file) // 通过append向form对象添加数据
const res = await upload(param, {
'Content-type': 'multipart/form-data'
});
console.log('res: ', res);
}
/** 上传文件接口 POST /api/common/upload */
function upload(data, options) {
return request(`${你自己项目后端的IP地址}/api/common/upload`, {
method: 'POST',
data,
...(options || {}),
});
}
五、前后端联调
前端上传图片调接口,发现有成功的返回,并且我们后端接口抛回来的ids和uploadDirs都能收到就代表我们OK了,然后我们用返回回来的uploadDirs跟服务器的IP地址拼接一下能出来对应的图片,那就相当于完全OK了!!!
入参:
返回值:
然后我们根据返回回来的uploadDirs去尝试回显一下图片,在这个地址前面拼接上服务器的ip地址:
成功显示~
且返回回来的ids也是跟数据库对应上的,功能完成!