@kne/fastify-sequelize
Version:
Fastify与Sequelize的深度集成插件,支持自动模型加载和分布式ID生成
149 lines (135 loc) • 5.68 kB
JavaScript
const fp = require('fastify-plugin');
const {Sequelize, DataTypes} = require('sequelize');
const fs = require('fs-extra');
const path = require('node:path');
const {glob} = require('glob');
const {transform, merge, camelCase, snakeCase, upperFirst, lowerFirst} = require('lodash');
const {Snowflake} = require('nodejs-snowflake');
const defaultConfig = {
db: {
dialect: 'sqlite', username: null, password: null
}, snowflake: {
instance_id: 1, custom_epoch: new Date('2024-01-01').getTime()
}, modelsPath: './models', sqlPath: './sql', prefix: 't_', glob: {}, syncOptions: {}, name: 'models'
};
const sequelize = fp(async (fastify, options) => {
const config = merge({}, defaultConfig, options);
const snowflake = new Snowflake(config.snowflake);
const sequelize = new Sequelize(config.db);
const modelList = [];
const definePrimaryType = (name, props) => {
return Object.assign({}, {
type: config.db?.dialect === 'sqlite' ? DataTypes.STRING : DataTypes.BIGINT, get() {
const value = this.getDataValue(name);
return value && value.toString();
}
}, props);
};
const addModels = async (modelsPath, options) => {
const db = {}, addModelsOptions = Object.assign({}, config, options);
const {name, pattern, syncOptions, ...globOptions} = merge({}, {
ignore: 'node_modules/**', pattern: '**/*.js'
}, config.glob, options);
const stat = typeof modelsPath === 'string' && (await fs.promises.stat(modelsPath).catch(() => {
}));
await (async () => {
const registerDB = (module, targetName) => {
const {name, model, associate, options} = module({
sequelize, DataTypes, definePrimaryType, fastify, options: addModelsOptions
});
const originModelName = name || targetName;
const modelName = addModelsOptions.modelPrefix ? `${addModelsOptions.modelPrefix}${upperFirst(originModelName)}` : originModelName;
if (!modelName) {
throw new Error('未能正确获取到modelName');
}
db[modelName] = sequelize.define(modelName, Object.assign({}, {
id: definePrimaryType('id', {primaryKey: true})
}, model), Object.assign({
paranoid: true,
tableName: (addModelsOptions.prefix || config.prefix || 't_') + snakeCase(modelName),
underscored: true
}, options));
db[modelName].beforeCreate(info => {
info.id = snowflake.getUniqueID();
return info;
});
db[modelName].beforeBulkCreate(infos => {
infos.forEach(info => {
info.id = snowflake.getUniqueID();
});
return infos;
});
db[modelName].associate = associate;
db[modelName].modelPrefix = addModelsOptions.modelPrefix;
};
if (stat && stat.isDirectory()) {
const files = await glob(pattern, Object.assign({}, globOptions, {cwd: modelsPath}));
await Promise.all(files.map(async file => {
const {default: module} = await import(`file://${path.resolve(modelsPath, file)}`);
registerDB(module, camelCase(path.basename(file, path.extname(file))));
}));
return;
}
if (stat && stat.isFile()) {
registerDB(require(modelsPath), camelCase(path.basename(modelsPath, path.extname(modelsPath))));
return;
}
if (typeof modelsPath === 'function') {
registerDB(modelsPath);
return;
}
console.warn('未发现任何models模块,ags:' + modelsPath);
})();
modelList.push(db);
return addModelsOptions.modelPrefix ? transform(db, (result, value, key) => {
result[lowerFirst(key.replace(new RegExp(`^${addModelsOptions.modelPrefix}`), ''))] = value;
}, {}) : db;
};
const stat = config.modelsPath && (await fs.promises.stat(path.join(process.cwd(), config.modelsPath)).catch(() => {
}));
let syncPromiseResolve;
const syncPromise = new Promise((resolve) => {
syncPromiseResolve = resolve;
});
fastify.decorate('sequelize', {
addModels,
Sequelize,
[config.name || defaultConfig.name]: stat && stat.isDirectory() && (await addModels(path.join(process.cwd(), config.modelsPath), config)),
instance: sequelize,
generateId: () => snowflake.getUniqueID(),
syncPromise,
sync: async options => {
modelList.forEach(db => {
Object.values(db).forEach(model => {
const target = model.modelPrefix ? transform(db, (result, value, key) => {
result[lowerFirst(key.replace(new RegExp(`^${model.modelPrefix}`), ''))] = value;
}, {}) : db;
if (model.associate) model.associate(target, fastify, options);
});
});
await sequelize.sync(Object.assign({}, config.syncOptions, options));
//增加更新表结构操作
console.log('-----------开始执行sql数据库更新脚本-----------');
try {
const sqlPath = path.join(process.cwd(), config.sqlPath);
if (await fs.exists(sqlPath)) {
const names = await fs.readdir(sqlPath);
for (const name of names) {
if (!name.endsWith('.sql')) continue;
const sql = await fs.readFile(path.join(process.cwd(), config.sqlPath, name), 'utf-8');
console.log('运行sql:', sql);
await sequelize.query(sql);
}
}
} catch (error) {
console.error(error);
}
console.log('-----------完成执行sql数据库更新脚本-----------');
console.log('models were synchronized successfully.');
syncPromiseResolve();
}
});
}, {
name: 'fastify-sequelize'
});
module.exports = sequelize;