egg.js typescript 开发采坑记录

常年使用sails 开发node 后端 苦不堪言,文档缺失,迭代较慢,并且周边也不是那么完善,虽说基本能满足需求 但是还是少了灵活性。 待到egg 推出之时就在观察,最近新开项目遂采用egg - ts

  • egg-mysql
    这玩意没有index.d.ts .所以 你想使用app.mysql 是编译不过的,所以要用 ts 的merge 来给Application上挂载一个mysql 我们可以这么做
./typings/index.d.ts 写入
declare module 'egg' {
  interface mysql {
    get(tableName: String, find: {}): Promise<Any>

    query(sql: String, values: Any[]): Promise<Any>
  }
  interface Application {
    mysql: mysql;
  }
}
可以在上面给mysql 加点方法这样就有提示了。

egg-sequelize
为node中较为常用的orm 框架。
这个玩意我们可以细说。
首先你要做一个model 来定义这个表中的字段,但是为了方便开发,我们得加点料。

  • /app/model/model.ts 这里写一个基类的basemodel 这样以后所有的model都可以用到基类
import { Application } from 'egg';
import { snakeCase } from 'lodash';
import * as moment from 'moment';
import { DefineAttributes, SequelizeStatic } from 'sequelize';
export default function BaseModel(
  app: Application,
  table: string,
  attributes: DefineAttributes,
  options: object = {},
) {
  const { Op, UUID, UUIDV4 } = app.Sequelize;

  const modelSchema = app.model.define(table, {
    id: {
      type: UUID,
      unique: true,
      primaryKey: true,
      allowNull: false,
      defaultValue: UUIDV4,
    },
    ...attributes,
    ...getDefaultAttributes(options, app.Sequelize),
  }, {
      timestamps: true, // 自动维护时间戳 [ created_at、updated_at ]
      underscored: true, // 不使用驼峰样式自动添加属性,而是下划线样式 [ createdAt => created_at ]
      // 禁止修改表名,默认情况下,sequelize将自动将所有传递的模型名称(define的第一个参数)转换为复数
      // 但是为了安全着想,复数的转换可能会发生变化,所以禁止该行为
      freezeTableName: false,
      ...options,
      scopes: {
        // 定义全局作用域,使用方法如: .scope('onlyTrashed') or .scope('onlyTrashed1', 'onlyTrashed12') [ 多个作用域 ]
        onlyTrashed: {
          // 只查询软删除数据
          where: {
            deleted_at: {
              [Op.not]: null,
            },
          },
        },
      },
    });

  modelSchema.getAttributes = (): string[] => {
    return Object.keys(attributes);
  };

  modelSchema.findAttribute = (attribute: string): object | undefined => {
    return (attributes as any)[attribute];
  };

  modelSchema.fillable = (): string[] => {
    return [];
  };

  modelSchema.hidden = (): string[] => {
    return [];
  };

  modelSchema.visible = (): string[] => {
    return [];
  };

  return modelSchema;
}

function getDefaultAttributes(options: object, sequelize: SequelizeStatic): object {
  const { DATE } = sequelize;

  const defaultAttributes = {
    created_at: {
      type: DATE,
      get() {
        return moment((this as any).getDataValue('created_at')).format('YYYY-MM-DD HH:mm:ss');
      },
    },
    updated_at: {
      type: DATE,
      get() {
        return moment((this as any).getDataValue('updated_at')).format('YYYY-MM-DD HH:mm:ss');
      },
    },
  };

  // 需要从 options 读取的配置信息,用于下方做过滤的条件
  const attributes = ['createdAt', 'updatedAt', 'deletedAt'];

  Object.keys(options).forEach((value: string) => {
    if (attributes.includes(value) && (options as any)[value] === false) {
      delete (defaultAttributes as any)[snakeCase(value)];
    }
  });
  return defaultAttributes || {};
}

  • /app/model/index.d.ts 将model 挂载到application上 同时给model 扩展方法。
import { User } from './user';
declare module 'egg' {
  interface Application {
    Sequelize: SequelizeStatic
    model: Sequelize
  }

  interface Context {
    model: {
      User: Model<User, {}>;
    }
  }
}
Mode
declare module 'sequelize' {
  interface Model<TInstance, TAttributes> {
    fillable(): string[];
    hidden(): string[];
    visible(): string[];
    getAttributes(): string[];
    findAttribute(attribute: string): object | undefined;

