【问题标题】:Model Loader for Sequelize v5 & TypescriptSequelize v5 和 Typescript 的模型加载器
【发布时间】:2019-10-17 23:58:30
【问题描述】:

之前曾在项目 (v4) 中使用过 Sequelize,但尝试使用 Sequelize v5 和 Typescript 开始一个新项目

我已按照 Sequelize 的文档了解如何在以下位置定义模型: https://sequelize.org/master/manual/typescript.html#usage-of--code-sequelize-define--code-

我现在有一个可用的 ORM,但仅在导入实际模型以供使用时,而不是通过从模型加载器中导入 db。
import { User } from "../db/models/user";

导入数据库,尝试访问db.User时返回undefined

试图弄清楚如何使用 Sequelize V5 和 Typescript 让模型加载器放置得很好,但目前它是空的。

现在,我可以看出它正在搜索 .js 文件。所以很明显它不会拿起 user.ts 文件。将其更改为 .ts 然后给我错误....

    at Sequelize.import (/node_modules/sequelize/lib/sequelize.js:486:38)
    at fs_1.default.readdirSync.filter.forEach.file (/src/db/models/index.ts:26:35)
    at Array.forEach (<anonymous>)
    at Object.<anonymous> (/src/db/models/index.ts:25:4)

我一直试图从网络搜索中获得明确的答案,但似乎是空的。试图让所有东西都玩得很好,这已经够令人头疼的了……此时我正在将迁移/播种器作为 js 文件运行,因为我不想处理 sequelize-typescript-clisequelize-typescript

src/db/models/user.ts 用户模型

