安装 password-hash 与测试
用来处理用户密码,不存储用户的明文密码,而是存储加密后的。
npm i password-hash -S
在 src 目录下面创建 db.ts , 定义模型我们使用define方法, 这些都是有代码提示的。
import * as Sequelize from 'sequelize';
import * as ph from 'password-hash';
import { resolve } from 'path';
const sq = new Sequelize('db', null, null,{
dialect: 'sqlite',
storage: resolve(__dirname, '../storage/db.sqlite3')
});
var User = sq.define('user', {
id:{
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
}, {
timestamps: false,
freezeTableName: true // Model tableName will be the same as the model name
});
User.create({
username: 'yugo',
email: 'belovedyogurt@gmail.com',
password: ph.generate('123456')
}).then(console.log)
首先编译
tsc
之后再运行
node dist/db.js
这样我们就可以在数据里面看到我们的数据了。
编写 Model 与 ava 测试文件
之前我们是把js文件编译到dist目录下管理,假如我们的test下面的测试文件也跑到dist目录下面的话就感觉很别扭,因为它没有在它该在的位置上,也就是不在其位而谋其政,尽管我们可以通过写2个 tsconfig.json 和 配置 exclude 选项让它们分开编译来解决这个问题,我们还不如就让 js 和 ts 在同一目录下来的简单。
修改我们的 db.ts
我们让他仅仅只提供数据库连接。
import * as Sequelize from 'sequelize';
import { resolve } from 'path';
const sq = new Sequelize('db', null, null,{
dialect: 'sqlite',
storage: resolve(__dirname, '../storage/db.sqlite3')
});
export default sq;
修改 tsconfig.json
在根目录下面新建一个 types 文件夹,在这里面你可以写你的代码提示文件,但是并不会被转换成模块。
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": true,
"lib": [
"es6",
"es7"
],
"typeRoots": [
"./types"
]
}
}
在 src 目录下面创建 model 文件夹,新建 todo.ts
、 todoFolder.ts
、 user.ts
三个文件。
model 下面都是存放我们的模型,通过 hasMany 定义模型之间的关系。
- todo.ts
import sq from '../db';
import * as Sequelize from 'sequelize';
import { TodoFolder } from './todoFolder'
const Todo = sq.define<any, any>('todo', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
text: {
type: Sequelize.TEXT
},
completed: {
type: Sequelize.BOOLEAN
},
'todo_folder_id': {
type: Sequelize.INTEGER,
references: {
model: TodoFolder,
key: 'id'
}
}
},{
freezeTableName: true // 模型名字与表名相同
});
TodoFolder.hasMany(Todo, { as: 'Todos', foreignKey: 'todo_folder_id' })
export { Todo }
- todoFolder.ts
import sq from '../db';
import * as Sequelize from 'sequelize';
import {User} from './user';
const TodoFolder = sq.define<any, any>('todo_folder', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
title: {
type: Sequelize.TEXT
},
'user_id': {
type: Sequelize.INTEGER,
references: {
model: User,
key: 'id'
}
}
},{
timestamps: false, // 关闭时间戳
freezeTableName: true // 模型名字与表名相同
})
User.hasMany(TodoFolder, { constraints: false, as: 'Folders', foreignKey: 'user_id' });
export { TodoFolder }
- user.ts
import sq from '../db';
import * as Sequelize from 'sequelize';
import * as ph from "password-hash";
const User = sq.define<any, IUser>('user', {
id:{
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
}, {
timestamps: false, // 关闭时间戳
freezeTableName: true // 模型名字与表名相同
});
interface IUser {
username: string,
email: string,
password: string
}
export default {
async createUser(user: IUser) {
return User.create({
username: user.username,
email: user.email,
password: ph.generate(user.password)
});
},
getOne: User.findById,
}
export { User }
为了确保这些 model 都已经正确可用了,我们需要编写我们的测试文件。
首先全局安装 ava 测试套件
npm i ava -g
在根目录初始化 ava 套件
ava --init
在根目录下面创建 test
文件夹,在里面新建 user.ts
一定不要把异步函数传给闭包,要不然有一些你意想不到的后果。
import UserUtil, { User } from '../src/model/user';
import { TodoFolder } from '../src/model/todoFolder';
import { Todo } from '../src/model/todo';
import test from 'ava';
import * as ph from 'password-hash';
// 错误的例子,最后一个 User 死活删除不了。
// async function deleteData(data: any[]){
// if(data.length == 0) return;
// data.forEach( async (i) => {
// await i.destroy();
// })
// }
async function deleteData(data: any[]){
if(data.length == 0) return;
const compose = data.map((i) => {
return i.destroy();
});
await Promise.all(compose);
}
async function destroyAll(){
// 因为外键依赖的原因,我们需要按顺序删除
let todos = await Todo.findAll();
let folders = await TodoFolder.findAll();
let users = await User.findAll();
await Promise.all([deleteData(todos), deleteData(folders), deleteData(users)])
console.log('aleady delete all data\n');
}
async function fackerData(){
const user = await UserUtil.createUser({
username: 'yugo',
email: 'belovedyogurt@gmail.com',
password: '123456'
});
const todoFolder = TodoFolder.build({
'user_id': user.id,
title: '生活'
});
await todoFolder.save();
const todo_1 = Todo.build({
text: '吃大餐!',
completed: true,
'todo_folder_id': todoFolder.id
});
const todo_2 = Todo.build({
text: '睡大觉!',
completed: false,
'todo_folder_id': todoFolder.id
});
await todo_1.save();
await todo_2.save();
console.log('facker data finished!\n');
}
test('test user create', async (t) => {
await destroyAll();
await fackerData();
const user = await User.find({
where: {
email: 'belovedyogurt@gmail.com'
}
});
let folders = await user.getFolders();
let todos = await folders[0].getTodos();
t.is(folders[0].title, '生活');
t.is(todos[0].completed, true);
t.is(todos[0].text, '吃大餐!');
t.is(ph.verify('123456', user.password), true)
});
每次运行测试之前必须先运行 tsc 编译好,之后再运行 ava 命令。
我们可以修改一下 package.json 的 scripts
"scripts": {
"test": "tsc && ava",
"tsc": "tsc --watch",
"start": "pm2 start src/index.js --watch src"
},
现在我们在根目录下运行
ava
到现在,我们的 Model 已经基本建立完成了,接下来我们进入业务编写环节。