安装 password-hash 与测试

用来处理用户密码,不存储用户的明文密码,而是存储加密后的。

  1. npm i password-hash -S

在 src 目录下面创建 db.ts , 定义模型我们使用define方法, 这些都是有代码提示的。

  1. import * as Sequelize from 'sequelize';
  2. import * as ph from 'password-hash';
  3. import { resolve } from 'path';
  4. const sq = new Sequelize('db', null, null,{
  5. dialect: 'sqlite',
  6. storage: resolve(__dirname, '../storage/db.sqlite3')
  7. });
  8. var User = sq.define('user', {
  9. id:{
  10. type: Sequelize.INTEGER,
  11. primaryKey: true,
  12. autoIncrement: true
  13. },
  14. username: {
  15. type: Sequelize.STRING,
  16. },
  17. email: {
  18. type: Sequelize.STRING
  19. },
  20. password: {
  21. type: Sequelize.STRING
  22. }
  23. }, {
  24. timestamps: false,
  25. freezeTableName: true // Model tableName will be the same as the model name
  26. });
  27. User.create({
  28. username: 'yugo',
  29. email: 'belovedyogurt@gmail.com',
  30. password: ph.generate('123456')
  31. }).then(console.log)

首先编译

  1. tsc

之后再运行

  1. node dist/db.js

这样我们就可以在数据里面看到我们的数据了。

编写 Model 与 ava 测试文件

之前我们是把js文件编译到dist目录下管理,假如我们的test下面的测试文件也跑到dist目录下面的话就感觉很别扭,因为它没有在它该在的位置上,也就是不在其位而谋其政,尽管我们可以通过写2个 tsconfig.json 和 配置 exclude 选项让它们分开编译来解决这个问题,我们还不如就让 js 和 ts 在同一目录下来的简单。

修改我们的 db.ts 我们让他仅仅只提供数据库连接。

  1. import * as Sequelize from 'sequelize';
  2. import { resolve } from 'path';
  3. const sq = new Sequelize('db', null, null,{
  4. dialect: 'sqlite',
  5. storage: resolve(__dirname, '../storage/db.sqlite3')
  6. });
  7. export default sq;

修改 tsconfig.json

在根目录下面新建一个 types 文件夹,在这里面你可以写你的代码提示文件,但是并不会被转换成模块。

  1. {
  2. "compilerOptions": {
  3. "module": "commonjs",
  4. "target": "es5",
  5. "noImplicitAny": false,
  6. "sourceMap": true,
  7. "lib": [
  8. "es6",
  9. "es7"
  10. ],
  11. "typeRoots": [
  12. "./types"
  13. ]
  14. }
  15. }

在 src 目录下面创建 model 文件夹,新建 todo.tstodoFolder.tsuser.ts 三个文件。

model 下面都是存放我们的模型,通过 hasMany 定义模型之间的关系。

  • todo.ts
  1. import sq from '../db';
  2. import * as Sequelize from 'sequelize';
  3. import { TodoFolder } from './todoFolder'
  4. const Todo = sq.define<any, any>('todo', {
  5. id: {
  6. type: Sequelize.INTEGER,
  7. primaryKey: true,
  8. autoIncrement: true
  9. },
  10. text: {
  11. type: Sequelize.TEXT
  12. },
  13. completed: {
  14. type: Sequelize.BOOLEAN
  15. },
  16. 'todo_folder_id': {
  17. type: Sequelize.INTEGER,
  18. references: {
  19. model: TodoFolder,
  20. key: 'id'
  21. }
  22. }
  23. },{
  24. freezeTableName: true // 模型名字与表名相同
  25. });
  26. TodoFolder.hasMany(Todo, { as: 'Todos', foreignKey: 'todo_folder_id' })
  27. export { Todo }
  • todoFolder.ts
  1. import sq from '../db';
  2. import * as Sequelize from 'sequelize';
  3. import {User} from './user';
  4. const TodoFolder = sq.define<any, any>('todo_folder', {
  5. id: {
  6. type: Sequelize.INTEGER,
  7. primaryKey: true,
  8. autoIncrement: true
  9. },
  10. title: {
  11. type: Sequelize.TEXT
  12. },
  13. 'user_id': {
  14. type: Sequelize.INTEGER,
  15. references: {
  16. model: User,
  17. key: 'id'
  18. }
  19. }
  20. },{
  21. timestamps: false, // 关闭时间戳
  22. freezeTableName: true // 模型名字与表名相同
  23. })
  24. User.hasMany(TodoFolder, { constraints: false, as: 'Folders', foreignKey: 'user_id' });
  25. export { TodoFolder }
  • user.ts
  1. import sq from '../db';
  2. import * as Sequelize from 'sequelize';
  3. import * as ph from "password-hash";
  4. const User = sq.define<any, IUser>('user', {
  5. id:{
  6. type: Sequelize.INTEGER,
  7. primaryKey: true,
  8. autoIncrement: true
  9. },
  10. username: {
  11. type: Sequelize.STRING,
  12. },
  13. email: {
  14. type: Sequelize.STRING
  15. },
  16. password: {
  17. type: Sequelize.STRING
  18. }
  19. }, {
  20. timestamps: false, // 关闭时间戳
  21. freezeTableName: true // 模型名字与表名相同
  22. });
  23. interface IUser {
  24. username: string,
  25. email: string,
  26. password: string
  27. }
  28. export default {
  29. async createUser(user: IUser) {
  30. return User.create({
  31. username: user.username,
  32. email: user.email,
  33. password: ph.generate(user.password)
  34. });
  35. },
  36. getOne: User.findById,
  37. }
  38. export { User }

