**前言: **
Egg.js为企业级开发应用,而产生的一门Node.js框架
它使用模块化渐进式开发,内置多进程管理(Node是单进程,无法使用多核CPU的能力),具有高度可扩展的插件机制。
基于Koa开发,性能优异, 框架稳定,测试覆盖率高
1.安装项目
1.安装模块
mkdir egg_demo 1.建立项目文件夹
yarn create egg --type=simple 2.创建简单类型的项目
yarn install(或者yarn) 3.安装项目依赖的包
2.开启项目
yarn dev 4.开启项目(默认7001端口)
yarn start 5.项目正常运行(但有运行时占用cpu非常大)
yarn 会自动查找模块,所以不需要install,一般也不需要global。 (安装的比较慢,可能需要等待3-4分钟)
yarn start 会在后台开启一个服务,关闭需要使用 yarn stop 结束。 用于项目 上线
yarn dev 可以对当前的变化进行更新。
如果程序在开发时出现线程断开的情况,需要首先考虑 plugin.js和config.default.js 的配置正确与否
2.项目结构
egg-example
├── app 应用程序
│ ├── controller 控制器
│ │ └── home.js
│ ├── public 公有路径
│ ├── service 服务层
│ ├── middleware 中间件
│ ├── view 视图文件
│ └── router.js 路由设置
├── config 配置文件
│ └── config.default.js
└── package.json
1.封装router文件
(1. 在当前位置引入全局应用对象, app/router.js 的对象内添加以下内容
require('./router/default')(app) // 导入模块, 需要添加全局应用对象
(2.新建立router路由文件夹, 创建default.js
module.exports = app=>{
// 单独分离出的路由
const {router,controller} = app
router.get('/default/index',controller.default.home.index)
}
1.创建控制器
yarn dev 功能可以对当前的请示结果进行分析。
函数内部必须使用async修饰, 在controller文件夹下新建“mini.js”文件,并填写以下内容。
'use strict';
const Controller = require('egg').Controller;
class MinicatController extends Controller {
async index() {
const { ctx } = this; // 获取当前的上下文对象
ctx.body = '<h1>I am a MiniCat</h1>';
}
async getGirls() {
const { ctx } = this;
await new Promise(resolve => { // 异步单元测试, 等待异步请求结果
setTimeout(() => {
resolve(ctx.body = '<h1>小妹向你走过去啦!</h1>');
});
});
}
}
module.exports = MinicatController;
2.测试控制器
向router添加路由
router.get('/mini', controller.minicat.index); // 添加路由
在test/app/controller 添加 minicat.test.js
'use strict';
const { app } = require('egg-mock/bootstrap');
describe('minicat test', () => {
it('minicat index', () => {
return app.httpRequest()
.get('/mini')
.expect(200)
.expect('<h1>I am a MiniCat</h1>');
});
it('minicat getGirls', () => {
return app.httpRequest()
.get('/getGirls')
.expect(200)
.expect('<h1>小妹向你走过去啦!</h1>');
});
});
Service就是在复杂业务场景下用于做业务逻辑封装的一个抽象层。就是把业务逻辑代码进一步细化和分类,所以和数据库交互的代码都放到Service中。这样作有三个明显的好处。
1.创建service服务层
在app下新建立minicat.js文件,并填写以下内容
'use strict';
const Service = require("egg").Service;
class MinicatService extends Service{
// Service方法也是异步方法
async getGirl3(id){
//因为没有真实连接数据库,所以模拟数据
return {
id:id,
name:'小红',
age:18
}
}
}
module.exports =MinicatService;
2.控制器与服务的交互
// // service数据层
async getGirls3(){
const {ctx} = this; // 向服务层传入数据
const res = await ctx.service.minicat.getGirl3(ctx.query.id);
ctx.body = res
}
中间件的作用域是全局,访问不同的页面, 只要添加ctx.session.xxx都会有效 。
1.配置中间件
在config/config.default.js 添加下面的内容
/* 配置中间件 */
config.middleware = [ 'counter' ];
2.创建中间件
官方要求中间件保存在 app/middleware 文件夹下。 其中,middleware是新创建的。
module.exports = options =>{
return async (ctx,next) =>{
if(ctx.session.counter){
ctx.session.counter++;
}else{
ctx.session.counter=1;
}
await next(); // 异步等待下一个方法或者页面,防止页面卡死
}
}
3.路由使用的中间件
在router.js中定义 counter 中间件。
每次访问路由都会执行一次添加,可以认为它是路由访问前,进行一定计数。
只要是调用ctx.session.counter, 次数都会加 1. 但如果路由也使用了这个中间件,则会加 2。
// 启动router中间件
const counter = app.middleware.counter()
router.post('/add1', controller.minicat.add1);
router.post('/show1',counter, controller.minicat.show1);
官方要求 ,定时任务需要按照Egg的约定,/app
目录下,新建shedule
文件夹。然后再创建getTime.js
文件
作用: 在一定的时间间隔内,对当前的配置进行设置。
创建后定时任务,重启项目后, 即可正常运行
const Subscription = require('egg').Subscription
class GetTime extends Subscription{
static get schedule(){
return {
// interval:'3s', // 间隔时间3s
cron: '*/3 * * * * *', // 间隔时间3s
type:'worker' // 类型
}
}
async subscribe(){ // 方法会自动执行, 一般会去检查配置
console.log(Date.now())
}
}
module.exports = GetTime; // 暴露这个类中的所有方法
// module.exports = { // 只是暴露这个类。
// GetTime:GetTime
// };
// export default GetTime; // es6 暴露方法无效
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ |
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, optional)
在public目录下添加 default.css 样式文件
则可以在view模板文件中直接找到
<link rel="stylesheet" href="/public/default.css">
直接访问public下的静态资源:(这是需要添加public路径的框架,但django的static也是需要添加到访问路径当中)
http://localhost:7001/public/assets/file/38e25517-3981-4987-aaf1-8c6c830ea097.jpg
控制器中使用视图文件
async getPage(){
render('hello.html')
}
__get方法
// 自由传参
const username = ctx.query.username;
// 严格传参(path路径)
const params = ctx.params.name
__post方法
>>>>> 注意:egg.js的post方法只遵循标准的x-www-form-urlencoded方式。 form-data不可以
// post请求数据,是保存的请求包体中。使用ctx.request.body
const title = ctx.request.body.title,
__headers请求头数据
// 获取token
const token = ctx.request.headers.token
__url, method请求数据
const url = ctx.request.url /method
__file文件数据
const req = ctx.request.files;
let temp = req[0].filepath //临时文件路径
__response响应数据
ctx.body = { msg:"hello"}
ctx.body = "测试成功" // ctx.body响应结果
1.基本操作
// 1.插入数据
const data = {
title: 'Hello World',
username:"xxx",
password:"xxxx"
}
res = await app.mysql.insert(tTable,data); // res2.insertId 可以查看插入的数据
// 2.删除数据
const data = {
id:123 // 根据目标id删除
}
res = await app.mysql.delete(tTable,data );
// 3.更新数据
const data = {
// id:123 // 根据目标id更新
username:'xxx',
password:'xxxx' //新数据
}
const options = {
where: {
custom_id: 456
}
};
res = await app.mysql.update(tTable,data, options);
// 4.查询数据
const options = {
where: { id: '23',name:'小明' },
columns: ['author', 'title'],
orders: [['created_at','desc'], ['id','desc']],
limit: 10,
offset: 0,
})
// select默认会获取所有数据
res = await app.mysql.select(tTable, options);
// get 查询第一条符合条件的数据
const post = await this.app.mysql.get(tTable, options);
// query方法执行原生sql语句
res = await app.mysql.query(`select * from ${tTable} where ${new_params} `)
2.高级操作
1.事务处理:
// 一般用于两种关联事务的处理。
const conn = await this.app.mysql.beginTransaction(); try {
await conn.insert('user', { 'username': 'hahahh','password':'xxx' });
const row = { id: 8,username: '王麻子'};
await conn.update('user', row);
await conn.commit();
} catch (err) {
await conn.rollback(); // rollback call won't throw err
throw err;
}
**注意: **
1.查询的表不能是有 ‘-'的表,否则查询失败,
2.eggjs支持post请求中的x-www-form-urlencoded格式,form-data格式 不支持
3.编码规范,注意在使用query方法前,对sql语句用unescape进行解码。
4.where, set , values 中的参数值需要是使用引号包含
**5.where 中的参数用and连接,set中的参数用逗号’,'连接 , values中的参数逗号‘,’连接,并用括号“()”包裹 **
6.目前eggjs的mysql中query方法,使用js还是无法生成符合这个方法的日期格式数据。
mysql语句专用的日期格式的 SQL函数: current_timestamp() , 获取当前时间,不需要引号包裹
7. 插入数据的日期格式就是字符串,使用SQL函数没有效果
// 保存到数据库的时间
// (1.mysql语句专用的日期格式的 SQL函数:
current_timestamp() // 获取当前时间,不需要引号包裹
// (2.获取当前日期的格式化数据
const mysqlDateType = (isvalues=false)=>{
// return (new Date()).toLocaleString()// 下午会出现异常.replace(/[\上\午\下\午]+?/gi,'')
if(isvalues){
// 插入数据的日期格式(自定义的格式化日期函数)
return DateFormat(new Date(),'YYYY-MM-DD HH:mm:ss');
}else{
return 'current_timestamp()'; // 获取当前日期的SQL函数
}
}
-------- controller控制器中
// 初始化,获取服务层的路径
AdminDataindex = this.ctx.service.admin.dataindex
HomeDataindex = this.ctx.service.home.dataindex
-------- services服务层中
// 执行函数,返回一个模型对象
const User = require("service/models/User")()
// 1.高级获取所有的字段
// sql查询语句, 选择的条件可以是一个数组,但要经过模板字符串转换
const sql = `select ${[...User.fillable,...User.hidden]} from ${User.table}`
// 获取单独的字段
const sql = `select ${User.fillable} from ${User.table}`
// 2.更新部分字段
const sql = `update user_msg set sex='女',token='${token}' where id='${user.id}'`
const res = await ctx.app.mysql.query(sql)
// 3.插入新数据
// 插入默认排序数据
const sql = `insert into user_msg values(NULL,"11","222")`
// 插入部分数据
const sql = `insert into user_msg(id,username,token) values(NULL,"11","222")`
const res = await ctx.app.mysql.query(sql)
// 数据 + 模板字符串,可以生成元组数据,
//(即数据展开状态,但模板字符串是不支持直接使用扩展运算符,将数组展开)
const sql = `insert into ${table}(${keys}) values(${t_values})`;
------ User.js模型层中
'use strict';
function User(){
return {
// 使用的表
table : 'user_msg',
// 主键
primaryKey : 'id',
// 排除的字段
hidden : [
'password',
'token'
] ,
// 可使用的字段
fillable :[
'username',
'sex',
'address',
'registerTime',
'updateTime',
'deleteTime',
'isDelete',
]
}
}
module.exports = User;
(1.get方法
控制器中编写方法
// 自由传参模式, query
// 则可以选择性添加参数
async getGirls() {
const { ctx } = this;
ctx.body = ctx.query.username;
}
// 严格传参模式 parmas
// 则必需添加相应的参数
async getGirls2(){
const {ctx} = this;
ctx.body = ctx.params.name
}
设置路由
router.get('/getGirls/', controller.minicat.getGirls); // 添加路由
router.get('/getGirls2/:name', controller.minicat.getGirls2); // 添加路由
请求方式
http://127.0.0.1:7001/getGirls?username=111
http://127.0.0.1:7001/getGirls2/20
(2.post方法
CSRF的全名为 Cross-site request forgery, 它的中文名为 伪造跨站请求。
注意:一定要x-www-form-urlencoded方式,form-data格式不可以
POST http://127.0.0.1:7001/add
Content-Type: application/x-www-form-urlencoded
name = minicat
或者使用JSon格式,注意空行
{
"name":"hello"
}
关闭csrf 安全策略
在config/config.default.js 下添加 以下配置项, 然后点击
接收post请求参数 和路由配置
// POSt请求
async add(){
const {ctx} = this;
let data = ctx.request.body // 获取数据包中的参数
ctx.body = { satus:200, data:data }
}
// post方法
router.post('/add', controller.minicat.add);
安装egg-mysql数据库
yarn add egg-mysql
添加插件配置
在config/plugins.js添加以下内容,
/* egg-mysql */
exports.mysql = {
enable:true,
package:'egg-mysql'
}
在config/config.default.js 添加以下内容
/* 安装mysql插件 */
config.mysql = {
app:true, // 是否挂载到app下
agent:false, // 是否挂载到代理下
client:{
host:'127.0.0.1', // 主机地址
port:'3306',
user:'root',
password:'xxxx',
database:'test-egg' // 数据库名称
}
}
然后创建 utf8mb4 编码格式的数据库 , 名称为test-egg, 建立grils的表
const res = await app.mysql.insert('girls',data); // 添加
const res= await app.mysql.select('girls',option) // 查询 ,会获取所有数据
const res= await app.mysql.update('girls',data,option) // 更新
const res= await app.mysql.delete('girls',data) // 删除
const res = app.mysql.query('select * from girls where id= ?',[params.id]) // 执行sql语句
注意:
service服务中的数据库操作,必须使用异常捕获,以保证程序的正常运行。
它们也都是在严格环境下,使用CommonJS 引入包的形式开发。
‘use strict’; require (“egg”)
自定义创建的类是从egg 包中继承相应 类, 并把类中的方法全部暴露。 module.exports = XXXService
service和controller中,类里面的方法都是异步方法。即使用async修饰方法, await异步等待。
控制器从service获取数据
注意: 实际上表单上的数据没有Number数字类型,都是字符串。因此,使用字符串查询对应的数据,也可以得到结果。
const params = {id:3 , name:'小白', age:20, skill: '唱歌'}
const res = await ctx.service.testdb.getGirl({id:ctx.query.id} )
const res = await ctx.service.testdb.addGril(params)
const res = await ctx.service.testdb.delGril({id:ctx.query.id} )
安装模块
yarn add egg-cors
配置信息 在config/plugins.js添加以下内容
exports.cors = {
enable:true,
package:'egg-cors'
}
在 config/config.default.js 添加以下内容
/* cors跨域 */
config.security = {
csrf: {enable: true},
domainWhiteList: [ 'http://localhost:3000','http://localhost:3001' ] // 白名单
};
config.cors = {
origin: ctx => ctx.get('origin'), //这种方式是允许所有的IP+端口跨域访问
// origin: 'http://localhost:3000', //
credentials: true, // 开启认证
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'
};
安装模块
yarn add egg-jwt
配置信息 在config/plugins.js添加以下内容
exports.cors = {
enable:true,
package:'egg-jwt'
}
在 config/config.default.js 添加以下内容
/*jwt认证*/
config.jwt = {
secret: 'AB12XY', // 不能设置为123456
enable: true, // default is false
match: '/jwt', // optional
expiresIn: '24h',
}
middleware中间件使用方法:
'use strict';
module.exports = () => {
return async function jwtErr(ctx, next) {
const headerStr = ctx.request.header.access_token;
if (headerStr) {
try {
console.log(headerStr,ctx.app.config.jwt.secret)
const options = {
secret: ctx.app.config.jwt.secret
}
// 解码token,需传加密时的 secret
const decode = await ctx.app.jwt.verify(headerStr, ctx.app.config.jwt.secret);
ctx.state.user = decode; // 信息存一下,这步很重要,业务里要用
await next();
} catch (error) {
ctx.status = 401;
// 翻译错误码
let message = error.message;
ctx.body = {
code: -1,
msg: message,
};
return;
}
} else {
ctx.status = 401;
ctx.body = {
code: -1,
msg: '没有token,无法访问',
};
return;
}
};
};
controller控制器中使用方法:
// 登录帐号,(返回token)
async login() {
const {ctx, app} = this;
const req = ctx.request.body;
// 空值验证
const errArr = app.validator.validate({
username: 'string',
password: 'string',
}, req);
if (errArr) {
ctx.body = { error:JSON.stringify(errArr), code:403 }
return;
}
let responseData = null;
try {
const params = {
where:{
username:req.username,
password:req.password,
}
}
// 查询是否存在当前用户
const data =await app.mysql.select('user-msg',params);
console.log(data,data.length,data.length > 0)
if (data.length > 0) {
const user = data[0];
const {jwt: {secret, expiresIn}} = app.config;
// 需传 secret 过期时间
const token = app.jwt.sign({
data:{
username: user.username,
password: user.password,
},
}, secret, {
expiresIn,
});
// 更新token
const params = { id:user.id, token:token }
const res = await app.mysql.update('user-msg',params)
responseData = { msg:'登录成功!', token:token, userinfo:user, code:200 }
} else {
responseData = { msg:'登录失败', error:'用户名或密码不正确', code:403 }
}
} catch (err) {
responseData = { msg:"出现异常!", error:err, code:500 }
}
ctx.body = responseData
}
安装模块
yarn add egg-validate
配置信息 在config/plugins.js添加以下内容
exports.cors = {
enable:true,
package:'egg-validate'
}
在 config/config.default.js 添加以下内容
/**验证参数 */
exports.validate = {
// convert: false,
// validateRoot: false,
};
controller中验证使用
// 空值验证
const errArr = app.validator.validate({
username: 'string',
password: 'string',
}, req);
if (errArr) {
ctx.body = { error:JSON.stringify(errArr), code:403 }
return;
}
安装模块
yarn add bcryptjs
配置信息 在config/plugins.js添加以下内容
exports.cors = {
enable:true,
package:'bcryptjs'
}
使用方法
引入模块
var bcrypt = require("bcryptjs");
var key = bcrypt.genSaltSync(10); // 设置加密密钥
var hashPwd = bcrypt.hashSync("123",key)
var res = bcrypt.compareSync("123",hashPwd) // true
egg接收文件上传_魔笛Love的博客-CSDN博客_ctx.multipart()
首先在config.default.js中配置file文件接收设置,注意在assets下创建一个temp用户保存临时图片的文件夹
/* file文件接收配置 */
config.multipart = {
mode: 'file',
tmpdir: './app/public/assets/temp',
cleanSchedule: {
cron: '0 0 4 * * *',
},
fileSize: '100mb',
whitelist() {
return true;
},
};
接收上传的文件
async avatarUpdate() {
const {ctx, app} = this;
const req = ctx.request.files;
// 临时文件的位置
let res = utils.saveFile(req[0].filepath)
let responseData = null;
if(res)
responseData = { msg:'提交成功!',data:res, code:200 }
else
responseData = { msg:'提交失败!', code:403 }
ctx.body = responseData
}
保存文件的模块
// (9.保存file文件
const saveFile = (file)=>{
try{
let date = Date.now()
let last = Math.floor(Math.random()*(99999-10000+1)+10000)
let filename = date+'.'+last+'.jpg'
let dirname = `/assets/file/${filename}`
// 注意把临时文件的“\”反斜杠转换成"/"下斜杠
let fileData = file.replace(/\\/gi,'\/')
// 当前的运行路径是项目的根目录
let saveDirname = 'app/public'+dirname
console.log("当前写入的位置是",saveDirname)
// // 方法一:分块写入(错误)
// var ws = fs.createWriteStream (saveDirname);
// ws.on('open', ()=> {
// const blockSize = 128;
// const nbBlocks = Math.ceil(fileData.length / (blockSize));
// for (let i = 0; i < nbBlocks; i += 1) {
// const currentBlock = fileData.slice(blockSize * i,
// Math.min(blockSize * (i + 1), fileData.length),);
// ws.write(currentBlock);
// }
// ws.end();
// });
// ws.on('error',(err)=>{console.log(err);reject(null);})
// ws.on('finish',()=>{resolve(dirname)})
// // 方法二:边读边写 (读写只能读一半)
// var rs = fs.createReadStream (fileData ); //创建一个可读流
// var ws =fs.createWriteStream( saveDirname);
// //如果要读取一个可读流中的数据,必须要为可读流绑定一个data事件,data事件绑定完毕,它会自动开始读取数据
// rs.once ( "open" ,function () {});
// rs. once ( "close" , function (){});
// ws.once ( "open" ,function () { });
// ws. once ( "close" , function (){
// rs.end();
// ws.end();
// });
// rs.on ( "data",function (data) {
// console.log (data) ;
// ws.write(data);
// });
// // pipe ( )可以将可读流中的内容,直接输出到可写流中
// rs.pipe (ws) ;
// 方法三:复制临时文件
fs.copyFile(fileData, saveDirname,(err)=>{
if(!err){
console.log('复制成功');
}
})
return dirname
}catch(e){
console.log("error is",e)
return null
}
}
在service目录下,新建token.js文件:
'use strict';
const Service = require('egg').Service;
const jwt = require('jsonwebtoken');
class TokenService extends Service {
async signJwt(_id) {
return jwt.sign({
data: {
_id,
},
exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 7),
}, 'this is secert key, you can replace it!');
}
async decodeJwt(token) {
try {
return jwt.verify(token, 'this is secert key, you can replace it!');
} catch (err) {
return err;
}
}
}
module.exports = TokenService;
服务端渲染的好处
EJS是什么
EJS是基于javascript的一种历史悠久的模板引擎,生态良好,性能稳定,得到普遍的认可以及广泛的使用。在php时代就已经是达到了顶峰。
安装与配置环境
yarn add egg-view-ejs
配置环境
在config/config.default.js
下添加以下内容
/*配置ejs */
config.view = {
mapping:{ /* 匹配文件 */
'.html':'ejs'
}
}
config.ejs = {
}
在config/plugins.js
添加
exports.ejs = {
enable:true,
package:"egg-view-ejs"
}
注意: 首先需要把module.exports 注释, 否则无法暴露这信exports.ejs模块
// ejs模板引擎
async index1(){
const {ctx} = this;
await ctx.render('minicat.html',{
id:2021,
name:'minicat',
age:"18",
skills:['坚持','努力','沉着']
})
// ctx.body = "hello"
// render方法返回的是promise对象,所以需要使用异步
}
模块需要放在app/view下, 创建minicat.html ,并在html插入以下内容
// 1.include组合文件
<% include header.html %> // 使用include加载文件,文件内只是一个标签
// 2.=赋值语句
<h2>现在是 // 赋值
<%=id %>
<%=name %>
<%=age %>
</h2>
// 3.for循环遍历
<ul>
<% for(var i=0;i<skills.length;i++){ %> // for循环遍历
<li><%=skills[i] %></li>
<% } %>
</ul>
// 4.if..else条件判断
<%if(islogin){ %
<button>已登录</button>
<%}else { %>
<button>已登录</button>
<%}%>
// 5.<% %> 变量语句
<% var name = "ejs模板" %>
静态css资源
根路径是在app, 只要是默认文件夹,就可以views , controller 直接找到 ”xxx.html 或者 xxx.js, “,
但是静态资源 public目录下,它并非默认文件夹,属于可修改配置。
/* 修改ejs 配置*/
config.ejs = {
// delimiter: "$" // 修改默认分隔符
}
/* 配置静态文件路径 */
config.static = {
// prefix:'/assets/' // 如果使用,则需要把public 改成static
}
在public目录下添加 default.css 样式文件
则可以在view模板文件中直接找到
<link rel="stylesheet" href="/public/default.css">
(1.什么是session?
(2.session的配置
在config/config.default.js
下添加
/* session的配置 */
config.session = {
key :"PANG_SESS", // 设置Key的默认值
httpOnly:true, // 设置服务端操作
maxAge:1000*60 , // 设置最大有效时间
renew: true, // 页面有访问动作自动刷新session, 一般用于是否需要是否登录
}
(3.session的操作
注意:与cookie不一样的sessionl默认支持中文, 不需要加密与解密。
ctx.session.username = "minicat" 添加session
ctx.session.username = null 删除session
const se = ctx.session.username 获取session
(1. 什么是cookie?
HTTP请求是无状态的,但是在开发时,有些情况是需要知道请求的人是谁的。为了解决这个问题,HTTP协议设计了一个特殊的请求头:
Cookie
。服务端可以通过响应头(set-cookie)将少量数据响应给客户端,浏览器会遵循协议将数据保留,并在下一次请求同一个服务的时候带上。
开发者选项的 ”网络导航项(network)“ 或者 ”应用(Application)“, 可以查看cookie的添加信息
**(2.cookie的配置 **
**ctx.cookies.set() **
第一个是key, 第二个是value, 第三个是配置项 ,这是一个对象的形式
maxAge: xxx 以毫秒计数
httpOnly:true 服务端专属修改cookie, 客户端不可以通过JS直接使用**document.cookie 获取cookie
**,但对开发都选项的控制台无影响。
encrypt:true 允许使用中文设置cookie, 注意,使用ctx.cookie.get.获取数值需要添加这个配置项 ctx.cookies.get("user",{encrypt:true})
ctx.cookies.get()
当获取cookie的时候, 如果使用了中文添加到cookie值时,需要解密,否则返回的结果是undefined;
第一个是key,第二是配置项,一般也添加 encrypt: true 来解密。
HttpOnly的设置:伪造Cookie来绕过登录是黑客经常使用的一种手段,所以为了安全,Egg.js默认设置只允许服务端来操作Cookie
(3.使用实例
视图页面minicat.html
<div>
<button onclick="add()" >增加Cookie</button>
<button onclick="del()" >删除Cookie</button>
<button onclick="editor()" >编辑Cookie</button>
<button onclick="show()" >展示Cookie</button>
</div>
<script>
function add(){
// 原生请求数据的api
fetch("/add",{
method:'post',
headers:{
'Content-type':"application/json"
}
})
}
function del(){
fetch("/del",{
method:'post',
headers:{
'Content-type':"application/json"
}
})
}
function editor(){
fetch("/editor",{
method:'post',
headers:{
'Content-type':"application/json"
}
})
}
function show(){
// fetch类似一个axios,但它是原生的api
fetch("/show",{
method:'post',
headers:{
'Content-type':"application/json"
}
}).then(response=>{
return response.json(); // 首先需要返回一个回调,promise对象
}).then(data=>{ // 再去获取响应结果
console.log(data);
}).catch(err=>{
console.log("错误是",err)
})
}
</script>
控制器 minicat.js
// Cookie的设置
async add(){ // 添加cookie
const ctx = this.ctx
ctx.cookies.set("user",'加入minicat.com ',{
maxAge:1000*60,
httpOnly:true,
encrypt:true
})
ctx.body = {
status:200,
data:"Cookie添加成功"
}
}
async del(){ // 修改cookie
const ctx = this.ctx
ctx.cookies.set("user",'')
ctx.body = {
status:200,
data:"Cookie删除成功"
}
}
async editor(){ // 编辑cookie(略)
const ctx = this.ctx
....
}
async show(){ // 获取cookie
const ctx = this.ctx
ctx.body = {
status:200,
msg:"Cookie获取成功",
data:ctx.cookies.get("user",{
encrypt:true // 返回结果前进行解密
})
}
}
1.什么是fetch?
fetch是浏览器提供的原生AJax接口
由于原来 的XMLHttpRequest不符合分离原则,基于事件模型处理异步请求逐渐淘汰。
Promise的处理具有更强大的优势。
// fetch类似一个axios,但它是原生的api
fetch("/show",{
method:'post',
headers:{
'Content-type':"application/json"
}
}).then(response=>{
return response.json(); // 首先需要返回一个回调,promise对象
}).then(data=>{ // 再去获取响应结果
console.log(data);
}).catch(err=>{
console.log("错误是",err)
})
2.原生的ajax
var xhttp = new XMLHttpRequest(); // 创建xml对象
xhttp.onreadystatechange = function() { // 设置响应函数
if (this.readyState === 4 && this.status === 200) {
console.log(this.responseText);
}
};
xhr.onload = function() {
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Oops, error");
};
xhttp.open("GET", "/", true); // 准备发送请求
xhttp.send(); // 响应成功后,会触发相应的函数,执行回调函数
官方要求扩展必须保存在 app/extend 文件夹下, extend是新创建的文件夹,
application 全局应用对象 app对象 this.app
context 请求上下文 ctx对象 this.ctx
request 请求级别的对象,提供了请求相关的属性和方法 ctx.request对象 this.ctx.request
response 请求级别的对象,提供了相应相关的属性和方法 ctx.response对象 this.ctx.response
helper 帮助函数 ctx.helper对象 this.ctx.helper
1.创建application.js文件, 并添加以下内容
function getTime(){
let now = new Date();
let year = now.getFullYear(); //得到年份
let month = now.getMonth()+1;//得到月份
let date = now.getDate();//得到日期
let hour= now.getHours();//得到小时数
let minute= now.getMinutes();//得到分钟数
let second= now.getSeconds();//得到秒数
let nowTime = year+'年'+month+'月'+date+'日 '+hour+':'+minute+':'+second;
return nowTime;
}
module.exports = { // 暴露模块
// 方法的扩展 app.currentTime()已经成为app下的全局方法
currentTime(){
const current = getTime();
return current;
},
// 属性的扩展 app.timeProp 已经成为app下的全局属性、
// 加入get,就会默认是一个属性,可以直接以属性的形式在`controller`方法里进行调用。
get timeProp(){
return getTime();
}
// 属性的扩展 app.token
set token(t){
let t = 'token';
}
}
2.控制器调用扩展
async getData(){
const {ctx , app} = this
let data = {
nowtime:app.currentTime(), // 调用自定义扩展的全局方法
property:app.timeProp // 调用自定义扩展的全局属性
property2:app.get("timeProp")// 调用自定义扩展的全局属性
}
app.token = "minicat.com"; // 获取属性, 也可以扩展为属性
app.set("token","minicat.com") // 设置属性, 也可以扩展为属性
}
1.创建一个context.js文件, 填写以下内容
module.exports = {
// 作用: 将get方法的自由传参query和 post请求传参合并,可以自定义获取参数中的数据
// 扩展ctx的方法
myquery(key){ // 方法扩展
const method = this.request.method
if(method == 'GET'){
return key ? this.query[key] : this.query;
}else{
return key ? this.request.body[key] : this.request.body;
}
}
}
2.控制器调用扩展
// 2.context
async newContext(){
const {ctx} = this ;
// 1.get请求
// 自由传参 ctx.query.username 为空
// 严格传参 ctx.params.name 不允许为空
// 2.post请求
// 获取数据包 ctx.request.body.name 为空
const params = ctx.myquery() // 默认为{}, 使用扩展方法
}
1.创建一个context.js文件, 填写以下内容
module.exports = {
// 作用:获取请求头上的数据, 一般用户获取 请求头上的token
// 扩展ctx.request属性
get token() {
return this.get("token");
},
};
2.控制器调用扩展
// 3.request
async newRequest(){
const {ctx} = this;
const token = ctx.request.token; // 自定义扩展属性1
const token = ctx.request.get("token"); // 自定义扩展属性2,
const method = ctx.request.method; // 获取默认方法
ctx.body = {
status:200,
body:token,
method:method
}
}
1.创建一个response.js文件, 填写以下内容
module.exports={
// 作用: response 可以设置响应token
// 扩展属性
set token(token){ // 设置响应token
this.set('token',token)
}
};
2.控制器调用扩展
async newResponse(){
const {ctx} = this;
ctx.response.token = "minicat.com"; // 自定义扩展属性1
ctx.response.set("token","minicat.com") // 自定义扩展属性2
ctx.body = { status:200, msg:'成功访问' }
}
1.创建一个helper.js文件, 填写以下内容
module.exports = {
base64Encode(str = '' ){
return new Buffer(str).toString('base64');
}
}
2.控制器调用扩展
async newResponse(){
const {ctx} = this;
const testBase64 = ctx.helper.base64Encode('jspang.com') // 扩展方法,方法加密
ctx.body = testBase64
}
1.请求类的方法
ctx.request 获取请求的数据
ctx.request.header.xxx 获取请求头上的数据
2.响应类的方法
ctx.response 获取请求的数据
3.中间件类的方法
ctx.app.mysql.xxx方法 执行数据库语句(异步方法)
ctx.app.jwt.xxx 方法 执行安装的jwt中间件中的方法
4.数据库类的方法
ctx.service.home. xxx 获取service服务层文件夹下的文件,调用方法
任何在config.webpack.js中添加的配置项
config.xxx= { xxx :xxx }
都能在所有页面中访问到
const abc = app.config.xxx;
或者使用相关的方法
app.xxx(xxxx)
使用commonJS语法,只能通过 require 引入包
const qs = require("querystring")
自定义模块
module.exports = {
// 音乐列表
music_box:"music_box"
}
// 引入模块
const table = require("../contant.js")
// 使用模块
table.music_box
在nodejs中,一个包就相当于一个模块,即使暴露了文件内的模块,依旧还有文件这层。这与 es6不同,并且commonjs不支持es6语法引入模块。
es6不用写后缀,但nodejs自定义的模块,就必需写后缀名, 这很大程度上讲,commonjs采用的是严格格式,对引入的文件格式要求比较高
不同的语言,取别名的方法有很大的差异, 当前模块适用于多数的webpack打包类型的框架。
安装模块: yarn add module-alias
(1.简单通用方法
----package.json中配置路径,添加下方内容
"_moduleAliases": {
"@root": ".",
"@app": "app"
},
---全局注册取别名模块, 内容添加以下内容(一般是项目的总配置文件)
module.exports = appInfo => {
....
/* 项目取别名 */
require('module-alias/register')
}
---控制器中使用
const func = require("@app/public/utils/func.js")
(2.标准建议方法
修改package.json, 对项目的规范性开发处理的很不好,很容易导致重要文件被不经意间修改,所以建议新建立单独的文件,用于填写重命名规则。
---- 在config项目下添加 config-alias.js文件, 添加以下内容
const moduleAlias = require('module-alias')
// 路径取别名, 当前路径不是根路径,必需查找到上一级“..”
moduleAlias.addAliases({
'@root' : __dirname+"/..",
'@app': __dirname + '/../app',
})
---- 全局注册取别名模块, 内容添加以下内容
module.exports = appInfo => {
....
/* 项目取别名 */
require('./config-alias.js') // 新添加的自定义文件
require('module-alias/register')
}
---- 控制器使用
const func = require("@app/public/utils/func.js")
yarn dev 开发者模式运行
yarn start 生产模式运行
1.文件路径引用问题
app是根目录, config是插件配置目录, test是测试目录
**controller
**是官方创建目录, 一般被 路由调用 。
(eg:controller.minicat.index
)
**public
**是官方创建目录 , 一般保存css,js等静态文件,但被view内的文件引用,需要采用根目录的形式。 (eg: /public/css/index.css
)
router.js
是官方创建文件, 一般是定义路由规则,以及与控制器、中间件的关系 。
(eg: router.get("/index",controller.minicat.index),
使用 app.middleware.counter()
)
service
是自定义官方建议目录, 一般被控制器调用,通过ctx 执行上下文获取路径和函数信息
(eg: ctx.service.testdb.addGirl(params))
view
是自定义官方建议目录, 一般由控制调用,用于返回一个html页面。 这个页面使用EJS模板引擎,将模板语法加入到html页面中。
(eg: ctx.render('minicat.html',{ id:2021, name:'minicat' }
)
middleware
是自定义官方建议目录, 一般由内部依赖的属性决定, 因为使用的是箭头函数,所以可以被整体引入和调用依赖属性 使用。
(eg: ctx.session.counter
)
extend
是自定义官方建议目录, 根据扩展目录, 只可以 被相应的对象调用。
(eg: app.currentTime() 和 app.timeProp
)
schedule
是自定义官方建议目录, 重启服务,自动调用。
2.学习总结:
egg.js是一门具有弹性的框架,可以根据建设网站需要,自由选择是否使用内置配置以及使用其它扩展插件。丰富的生态圈,强大的性能值得我们去学习。
因为采用的是渐近式开发,它的体积虽然比其它node.js框架更加庞大,但它是以轻量化的形式面向开发者使用。基于内置多进程管理,对于系统的处理事件效率和运行的稳定性都是极大的提升。