import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
import { HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, Association, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize';
const db = require('./index')
import * as bcrypt from "bcryptjs";

export interface UserAttributes extends Model {
  id: string;
  email: string;
  username: string;
  password: string;
  createdAt: Date;
  updatedAt: Date;
  validatePassword(password: string): boolean;
  generateHash(password: string): string;
}

export type UserModel = typeof Model & {
  new (): UserAttributes;
};

export const User = <UserModel>db.sequelize.define("User", {
  id: {
    type: DataTypes.UUID,
    allowNull: false,
    primaryKey: true
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  username: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false,
  }
},
{
  tableName: "User",
  freezeTableName: true,
 });

 User.prototype.validatePassword = function (password: string) {

  return bcrypt.compareSync(password, this.password)
 }

 User.prototype.generateHash = function (password: string) {
    return bcrypt.hashSync(password, bcrypt.genSaltSync(10))
  }

src/db/models/index.ts 模型加载器

'use strict';

import fs from "fs";
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(module.filename);

const env = process.env.NODE_ENV || 'development';
const config = require(`${__dirname}/../config/config.json`)[env];

interface DB {
  [key: string]: any;
}

var db: DB = {};

const sequelize = new Sequelize(config.database, config.username, config.password, config);

fs.readdirSync(__dirname)
  .filter(file => {
    return (
      file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
    );
  })
  .forEach(file => {
    const model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });
// Important: creates associations based on associations defined in associate function in the model files
Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

进一步阅读https://sequelize.org/master/manual/typescript.html#usage

定义模型似乎更清晰一些(但有点多余的方式),但是从 index.js 初始化 Sequelize 时如何调用这个 init 方法?

【问题讨论】:

    标签: typescript express sequelize.js


    【解决方案1】:

    所以我已经让它工作了,但是在一个非循环的模型加载器中。我忽略了定义文档, https://sequelize.org/master/manual/typescript.html#usage-of--code-sequelize-define--code-

    这里是冗长的课堂方法:https://sequelize.org/master/manual/typescript.html#usage

    我将完成设置 2 个模型及其关联的过程,希望能帮助那些试图将 Typescript 与 Sequelize v5 集成的人。

    肯定会喜欢这种方法的反馈。

    让我们从用户和相关身份(用于访问 API)的类开始

    /src/db/models/user.ts

    import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
    import { Association, HasManyGetAssociationsMixin, HasManyAddAssociationMixin, HasManyHasAssociationMixin, HasManyCountAssociationsMixin, HasManyCreateAssociationMixin } from 'sequelize';
    import { Identity } from './identity';
    export class User extends Model {
      public id!: string; // Note that the `null assertion` `!` is required in strict mode.
      public active!: boolean;
    
      // timestamps!
      public readonly createdAt!: Date;
      public readonly updatedAt!: Date;
    
      public getIdentities!: HasManyGetAssociationsMixin<Identity>; // Note the null assertions!
      public addIdentity!: HasManyAddAssociationMixin<Identity, number>;
      public hasIdentity!: HasManyHasAssociationMixin<Identity, number>;
      public countIdentities!: HasManyCountAssociationsMixin;
      public createIdentity!: HasManyCreateAssociationMixin<Identity>;
    
      // You can also pre-declare possible inclusions, these will only be populated if you
      // actively include a relation.
      public readonly identities?: Identity[]; // Note this is optional since it's only populated when explicitly requested in code
    
      public static associations: {
        identities: Association<User, Identity>;
      };
    
    }
    
    export function initUser(sequelize: Sequelize): void {
      User.init({
        id: {
          type: DataTypes.UUID,
          primaryKey: true,
        },
        active: {
          type:DataTypes.BOOLEAN,
          defaultValue: true,
          allowNull: false
        }
      }, {
        tableName: 'User', 
        sequelize: sequelize, // this bit is important
      });
    
    
    }
    
    export function associateUser(): void {
      // Here we associate which actually populates out pre-declared `association` static and other methods.
      User.hasMany(Identity, {
        sourceKey: 'id',
        foreignKey: 'UserId',
        as: 'identities' // this determines the name in `associations`!
      });
    }
    
    

    /src/db/models/identity.ts

    import { Sequelize, Model, DataTypes, BuildOptions } from 'sequelize';
    import { Association, HasOneGetAssociationMixin, HasOneCreateAssociationMixin } from 'sequelize';
    import { User } from './user'
    
    import * as bcrypt from "bcryptjs";
    
    export class Identity extends Model {
      public id!: string; // Note that the `null assertion` `!` is required in strict mode.
      public username!: string;
      public password!: string;
      public UserId: string;
      public active!: boolean;
    
      // timestamps!
      public readonly createdAt!: Date;
      public readonly updatedAt!: Date;
    
      public getUser!: HasOneGetAssociationMixin<User>; // Note the null assertions!
    
      // You can also pre-declare possible inclusions, these will only be populated if you
      // actively include a relation.
      public readonly user?: User; // Note this is optional since it's only populated when explicitly requested in code
    
      public static associations: {
        user: Association<Identity, User>;
      };
    
      public validatePassword(password: string) : boolean {
        return bcrypt.compareSync(password, this.password)
      }
    }
    
    export function initIdentity(sequelize: Sequelize): void {
      Identity.init({
        id: {
          type: DataTypes.UUID,
          primaryKey: true,
        },
        username: {
          type: DataTypes.STRING,
          allowNull: false,
          unique: true
        },
        password: {
          type: DataTypes.STRING,
          allowNull: false
        },
        UserId: {
          type: DataTypes.UUID,
          allowNull: true
        },
        active: {
          type:DataTypes.BOOLEAN,
          defaultValue: true,
          allowNull: false
        }
      }, {
        tableName: 'Identity', 
        sequelize: sequelize, // this bit is important
      });
    
    }
    
    export function associateIdentity(): void {
      // Here we associate which actually populates out pre-declared `association` static and other methods.
      Identity.belongsTo(User, {targetKey: 'id'});
    }
    
    
    

    因此,在此之后,我们已经声明了与 Sequelize 和数据库相关的所有“虚拟”成员和函数。此外还有 init&lt;model&gt; && associate&lt;model&gt; 函数,它们将用于将所有内容联系在一起。

    注意您可能会注意到,在identity.ts 中,在关联中使用的是 UserId 而不是 userId。出于某种原因,它一直假设关联将通过 UserId,即使我使用了 userId。进行查询时,它抱怨没有列 UserId (但 userId )。因此,将其更新为大写“U”解决了它。 我不确定它为什么现在这样做。

    现在把它们联系在一起

    /src/db/index.ts

    import { initUser, associateUser } from "./user";
    import { initIdentity, associateIdentity } from "./identity";
    
    const Sequelize = require('sequelize');
    
    const env = process.env.NODE_ENV || 'development';
    const config = require(`${__dirname}/../config/config.json`)[env];
    
    
    interface DB {
      [key: string]: any;
    }
    
    const sequelize = new Sequelize(config.database, config.username, config.password, config);
    
    initUser(sequelize);
    initIdentity(sequelize)
    
    associateUser();
    associateIdentity();
    
    const db = {
      sequelize,
      Sequelize,
      User: sequelize.models.User,
      Identity: sequelize.models.Identity
    }
    
    module.exports = db;
    

    平时要做的模型加载,进入目录,找到所有模型,然后将它们导入到sequelize中。现在就像我之前说的,尝试在模型类中使用 define 会在尝试通过此模型加载器时导致问题,因为非 Typescript 版本总是寻找 *.js 而不是 *.ts。更改为 *.ts 会使 define 调用中的所有内容都崩溃。 (更不用说,因为所有这些代码都会被转译成 js 文件,这不会反过来导致问题吗?)

    但是正如你所看到的,我是手动而不是循环执行所有操作。可能有更好的循环方式来做到这一点,但目前这已经足够了。

    通过调用它们的init&lt;model&gt; 函数来初始化模型。初始化后通过associate&lt;model&gt;的函数调用创建它们的关联

    在启动我的快速服务器之前,我需要索引文件,这一切都开始了。轰隆隆。

    关于我的方法的其他注意事项 我不想安装超出我需要的软件包。所以我避开了 sequelize-typescript 和 sequelize-typescript-cli。这意味着我所有的种子文件和迁移文件都需要手工制作,不使用 cli(真的没那么糟糕),并且不是 *.ts 而是 *.js。

    示例: 20191017135846-create-identity.js

    'use strict'
    module.exports = {
      up: (queryInterface, Sequelize) => {
        return queryInterface.createTable({tableName:'Identity'}, {
          id: {
            type: Sequelize.UUID,
            defaultValue: Sequelize.UUIDV4,
            allowNull: false,
            autoIncrement: false,
            primaryKey: true,
          },
          username: {
            type: Sequelize.STRING,
            allowNull: false,
            unique: true,
          },
          password: {
            type: Sequelize.STRING,
            allowNull: false,
          },
          UserId: {
            type: Sequelize.UUID,
            references: {
              model: 'User', // name of Target model
              key: 'id', // key in Target model that we're referencing
            },
            onUpdate: 'CASCADE',
            onDelete: 'SET NULL',
          },
          active: {
            type: Sequelize.BOOLEAN,
            defaultValue: true,
            allowNull: false,
          },
          createdAt: {
            allowNull: false,
            type: Sequelize.DATE,
            defaultValue: Sequelize.NOW
          },
          updatedAt: {
            allowNull: false,
            type: Sequelize.DATE,
            defaultValue: Sequelize.NOW
          },
        })
      },
      down: (queryInterface) => {
        return queryInterface.dropTable({tableName:'Identity', schema:'public'})
      }
    }
    

    20191015141822-seed-users.js

    'use strict'
    var moment = require('moment');
    var uuidv4 = require('uuid/v4');
    const bcrypt = require('bcryptjs');
    
    module.exports = {
      up: async (queryInterface) => {   
          // User
          const user1Id = uuidv4();
          await queryInterface.bulkInsert('User', 
            [
              {
                id:user1Id,
                createdAt: new Date( moment.utc().format() ), 
                updatedAt: new Date( moment.utc().format() )
              }
            ], 
          )
          await queryInterface.bulkInsert('Identity', 
            [
              {
                id:uuidv4(),
                username: "user1",
                password: bcrypt.hashSync('password', bcrypt.genSaltSync(10)),
                UserId: user1Id,
                createdAt: new Date( moment.utc().format() ), 
                updatedAt: new Date( moment.utc().format() )
              }
            ], 
          )
    
          const user2Id = uuidv4();
          await queryInterface.bulkInsert('User', 
            [
              {
                id:user2Id,
                createdAt: new Date( moment.utc().format() ), 
                updatedAt: new Date( moment.utc().format() )
              }
            ], 
          )
          await queryInterface.bulkInsert('Identity', 
            [
              {
                id:uuidv4(),
                username: "user2",
                password: bcrypt.hashSync('password', bcrypt.genSaltSync(10)),
                UserId: user2Id,
                createdAt: new Date( moment.utc().format() ), 
                updatedAt: new Date( moment.utc().format() )
              }
            ], 
          )
    
          const user3Id = uuidv4();
          await queryInterface.bulkInsert('User', 
            [
              {
                id:user3Id,
                createdAt: new Date( moment.utc().format() ), 
                updatedAt: new Date( moment.utc().format() )
              }
            ], 
          )
          await queryInterface.bulkInsert('Identity', 
            [
              {
                id:uuidv4(),
                username: "user3",
                password: bcrypt.hashSync('password', bcrypt.genSaltSync(10)),
                UserId: user3Id,
                createdAt: new Date( moment.utc().format() ), 
                updatedAt: new Date( moment.utc().format() )
              }
            ], 
          )
    
    
      },
      down: async (queryInterface) => {
        await queryInterface.bulkDelete({ tableName: 'User'}, null, {})
      }
    }
    

    此时你可以运行哪个

    sequelize db:migrate
    sequelize db:seed:all
    

    一切正常,可以访问数据库。

    现在使用类/打字稿我注意到将模型添加到和导出的 db 对象是多余的......

    我可以通过导入访问所需的模型

    从“../db/models/user”导入{用户} 或者 要求('./db/models/index')

    然后我可以执行 User.findAll() 或使用其他导入 db.User.findAll()

    【讨论】:

    • 感谢您抽出宝贵时间回答您自己的问题。你需要反馈,我想给你反馈,但不是现在,我很忙。你能在几周后提醒我吗?请随时在此处或在 github 问题 (@papb) 上联系我。
    猜你喜欢
    • 2019-08-05
    • 2019-11-07
    • 2019-11-29
    • 1970-01-01
    • 2019-01-23
    • 2015-10-18
    • 2020-05-08
    • 1970-01-01
    • 2020-09-09
    相关资源
    最近更新 更多