This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging
The file upload function is often used in daily business writing. This article describes how to upload a file in file mode on the Eggjs Multipart module. The file md5 is used to check whether the file exists and the file is not generated repeatedly on the server. Suitable for profile picture and other small file upload.
The uploaded resources are stored in the static resource directory configured by the server. Nginx resolves the static resources to facilitate the whole package of various cloud OSS to be uploaded. The code returns resources without domain names by default. You can configure static resource domain name concatenation on the front end, or concatenate domain names in the Sequelize collector.
I. File upload process
- The framework will put the submitted files in the configuration
tmpdir
Directory is created as a temporary file. - And the basic information of the file and temporary file absolute path back, we use the frame returned temporary file file absolute path can pass
fs
Operation file. - Save the file to the configured
userConfig.file.disk
Directory. Through the nginxdisk
The directory resolves to a static resource domain name, and the uploaded file can be accessed.
// Obtain an example file upload request
const files = this.ctx.request.files;
console.log(files);
// The result prints the file information in the request
[{
field: 'file'.filename: '2.png'.encoding: '7bit'.mime: 'image/png'.fieldname: 'file'.transferEncoding: '7bit'.mimeType: 'image/png'.filepath: '/myproject/tmp/egg-base-api/2021/07/27/15/3a809821-023a-4cbd-98ad-40787ac516d8.png'
}]
Copy the code
2. Configuration files
Modify the multipart configuration:
-
Mode File upload mode. The default file upload mode is STREAM.
-
Tmpdir Temporary directory path. You are advised to set this parameter to facilitate the clearing of uploaded temporary files.
-
FileExtensions uploadsupported fileExtensions. Using whitelist overrides the framework’s default whitelist.
-
CleanSchedule automatically cleans the configuration of temporary files. By default, the framework cleans temporary files before today at 4:30 am every day. You can configure cron expressions.
Added user-defined configuration:
userConfig.file.disk
To configure the directory for storing uploaded resources on the server, you need to give node write permission.
// config/config.default.js
const path = require('path');
module.exports = appInfo= > {
const config = exports = {};
// ...
config.multipart = {
// File upload mode: adopt temporary file mode.
mode: 'file'.// Configure the directory of temporary files and change it to TMP in the root directory of the project for convenient management
tmpdir: path.join(__dirname, '.. '.'tmp', appInfo.name),
// Whitelists of upload types are allowed
fileExtensions: [ '.jpg'.'.jpeg'.'.png'.'.gif'.'.bmp'.'.webp',].// File size limit
fileSize: '20mb'.cleanSchedule: { cron: '0 30 4 * * *',}};// Customize the configuration
const userConfig = {
file: {
// The path where your static resources are stored
disk: '/home/www/static/upload',}}; };Copy the code
Data table structure and model
-
Table structure
Path Relative path of a file, which can be combined with a static resource domain name to form a complete accessible URL.
Name The original name of the file;
Ext file suffix;
Size File size;
Md5 Indicates the hash value of a file, which is used to determine the uniqueness of the file.
CREATE TABLE `file` (
`id` int NOT NULL AUTO_INCREMENT,
`path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ' ' COMMENT 'File path',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ' ' COMMENT 'Original file name',
`ext` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ' ' COMMENT 'File suffix',
`size` int NOT NULL DEFAULT '0' COMMENT 'File size',
`md5` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ' ' COMMENT 'file md5',
`create_time` datetime DEFAULT NULL COMMENT 'Creation time',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`delete_time` datetime DEFAULT NULL COMMENT 'Delete logical delete'.PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
Copy the code
-
model
The model uses logical deletes and retrieves. Refer to the previous article.
// app/model/file.js
const dayjs = require('dayjs');
module.exports = app= > {
const { STRING, INTEGER, DATE } = app.Sequelize;
return app.model.define('file', {
id: { type: INTEGER, primaryKey: true.autoIncrement: true,},path: STRING,
name: STRING,
ext: STRING,
size: STRING,
md5: STRING,
create_time: {
type: DATE,
get () {
const time = this.getDataValue('create_time');
return time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : null; }},update_time: {
type: DATE,
get () { // Obtain the data when the format time
const time = this.getDataValue('update_time');
return time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : null; }},delete_time: DATE,
}, {
freezeTableName: false.tableName: 'file'.underscored: false.paranoid: true.timestamps: true.// Enable automatic time to use logical delete
createdAt: false.updatedAt: false.deletedAt: 'delete_time'.// Delete the field logically
});
};
Copy the code
Routing, controllers, and services
- router
// app/router.js
module.exports = app= > {
const { router, controller } = app;
router.post('/admin/file', controller.file.upload);
};
Copy the code
- controller
// app/controller/file.js
const BaseController = require('./base');
class FileController extends BaseController {
async upload () {
/ / if you use multiple file upload can be introduced to false (CTX) service) file. The uploadFile (false))
const data = await this.ctx.service.file.uploadFile(true);
this.success(data); }}module.exports = FileController;
Copy the code
-
service
The specific logic is as follows, which can also be implemented in other languages.
-
Get all the files in the request, loop.
-
Basic file information, including file size, MD5, and fileBuffer, was obtained.
-
Run the MD5 command to check whether the file exists in the database.
-
If it exists, file information is returned directly. No file is saved to a static resource directory, file information is stored in the database, and file information is returned.
-
// app/service/file.js
const Service = require('egg').Service;
const crypto = require('crypto');
const UUID = require('uuid').v4;
const dayjs = require('dayjs');
const fs = require('fs');
const path = require('path');
// Failed to upload the custom file
const FileException = require('.. /exception/file');
class FileService extends Service {
// File upload method: single:true Single file single:false multi-file
async uploadFile (single = false) {
if (!this.ctx.request.files || this.ctx.request.files.length === 0) {
throw new FileException('Please select file');
}
const rets = [];
// Get temporary file loop operation
const files = this.ctx.request.files;
for (let i = 0; i < files.length; i++) {
if (single && i > 0) { // If only the first file is processed for single file upload, then the loop is closed
break;
}
const file = files[i];
try {
// Get the basic information about the file: size, hash, fileBuffer
const { size, md5, data } = await this.checkFileInfo(file);
// Check whether the file exists in the database. If it exists, return the file information directly, without saving the file.
const exists = await this.checkFileExists(md5);
if (exists) {
rets.push({ key: i, ... exists }); }else { // If not, save the file to the server and create a record in the database.
// Save the file to the hard disk
const res = await this.putFile(data, file.filename);
// Save the file to the database
await this.app.model.File.create({
path: res.path,
name: file.filename,
ext: res.ext,
size,
md5,
create_time: new Date()}); rets.push({key: i,
path: res.path,
url: '/'+ res.path, }); }}catch (e) {
throw new FileException('File upload failed'); }}return single ? rets[0] : rets;
}
// Get the file information
async checkFileInfo (file) {
return new Promise((resolve, reject) = > {
const fsHash = crypto.createHash('md5');
fs.readFile(file.filepath, (err, data) = > { // Read the file
if (err) {
reject(err);
} else {
// Obtain the file md5
const md5 = fsHash.update(data).digest('hex');
// Get the file size
constsize = data.length; resolve({ size, md5, data }); }}); }); }// Save the file to the hard disk
async putFile (flieBuffer, filename) {
return new Promise((resolve, reject) = > {
const { dir, local } = this.createUploadPath();
const ext = this.getFileExt(filename);
// Save the file name, use the UUID
const targetName = UUID() + '. ' + ext;
/ / write file
fs.writeFile(dir + '/' + targetName, flieBuffer, err= > {
if (err) {
reject(err);
} else {
resolve({
path: local + '/'+ targetName, ext, }); }}); }); }// The database uses the MD5 of the file to find whether it exists
async checkFileExists (md5) {
const exists = await this.app.model.File.findOne({ where: { md5 } });
// Return the file information if it exists
if(exists ! = =null) {
return {
id: exists.id,
path: exists.path,
url: '/' + exists.path,
};
}
return null;
}
// Obtain without taking. The suffix
getFileExt (filename) {
const ext = path.extname(filename);
if (this.app._.startsWith(ext, '. ')) {
return ext.substr(1);
}
return ext;
}
// Create a directory for saving files. Default is one directory per day
createUploadPath () {
// Obtain the file location in the configuration
const disk = this.app.config.file.disk;
const local = dayjs().format('YYYYMMDD');
const dir = disk + '/' + local;
if(! fs.existsSync(dir)) {// Create a directory if it does not exist
fs.mkdirSync(dir);
}
return{ dir, local }; }}module.exports = FileService;
Copy the code
4. Upload test
- Service. UploadFile (true)
- Upload multiple files (service.uploadFile(false))