    getDataValue(key: string): any;

    setDataValue(key: string, value: any): void;
  }
}
  • app/model/user.ts
import { Application } from 'egg';
import BaseModel from './model';

export default function User(app: Application) {
  const { INTEGER, DATE, STRING, BOOLEAN } = app.Sequelize;

  const modelSchema = BaseModel(app, 'users', {
    name: {
      type: STRING(32),
      unique: true,
      allowNull: false,
      comment: '用户名',
    },
    email: {
      type: STRING(64),
      unique: true,
      allowNull: true,
      comment: '邮箱地址',
    },

    phone: {
      type: STRING(20),
      unique: true,
      allowNull: true,
      comment: '手机号码',
    },
    avatar: {
      type: STRING(150),
      allowNull: true,
      comment: '头像',
    },
    real_name: {
      type: STRING(30),
      allowNull: true,
      comment: '真实姓名',
    },
    signature: {
      type: STRING(255),
      allowNull: true,
      comment: '签名',
    },
    notify_count: {
      type: INTEGER,
      allowNull: false,
      defaultValue: 0,
      comment: '消息通知个数',
    },
    status: {
      type: BOOLEAN,
      allowNull: false,
      defaultValue: 1,
      comment: '用户状态: 1 正常; 0 禁用',
    },
    password: {
      type: STRING(255),
      allowNull: false,
    },
    last_actived_at: DATE,
  }, {
      paranoid: true,

      setterMethods: {
        async password(value: any) {
          (this as any).setDataValue('password', await app.createBcrypt(value))
        },
      },
    });
  return modelSchema;
}

这里我们就完成了一个model的开发

我们说这是采坑 那么就要说哪里坑了? 上面除了自己搞index.d.ts有点麻烦之外其他看着还好。
注意了 坑来了~
用sequelize 那么migration 也要用了吧?

我们再package.json 内 的script 加入

"migrate:new": "egg-sequelize migration:create",
    "migrate:up": "egg-sequelize db:migrate",
    "migrate:down": "egg-sequelize db:migrate:undo"

执行命令npm run migration:create -- --name user生成一个migrations 文件夹,还会有一个空的文件 ....
1.你需要自己把user.ts 内的model复制一份过去。
2.你需要执行 ·npm run migration:up·



Sequelize CLI [Node: 8.11.3, CLI: 4.0.0, ORM: 4.38.0]

Loaded configuration file "node_modules/egg-sequelize/lib/database.js".
Using environment "development".

ERROR: Dialect needs to be explicitly supplied as of v4.0.0

采坑了吧!
我们看源文件 是需要从配置里 config.default.js读取 而非 config.default.ts,所以我们需要再建一个文件config.default.js.bak 这样做的目的是为了避免被egg 把js文件清除。 改bak 为 普通的js 再执行 up 会发现终于成功了。。。。

2018-11-05
不知为何 egg-sequlize 安装不上 (命令行命令无用),隧只能做些改变 使用 sequlize-cli 这一套。。。
有个注意项: 需要自己写个 config.json给 seqlize去读
这个问题还带出来个 mysql 字符集问题,原因就是config,json 没有指定为utf8mb4 所以默认为latin,导致中文或者emoji无法存入。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Egg.js是阿里开源的基于Koa的一个企业级Node框架,具体介绍在这里不做详细说明,想要了解更多可以查看Egg...
    白酒__阅读 8,979评论 0 3
  • 前段时间听说,办公室里的一个女生因为失恋,不甘心就这么跟前任分开,跑去借酒消愁,喝到胃出血,在医院躺了半个月。 住...
    设计师萨克斯阅读 236评论 0 0
  • 昨天聊的太嗨到很晚就没有写。今天必须补上。最需要记录的必须是在张琪家过夜。第一次来到属于一个人的小空间,真的好棒。...
    Sanity娜娜阅读 184评论 1 1
  • 王小波在《小说的艺术》中说道:“写小说则需要深得虚构之美,也需要些无中生有的才能。”《水浒传》第二十三回“...
    文可清心也阅读 504评论 0 2
  • 风呼啸,雨瓢泼,天昏地暗雷电摧,风吹树摇行人稀,万千雨点显威风。
    纪尚凯阅读 145评论 0 0