一、创建安装egg脚手架
1.安装脚手架
npm init egg --type=simple
2.安装依赖
npm i
3.运行
npm run dev
二、配置数据库
1.安装数据库插件
npm install --save egg-sequelize mysql2
2.开启插件
config/plugin.js
中引入 egg-sequelize 插件
sequelize: {
enable: true,
package: 'egg-sequelize',
}
3.配置数据库
文件路径:config/config.default.js
config.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
username: 'root',
password: 'root',
port: 3306,
database: '数据库表名',
// 中国时区
timezone: '+08:00',
define: {
// 取消数据表名复数
freezeTableName: true,
// 自动写入时间戳 created_at updated_at
timestamps: true,
// 字段生成软删除时间戳 deleted_at
// paranoid: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
// deletedAt: 'deleted_at',
// 所有驼峰命名格式化
underscored: true
}
};
4.创建数据库(也可以自己手动创建)
用命令创建
1.安装插件
npm install --save-dev sequelize-cli
2.创建配置文件
路径:根目录下穿件.sequelizerc文件
'use strict';
const path = require('path');
module.exports = {
config: path.join(__dirname, 'database/config.json'),
'migrations-path': path.join(__dirname, 'database/migrations'),
'seeders-path': path.join(__dirname, 'database/seeders'),
'models-path': path.join(__dirname, 'app/model'),
};
3.初始化 Migrations 配置文件和目录
以下两个命令在整个项目中只需要运行一次
npx sequelize init:config
npx sequelize init:migrations
运行完以上命令后根目录下会多出一个database文件夹
database文件夹下会有migarations目录和config.json文件
config.json文件中有三个对象
development:代表开发环境
test:代表测试环境
production:代表生产环境
4.配置以上三个环境中的数据库参数
{
"development": {
"username": "root",
"password": root,
"database": "数据库名",
"host": "127.0.0.1",
"dialect": "mysql",
"timezone": "+08:00"
}
//以下两个同上
}
5.创建数据库
npx sequelize db:create
这时候数据库中会多了一个数据库
库名为三个环境中database中配置的名称
6.创建数据表
创建数据表
npx sequelize migration:generate --name=init-user
执行完以上命令后migarations目录下会多出一个xxxxxx-init-user.js文件
7.配置数据表
database/migarations/xxxxxx-init-user.js文件
'use strict';
module.exports = {
async up (queryInterface, Sequelize) {
const {INTEGER,STRING,DATE,ENUM} = Sequelize;
await queryInterface.createTable('user',{
id:{
type:INTEGER(20).UNSIGNED, //UNSIGNED代表无符号
primaryKey:true, //主键
autoIncrement:true //自动递增
},
username:{
type:STRING(30),
allowNull:false, //是否允许为空
defaultValue:'', //默认值为空
comment:'用户名称', //备注
unique:true //是否是唯一的
},
password:{
type:STRING(200),
allowNull:false,
defaultValue:''
},
avatar_url:{
type:STRING(200),
allowNull:false,
defaultValue:''
},
sex:{
type:ENUM,
values:['男','女','保密'],
allowNull:true,
defaultValue:'男',
comment:'用户性别'
},
created_at:DATE,
updated_at:DATE
})
},
async down (queryInterface, Sequelize) {
await queryInterface.dropTable('user')
}
};
# 升级数据库
npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all
此时数据表已经创建完成。
8.创建数据模型
用途:某个表的增删改查
创建:app/model/表名.js
'use strict';
module.exports = app => {
const { STRING,INTEGER, DATE, ENUM } = app.Sequelize;
const User = app.model.define('user',{
id:{
type:INTEGER(20).UNSIGNED, //UNSIGNED代表无符号
primaryKey:true, //主键
autoIncrement:true //自动递增
},
username:{
type:STRING(30),
allowNull:false, //是否允许为空
defaultValue:'', //默认值为空
comment:'用户名称', //备注
unique:true //是否是唯一的
},
password:{
type:STRING(200),
allowNull:false,
defaultValue:'',
/**
* 修改器
set(val){
//可以在这对密码进行加密,然后
let hash = 加密命令
this.setDataValue('password',hash)
}
* */
},
avatar_url:{
type:STRING(200),
allowNull:false,
defaultValue:''
},
sex:{
type:ENUM,
values:['男','女','保密'],
allowNull:true,
defaultValue:'男',
comment:'用户性别'
},
created_at:{
type:DATE,
//转换成时间戳
get(){
const val = this.getDataValue('created_at');
return (new Date(val)).getTime();
}
},
updated_at:{
type:DATE,
//转换成时间戳
get(){
const val = this.getDataValue('updated_at');
return (new Date(val)).getTime();
}
}
});
return User;
}
其他参数,随用随添加
//自定义表名
'freezeTableName':true,
'tableName':'xxxxxx',
//是否需要增加createdAt、updatedAt、deletedAt字段
'timestamps':true,
//不需要createdAt字段
'createdAt':false,
//将updatedAt字段改革名
'updatedAt':'新名',
//将deletedAt字段改名
//同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,二十设置deletedAt为当前时间)
'deletedAt':'dtimm',
'paranoid':true,
//此参数需要配置在app/model/控制器/updated:DATE
// },{
//这里面
//}
9.增删改查
await this.app.model.User.方法名
增
//单个
create(obj)
//批量
bulkCreate(Array)
删
//删除单个
async delet(){
let id = this.ctx.params.id ? parseInt(this.ctx.params.id) : 0;
let data = await this.app.model.User.findByPk(id);
if(!data){
return this.ctx.body = {
msg:'fail',
data:"该数据不存在。"
};
}
let res = await data.destroy();
this.ctx.body = {
msg:'ok',
data:res
}
};
//删除多个
async deletMore(){
const Op = this.app.model.Sequelize.Op;
let data = await this.app.model.User.destroy({
where:{
id:{
[Op.lte]:7 //删除id小于7的数据
}
}
});
this.ctx.body = {
msg:'ok',
data:res
}
};
改
//连接中需要传要修改的id过来
//首先获取id
let id = this.ctx.params.id ? parseInt(this.ctx.params.id) : 0;
//获取指定的记录
let data = await this.app.model.User.findByPk(id);
let params = this.ctx.request.body;
//判断数据是否存在
if(!data){
return this.ctx.body = {
msg:"fail",
data:"该记录不存在。"
}
}
//let res = await data.update(params,{fielda:['username']});
//添加{fielda:['username']}标识仅仅修改username字段,其余的均不更改
let res = await data.update(params);
this.ctx.body = {
msg:"ok",
data:res
}
}
查
//单个,无条件
findByPk(parseInt(id)); //parseInt转换成int类型
//单个,加条件过滤
findOne({
where:{
id:2,
sex:"女",
条件....
}
})
const Op = this.app.Sequelize.Op;
findAndCountAll({
where:{
username:{
[Op.like] : "%5%"
},
条件:{
.....
}
}
});
//全部
findAll()
可以加条件
方法:
过滤参数
只显示数组中的参数:
attributes:['username','sex',...]
相反:
attributes:{
exclude:['password'] //除了password不显示,其余均显示
}
//排序,如果没作用,调换一下条件顺序试试
order:[
['id','DESC'], //DESC降序,ASC升序
条件...
]
//分页
首先在连接中需要加入页码参数?page=1
在查询的开始就要拿到page的参数
let page = this.ctx.quer.page ? parseInt(this.ctx.quer.page) : 1; //页码
let limit = 5;
let offset = (page - 1) * 5; //计算偏移
//以下写到findAll()方法中的一级和where是同级
offset, //偏移,从哪开始
limit //显示5条
//查全部并计数
findAndCountAll()
Op参数
[Op.and]: {a: 5} // 且 (a = 5)
[Op.or]: [{a: 5}, {a: 6}] // (a = 5 或 a = 6)
[Op.gt]: 6, // id > 6
[Op.gte]: 6, // id >= 6
[Op.lt]: 10, // id < 10
[Op.lte]: 10, // id <= 10
[Op.ne]: 20, // id != 20
[Op.eq]: 3, // = 3
[Op.not]: true, // 不是 TRUE
[Op.between]: [6, 10], // 在 6 和 10 之间
[Op.notBetween]: [11, 15], // 不在 11 和 15 之间
[Op.in]: [1, 2], // 在 [1, 2] 之中
[Op.notIn]: [1, 2], // 不在 [1, 2] 之中
[Op.like]: '%hat', // 包含 '%hat'
[Op.notLike]: '%hat' // 不包含 '%hat'
[Op.iLike]: '%hat' // 包含 '%hat' (不区分大小写) (仅限 PG)
[Op.notILike]: '%hat' // 不包含 '%hat' (仅限 PG)
[Op.regexp]: '^[h|a|t]' // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG)
[Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (仅限 PG)
[Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG)
[Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike
[Op.overlap]: [1, 2] // && [1, 2] (PG数组重叠运算符)
[Op.contains]: [1, 2] // @> [1, 2] (PG数组包含运算符)
[Op.contained]: [1, 2] // <@ [1, 2] (PG数组包含于运算符)
[Op.any]: [2,3] // 任何数组[2, 3]::INTEGER (仅限PG)
[Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 本例使用 PG
三、关闭csrf开启跨域
1.安装save
npm i egg-cors --save
2.配置插件
文件路径:config/plugin.js
//跨域
cors:{
enable: true,
package: 'egg-cors',
},
3.关闭csrf开启跨域
文件路径:config/config.default.js
全局关闭
config.security = {
// 关闭 csrf
csrf: {
enable: false,
},
// 跨域白名单
domainWhiteList: [],
};
// 允许跨域的方法
config.cors = {
origin: '*',
allowMethods: 'GET, PUT, POST, DELETE, PATCH'
};
//写在这的上边
//return{
//...config,
//...xxxxx
//}
过滤关闭(这里表示凡是以/api开头的路由都不需要经过csrf验证)
config.security = {
//关闭 csrf
//api这个就不需要用csrf验证,所以用这段代码可以排除掉以/api开头的请求
csrf: {
headerName:'x-csrf-token',
ignore:ctx => {
return ctx.request.url.startsWith('/api')
}
},
// 跨域白名单
domainWhiteList: [],
};
// 允许跨域的方法
config.cors = {
origin: '*',
allowMethods: 'GET, PUT, POST, DELETE, PATCH'
};
四、中间件
错误异常处理
1.新建文件夹 app/middleware
2.新建中间件文件 app/middleware/error_hendler.js
module.exports = () => {
return async function errorHandler(ctx,next){
try {
await next();
}catch(err){
ctx.app.emit('error',err,ctx);
const status = err.status || 500;
const error = status === 500 && ctx.app.config.env === 'prod'
? 'Internal Server Error'
: err.message;
ctx.body = { error};
if(status === 422){
ctx.body.detail = err.errors;
}
ctx.status = status
}
};
};
4.开启中间件
app/config/config.default.js
// add your middleware config here
config.middleware = ['errorHendler'];
5.配置中间件
app/config/config.default.js
在config.middleware = ['errorHendler'];下继续写配置
config.errorHendler= {
enable:true, //是否开启中间件
match:["/user/list"] //设置哪些路由走中间件
//ignore:["/user/list"] //设置哪些路由不走中间件
/**
1.match和ignore不能同时使用
2.例如:match:["/user"],只要包含/user的任何页面都生效
**/
//match和ignore支持多种类型配置方式:字符串、正则、函数(推荐)
match(ctx){
//只有ios设备才开启
const res = /iphone|ipad|ipod/i;
return res.test(ctx.get('user-agent'));
}
};
五、参数验证
安装插件
npm i egg-valparams --save
配置插件
//config/plugin.js
valparams : {
enable : true,
package: 'egg-valparams'
},
//app/config/config.default.js
config.valparams = {
locale : 'zh-cn',
throwError: true
};
//使用
/*获取传过来的参数*/
let params = this.ctx.request.body;
this.ctx.validate({
username:{
type: 'string', //数据类型
required: true, //是否必填
desc: '用户名' //字段描述
},
password:{
type: 'string',
required: true,
desc: '密码'
},
sex:{
type:'string',
required: false,
defValue: '保密', //默认值
desc: '性别'
}
});
//写入数据库
ValParams API 说明
参数验证处理
Valparams.setParams(req, params, options);
Param | Type | Description | Example |
---|---|---|---|
req | Object | request 对象,这里我们就是取相应的三种请求的参数进行参数验证 | {params, query, body} |
params | Object | 参数的格式配置 { pname: {alias, type, required, range: {in, min, max, reg, schema }, defValue, trim, allowEmptyStr, desc[, detail] } } | {sysID : {alias:'sid',type: 'int', required: true, desc: '所属系统id'}} |
params[pname] | String | 参数名 | |
params[pname].alias | String | 参数别名,可以使用该参数指定前端使用的参数名称 | |
params[pname].type | String | 参数类型 | 常用可选类型有 int, string, json 等,其他具体可见下文或用 Valparams.vType 进行查询 |
params[pname].required | Boolean | 是否必须 | |
params[pname].range | Object | 参数范围控制 | {min: '112.80.248.10', max: '112.80.248.72'} |
params[pname].range.min | ALL | 最小值、最短、最早(不同 type 参数 含义有所差异) | |
params[pname].range.max | ALL | 最大值、最长、最晚(不同 type 参数 含义有所差异) | |
params[pname].range.in | Array | 在XX中,指定参数必须为其中的值 | |
params[pname].range.reg | RegExp | 正则判断,参数需要符合正则 | |
params[pname].range.schema | Object | jsonSchema,针对JSON类型参数有效,使用ajv对参数进行格式控制 | |
params[pname].defValue | ALL | 默认值,没传参数或参数验证出错时生效,此时会将该值赋值到相应参数上 | |
params[pname].trim | Boolean | 是否去掉参数前后空格字符,默认false | |
params[pname].allowEmptyStr | Boolean | 是否允许接受空字符串,默认false | |
params[pname].desc | String | 参数含义描述 | |
options | Object | 参数关系配置 | |
options.choices | Array | 参数挑选规则 | [{fields: ['p22', 'p23', 'p24'], count: 2, force: true}] 表示'p22', 'p23', 'p24' 参数三选二 |
options.choices[].fields | Array | 涉及的参数 | |
options.choices[].count | Number | 需要至少传 ${count} 个 | |
options.choices[].force | Boolean | 默认 false,为 true 时,涉及的参数中只能传 ${count} 个, 为 false 时,可以多于 ${count} 个 | |
options.equals | Array | 参数相等 | [['p20', 'p21'], ['p22', 'p23']] 表示 'p20', 'p21' 两个值需要相等,'p22', 'p23' 两个值需要相等 |
options.equals[] | Array | 涉及的参数(涉及的参数的值需要是相等的) | |
options.compares | Array | 参数大小关系 | [['p25', 'p26', 'p27']] 表示 'p25', 'p26', 'p27' 必须符合 'p25' <= 'p26' <= 'p27' |
options.compares[] | Array | 涉及的参数(涉及的参数的值需要是按顺序从小到大的) | |
options.cases | Object | 参数条件判断 | [{when: ['p30'], then: ['p31'], not: ['p32']}] 表示 当传了 p30 就必须传 p31 ,同时不能传p32 |
options.cases.when | Array | 条件 | |
options.cases.when[] | String | 涉及的参数,(字符串)只要接收到的参数有这个字段即为真 | |
options.cases.when[].field | 涉及的参数的名(对象) | --- | |
options.cases.when[].value | 涉及的参数的值(对象)需要参数的值与该值相等才为真 | --- | |
options.cases.then | Array | 符合when条件时,需要必传的参数 | |
options.cases.not | Array | 符合when条件时,不能接收的参数 |
功能参数
1.获取get连接的数据
this.ctx.params.xx; 解释:xx代表的是参数
获取url的问号get传值参数
http://127.0.0.1/1?user=2&paw=3
获取1 this.ctx.query.路由名问号后面的参数名
获取2 this.ctx.query.user;
获取3 this.ctx.query.paw;
2.获取psot数据
this.ctx.request.body
3.从某个数组中拿到指定参数的数据
数组名.find(item => item.字段 == 参数); 解释:字段代表对象中的字段名 参数代表指定的参数
4.修改状态码
this.ctx.status = 201;
错误处理
1.ctx is not defined 某的地方缺少this,在ctx前加上this.
2.missing csrf token 关闭跨域