做前端的,在node出现后开始做类似后台类的操作,对于怎么操作数据库刚开始还是很慌的,找到一个工具orm,屏蔽底层的具体实现,只通过相应的对象类操作完成数据库操作,这边选定sequelize。当然是用免费的mysql数据库咯。
首先肯定是根据需求在数据库建好相应的数据库以及数据表,然后用sequelize-auto将数据表生成基本的model对象,用于生成model的文件generate_model.js可以这么写
const SeqAuto = require('sequelize-auto')
const path = require('path')
const tables = process.argv.slice(2)
if (tables.length === 0) {
throw new Error('No table specified\nUsage: node generate_model table1 table2 table3\n')
}
const auto = new SeqAuto('database', 'username', 'password', {
host: '127.0.0.1',
dialect: 'mysql',
directory: path.resolve(__dirname, './generated'),
additional: {
timestamps: false
},
tables: tables
})
auto.run(err => {
if (err) {
throw err
}
console.log('\n\nProcess completed successfully.')
console.log('You may need to add these Foreign Keys mannually:\n', auto.foreignKeys)
})
运行node generate_model table_name,即可在generate文件夹下,生成对应的model文件,这里以package资产包表为例
/* jshint indent: 1 */
module.exports = function(sequelize, DataTypes) {
return sequelize.define('package', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
product_id: {
type: DataTypes.BIGINT,
allowNull: true
},
name: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true
},
dept: {
type: DataTypes.CHAR(10),
allowNull: false
},
type: {
type: DataTypes.INTEGER(6),
allowNull: false
},
pack_date: {
type: DataTypes.DATE,
allowNull: false
},
portfolio: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
tableName: 'package',
timestamps: false
});
};
这些只是单纯的数据结构,以及一些字段要求和主键类关系,但是一张表可能和另一张表存在关系等,需要对该model进行一些丰富,改良之后的样子暂时称之为schema大概是这样
import { recordStates, assetTypes, getEnumString, getDateString } from '../enum'
import packageProps from '../props/package'
import _ from 'lodash'
import moment from 'moment'
// getter setter and pseudo properties are manually amend.
// DO NOT overwrite all these context!
export default (sequelize, DataTypes) => {
const PackageInfo = sequelize.define('packageInfo', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
product_id: {
type: DataTypes.BIGINT,
allowNull: true
},
name: {
type: DataTypes.STRING(100),
allowNull: false
},
dept: {
type: DataTypes.CHAR(10),
allowNull: false
},
type: {
type: DataTypes.INTEGER(6),
allowNull: false
},
pack_date: {
type: DataTypes.DATE,
allowNull: false,
set (val) {
this.setDataValue('pack_date', moment(val))
}
},
portfolio: {
type: DataTypes.CHAR(100),
allowNull: true
}
}, {
getterMethods: {
_type () {
return getEnumString(this, 'type', assetTypes)
},
_product_state () {
return getEnumString(this, 'product_state', productSates)
},
_maker_time () {
return getDateString(this, 'maker_time', 'YYYY-MM-DD HH:mm:ss')
},
_pack_date () {
return getDateString(this, 'pack_date')
}
},
tableName: 'package',
timestamps: false,
defaultScope: {
where: {
state: { $not: -1 }
}
},
scopes: {
deleted: {
where: {
state: -1
}
}
}
})
// set the plural for model
PackageInfo.plural = PackageInfo.name + 's'
// tell models to export this as audit item
PackageInfo.isAuditItem = true
PackageInfo.auditItemIdx = 1
PackageInfo.propertyKeys = packageProps
PackageInfo.associate = models => {
// associate with ProductInfo
PackageInfo.hasOne(models.productInfo, { foreignKey: 'package_id' })
// associate with Package Items
PackageInfo.hasMany(models.mortgageItem, {
foreignKey: 'package_id',
constraints: false
})
}
PackageInfo.hooks = models => {
PackageInfo.afterCreate(async (instance, options) => {
let record = instance.toJSON()
record.update_by = record.maker
record.update_time = record.maker_time
await models.packageInfoAudit.create(record, { transaction: options.transaction })
})
PackageInfo.afterUpdate(async (instance, options) => {
let record = Object.assign(instance.toJSON(), options.audit)
let prev = instance.previous('version')
if ( !(prev === instance.toJSON().version) ) {
if (!_.isEmpty(options.changes)) {
_.merge(record, options.changes)
}
await models.packageInfoAudit.create(record, { transaction: options.transaction })
} else {
const prevInfo = await models.packageInfoAudit.findOne({
where:{
id: record.id,
version: record.version,
state: {$gte: 0}
}
})
if (!_.isNil(prevInfo)) {
await prevInfo.update({
product_id: record.product_id,
product_state: 1
}, { transaction: options.transaction })
}
}
})
}
return PackageInfo
}
可以增加一些,数据库中的数据字典值的输出,定义表之间的关系,比如,基于一个资产包只能创建一个产品,一个资产包下面可以有多个资产项,甚至,可以提前配置好钩子,在该表create或者update之后或者之前等进行相关的操作。定义scope作为某些查询的默认条件等。
在理清各表之间的关系,建立好每个表对应的schema之后,就是将这些东西抛出来用啦~
import fs from 'fs'
import path from 'path'
import Sequelize from 'sequelize'
import config from '../config/db.json'
import log4js from 'log4js'
import modelProps from './props'
const logger = log4js.getLogger('models')
const dbConfig = config[process.env.NODE_ENV]
if (dbConfig === undefined) {
throw new Error('Database config is missing for ' + process.env.NODE_ENV)
}
const models = {
AuditItems: {},
PackageItems: {},
PackageItemsAudit: {}
}
const schemas = fs.readdirSync(path.resolve(__dirname, './schemas'))
const sequelize = new Sequelize(dbConfig.uri, {
benchmark: true,
pool: config.pool,
logging: (msg, cost) => logger.debug(`${msg} costs ${cost}ms.`)
})
// const sequelize = new Sequelize('abs_es', 'root', '', {
// host: 'localhost',
// dialect: 'mysql',
// benchmark: true,
// pool: config.pool,
// logging: (msg, cost) => logger.debug(`${msg} costs ${cost}ms.`)
// })
schemas.forEach( schema => {
let schemaName = schema.split('.')[0]
let model = sequelize.import(`./schemas/${schemaName}`)
models[model.name] = model
logger.info('[AES] Model', model.name, 'is mounted')
if (model.isAuditItem) {
models.AuditItems[model.auditItemIdx] = model
} else if (model.isPackageItem) {
models.PackageItems[model.packageItemIdx] = model
} else if (model.isPackageItemAudit) {
models.PackageItemsAudit[model.packageItemAuditIdx] = model
}
})
Object.keys(models).forEach( name => {
let model = models[name]
if ('associate' in model) {
logger.info('[AES] Association of model', model.name, 'is ready')
model.associate(models)
}
if ('hooks' in model) {
logger.info('[AES] Hooks of model', model.name, 'is ready')
model.hooks(models)
}
})
export const getTransaction = async () => {
return await sequelize.transaction()
}
export const MAX = column => {
return sequelize.fn('MAX', sequelize.col(column))
}
export default models
大概展示一下,每种操作的基本用法
let orderInfo = await Models.packageInfo.scope(null).findById(package_id)
let packages = await Models.packageInfoAudit.findAll({
where: {
id: ctx.state.records.related.id,
state: 1,
product_id: relatedProduct.id
},
order: [ ['pack_date', 'DESC'] ]
})
packages = JSON.parse(JSON.stringify(packages))
const record = await Models.productInfo.create(productData, {
transaction: txn,
include: [
{ model: docModel, audit: ctx.state.audits, as: docModel.alias },
{ model: ceModel, audit: ctx.state.audits, as: ceModel.alias },
{ model: sdModel, audit: ctx.state.audits, as: sdModel.alias }
]
})
const newRecord = await ctx.state.records.product.update({
state: ctx.state.changes.state,
version: ctx.state.records.product.version + 1,
maker: ctx.state.audits.update_by,
maker_time: ctx.state.audits.update_time
}, _.merge({
changes:ctx.state.changes.product
}, txno))
const record = await Models.packageInfo.findOne({
where: {
id: ctx.state.records.related.id
},
include: [{
model: Models.productInfo,
as: Models.productInfo.name,
required: false
}],
lock: txnLocks.UPDATE
})
try {
const deletedRecord = await ctx.state.records.package.update({
state: ctx.state.changes.state,
version: ctx.state.records.package.version + 1,
maker: ctx.state.audits.update_by,
maker_time: ctx.state.audits.update_time
}, txno)
await txn.commit()
// await txn.rollback()
return ctx.success('资产包删除成功,等待审核')
} catch (err) {
await txn.rollback()
if (err.errors && err.errors.length > 0) {
logger.info("failed error", err)
return ctx.error(errMap[err.errors[0].message] || err.errors[0])
}
throw err
}