就是能够使用html文件并且把处理好的html文件返回给浏览器,需要借助egg-view-ejs。
yarn add egg-view-ejs
config\plugin.js中登记:
exports.ejs = {
enable: true,
package: 'egg-view-ejs'
};
然后再进行配置config\config.default.js:
config.view = {
mapping: {
".html": "ejs"
}
};
config.ejs = {
};
然后创建html静态文件,例如app\view\user.html,然后在业务代码中:
await ctx.render('user.html'); // 替换掉返回body的内容,egg会自动去view文件夹找对应user.html的文件返回
如果想给html文件传入一些变量:
await ctx.render('user.html', { // 第二个参数写对象,传入一些变量
id: 100
})
然后在html中使用变量:
<body>
id: <%= id %> // 固定写法取变量
<%# 这样写注释 %>
// 写js语句比较麻烦
<ul id="user">
<% for(var i=0; i<lists.length; i++){ %> // lists是传入的一个数组
<li>
<%= lists[i] %>
li>
<% } %>
ul>
body>
如果觉得<%= id %>
写法不好,要换成<$= id $>
,可以去config\config.default.js配置:
config.ejs = {
delimiter: "$"
};
如果不想全局配置这个东西,可以单独配置
await ctx.render('user.html', { // 第二个参数写对象,传入一些变量
id: 100
},{
delimiter: '$' // 第三个参数写一些单独的配置
})
默认egg会从view文件夹中去读取html文件,如果想从其他文件夹中读取,可以在全局配置中更改config\config.default.js:
const path = require('path');
// ...
config.view = {
mapping: {
".html": "ejs"
},
root: [
path.join(appInfo.baseDir, "app/html"), // 多加个html文件夹,egg就会从这个文件夹里找
path.join(appInfo.baseDir, "app/view") // 虽然是默认,但是当你添加了其他文件夹,默认的也要写上
].join(",")
};
例如html/a.html:
<h1>a.htmlh1>
在html/index.html中要引入这个文件:
<body>
<% include a.html %>
body>
其他的静态资源都放在assets文件夹里,然后利用默认安装的egg-static库去完成配置config\config.default.js:
config.static = {
prefix: "/assets/",
dir: path.join(appInfo.baseDir, "app/assets")
};
然后就可以引入静态资源了,例如html/index.html中
...
<title>usertitle>
<link rel="stylesheet" type="text/css" href="assets/css/user.css"/> // 正常引入
head>
<body>
<img src="assets/img/f1.jpg" />
<script src="assets/js/user.js">script>
...
模拟写一个登陆接口:
async login() {
const { ctx } = this;
const body = ctx.request.body;
ctx.cookies.set("user", JSON.stringify(body), { // 设置cookie,返回给浏览器时,浏览器会自动写入cookie里
maxAge: 1000 * 60 * 10, // 有效时间
httpOnly: false, // cookie禁止前端修改
});
ctx.body = {
status: 200,
data: body
};
}
// 路由配置就省略了
如果想在模板使用到cookie:
async index() {
const { ctx } = this;
// 设置中文内容的cookie方法一
ctx.cookies.set("zh", "测试", { // 设置中文内容的cookie有个条件,就是一定要加密
encrypt: true
});
const zh = ctx.cookies.get("zh", { // 获取要解密
encrypt: true
});
// console.log(zh)
// 设置中文内容的cookie方法二,用base64加解密
ctx.cookies.set("base64", this.encode("中文base64"));
const base64 = this.decode(ctx.cookies.get("base64"));
// ctx.body = 'user index';
const user = ctx.cookies.get("user"); // 获取指定cookies
await ctx.render('user.html', {
user: user ? JSON.parse(user) : null, // 传入模版
zh,
base64
});
}
修改cookie,举例登陆退出接口:
async logout() {
const { ctx } = this;
ctx.cookies.set("user", null);
ctx.body = {
status: 200
};
}
// 路由配置就省略了
在接口中
// 添加,添加的session会默认存到浏览器的cookie字段中
ctx.session.user = body;
ctx.session.zh = "中文也可以";
ctx.session.test = "test";
//清除session
ctx.session.user = null;
// 获取session
const session = ctx.session.user;
const zhSession = ctx.session.zh;
在config\config.default.js中可设置
config.session = {
key: "MUKE_SESS", // 设置存在cookie时,key的名字
httpOnly: true, // 只能在服务端修改
maxAge: 1000 * 50, // 生效时间
renew: true // 每次刷新页面时都会重置过期时间
};
如果后面session越来越多,cookie就存不下了,这时候把session存在服务器内存中,cookie只对其保存服务器session的引用地址就可以了,创建根目录/app.js
module.exports = app => {
const store = {};
app.sessionStore = { // 就这样设置,底层就会帮我们存在内存中
async get(key){
console.log("--store--", store)
return store[key];
},
async set(key, value, maxAge){
store[key] = value;
},
async destroy(key){
store[key] = null;
}
};
}
async curlGet() { // 调用get接口
const { ctx, app } = this;
const res = await ctx.curl("http://localhost:7001/", {
dataType: "json" // 如果接口返回的只是字符串,那么就写 dataType: "text"
});
console.log(res);
ctx.body = {
status: 200,
data: res.data // 获取接口返回的data
};
}
async curlPost() {
const { ctx } = this;
const res = await ctx.curl("http://localhost:7001/login", {
method: "post",
contentType: "json",
data: ctx.request.body, // 请求的参数
dataType: "json"
});
console.log(res);
ctx.body = res.data; // res.data这里包含了 status: 200
}
// 路由配置就不记录了
中间件就是当有接口请求的时候,会经过的地方,我们可以在这里做一些事情。egg规定app/middleware文件夹里面放中间件。例如middleware/m1.js:
module.exports = options => {
return async (ctx, next) => {
console.log('m1 start');
await next(); // 这里相当于把上下隔开,上面是入口,下面是出口
console.log('m1 end');
}
}
middleware/m2.js:
module.exports = options => {
return async (ctx, next) => {
console.log('m2 start');
await next();
console.log('m2 end');
}
}
然后要去全局注册config\config.default.js:
config.middleware = ['m1', 'm2']; // 顺序很重要,规定中间件经过的顺序
当请求发起的时候,过程如图:
来实现一个接口日志生成功能app\middleware\httpLog.js
const dayjs = require('dayjs'); // 安装个第三方库来解决
const fs = require('fs'); // 用来生成日志文件
module.exports = options => {
return async (ctx, next) => {
// console.log(ctx)
console.log(options) // 获取到config\config.default.js里设置的httpLog.js专用全局变量
const sTime = Date.now(); // 请求开始时间
const startTime = dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss"); // 用来放在日志里的请求开始时间
const req = ctx.request; // 获取到请求内容
await next();
const log = { // 日志格式
method: req.method, // 请求方法
url: req.url, // 请求地址
data: req.body, // 请求数据
startTime, // 开始时间
endTime: dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss"), // 结束时间
timeLength: Date.now() - sTime // 请求总时间
};
// console.log(log)
// console.log(ctx.app.baseDir) 可以拿到工程在电脑上的路径地址
// 下面定义日志内容 时间+标志+日志数据+换行
const data = dayjs(Date.now()).format("YYYY-MM-DD HH:mm:ss") + " [httpLog] " + JSON.stringify(log) + '\r\n';
fs.appendFileSync(ctx.app.baseDir + '/htttpLog.log', data); // 日志文件生成
}
}
然后去全局注册
config.middleware = ['httpLog'];
config.httpLog = { // 这个是一些全局变量
type: 'all'
};
中间件还可以指定接口执行,在路由配置中:
module.exports = app => {
const { router, controller } = app;
const userExist = app.middleware.中间件名称(); // 这个中间件的逻辑是如果用户存在就await next();,不存在就直接return,后面controller.user.detail都不执行了,中间件直接返回错误信息给前端
router.post('/api/user/detail', userExist, controller.user.detail); // 第二个参数直接传入中间件
};
我们可以在ctx和app上定义一些公共的方法和属性,首先是app,egg规定需要把定义的方法和属性写在app\extend\application.js中:
const path = require('path');
module.exports = {
// 方法扩展
package(key){
const pack = getPack(); // 拿到package.json内容
return key ? pack[key] : pack; // 返回指定内容
},
// 属性扩展
get allPackage(){
return getPack(); // 返回package.json内容
}
};
function getPack(){
const filePath = path.join(process.cwd(), "package.json"); // 获取到package.json文件的地址
const pack = require(filePath); // 拿到文件内容
return pack;
}
在接口定义中使用:
async newApplication(){
const { ctx, app } = this;
const packageInfo = app.package('scripts'); // 这样通过自定义公共方法获取到package.json指定内容
// console.log(packageInfo)
const allPack = app.allPackage; // 这样通过自定义公共属性获取到package.json指定内容
console.log(allPack)
ctx.body = 'newApplication';
}
ctx在app\extend\context.js中定义:
module.exports = {
params(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;
}
}
};
在接口定义中使用:
async newContext(){
const { ctx } = this;
const params = ctx.params('id'); // 通过公共方法拿到指定入参
console.log(params)
ctx.body = 'newContext';
}
ctx下面还有很多属性,这些属性还可以进行自定义方法和属性,例如ctx.request,app\extend\request.js:
module.exports = {
get token() { // 获取token 为啥有个get关键词,我还不知道
console.log('header', this.header); // 获取请求头里的东西
return this.get('token'); // 请求头里的属性用get去获取
}
};
// 接口中使用
async getToken(){
const { ctx } = this;
const token = ctx.request.token; // 为什么不是执行方法,问就是固定写法
ctx.body = token;
}
例如ctx.response,app\extend\response.js:
module.exports = {
set token(token){
console.log('token', this)
this.set('token', token);
}
};
// 接口中使用
async setToken(){
const { ctx } = this;
ctx.response.token = 'abc123abc';
ctx.body = '相应头多个token';
}
例如ctx.helper,\app\extend\helper.js:
module.exports = {
base64Encode(str = ''){ // 字符串转base64
return new Buffer(str).toString('base64');
}
};
// 接口中使用
async setToken(){
const { ctx } = this;
ctx.response.token = 'abc123abc';
const base64Parse = ctx.helper.base64Encode('newResponse');
ctx.body = base64Parse;
}
除了第三方插件,egg还允许我们写简单的自定义插件,插件一般用来处理业务逻辑,以中间件的形式去写。插件的功能不能影响egg的核心,例如router.js和controller控制器。
例如写个验证用户是否登陆的业务逻辑,首先egg规定插件的创建方式必须遵守
-lib
-plugin
-egg-auth // 这里注意一定是egg-开头的
-app
--middleware // 插件的原理就是中间件
auth.js // 这里就写逻辑了
--package.json // 说明文件
package.json:
{
"name": "egg-auth", // 插件名
"eggPlugin": {
"name": "auth" // 文件名
}
}
auth.js:
module.exports = options => {
console.log('options', options) // 和中间件一样接收全局变量,在config\config.default.js这里配的
return async (ctx, next) => {
const url = ctx.request.url; // 获取到请求地址
console.log('url', url);
const user = ctx.session.user; // 拿到session设置的用户信息
if (!user && !options.exclude.includes(ctx.request.url.split('?')[0])) { // 这里限制一些接口不需要用户是否登陆的判断
ctx.body = {
status: 1001,
errMsg: '用户未登录'
};
} else {
await next();
}
}
}
写完插件后记得登记一下config\plugin.js:
const path = require('path');
exports.auth = {
enable: true,
path: path.join(__dirname, '../lib/plugin/egg-auth')
};
上面需要的全局变量在config\config.default.js设置:
config.auth = { // auth专用变量
exclude: ['/home', '/user', '/login', '/logout'] // 这个变量用来剔除不需要用户登陆检验的接口
};
然后在全局中启用,app.js:
module.exports = app => {
app.config.coreMiddleware.push('auth'); // 如果是多个自定义插件,要注意顺序,从下到下经过
// app.config.coreMiddleware.push('xxx');
// console.log(app.config.coreMiddleware) 通过打印可以看到经过了哪些插件,且是按照顺序的
}
之后所有接口就生效了(除了上面排除的那四个)。