做系统网站,企业网站html模板免费下载,成都工商注册咨询电话,全国建筑行业查询平台一、egg.js
1. 什么是egg.js
express是基于es5的web开发框架koa1.x 是express原班人员打造的基于es6的web开发框架koa2.x 是express原班人员打造的基于es7的web开发框架egg 是阿里基于koa有约束和规范的企业级web开发框架
2. egg.js的基本使用
2.1 安装
# 初始化
npm init…一、egg.js
1. 什么是egg.js
express是基于es5的web开发框架koa1.x 是express原班人员打造的基于es6的web开发框架koa2.x 是express原班人员打造的基于es7的web开发框架egg 是阿里基于koa有约束和规范的企业级web开发框架
2. egg.js的基本使用
2.1 安装
# 初始化
npm init -y
# 安装eggegg模块是egg.js的核心模块
npm i egg -S
# 安装egg-bin这个模块用于快速启动项目
npm i egg-bin -D2.2 启动快速启动项目用于本地开发调试的模块
// package.json
scripts: {dev: egg-bin dev
}2.3 目录结构
egg-project
├── package.json
├── app.js可选
├── agent.js可选
├── app
| ├── router.js
│ ├── controller
│ │ └── home.js
│ ├── service可选
│ │ └── user.js
│ ├── middleware可选
│ │ └── response_time.js
│ ├── schedule可选
│ │ └── my_task.js
│ ├── public可选
│ │ └── reset.css
│ ├── view可选
│ │ └── home.tpl
│ └── extend可选
│ ├── helper.js可选
│ ├── request.js可选
│ ├── response.js可选
│ ├── context.js可选
│ ├── application.js可选
│ └── agent.js可选
├── config
| ├── plugin.js
| ├── config.default.js
│ ├── config.prod.js
| ├── config.test.js可选
| ├── config.local.js可选
| └── config.unittest.js可选
└── test├── middleware| └── response_time.test.js└── controller└── home.test.js由框架约定的目录
app/router.js 用于配置 URL 路由规则具体参见 Router。app/controller/** 用于解析用户的输入处理后返回相应的结果具体参见 Controller。app/service/** 用于编写业务逻辑层建议使用具体参见 Service。app/middleware/** 用于编写中间件具体参见 Middleware。app/public/** 用于放置静态资源具体参见内置插件 egg-static。app/extend/** 用于框架的扩展具体参见 框架扩展。config/config.{env}.js 用于编写配置文件具体参见 配置。config/plugin.js 用于配置需要加载的插件具体参见 插件。test/** 用于单元测试具体参见 单元测试。app.js 和 agent.js 用于自定义启动时的初始化工作具体参见 启动自定义。关于 agent.js 的作用参见 Agent 机制。
由内置插件约定的目录
app/public/** 用于放置静态资源具体参见内置插件 egg-static。app/schedule/** 用于定时任务具体参见 定时任务。
若需自定义自己的目录规范参见 Loader API
app/view/** 用于放置模板文件具体参见 模板渲染。app/model/** 用于放置领域模型如 egg-sequelize 等领域类相关插件。
2.4 app/router.js
在router.js中必须暴露出去一个方法这个方法接受一个参数这个参数就算服务器的实例对象
module.exports app {console.log(app);/*** {env: local,name: egg,baseDir: C:\\Users\\Administrator\\Desktop\\egg,subdomainOffset: 2,config: egg config,controller: egg controller,httpclient: egg httpclient,loggers: egg loggers,middlewares: egg middlewares,router: egg router,serviceClasses: egg serviceClasses}*/// 从服务器实例上解构出处理路由的对象和处理控制器的对象const { router, controller } app// 监听路由请求// controller.home相当于拿到了controller目录下的home.js如果有多级可以通过.语法使用router.get(/, controller.home.index)
}2.5 app/controller/xx.jsxx.js可自定义名称
这里是app/controller/home.js
const Controller require(egg).Controllerclass HomeController extends Controller {async index() {this.ctx.body hello egg.js}
}module.exports HomeController在eggjs中会自动给控制器挂载一些属性
this.ctx当前请求的上下文 Context 对象的实例通过它我们可以拿到框架封装好的处理当前请求的各种便捷属性和方法。this.app当前应用 Application 对象的实例通过它我们可以拿到框架提供的全局对象和方法。this.service应用定义的 Service通过它我们可以访问抽象出的业务层等价于 this.ctx.service。this.config应用运行时的配置项。this.loggerlogger 对象上面有四个方法debug、info、warn、error分别代表打印四个不同级别的日志。使用方法和效果与 context logger 中介绍的相同但是通过这个 logger 对象记录的日志在日志前面会加上打印该日志的文件路径以便快速定位日志打印位置。
3. egg.js 处理get/post请求
app/router.js
module.exports app {const { router, controller } approuter.get(/getquery, controller.home.getquery)router.get(/getparams/:name/:age, controller.home.getparams)router.post(/postbody, controller.home.postbody)
}app/controller/home.js
const Controller require(egg).Controllerclass HomeController extends Controller {// 获取query参数async getquery() {// let query this.ctx.request.querylet query this.ctx.querythis.ctx.body query}// 获取动态路由参数async getparams() {let params this.ctx.paramsthis.ctx.body params}// 获取post请求参数post请求默认会被阻止掉需要在 config/config.default.js 中添加配置async postbody() {let body this.ctx.request.bodythis.ctx.body body}
}module.exports HomeController注意post请求默认会被阻止掉需要在 config/config.default.js 中添加配置 // config/config.default.js
module.exports {security: {// 跨站点请求csrf: {ignoreJSON: true, // 默认为 false设置为 true 时会忽略所有 content-type 为 application/json 的请求}},
};4. 处理静态资源
在app目录下新建public目录在浏览器中通过http://xxx.com/public/xxx.png即可访问
5. 处理动态资源
需要使用插件插件特殊的中间件
5.1 插件的使用
安装
npm i egg-view-ejs对插件进行配置在config目录下新建plugin.js文件
// exports.xxx
exports.ejs {enable: true,package: egg-view-ejs // 配置使用的插件
}在config.default.js中添加配置
// config/config.default.js
module.exports {// view目录下的文件view: {mapping: {.html: ejs // 在哪个后缀的文件使用插件}}
};在app目录中新建view目录将动态网页放到这个目录下在控制器中通过上下文render方法渲染
const Controller require(egg).Controllerclass HomeController extends Controller {async index() {// app/view/demo.htmlawait this.ctx.render(demo, { msg: 数据 })}
}module.exports HomeController6. 数据处理
在eggjs中无论是数据库中的数据还是处理网络数据都是在Service中处理的
每一次用户请求框架都会实例化对应的 Service 实例。因为它继承自 egg.Service。和控制器一样Service类的this上也挂载了很多属性 this.ctx当前请求的上下文 Context 对象实例。通过它我们可以获取框架封装的处理当前请求的各种便捷属性和方法。 this.app当前应用 Application 对象实例。通过它我们可以访问框架提供的全局对象和方法。 this.service应用定义的 Service。通过它我们可以访问到其他业务层等同于 this.ctx.service。 this.config应用运行时的 配置项。 this.loggerlogger 对象。它有四个方法debuginfowarnerror分别代表不同级别的日志。使用方法和效果与 context logger 所述一致。但通过这个 logger 记录的日志在日志前会加上文件路径方便定位日志位置。
在this.ctx上下文对象上还挂载了其他的属性
使用 this.ctx.curl 发起网络调用。通过 this.ctx.service.otherService 调用其他 Service。调用 this.ctx.db 发起数据库操作db 可能是插件预挂载到 app 上的模块。
在app目录下新建service目录新建js文件
// app/service/home.js
const Service require(egg).Serviceclass HomeService extends Service {async findNews() {// 在Service定义的方法中处理数据库和网络的数据即可// 1. 发送get请求不带参数let response await this.ctx.curl(http://jsonplaceholder.typicode.com/posts)// 2. 发送get请求带参数let response await this.ctx.curl(http://jsonplaceholder.typicode.com/comments?postId1)// 3. 发送post请求不带参数let response await this.ctx.curl(http://jsonplaceholder.typicode.com/posts, {method: POST})// 4. 发送post请求带参数let response await this.ctx.curl(xxx, {method: POST,data: {id: 1}})return response.data}
}module.exports HomeService注意 service目录必须放在app目录中service目录支持多级目录如果是多级目录在调用的时候可以通过链式调用service目录下的js文件如果是以_或首字母大写那么在调用的时候必须转换成驼峰命名 get_user.js getUser GetUser.js getUser 在控制器中调用
通过ctx上下文的service获取
// app/controller/home.js
const Controller require(egg).Controllerclass HomeController extends Controller {async news() {let data await this.ctx.service.home.findNews()this.ctx.body data}
}module.exports HomeController路由写接口
// app/router.js
module.exports app {const { router, controller } approuter.get(/news, controller.home.news)
}7. 处理cookie
生成cookie和获取cookie
不加密
class HomeController extends Controller {async setCookie() {this.ctx.cookies.set(name, xiaotian, {path: /,maxAge: 24 * 60 * 60 * 1000, // 有效时间httpOnly: true, // 只允许在服务端修改signed: true, // 生成cookie的时候同时生成一个签名根据config/config.default.js的keys设置。默认为true})this.ctx.body 设置成功}async getCookie() {let cookie this.ctx.cookies.get(name)this.ctx.body 获取cookie成功, cookie}
}加密和解密 encrypt: true
class HomeController extends Controller {async setCookie() {this.ctx.cookies.set(name, xiaotian, {path: /,maxAge: 24 * 60 * 60 * 1000, // 有效时间httpOnly: true, // 只允许在服务端修改signed: true, // 生成cookie的时候同时生成一个签名根据config/config.default.js的keys设置。默认为trueencrypt: true // 对cookie加密后再保存})this.ctx.body 设置成功}async getCookie() {let cookie this.ctx.cookies.get(name, {signed: true,encrypt: true})this.ctx.body 获取cookie成功, cookie}
}8. 处理日志
eggjs自动生成logs目录
8.1 日志分类
${appInfo.name}-web.log例如 example-app-web.log应用相关日志供应用开发者使用的日志。我们在绝大多数情况下都在使用它。egg-web.log框架内核、插件日志。common-error.logctx.logger.err 输出的错误日志egg-agent.log进程日志框架和使用到 agent 进程执行任务的插件会打印一些日志到这里。egg-schedule.log定时任务的日志。
8.2 日志切割
在eggjs中默认会自动切割日志每一天就是一个新的日志文件
9. 定时任务
9.1 使用场景
定时进行文件切割临时文件删除定时上报应用状态
9.2 如何编写定时任务
在app目录下新建schedule目录
所有的定时任务都统一存放在 app/schedule 目录下每一个文件都是一个独立的定时任务可以配置定时任务的属性和要执行的方法。
const Subscription require(egg).Subscription;class UpdateCache extends Subscription {// 通过 schedule 属性来设置定时任务的执行间隔等配置static get schedule() {return {interval: 1m, // 1 分钟间隔type: all, // all表示当前服务器上所有相同node进程之前说过同一个node项目可以开启多个进程都执行};}// subscribe 是真正定时任务执行时被运行的函数async subscribe() {const res await this.ctx.curl(http://www.api.com/cache, {dataType: json,});// cache 是自定义的this.ctx.app.cache res.data;}
}module.exports UpdateCache;还可以简写为
module.exports {schedule: {interval: 1m, // 1 分钟间隔type: all,},async task(ctx) {const res await ctx.curl(http://www.api.com/cache, {dataType: json,});ctx.app.cache res.data;},
};这个定时任务会在每一个 Worker 进程上每 1 分钟执行一次将远程数据请求回来挂载到 app.cache 上。cache是自定义的
10. 启动自定义
我们常常需要在应用启动期间进行一些初始化工作待初始化完成后应用才可以启动成功并开始对外提供服务。
框架提供了统一的入口文件app.js进行启动过程自定义。这个文件需要返回一个 Boot 类。我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。
框架提供了以下 生命周期函数 供开发人员处理
配置文件即将加载这是最后动态修改配置的时机configWillLoad配置文件加载完成configDidLoad文件加载完成didLoad插件启动完毕willReadyworker 准备就绪didReady应用启动完成serverDidReady应用即将关闭beforeClose。
在根目录下新建app.js
// app.js
class AppBootHook {constructor(app) {this.app app;}configWillLoad() {// 此时 config 文件已经被读取并合并但还并未生效// 这是应用层修改配置的最后机会// 注意此函数只支持同步调用// 例如参数中的密码是加密的在此处进行解密this.app.config.mysql.password decrypt(this.app.config.mysql.password);// 例如插入一个中间件到框架的 coreMiddleware 之间const statusIdx this.app.config.coreMiddleware.indexOf(status);this.app.config.coreMiddleware.splice(statusIdx 1, 0, limit);}async didLoad() {// 所有配置已经加载完毕// 可以用来加载应用自定义的文件启动自定义服务// 例如创建自定义应用的实例this.app.queue new Queue(this.app.config.queue);await this.app.queue.init();// 例如加载自定义目录this.app.loader.loadToContext(path.join(__dirname, app/tasks), tasks, {fieldClass: tasksClasses,});}async willReady() {// 所有插件已启动完毕但应用整体尚未 ready// 可进行数据初始化等操作这些操作成功后才启动应用// 例如从数据库加载数据到内存缓存this.app.cacheData await this.app.model.query(QUERY_CACHE_SQL);}async didReady() {// 应用已启动完毕const ctx await this.app.createAnonymousContext();await ctx.service.Biz.request();}async serverDidReady() {// http/https 服务器已启动开始接收外部请求// 此时可以从 app.server 获取 server 实例// 可以执行一些需要初始化的文件}
}module.exports AppBootHook;11. 操作mysql
11.1 安装
npm i --save egg-mysql11.2 开启插件
// config/plugin.js
exports.mysql {enable: true,package: egg-mysql
};11.3 配置
// config/config.default.js
exports.mysql {// 单数据库信息配置client: {// hosthost: mysql.com,// 端口号port: 3306,// 用户名user: test_user,// 密码password: test_password,// 数据库名database: test},// 是否加载到 app 上默认开启app: true,// 是否加载到 agent 上默认关闭agent: false
};11.4 使用
await app.mysql.query(sql, values); // 单实例可以直接通过 app.mysql 访问11.5 如何编写 CRUD 语句
Create
可以直接使用 insert 方法插入一条记录
// 插入
const result await this.app.mysql.insert(posts, { title: Hello World }); // 在 posts 表中插入 title 为 Hello World 的记录// SQL 语句相当于
// INSERT INTO posts(title) VALUES(Hello World);console.log(result);
// 输出为
// {
// fieldCount: 0,
// affectedRows: 1,
// insertId: 3710,
// serverStatus: 2,
// warningCount: 2,
// message: ,
// protocol41: true,
// changedRows: 0
// }// 判断插入成功
const insertSuccess result.affectedRows 1;Read
可以直接使用 get 方法或 select 方法获取一条或多条记录。select 方法支持条件查询与结果定制。 可以使用 count 方法对查询结果的所有行进行计数。
查询一条记录
const post await this.app.mysql.get(posts, { id: 12 });// SQL 语句相当于
// SELECT * FROM posts WHERE id 12 LIMIT 0, 1;查询全表
const results await this.app.mysql.select(posts);// SQL 语句相当于
// SELECT * FROM posts;条件查询和结果定制
const results await this.app.mysql.select(posts, { // 搜索 posts 表where: { status: draft, author: [author1, author2] }, // WHERE 条件columns: [author, title], // 要查询的字段orders: [[created_at,desc], [id,desc]], // 排序方式limit: 10, // 返回数据量offset: 0, // 数据偏移量
});// SQL 语句相当于
// SELECT author, title FROM posts
// WHERE status draft AND author IN(author1,author2)
// ORDER BY created_at DESC, id DESC LIMIT 0, 10;统计查询结果的行数
const total await this.app.mysql.count(posts, { status: published }); // 统计 posts 表中 status 为 published 的行数// SQL 语句相当于
// SELECT COUNT(*) FROM posts WHERE status publishedUpdate
可以直接使用 update 方法更新数据库记录。
// 修改数据
const row {id: 123,name: fengmk2,otherField: other field value, // 其他想要更新的字段modifiedAt: this.app.mysql.literals.now, // 数据库服务器上的当前时间
};
const result await this.app.mysql.update(posts, row); // 更新 posts 表中的记录// SQL 语句相当于
// UPDATE posts SET name fengmk2, modifiedAt NOW() WHERE id 123;// 判断更新成功
const updateSuccess result.affectedRows 1;// 如果主键是自定义的 ID 名称如 custom_id则需要在 where 里配置
const row2 {name: fengmk2,otherField: other field value, // 其他想要更新的字段modifiedAt: this.app.mysql.literals.now, // 数据库服务器上的当前时间
};const options {where: {custom_id: 456}
};
const result2 await this.app.mysql.update(posts, row2, options); // 更新 posts 表中的记录// SQL 语句相当于
// UPDATE posts SET name fengmk2, modifiedAt NOW() WHERE custom_id 456 ;// 判断更新成功
const updateSuccess2 result2.affectedRows 1;Delete
可以直接使用 delete 方法删除数据库记录。
const result await this.app.mysql.delete(posts, {author: fengmk2,
});// SQL 语句相当于
// DELETE FROM posts WHERE author fengmk2;11.6 直接执行 SQL 语句
插件本身也支持拼接与直接执行 SQL 语句。使用 query 方法可以执行合法的 SQL 语句。
const postId 1;
const results await this.app.mysql.query(update posts set hits (hits ?) where id ?, [1, postId]);// update posts set hits (hits 1) where id 1;12. eggjs的配置文件
config
|- config.default.js 所有环境都会加载如果其他文件有同名会覆盖掉默认的
|- config.prod.js 只有上线环境才会加载
|- config.unittest.js 只有测试环境才会加载
|- config.local.js 只有开发环境才会加载config.default.js 为默认的配置文件所有环境都会加载这个配置文件一般也会作为开发环境的默认配置文件。
当指定 env 时会同时加载默认配置和对应的配置具名配置文件。具名配置和默认配置将合并成最终配置具名配置项会覆盖默认配置文件的同名配置。例如prod 环境会加载 config.prod.js 和 config.default.js 文件config.prod.js 会覆盖 config.default.js 的同名配置。
但通常会用 cross-env 第三方库来配置项目环境
二、pm2
1. pm2 的好处
pm2 进程守护可以在程序崩溃后自动重启pm2 自带日志记录功能可以很方便的记录错误日志和自定义日志pm2 可以启动多个node进程充分利用服务器资源
2. pm2 的基本使用
2.1 安装pm2
npm i pm2 -g2.2 查看pm2版本
pm2 --version2.3 启动pm2
pm2 start index.js3. pm2 常用指令
3.1 启动应用程序
pm2 start index.js3.2 列出启动的所有应用程序
pm2 list3.3 重启应用程序
pm2 restart 应用id/name3.4 杀死并重启所有进程
pm2 restart all3.5 查看应用程序详细信息
pm2 info 应用id/namepm2 show3.6 显示指定应用程序的日志
pm2 log 应用id/name3.7 监控应用程序
pm2 monit 应用id/name3.8 停止应用程序
pm2 stop 应用id/name3.9 停止所有应用程序
pm2 stop all3.10 关闭并删除指定应用程序
pm2 delete 应用id/name3.11 关闭并删除所有应用程序
pm2 delete all3.12 杀死pm2管理的所有进程
pm2 kill4. pm2常用配置
新建pm2.confif.json文件
{name: 应用程序名称,script: 入口文件名称,watch: true, // 文件被修改是否自动重启ignore_watch: [ // 忽略监听哪些文件的改变node_modules,logs],error_file: logs/错误日志文件名称.log,out_file: logs/自定义日志文件名称.log,log_date_format: yyyy-MM-dd HH:mm:ss // 给日志添加时间
}运行
pm2 start pm2.config.json5. 负载均衡
node是单线程的服务器是多核的要充分利用服务器资源可以使用pm2启动多个node进程只需要在配置文件中增加 instances 配置可以启动多个node进程想启动几个就启动几个但是不能超过服务器cpu的核数
{name: 应用程序名称,script: 入口文件名称,watch: true, // 文件被修改是否自动重启ignore_watch: [ // 忽略监听哪些文件的改变node_modules,logs],error_file: logs/错误日志文件名称.log,out_file: logs/自定义日志文件名称.log,log_date_format: yyyy-MM-dd HH:mm:ss, // 给日志添加时间instances: 4 // 开启多个node进程不能超过cpu核数
}