当前位置: 首页 > news >正文

基于 Egg.js 框架的 Node.js 服务构建之用户管理设计

前言

近来公司需要构建一套 EMM(Enterprise Mobility Management)的管理平台,就这种面向企业的应用管理本身需要考虑的需求是十分复杂的,技术层面管理端和服务端构建是架构核心,客户端本身初期倒不需要那么复杂,作为移动端的负责人(其实也就是一个打杂的小组长),这个平台架构我自然是免不了去参与的,作为一个前端 jser 来公司这边总是接到这种不太像前端的工作,要是以前我可能会有些抵触这种业务层面需要考虑的很多,技术实现本身又不太容易积累技术成长的活。这一年我成长了太多,总是尝试着去做一些可能自己谈不上喜欢但还是有意义的事情,所以这次接手这个任务还是想好好把这个事情做好,所以想考虑参与到 EMM 服务端构建。其实话又说回来,任何事只要想去把它做好,怎么会存在有意义还是没意义的区别呢?

考虑到基于 Node.js 构建的服务目前越来越流行,也方便后续放在平台容器云上构建微服务,另外作为一个前端 jser 出身的程序员,使用 Node.js 来构建服务格外熟悉。之前学习过一段时间 Egg.js,这次毫不犹豫的选择了基于 Egg.js 框架来搭建。

为什么是 Egg.js ?

去年在 gitchat JavaScript 进阶之 Vue.js + Node.js 入门实战开发 中安利过 Egg.js,那个时候是初接触 Egg.js,但是还是被它惊艳到了,Egg 继承于 Koa,奉行『约定优于配置』,按照一套统一的约定进行应用开发,插件机制也比较完善。虽然说 Egg 继承于 Koa,大家可能觉得完全可以自己基于 Koa 去实现一套,没必要基于这个框架去搞,但是其实自己去设计一套这样的框架,最终也是需要去借鉴各家所长,时间成本上短期是划不来的。Koa 是一个小而精的框架,而 Egg 正如文档说的为企业级框架和应用而生,对于我们快速搭建一个完备的企业级应用还是很方便的。Egg 功能已经比较完善,另外如果没有实现的功能,自己根据 Koa 社区提供的插件封装一下也是不难的。

ORM 设计选型

在数据库选择上本次项目考虑使用 MySQL,而不是 MongoDB,开始使用的是 egg-mysql 插件,写了一部分后发现 service 里面写了太多东西,表字段修改会影响太多代码,在设计上缺乏对 Model 的管理,看到资料说可以引入 ORM 框架,比如 sequelize,而 Egg 官方恰好提供了 egg-sequelize 插件。

什么是 ORM ?

首先了解一下什么是 ORM ?

