CTF Rank网站开发笔记(四)——express+ejs+sequelize+mysql踩坑记录
昨天已经把数据库设计完,终于可以开始正式网站开发了,本来想的挺简单,去年10月用express+ejs+monodb写过一个简单的网站,结果这次用express+ejs+mysql时候还是踩了不少坑。这里记录一下。
首先是express项目的创建,现在express已经出到express4了,并且Jetbrains推出的Webstorm已经集成了express-generator,可以直接生成express项目,这一点还是很方便的。一键就可以把脚手架生成好,因为要求不高,我这里选的模板引擎是ejs。
网站不复杂的情况下,CSS不用预处理直接写原生CSS也无所谓,所以就这样开始了踩坑的路。
第一个坑:
首先,node.js不比java,没有Java里面类似mybatis或者Hibernate这种一站式的ORM框架,也没有java里那么多层次,一开始我用了mysql中间件,但是这样只能进行原生查询,像我这样java写习惯的人,怎么能忍。
好在我找到了sequelize框架,是node.js和io.js里的一个ORM框架,支持的数据库有PostgreSQL, MySQL, MariaDB, SQLite和MSSQL,不过使用的时候还是需要依赖相应数据库的驱动。另外最让我满意的一点就是sequelize还提供了一个sequelize-auto的CLI工具可以直接连接数据库生成对应的DAO结构,就像mybatis-generator一样。这一点是我最喜欢的一点了。所以就打算采用sequelize框架。
安装很方便,用npm就行:
npm install sequelize sequelize-cli mysql --save
npm install sequelize-auto -g
安装完成之后,就可以使用sequelize-auto工具生成对应DAO结构了:
使用方法很简单:
sequelize-auto -h -d -u -x [password] -p [port] --dialect [dialect] -c [/path/to/config] -o [/path/to/models] -t [tableName] -C
Options:
-h, --host IP/Hostname for the database. [required]
-d, --database Database name. [required]
-u, --user Username for database.
-x, --pass Password for database.
-p, --port Port number for database.
-c, --config JSON file for Sequelize's constructor "options" flag object as defined here: https://sequelize.readthedocs.org/en/latest/api/sequelize/
-o, --output What directory to place the models.
-e, --dialect The dialect/engine that you're using: postgres, mysql, sqlite
-a, --additional Path to a json file containing model definitions (for all tables) which are to be defined within a model's configuration parameter. For more info: https://sequelize.readthedocs.org/en/latest/docs/models-definition/#configuration
-t, --tables Comma-separated names of tables to import
-C, --camel Use camel case to name models and fields
我的用的命令如下:
sequelize-auto -o "./models" -d ctfrank -h localhost -u root -p 3306 -e mysql
这样就会在models文件夹下生成数据库的表结构:
是不是非常方便呢?使用的时候就只需要sequelize.import对应的模块就可以了。
当然每次都import会很不方便,所以在使用之前需要封装一下,在models文件夹里添加了一个index.js文件,内容如下:
"use strict"; var fs = require('fs'); var path = require('path'); var Sequelize = require('sequelize'); var basename = path.basename(module.filename); var config = require('../conf/dbconn'); var sequelize = new Sequelize( config.mysql.database, config.mysql.user, config.mysql.password, { 'dialect': 'mysql', 'host': config.mysql.host, 'port': config.mysql.port, 'logging':false, define:{ timestamps: false } }); var db = {}; fs.readdirSync(__dirname) .filter(function(file) { return (file.indexOf('.') !== 0) && (file !== basename); }) .forEach(function(file) { var model = sequelize.import(path.join(__dirname, file)); db[model.name] = model; }); Object.keys(db).forEach(function(modelName) { if ('associate' in db[modelName]) { db[modelName].associate(db); } }); db.sequelize = sequelize; db.Sequelize = Sequelize; module.exports = db;
这样封装过之后,在以后使用的页面只需要吧models文件夹require()进来然后就可以使用models.xxxx.findAll()进行查询了,其中xxxx是对应表导出的DAO模块名称。
第二个坑:
上述代码看似没问题,可是实际运行起来却无法从数据库里正常查询出内容,报这个错:
ER_BAD_FIELD_ERROR: Unknown column ‘createdAt’ in ‘field list’
查了半天才知道,原来sequelize默认启用的timestamp功能,就是会自动更新createdAt和updatedAt这两个field,一般来说我们在设计数据库的时候都会设计这两个字段方便以后的处理,而我设计的时候并没有设计,即使设计了相似功能的字段名字也不一定叫这个,因此就造成了悲剧。
也有大神指出其sequelize的默认查询语句是这样的
SELECT `users`.*, `userDetails`.`userId` AS `userDetails.userId`,`userDetails`.`firstName` AS `userDetails.firstName`,`userDetails`.`lastName` AS `userDetails.lastName`, `userDetails`.`birthday` AS `userDetails.birthday`, `userDetails`.`id` AS `userDetails.id`, `userDetails`.`createdAt` AS `userDetails.createdAt`, `userDetails`.`updatedAt` AS `userDetails.updatedAt` FROM `users` LEFT OUTER JOIN `userDetails` AS `userDetails` ON `users`.`id` = `userDetails`.`userId`;
解决方法,在创建Sequelize对象的时候,定义属性timestamp并设置为false:
var sequelize = new Sequelize( config.mysql.database, config.mysql.user, config.mysql.password, { 'dialect': 'mysql', 'host': config.mysql.host, 'port': config.mysql.port, 'logging':false, define:{ timestamps: false } });
问题就解决。
第三个坑:
异步回调的问题,由于有时候需要根据一个查询结果去查询另外一个结果,最后才能去渲染。然而由于Javascript的异步特性,每次findAll返回值需要通过回调函数处理,所以这里写起来代码会非常难受。
找了半天才发现Promise这个好东西,其实findAll()返回的就是一个Promise,所以通过.then函数可以将结果进行一定同步,一个.then执行完成后,返回的还是一个promise,这样通过.then的串联,可以使代码可读性增强,也可以通过.catch方法捕获异常。
大概像这样:
models.xxxx.findAll().then(function(data) { //do task1 }).then(function () { //do task2 }).then(function () { ...//task n }).catch(function (err) { // handle err });
这样就可以将task1-taskn按照希望的同步顺序执行下去,代码可读性也会更好一些。
关于日期格式化:
以前后端是Java的时候,一直在为前后端日期如何同步蛋疼,如果直接传字符串,前端要修改格式就很难,如果将Java的Date直接转为json格式,在js里又不好用,不过直接用express写的话前后端是统一的,倒是没有这个蛋疼的问题。这次也搜到一个很好用的库叫moment.js,用这个库来处理日期之间的格式化和转换,用着还是挺爽的。
不得不说,很多东西第一次写和第二次写感觉还是完全不一样的,记得上次写node.js项目还是去年10月份的时候,那时候express还是3,里面还没有router模块,现在写法上有些地方也已经不一样了。
还是需要不停学习,要不然跟不上时代了。