为了确保这些 model 都已经正确可用了,我们需要编写我们的测试文件。

首先全局安装 ava 测试套件

  1. npm i ava -g

在根目录初始化 ava 套件

  1. ava --init

在根目录下面创建 test 文件夹,在里面新建 user.ts

一定不要把异步函数传给闭包,要不然有一些你意想不到的后果。

  1. import UserUtil, { User } from '../src/model/user';
  2. import { TodoFolder } from '../src/model/todoFolder';
  3. import { Todo } from '../src/model/todo';
  4. import test from 'ava';
  5. import * as ph from 'password-hash';
  6. // 错误的例子,最后一个 User 死活删除不了。
  7. // async function deleteData(data: any[]){
  8. // if(data.length == 0) return;
  9. // data.forEach( async (i) => {
  10. // await i.destroy();
  11. // })
  12. // }
  13. async function deleteData(data: any[]){
  14. if(data.length == 0) return;
  15. const compose = data.map((i) => {
  16. return i.destroy();
  17. });
  18. await Promise.all(compose);
  19. }
  20. async function destroyAll(){
  21. // 因为外键依赖的原因,我们需要按顺序删除
  22. let todos = await Todo.findAll();
  23. let folders = await TodoFolder.findAll();
  24. let users = await User.findAll();
  25. await Promise.all([deleteData(todos), deleteData(folders), deleteData(users)])
  26. console.log('aleady delete all data\n');
  27. }
  28. async function fackerData(){
  29. const user = await UserUtil.createUser({
  30. username: 'yugo',
  31. email: 'belovedyogurt@gmail.com',
  32. password: '123456'
  33. });
  34. const todoFolder = TodoFolder.build({
  35. 'user_id': user.id,
  36. title: '生活'
  37. });
  38. await todoFolder.save();
  39. const todo_1 = Todo.build({
  40. text: '吃大餐!',
  41. completed: true,
  42. 'todo_folder_id': todoFolder.id
  43. });
  44. const todo_2 = Todo.build({
  45. text: '睡大觉!',
  46. completed: false,
  47. 'todo_folder_id': todoFolder.id
  48. });
  49. await todo_1.save();
  50. await todo_2.save();
  51. console.log('facker data finished!\n');
  52. }
  53. test('test user create', async (t) => {
  54. await destroyAll();
  55. await fackerData();
  56. const user = await User.find({
  57. where: {
  58. email: 'belovedyogurt@gmail.com'
  59. }
  60. });
  61. let folders = await user.getFolders();
  62. let todos = await folders[0].getTodos();
  63. t.is(folders[0].title, '生活');
  64. t.is(todos[0].completed, true);
  65. t.is(todos[0].text, '吃大餐!');
  66. t.is(ph.verify('123456', user.password), true)
  67. });

每次运行测试之前必须先运行 tsc 编译好,之后再运行 ava 命令。

我们可以修改一下 package.json 的 scripts

  1. "scripts": {
  2. "test": "tsc && ava",
  3. "tsc": "tsc --watch",
  4. "start": "pm2 start src/index.js --watch src"
  5. },

现在我们在根目录下运行

  1. ava

到现在,我们的 Model 已经基本建立完成了,接下来我们进入业务编写环节。