对象关系映射(英语:Object Relational Mapping,简称 ORM,或 O/RM,或 O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。

类似于 J2EE 中的 DAO 设计模式,将程序中的数据对象自动地转化为关系型数据库中对应的表和列,数据对象间的引用也可以通过这个工具转化为表。这样就可以很好的解决我遇到的那个问题,对于表结构修改和数据对象操作是两个独立的部分,从而使得代码更好维护。其实是否选择 ORM 框架,和以前前端是选择模板引擎还是手动拼字符串一样,ORM 框架避免了在开发的时候手动拼接 SQL 语句,可以防止 SQL 注入,另外也将数据库和数据 CRUD 解耦,更换数据库也相对更容易。

sequelize 框架

sequelize 是 Node.js 社区比较流行的一个 ORM 框架,相关文档:

  • sequelize.js 文档:http://docs.sequelizejs.com/

sequelize 使用

安装:

$ npm install --save sequelize

建立连接:

const Sequelize = require("sequelize"); // 完整用法 const sequelize = new Sequelize("database", "username", "password", { host: "localhost", dialect: "mysql" | "sqlite" | "postgres" | "mssql", operatorsAliases: false, pool: { max: 5, min: 0, acquire: 30000, idle: 10000 }, // SQLite only storage: "path/to/database.sqlite" }); // 简单用法 const sequelize = new Sequelize("postgres://user:pass@example.com:5432/dbname"); 

校验连接是否正确:

sequelize
  .authenticate()
  .then(() => { console.log("Connection has been established successfully."); }) .catch(err => { console.error("Unable to connect to the database:", err); }); 

定义 Model :

定义一个 Model 的基本语法:

sequelize.define("name", { attributes }, { options }); 

例如:

const User = sequelize.define("user", { username: { type: Sequelize.STRING }, password: { type: Sequelize.STRING } }); 

对于一个 Model 字段类型设计,主要考虑以下几个方面:

Sequelize 默认会添加 createdAt 和 updatedAt,这样可以很方便的知道数据创建和更新的时间。如果不想使用可以通过设置 attributes 的 timestamps: false;

Sequelize 支持丰富的数据类型,例如:STRING、CHAR、TEXT、INTEGER、FLOAT、DOUBLE、BOOLEAN、DATE、UUID 、JSON 等多种不同的数据类型,具体可以看文档:DataTypes。

Getters & setters 支持,当我们需要对字段进行处理的时候十分有用,例如:对字段值大小写转换处理。

const Employee = sequelize.define("employee", { name: { type: Sequelize.STRING, allowNull: false, get() { const title = this.getDataValue("title"); return this.getDataValue("name") + " (" + title + ")"; } }, title: { type: Sequelize.STRING, allowNull: false, set(val) { this.setDataValue("title", val.toUpperCase()); } } }); 

字段校验有两种类型:非空校验及类型校验,Sequelize 中非空校验通过字段的 allowNull 属性判定,类型校验是通过 validate 进行判定,底层是通过 validator.js 实现的。如果模型的特定字段设置为允许 null(allowNull:true),并且该值已设置为 null,则 validate 属性不生效。例如,有一个字符串字段,allowNull 设置为 true,validate 验证其长度至少为 5 个字符,但也允许为空。

const ValidateMe = sequelize.define("foo", { foo: { type: Sequelize.STRING, validate: { is: ["^[a-z]+$", "i"], // will only allow letters is: /^[a-z]+$/i, // same as the previous example using real RegExp not: ["[a-z]", "i"], // will not allow letters isEmail: true, // checks for email format (foo@bar.com) isUrl: true, // checks for url format (http://foo.com) isIP: true, // checks for IPv4 (129.89.23.1) or IPv6 format isIPv4: true, // checks for IPv4 (129.89.23.1) isIPv6: true, // checks for IPv6 format isAlpha: true, // will only allow letters isAlphanumeric: true, // will only allow alphanumeric characters, so "_abc" will fail isNumeric: true, // will only allow numbers isInt: true, // checks for valid integers isFloat: true, // checks for valid floating point numbers isDecimal: true, // checks for any numbers isLowercase: true, // checks for lowercase isUppercase: true, // checks for uppercase notNull: true, // won't allow null isNull: true, // only allows null notEmpty: true, // don't allow empty strings equals: "specific value", // only allow a specific value contains: "foo", // force specific substrings notIn: [["foo", "bar"]], // check the value is not one of these isIn: [["foo", "bar"]], // check the value is one of these notContains: "bar", // don't allow specific substrings len: [2, 10], // only allow values with length between 2 and 10 isUUID: 4, // only allow uuids isDate: true, // only allow date strings isAfter: "2011-11-05", // only allow date strings after a specific date isBefore: "2011-11-05", // only allow date strings before a specific date max: 23, // only allow values <= 23 min: 23, // only allow values >= 23 isCreditCard: true, // check for valid credit card numbers // custom validations are also possible: isEven(value) { if (parseInt(value) % 2 != 0) { throw new Error("Only even values are allowed!"); // we also are in the model's context here, so this.otherField // would get the value of otherField if it existed } } } } }); 

最后我们说明一个最重要的字段主键 id 的设计, 需要通过字段 primaryKey: true 指定为主键。MySQL 里面主键设计主要有两种方式:自动递增;UUID。

自动递增设置 autoIncrement: true 即可,对于一般的小型系统这种方式是最方便,查询效率最高的,但是这种不利于分布式集群部署,这种基本用过 MySQL 里面应用都用过,这里不做深入讨论。

UUID, 又名全球独立标识(Globally Unique Identifier),UUID 是 128 位(长度固定)unsigned integer, 能够保证在空间(Space)与时间(Time)上的唯一性。而且无需注册机制保证, 可以按需随时生成。据 WIKI, 随机算法生成的 UUID 的重复概率为 170 亿分之一。Sequelize 数据类型中有 UUID,UUID1,UUID4 三种类型,基于node-uuid 遵循 RFC4122。例如:

const User = sequelize.define("user", { id: { type: Sequelize.UUID, primaryKey: true, allowNull: false, defaultValue: Sequelize.UUID1 } }); 

这样 id 默认值生成一个 uuid 字符串,例如:'1c572360-faca-11e7-83ee-9d836d45ff41',很多时候我们不太想要这个 - 字符,我们可以通过设置 defaultValue 实现,例如:

const uuidv1 = require("uuid/v1"); const User = sequelize.define("user", { id: { type: Sequelize.UUID, primaryKey: true, allowNull: false, defaultValue: function() { return uuidv1().replace(/-/g, ""); } } }); 

使用 Model 对象:

对于 Model 对象操作,Sequelize 提供了一系列的方法:

  • find:搜索数据库中的一个特定元素,可以通过 findById 或 findOne;
  • findOrCreate:搜索特定元素或在不可用时创建它;
  • findAndCountAll:搜索数据库中的多个元素,返回数据和总数;
  • findAll:在数据库中搜索多个元素;
  • 复杂的过滤/ OR / NOT 查询;
  • 使用 limit(限制),offset(偏移量),order(顺序)和 group(组)操作数据集;
  • count:计算数据库中元素的出现次数;
  • max:获取特定表格中特定属性的最大值;
  • min:获取特定表格中特定属性的最小值;
  • sum:特定属性的值求和;
  • create:创建数据库 Model 实例;
  • update:更新数据库 Model 实例;
  • destroy:销毁数据库 Model 实例。

通过上述提供的一系列方法可以实现数据的增删改查(CRUD),例如:

User.create({ username: "fnord", job: "omnomnom" }) .then(() => User.findOrCreate({ where: { username: "fnord" }, defaults: { job: "something else" } }) ) .spread((user, created) => { console.log( user.get({ plain: true }) ); console.log(created); /* In this example, findOrCreate returns an array like this: [ { username: 'fnord', job: 'omnomnom', id: 2, createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) }, false ] */ }); 

egg-sequelize 插件

文档:egg-sequelize:https://github.com/eggjs/egg-sequelize

源码简析

这里我们暂时先不分析 egg 插件规范,暂时先只看看 egg-sequelize/lib/loader.js 里面的实现:

"use strict";

const path = require("path"); const Sequelize = require("sequelize"); const MODELS = Symbol("loadedModels"); const chalk = require("chalk"); Sequelize.prototype.log = function() { if (this.options.logging === false) { return; } const args = Array.prototype.slice.call(arguments); const sql = args[0].replace(/Executed \(.+?\):\s{0,1}/, ""); this.options.logging.info("[model]", chalk.magenta(sql), `(${args[1]}ms)`); }; module.exports = app => { const defaultConfig = { logging: app.logger, host: "localhost", port: 3306, username: "root", benchmark: true, define: { freezeTableName: false, underscored: true } }; const config = Object.assign(defaultConfig, app.config.sequelize); app.Sequelize = Sequelize; const sequelize = new Sequelize( config.database, config.username, config.password, config ); // app.sequelize Object.defineProperty(app, "model", { value: sequelize, writable: false, configurable: false }); loadModel(app); app.beforeStart(function*() { yield app.model.authenticate(); }); }; function loadModel(app) { const modelDir = path.join(app.baseDir, "app/model"); app.loader.loadToApp(modelDir, MODELS, { inject: app, caseStyle: "upper", ignore: "index.js" }); for (const name of Object.keys(app[MODELS

转载于:https://www.cnblogs.com/zzsdream/p/11055668.html

相关文章:

  • WPF — Grid布局中行的高度和列的高度值定义的三种形式
  • 判断两个字符串是否互为变形词
  • Blob
  • JVM学习笔记:对象的内存布局和访问定位
  • 图像质量评估-锐度
  • NKOJ4241 蚯蚓 (【NOIP2016 DAY2】)
  • spring IOC bean中注入bean
  • 【OpenJ_Bailian - 4110】圣诞老人的礼物-Santa Clau’s Gifts (贪心)
  • centos7通过yum安装docker
  • 【Beta】Scrum meeting 2
  • 在Windows下搭建Gitlab服务器
  • mysql 是如何保证在高并发的情况下autoincrement关键字修饰的列不会出现重复
  • Docker是什么?可以用Docker做什么?
  • 《坐热板凳》第九次团队作业:Beta冲刺与验收准备(补交:实验十二 第八次团队作业:软件测试与ALPHA冲刺)...
  • 14-使用Vue来实现JQuery的动画效果
  • Apache Pulsar 2.1 重磅发布
  • ES6核心特性
  • golang 发送GET和POST示例
  • Magento 1.x 中文订单打印乱码
  • maya建模与骨骼动画快速实现人工鱼
  • PHP变量
  • React as a UI Runtime(五、列表)
  • Vue.js 移动端适配之 vw 解决方案
  • 代理模式
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 目录与文件属性:编写ls
  • 设计模式(12)迭代器模式(讲解+应用)
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 写给高年级小学生看的《Bash 指南》
  • 一起参Ember.js讨论、问答社区。
  • 用Canvas画一棵二叉树
  • 优化 Vue 项目编译文件大小
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • $forceUpdate()函数
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (1)Android开发优化---------UI优化
  • (2015)JS ES6 必知的十个 特性
  • (poj1.2.1)1970(筛选法模拟)
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (简单有案例)前端实现主题切换、动态换肤的两种简单方式
  • (接口自动化)Python3操作MySQL数据库
  • (三)elasticsearch 源码之启动流程分析
  • (三)uboot源码分析
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (转)四层和七层负载均衡的区别
  • **CI中自动类加载的用法总结
  • *2 echo、printf、mkdir命令的应用
  • .aanva