小米商城:须要修改的东西javascript
//npm 建立 npm init egg --type=simple npm i npm run dev //yarn 建立 https://blog.yiiu.co/2018/04/20/eblog-egg/ 1. npm 全局安装egg-init 2. egg-init --type=simple 3. yarn install
/app
/controller
/public 静态资源,css,img
/view 视图层
/service 模型层次
/middleware 中间件(例如权限判断)
/extend 扩展写法
/config 配置文件css
编写过程:MODEL->ROUTER->CONTROLLER->VIEWhtml
ctx对象前端
https://blog.yiiu.co/2018/04/20/eblog-egg/java
controller里取值方法 请求数据有三种: query, params, body query取值方式:const id = this.ctx.request.query.id 适用于:/post?id=1 params 取值方式:const id = this.ctx.params.id 适用于:/post/1 body 取值方式 const id = this.ctx.request.body.id 适用于form表单提交 //相应消息内容 this.ctx.body = "响应的信息到前台"; //get let id = this.ctx.request.query.id; //post let id = this.ctx.request.body.id; //请求链接 console.log('ctx.request.url :', ctx.request.url);
获取get传值:
let query = this.ctx.query;node
动态路由(带参数路由)jquery
router.get('/newslist/:id', )linux
加载模板引擎webpack
egg-view-ejsgit
L6 extend写法对内置对象进行扩展,silly-datetime格式化时间,在模板中进行调用
所有配置属性
module.exports = appInfo => { return { logger: { dir: path.join(appInfo.baseDir, 'logs'), }, }; };
中间件 记得await next();
post 路由的页面不能直接用this.ctx.body返回,须要使用页面跳转this.ctx.redirect
字符串和objectid 的类型转换this.app.mongoose.Types.ObjectId(module_id);
config的default.js配置及获取
const userConfig = { proxy: { pageSize: 10 } } //控制器非路由方法中获取 const appkey = this.app.config.proxy.pageSize; //控制器路由方法 let pageSize = this.config.proxy.pageSize;
// 方法一 class OrderController extends Controller { // async index() { //渲染表单的时候把csrf带上 await this.ctx.render('order', { csrf: this.ctx.csrf }); } // 打印post提交的数据 async submit() { console.log(this.ctx.request.body); } }
利用中间件将 csrf传递到koa的state中成为全局变量
// 在/middleware/auth.js目录下添加中间件 module.exports=(option, app)=> { return async function auth(ctx, next) { //设置模板全局变量 ctx.state.csrf = ctx.csrf; await next(); } } // default.config.js中引入中间件 config.middleware = ['auth']; //以后再render中就再也不须要传递csrf了 //////// 以后再表单的post请求时候带上_csrf <form action="/order/submit?_csrf=<%=csrf%>" method="POST" name="order"> </form>
关于CryptoJS AES后输出hex的16进制和Base64的问题。 - 简书
[Use CryptoJS encrypt message by DES and direct decrypt ciphertext, compatible with Java Cipher.getInstance("DES") · GitHub]
JavaScript Crypto-JS 使用手册 – 抒写(https://gist.github.com/ufologist/5581486)
let mid = '12345'; let secretkey = '74aaaaa655aaaaab97fe3793aaaa2a'; let {packages, accountName, idCard, phone, address} = this.ctx.request.body; let post_json = JSON.stringify({ mid, packages, accountName, idCard, phone, address }); //CRYPTOJS的toString的用法 // let secretkey = '74d4936557a54c3b97fe3793bbd1442a'; // let mid = '10536'; let keyHex = CryptoJS.enc.Hex.parse(CryptoJS.enc.Utf8.parse(secretkey).toString(CryptoJS.enc.Hex)); let encrypted = CryptoJS.DES .encrypt(post_json, keyHex, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) .ciphertext.toString(); console.log(encrypted);
1.ejs
2.art-template
some方法
cookies保存在客户端
session保存在服务端,当浏览器访问服务器并发送第一次请求时,服务器端会建立一个session对象,生成一个相似于key,value的键值对,而后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie,找到对应的session(value)。
cookie和session的必须为字符串
this.ctx.cookies.set('username', 'zhangsj'); this.ctx.cookies.get('username');
实现同一个浏览器访问同一个域的时候,不一样页面之间的数据共享
实现数据的持久化
egg.js中的cookies默认状况下没法设置中文
//方法一 cookie加密以后能够设置中文 this.ctx.cookies.set('username', 'vhzngsj', { maxAge: 1000*60*60*24 //存储一天, httpOnly: true, signed: true, //对cookie进行签名防止用户手动修改 encrytp: true //对cookie进行加密, 获取的时候须要进行解密,加密以后的cookie能够设置中文 }); //方法二 console.log(new Buffer('hello, world!').toString('base64')); // 转换成 base64 字符串: aGVsbG8sIHdvcmxkIQ== console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString()); // 还原 base64 字符串: hello, world!
cookie用JSON.stringify和JSON.parse能够存储对象
清除cookie
async loginOut() { //cookies 置空 this.ctx.cookies.set('unserinfo', null); //或者设置过时时间为0 this.ctx.cookies.set('username', null, { maxAge: 0 }); this.ctx.rediredc('/news'); //路由跳转 }
当浏览器访问服务器并发送第一次请求时,服务器端会建立一个session对象,生成一个相似于key,value的键值对,而后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie,找到对应的session(value)。
要访问session必须借助本地保存的cookies
使用
// 设置 this.ctx.session.userinfo = { name: '张三', age: '20' }, // 获取 var userinfo = this.ctx.session.username; // this.ctx.session.username = "";
session的配置
// /config.default.js config.session= { key: 'SESSION_ID', //设置session对应cookie在浏览器端的key maxAge: 2000, httpOnly: true, encrypt: true, renew: true //每次刷新页面时session会自动延期 } //或者 修改session 过时时间 this.ctx.session.maxAge = 2000; //秒
全局中间
mdoule.exports=(options, app)=>{ return asycn function auth(ctx, next) { //xxxx await next(); } } //配置: config.middleware=['auth'] //传参 config.auth={ title: 'dfsfsf' }
路由中间件
/router.js module.exports = app => { const { router, controller } = app; // 路由中获取中间件 const auth = app.middleware.auth({ attr: 'this is router.js middleware' }); //配置路由中间件 router.get('/', auth,controller.home.index); router.get('/news', controller.news.index); router.get('/shop', controller.shop.index); };
框架默认中间节
//config.default.js
config.bodyParser={ jsonLimit: '10mb' //Default is 1mb. }
使用koa
//koa-jsonp // 1.安装 npm install koa-jsonp --save //引入 /middleware/jsonp.js let jsonp = require('koa-jsonp'); module.exports= jsonp; //在config.default.js config.middleware=['jsonp'];
//koa-compress //1. 安装 //npm i koa-compress -S //中间件的配置: //config.default.js config.cmpress = { threshold: 1024 }
非标准中间件的引入
```js
const webpackMiddleware = require('some-koa-middleware');
module.exports = (options, app) => {
return webpackMiddleware(options.compiler, options.others);
}
- KOA获取域名 this.ctx.protocol /this.ctx.host / hostname/this.ctx.origin 协议,主机带端口,不带端口,完整域名
通用中间件配置
//config.default.js config.compress = { enable: false, match: '/news', ignore: '/news', // ignore和match是相反的配置,不能同时配置到同一个中间件中 //经过match()方法配置 mathc(ctx) { // ctx 上下文 能够得到请求地址 console.log(ctx.request.url); if(ctx.request.url=='/shop' || ctx.request.url=='/news') { return true; } return false; } threshold: 1024 }
中间件配合项目 LESSON13
中间件命名, auth_api.js => authApi
this.ctx.status = 301; this.ctx.redirect('\nnn');
//302 301重定向 // 有利于seo优化 router.redirect('\news', '\', 302);
//新建router文件夹进行分组 // app/router/admin.js module.exports = app => { app.router.get('/admin/user', app.controller.admin.user); app.router.get('/admin/log', app.controller.admin.log); }; // 在router.js中进行引入 // app/router.js module.exports = app => { require('./router/news')(app); require('./router/admin')(app); };
过几秒跳转到某个网页
<meta http-equiv="refresh" content="3:url=/">
在/app/core/base.js中定义base基类, 继承
控制器的兼容性写法
'use strict'; const Controller = require('egg').Controller; class HomeController extends Controller { //把ctx当作参数传入 等同于在函数内使用 this.ctx async index(ctx) { await ctx.render('home'); } } module.exports = HomeController;
定时执行某些任务,定时清理缓存,定时报告
/app/schedule
watchfile.js
1.继承schedule写法
2.导出对象写法
3.导出函数,函数返回对象
cherrio 模块爬虫
const $=cheerio.load('<h2 class="title">Hello world</h2>")
$('title').html()
获取了要匹配的标题的内容var url="http://news.baidu.com//";
安装插件
npm i egg-mongo-native --save
聚合管道:关联查询与数据统计
$project 增长、删除、重命名字段 投影筛选
$match 条件匹配。只知足条件的文档才能进入下
$limit 限制结果的数量
$skip 跳过文档的数量
$sort 条件排序
$group 条件组合结果 统计
$lookup 用以引入其它集合的数据 (表关联查询)
scheme-》表-》集合
定义model-》操做数据库
安装npm i egg-mongoose --save
在/app/model中操做数据库
字符串和objectid 的类型转换this.app.mongoose.Types.ObjectId(module_id);
关联查询:对id的查询要现将字符串类型转换为 ObjectID类型,
let _id = this.ctx.session.proxyuserinfo._id; // console.log('--------_id---------'); // console.log(_id); result = await this.ctx.model.Proxy.aggregate([ { $lookup: { from: 'proxy', localField: '_id', foreignField: 'pid', as: 'items' } }, { $match: { _id } } ]); } //从订单表order中查商品goods,order表是主表格,localField是order表中的外键名称,foreignField是在goods表中 let result = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'goods', localField: 'goods_id', foreignField: '_id', as: 'items' } }, { $match: { proxy_id } } ]);
增删改查
//增 let access = new this.ctx.model.Access(addResult); await access.save(); //改 let result = await this.ctx.model.ContractTpl.update({ _id }, form); console.log(result); //查 let result = await this.ctx.model.Access.find({ module_id: '0' }); //删除多个 $in let result = await this.ctx.model.ContractTplAttr.find({ tplId: tplId }); // console.log(result); let tplIdArr = []; if (result) { for (let i = 0; i < result.length; i++) { tplIdArr.push(result[i]._id); } } if (tplIdArr) { result = await this.ctx.model.ContractTplAttr.deleteMany({ _id: { $in: tplIdArr } }) }
筛选查询
// 注意筛选查询的顺序 $look->$sort->筛选 const result = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'order_item', localField: '_id', foreignField: 'order_id', as: 'orderItems', }, }, { $sort: {"add_time":-1} //时间 }, { $match:{"uid":this.app.mongoose.Types.ObjectId(uid)} //条件 }, { $skip: (page - 1) * pageSize, }, { $limit: pageSize, } ]);
mongodb查询时间 https://stackoverflow.com/questions/11973304/mongodb-mongoose-querying-at-a-specific-date http://www.javashuo.com/article/p-qkhrcdhg-nr.html
excel的mime类型: https://stackoverflow.com/questions/4212861/what-is-a-correct-mime-type-for-docx-pptx-etc
https://github.com/eggjs/egg/issues/965 文件下载git问题
//获取将要提供下载的文件的绝对路径 const filePath = path.resolve('./hello.xlsx'); console.log('-------filePath--------'); console.log(filePath); //读取文件到缓冲区 const buf = fs.readFileSync(filePath); //删除文件 fs.unlinkSync(filePath); //设置下载时候的文件名 this.ctx.attachment('hello.xlsx'); //设置下载文件的mime类型 this.ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); //文件赋值给body,下载 this.ctx.body = buf;
https://github.com/okoala/egg-jwt/blob/master/test/fixtures/apps/jwt-app.jwt/app/controller/success.js
用jsonwebtoken对application进行了extend进而实现egg-jwt
用法:
npm 安装
plgin.js
// {app_root}/config/plugin.js exports.jwt = { enable: true, package: "egg-jwt" };
config.js
// {app_root}/config/config.default.js exports.jwt = { secret: "123456" };
在路由中使用,在router.js 中引入而后在router中使用便可
全局使用,经过app.js引入使用:
//config.default.js中 config.jwt = { secret: '123456', enable: true, ignore: '/login', } ///////////剩下两步不知道啥用处 //app.js中引入 module.exports = app => { app.config.appMiddleware.unshift('jwtErrorHandler'); }; //???????? exports.keys = 'egg-jwt'; //?????
https://www.cnblogs.com/daysme/p/10250224.html
centos 7安装phantomjs
wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 yum install bzip2 # 安装bzip2 tar -jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2 mv phantomjs-2.1.1-linux-x86_64 /usr/local/src/phantomjs ln -sf /usr/local/src/phantomjs/bin/phantomjs /usr/local/bin/phantomjs yum install fontconfig freetype2 phantomjs -v # 测试版本号
安装中文字体
yum install bitmap-fonts bitmap-fonts-cjk yum groupinstall "fonts" -y # 安装字体相关的依赖包 fc-cache # 刷新字体缓存
全局指定中间件登录路由
权限判断:
egg-mongoose 安装 配置 L31
安装 npm i egg-mongoose -S
/config/plugin.js
//mongodb://127.0.0.1/eggcms //mongodb://eggadmin:123456@localhost:27017/eggcms exports.mongoose = { client: { url: 'mongodb://127.0.0.1/eggcms', options: {}, }, };
/config/config.default.js
建立model /model user.js
module.exports = app => { const mongoose = app.mongoose; /*引入创建链接的mongoose */ const Schema = mongoose.Schema; //数据库表的映射 const UserSchema = new Schema({ username: { type: String }, password: { type: String }, status:{ type:Number, default:1 } }); return mongoose.model('User', UserSchema,'user'); }
在控制器中使用mongoose.find
'use strict'; const Controller = require('egg').Controller; class UserController extends Controller { async index() { var userList=await this.service.user.getUserList(); console.log(userList); this.ctx.body='我是用户页面'; } async addUser() { //增长数据 var user=new this.ctx.model.User({ username:'李四', password:'123456' }); var result=await user.save(); console.log(result) this.ctx.body='增长用户成功'; } async editUser() { //增长数据 await this.ctx.model.User.updateOne({ "_id":"5b84d4405f66f20370dd53de" },{ username:"哈哈哈", password:'1234' },function(err,result){ if(err){ console.log(err); return; } console.log(result) }) this.ctx.body='修改用户成功'; } async removeUser() { //增长数据 var rel =await this.ctx.model.User.deleteOne({"_id":"5b84d4b3c782f441c45d8bab"}); cnsole.log(rel); this.ctx.body='删除用户成功'; } } module.exports = UserController;
用户属于某种角色角,色拥有权限
权限的级联删除,级联更新(模块修改以后,属于模块的操做将没法显示)
注意几个点: dom中的this,jquery中的$(function)
form 表单中必须加 enctype="multipart/form-data"。表单默认提交数据的方式是
application/x-www-form-urlencoded 不是不能上传文件,是只能上传文本格式的文件,
multipart/form-data 是将文件以二进制的形式上传,这样能够实现多种类型的文件上传。
enctype="multipart/form-data"之后,后台将无法经过 this.ctx.request.body来接收表单的数据。
须要使用egg-multipart
注意上传文件时候的csrf的写法
安装pump模块,防止module卡死
上传文件的stream示例
FileStream { _readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: BufferList { head: [Object], tail: [Object], length: 1 }, length: 63967, pipes: null, pipesCount: 0, flowing: null, ended: false, endEmitted: false, reading: false, sync: true, needReadable: false, emittedReadable: false, readableListening: false, resumeScheduled: false, paused: true, emitClose: true, destroyed: false, defaultEncoding: 'utf8', awaitDrain: 0, readingMore: false, decoder: null, encoding: null }, readable: true, _events: [Object: null prototype] { end: [Function] }, _eventsCount: 1, _maxListeners: undefined, truncated: false, _read: [Function], //重点部分 fieldname: 'focus_img', filename: 's3.png', encoding: '7bit', transferEncoding: '7bit', mime: 'image/png', mimeType: 'image/png' }
能够将除了文件的其它字段提取到 parts 的 filed 中
getFileStream, 上传且只能上传一个文件
//注意在循环读取多个文件的时候使用continue时要把stream消费掉 //上传到本地模式 //循环读取多个文件 while ((stream = await parts()) != null) { if (!stream.filename) { //await pump(stream, writeStream); //写入并销毁当前流 (egg demo提供的) //continue; 销毁 break; } // 表单中的file类型的name let fieldname = stream.fieldname; // 图片上传的目录 let dir = await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); //写入并销毁当前流 (egg demo提供的) files = Object.assign(files, { [fieldname]: dir.saveDir }); } //上传到oss模式 //上传文件到oss async doAdd() { let parts = this.ctx.multipart({ autoFields: true }); //autoFileds能够将除了文件的其它字段提取到 parts 的 filed 中 let files = {}; let stream; console.log('---------fieldname--------'); //循环读取多个文件 while ((stream = await parts()) != null) { if (!stream.filename) { break; } // 表单中的file类型的name let fieldname = stream.fieldname; let d = await this.service.tools.getTime(); let name ='goods/' +d+ path.extname(stream.filename); const result = await this.ctx.oss.put(name, stream); files = Object.assign(files, { [fieldname]: result.url }); } // 写入数据库 let goods = new this.ctx.model.Goods(Object.assign(files, parts.field)); await goods.save(); console.log('------files--------------'); console.log(Object.assign(files, parts.field)); await this.success('/admin/goods', '添加号卡成功'); }
注意: 在/service和/model中多个单词的文件命名使用下划线隔开,调用时使用大驼峰
而控制器中使用小驼峰命名,使用时用小驼峰。
goodsTypeAttribute中的cate_id关联到商品类型(手机、电视)
attr_type属性的录入方式,check_box,
设置iframe的高度,在/view/main/index.html
多个组件的事件触发
$(function(){ $("input[name='attr_type']").change(function(){ console.log($(this).val()); if($(this).val()==3){ $('#attr_value').attr('disabled',false) }else{ $('#attr_value').attr('disabled',true) } }); })
3. iframe找不到 ### L62 商品类型属性的修改 //添加分类 **注意**checked==true的用法, /view/goodsTypeAttribute/edit.html ### l63 商品分类增删改查 1. 商品分类和商品关联,用于商品的筛选 2. 上传图片 3. string类型和objectid 类型转换 4. **jimp包动态生成缩略图** - npm i --save jimp - 引入· ```js //上传图片成功之后生成缩略图 Jimp.read(target, (err, lenna) => { if (err) throw err; lenna .resize(200, 200) // resize .quality(90) // set JPEG quality .write(target + '_200x200' + path.extname(target)); // save });
商品分类的修改
注意busboy的坑,multipart上传上传时候有表单field数量的限制,注意
在config.default.js中进行配置
//配置表单数量 exports.multipart = { fields: '50' };
商品界面布局:
添加商品属性的后台界面编写
'注意:'默认列表合并 /pulic/admin/js/base.js
toggleAside
$('.aside>li:nth-child(1) ul, .aside>li:nth-childe(2) ul, .aside>li:nth-childe(3) ul').hide();
bootstrap tab切换 bootstrap tabs
在page_header中引入bootstrap.min.js
须要本身增长颜色的增删改查
$(function) $.get() $().hide()
404 路由错误
403 csrf错误
500 服务器错误
mongodb分页
db.表名.find().skip((page-1)*pageSize).limit(pageSize)
#规定每页 8 条数据的查询方式 #第一页 查询0-7条数据 db.表名.find().skip(0).limit(8) #查询第二页(page=2): db.表名.find().skip(8).limit(8) #查询第四页(page=4): db.表名.find().skip(24).limit(8)
mongoose分页 https://mongoosejs.com/docs/queries.html
Person. find({ occupation: /host/, 'name.last': 'Ghost', age: { $gt: 17, $lt: 66 }, likes: { $in: ['vaporizing', 'talking'] } }). limit(10). sort({ occupation: -1 }). select({ name: 1, occupation: 1 }). exec(callback); // Using query builder Person. find({ occupation: /host/ }). where('name.last').equals('Ghost'). where('age').gt(17).lt(66). where('likes').in(['vaporizing', 'talking']). limit(10). sort('-occupation'). select('name occupation'). exec(callback);
jqPaginator
引入jQuery
定义一个div,div的class为pagination
jqPaginator初始化
<script src="/public/sell/js/jqPaginator.js"></script> <div id="page" class="pagination"></div> $("#page").jqpaginatork({ totalPages: 100, visiblePages:10, currentPage:1, onPagechange:function(num, type){ $('#text').html('当前第'+num+'页'); }});
聚合管道+计数
async index() { let proxy_id = this.app.mongoose.Types.ObjectId(this.ctx.request.query.id); let proxy = await this.ctx.model.Proxy.find({ _id: proxy_id }); let page = this.ctx.request.query.page || 1; //每一页数量 let pageSize = 10; //获取数据总数量 let totalNum = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'goods', localField: 'goods_id', foreignField: '_id', as: 'items' } }, { $match: { proxy_id } }, { //计数 $group: { _id: proxy_id, count: { $sum: 1 } } } ]); if(totalNum[0] != null) { totalNum = totalNum[0].count; } else { totalNum = 0; } let result = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'goods', localField: 'goods_id', foreignField: '_id', as: 'items' } }, { $match: { proxy_id } }, { //跳过 $skip: (page-1)*pageSize }, { //限制 $limit: pageSize } ]); // console.log(JSON.stringify(result); await this.ctx.render('sell/order/index', { list: result, proxy: proxy[0], proxy_id, totalPages: Math.ceil(totalNum / pageSize), page }); }
https://a.itying.com/api1/newslist 或 https://a.itying.com/api2/newslist
egg-cors 容许跨域
npm i egg-cors --save //plug.js cors = { enable: true, package: 'egg-cors' } //在egg.config.js中配置 //和容许的请求方法 config.cors = { origin: '*', allowMethos: 'GET, HEAD, PUT, DELETE, POST, PATCH' } //容许的域名 config.security ={ //csrf 忽略某些url casrf: { ignore: ctx=> { if(ctx.request.url.indexOf('/api') != -1) { return true; } else { return false; } } } //不知道这个有啥用? csrf白名单,若是没有配置或者为空则默认csrf放过全部域名 domainWhiteList: ['http://localhost:8080'] }
//config.default.js exports.cors = { credentials: true; origin: '*', //cookie须要修改成具体地址 allowMethods: 'GET, PUT, POST, DELETE', credentials: true //cookies跨域 } 前端: {credentials: true}
小米商城:须要修改的东西
//npm 建立 npm init egg --type=simple npm i npm run dev //yarn 建立 https://blog.yiiu.co/2018/04/20/eblog-egg/ 1. npm 全局安装egg-init 2. egg-init --type=simple 3. yarn install
/app
/controller
/public 静态资源,css,img
/view 视图层
/service 模型层次
/middleware 中间件(例如权限判断)
/extend 扩展写法
/config 配置文件
编写过程:MODEL->ROUTER->CONTROLLER->VIEW
ctx对象
https://blog.yiiu.co/2018/04/20/eblog-egg/
controller里取值方法 请求数据有三种: query, params, body query取值方式:const id = this.ctx.request.query.id 适用于:/post?id=1 params 取值方式:const id = this.ctx.params.id 适用于:/post/1 body 取值方式 const id = this.ctx.request.body.id 适用于form表单提交 //相应消息内容 this.ctx.body = "响应的信息到前台"; //get let id = this.ctx.request.query.id; //post let id = this.ctx.request.body.id; //请求链接 console.log('ctx.request.url :', ctx.request.url);
获取get传值:
let query = this.ctx.query;
动态路由(带参数路由)
router.get('/newslist/:id', )
加载模板引擎
egg-view-ejs
L6 extend写法对内置对象进行扩展,silly-datetime格式化时间,在模板中进行调用
所有配置属性
module.exports = appInfo => { return { logger: { dir: path.join(appInfo.baseDir, 'logs'), }, }; };
中间件 记得await next();
post 路由的页面不能直接用this.ctx.body返回,须要使用页面跳转this.ctx.redirect
字符串和objectid 的类型转换this.app.mongoose.Types.ObjectId(module_id);
config的default.js配置及获取
const userConfig = { proxy: { pageSize: 10 } } //控制器非路由方法中获取 const appkey = this.app.config.proxy.pageSize; //控制器路由方法 let pageSize = this.config.proxy.pageSize;
// 方法一 class OrderController extends Controller { // async index() { //渲染表单的时候把csrf带上 await this.ctx.render('order', { csrf: this.ctx.csrf }); } // 打印post提交的数据 async submit() { console.log(this.ctx.request.body); } }
利用中间件将 csrf传递到koa的state中成为全局变量
// 在/middleware/auth.js目录下添加中间件 module.exports=(option, app)=> { return async function auth(ctx, next) { //设置模板全局变量 ctx.state.csrf = ctx.csrf; await next(); } } // default.config.js中引入中间件 config.middleware = ['auth']; //以后再render中就再也不须要传递csrf了 //////// 以后再表单的post请求时候带上_csrf <form action="/order/submit?_csrf=<%=csrf%>" method="POST" name="order"> </form>
关于CryptoJS AES后输出hex的16进制和Base64的问题。 - 简书
[Use CryptoJS encrypt message by DES and direct decrypt ciphertext, compatible with Java Cipher.getInstance("DES") · GitHub]
JavaScript Crypto-JS 使用手册 – 抒写(https://gist.github.com/ufologist/5581486)
let mid = '12345'; let secretkey = '74aaaaa655aaaaab97fe3793aaaa2a'; let {packages, accountName, idCard, phone, address} = this.ctx.request.body; let post_json = JSON.stringify({ mid, packages, accountName, idCard, phone, address }); //CRYPTOJS的toString的用法 // let secretkey = '74d4936557a54c3b97fe3793bbd1442a'; // let mid = '10536'; let keyHex = CryptoJS.enc.Hex.parse(CryptoJS.enc.Utf8.parse(secretkey).toString(CryptoJS.enc.Hex)); let encrypted = CryptoJS.DES .encrypt(post_json, keyHex, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) .ciphertext.toString(); console.log(encrypted);
1.ejs
2.art-template
some方法
cookies保存在客户端
session保存在服务端,当浏览器访问服务器并发送第一次请求时,服务器端会建立一个session对象,生成一个相似于key,value的键值对,而后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie,找到对应的session(value)。
cookie和session的必须为字符串
this.ctx.cookies.set('username', 'zhangsj'); this.ctx.cookies.get('username');
实现同一个浏览器访问同一个域的时候,不一样页面之间的数据共享
实现数据的持久化
egg.js中的cookies默认状况下没法设置中文
//方法一 cookie加密以后能够设置中文 this.ctx.cookies.set('username', 'vhzngsj', { maxAge: 1000*60*60*24 //存储一天, httpOnly: true, signed: true, //对cookie进行签名防止用户手动修改 encrytp: true //对cookie进行加密, 获取的时候须要进行解密,加密以后的cookie能够设置中文 }); //方法二 console.log(new Buffer('hello, world!').toString('base64')); // 转换成 base64 字符串: aGVsbG8sIHdvcmxkIQ== console.log(new Buffer('aGVsbG8sIHdvcmxkIQ==', 'base64').toString()); // 还原 base64 字符串: hello, world!
cookie用JSON.stringify和JSON.parse能够存储对象
清除cookie
async loginOut() { //cookies 置空 this.ctx.cookies.set('unserinfo', null); //或者设置过时时间为0 this.ctx.cookies.set('username', null, { maxAge: 0 }); this.ctx.rediredc('/news'); //路由跳转 }
当浏览器访问服务器并发送第一次请求时,服务器端会建立一个session对象,生成一个相似于key,value的键值对,而后将key(cookie)返回到浏览器(客户)端,浏览器下次再访问时,携带key(cookie,找到对应的session(value)。
要访问session必须借助本地保存的cookies
使用
// 设置 this.ctx.session.userinfo = { name: '张三', age: '20' }, // 获取 var userinfo = this.ctx.session.username; // this.ctx.session.username = "";
session的配置
// /config.default.js config.session= { key: 'SESSION_ID', //设置session对应cookie在浏览器端的key maxAge: 2000, httpOnly: true, encrypt: true, renew: true //每次刷新页面时session会自动延期 } //或者 修改session 过时时间 this.ctx.session.maxAge = 2000; //秒
全局中间
mdoule.exports=(options, app)=>{ return asycn function auth(ctx, next) { //xxxx await next(); } } //配置: config.middleware=['auth'] //传参 config.auth={ title: 'dfsfsf' }
路由中间件
/router.js module.exports = app => { const { router, controller } = app; // 路由中获取中间件 const auth = app.middleware.auth({ attr: 'this is router.js middleware' }); //配置路由中间件 router.get('/', auth,controller.home.index); router.get('/news', controller.news.index); router.get('/shop', controller.shop.index); };
框架默认中间节
//config.default.js
config.bodyParser={ jsonLimit: '10mb' //Default is 1mb. }
使用koa
//koa-jsonp // 1.安装 npm install koa-jsonp --save //引入 /middleware/jsonp.js let jsonp = require('koa-jsonp'); module.exports= jsonp; //在config.default.js config.middleware=['jsonp'];
//koa-compress //1. 安装 //npm i koa-compress -S //中间件的配置: //config.default.js config.cmpress = { threshold: 1024 }
非标准中间件的引入
```js
const webpackMiddleware = require('some-koa-middleware');
module.exports = (options, app) => {
return webpackMiddleware(options.compiler, options.others);
}
- KOA获取域名 this.ctx.protocol /this.ctx.host / hostname/this.ctx.origin 协议,主机带端口,不带端口,完整域名
通用中间件配置
//config.default.js config.compress = { enable: false, match: '/news', ignore: '/news', // ignore和match是相反的配置,不能同时配置到同一个中间件中 //经过match()方法配置 mathc(ctx) { // ctx 上下文 能够得到请求地址 console.log(ctx.request.url); if(ctx.request.url=='/shop' || ctx.request.url=='/news') { return true; } return false; } threshold: 1024 }
中间件配合项目 LESSON13
中间件命名, auth_api.js => authApi
this.ctx.status = 301; this.ctx.redirect('\nnn');
//302 301重定向 // 有利于seo优化 router.redirect('\news', '\', 302);
//新建router文件夹进行分组 // app/router/admin.js module.exports = app => { app.router.get('/admin/user', app.controller.admin.user); app.router.get('/admin/log', app.controller.admin.log); }; // 在router.js中进行引入 // app/router.js module.exports = app => { require('./router/news')(app); require('./router/admin')(app); };
过几秒跳转到某个网页
<meta http-equiv="refresh" content="3:url=/">
在/app/core/base.js中定义base基类, 继承
控制器的兼容性写法
'use strict'; const Controller = require('egg').Controller; class HomeController extends Controller { //把ctx当作参数传入 等同于在函数内使用 this.ctx async index(ctx) { await ctx.render('home'); } } module.exports = HomeController;
定时执行某些任务,定时清理缓存,定时报告
/app/schedule
watchfile.js
1.继承schedule写法
2.导出对象写法
3.导出函数,函数返回对象
cherrio 模块爬虫
const $=cheerio.load('<h2 class="title">Hello world</h2>")
$('title').html()
获取了要匹配的标题的内容var url="http://news.baidu.com//";
安装插件
npm i egg-mongo-native --save
聚合管道:关联查询与数据统计
$project 增长、删除、重命名字段 投影筛选
$match 条件匹配。只知足条件的文档才能进入下
$limit 限制结果的数量
$skip 跳过文档的数量
$sort 条件排序
$group 条件组合结果 统计
$lookup 用以引入其它集合的数据 (表关联查询)
scheme-》表-》集合
定义model-》操做数据库
安装npm i egg-mongoose --save
在/app/model中操做数据库
字符串和objectid 的类型转换this.app.mongoose.Types.ObjectId(module_id);
关联查询:对id的查询要现将字符串类型转换为 ObjectID类型,
let _id = this.ctx.session.proxyuserinfo._id; // console.log('--------_id---------'); // console.log(_id); result = await this.ctx.model.Proxy.aggregate([ { $lookup: { from: 'proxy', localField: '_id', foreignField: 'pid', as: 'items' } }, { $match: { _id } } ]); } //从订单表order中查商品goods,order表是主表格,localField是order表中的外键名称,foreignField是在goods表中 let result = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'goods', localField: 'goods_id', foreignField: '_id', as: 'items' } }, { $match: { proxy_id } } ]);
增删改查
//增 let access = new this.ctx.model.Access(addResult); await access.save(); //改 let result = await this.ctx.model.ContractTpl.update({ _id }, form); console.log(result); //查 let result = await this.ctx.model.Access.find({ module_id: '0' }); //删除多个 $in let result = await this.ctx.model.ContractTplAttr.find({ tplId: tplId }); // console.log(result); let tplIdArr = []; if (result) { for (let i = 0; i < result.length; i++) { tplIdArr.push(result[i]._id); } } if (tplIdArr) { result = await this.ctx.model.ContractTplAttr.deleteMany({ _id: { $in: tplIdArr } }) }
筛选查询
// 注意筛选查询的顺序 $look->$sort->筛选 const result = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'order_item', localField: '_id', foreignField: 'order_id', as: 'orderItems', }, }, { $sort: {"add_time":-1} //时间 }, { $match:{"uid":this.app.mongoose.Types.ObjectId(uid)} //条件 }, { $skip: (page - 1) * pageSize, }, { $limit: pageSize, } ]);
mongodb查询时间 https://stackoverflow.com/questions/11973304/mongodb-mongoose-querying-at-a-specific-date http://www.javashuo.com/article/p-qkhrcdhg-nr.html
excel的mime类型: https://stackoverflow.com/questions/4212861/what-is-a-correct-mime-type-for-docx-pptx-etc
https://github.com/eggjs/egg/issues/965 文件下载git问题
//获取将要提供下载的文件的绝对路径 const filePath = path.resolve('./hello.xlsx'); console.log('-------filePath--------'); console.log(filePath); //读取文件到缓冲区 const buf = fs.readFileSync(filePath); //删除文件 fs.unlinkSync(filePath); //设置下载时候的文件名 this.ctx.attachment('hello.xlsx'); //设置下载文件的mime类型 this.ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); //文件赋值给body,下载 this.ctx.body = buf;
https://github.com/okoala/egg-jwt/blob/master/test/fixtures/apps/jwt-app.jwt/app/controller/success.js
用jsonwebtoken对application进行了extend进而实现egg-jwt
用法:
npm 安装
plgin.js
// {app_root}/config/plugin.js exports.jwt = { enable: true, package: "egg-jwt" };
config.js
// {app_root}/config/config.default.js exports.jwt = { secret: "123456" };
在路由中使用,在router.js 中引入而后在router中使用便可
全局使用,经过app.js引入使用:
//config.default.js中 config.jwt = { secret: '123456', enable: true, ignore: '/login', } ///////////剩下两步不知道啥用处 //app.js中引入 module.exports = app => { app.config.appMiddleware.unshift('jwtErrorHandler'); }; //???????? exports.keys = 'egg-jwt'; //?????
https://www.cnblogs.com/daysme/p/10250224.html
centos 7安装phantomjs
wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 yum install bzip2 # 安装bzip2 tar -jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2 mv phantomjs-2.1.1-linux-x86_64 /usr/local/src/phantomjs ln -sf /usr/local/src/phantomjs/bin/phantomjs /usr/local/bin/phantomjs yum install fontconfig freetype2 phantomjs -v # 测试版本号
安装中文字体
yum install bitmap-fonts bitmap-fonts-cjk yum groupinstall "fonts" -y # 安装字体相关的依赖包 fc-cache # 刷新字体缓存
全局指定中间件登录路由
权限判断:
egg-mongoose 安装 配置 L31
安装 npm i egg-mongoose -S
/config/plugin.js
//mongodb://127.0.0.1/eggcms //mongodb://eggadmin:123456@localhost:27017/eggcms exports.mongoose = { client: { url: 'mongodb://127.0.0.1/eggcms', options: {}, }, };
/config/config.default.js
建立model /model user.js
module.exports = app => { const mongoose = app.mongoose; /*引入创建链接的mongoose */ const Schema = mongoose.Schema; //数据库表的映射 const UserSchema = new Schema({ username: { type: String }, password: { type: String }, status:{ type:Number, default:1 } }); return mongoose.model('User', UserSchema,'user'); }
在控制器中使用mongoose.find
'use strict'; const Controller = require('egg').Controller; class UserController extends Controller { async index() { var userList=await this.service.user.getUserList(); console.log(userList); this.ctx.body='我是用户页面'; } async addUser() { //增长数据 var user=new this.ctx.model.User({ username:'李四', password:'123456' }); var result=await user.save(); console.log(result) this.ctx.body='增长用户成功'; } async editUser() { //增长数据 await this.ctx.model.User.updateOne({ "_id":"5b84d4405f66f20370dd53de" },{ username:"哈哈哈", password:'1234' },function(err,result){ if(err){ console.log(err); return; } console.log(result) }) this.ctx.body='修改用户成功'; } async removeUser() { //增长数据 var rel =await this.ctx.model.User.deleteOne({"_id":"5b84d4b3c782f441c45d8bab"}); cnsole.log(rel); this.ctx.body='删除用户成功'; } } module.exports = UserController;
用户属于某种角色角,色拥有权限
权限的级联删除,级联更新(模块修改以后,属于模块的操做将没法显示)
注意几个点: dom中的this,jquery中的$(function)
form 表单中必须加 enctype="multipart/form-data"。表单默认提交数据的方式是
application/x-www-form-urlencoded 不是不能上传文件,是只能上传文本格式的文件,
multipart/form-data 是将文件以二进制的形式上传,这样能够实现多种类型的文件上传。
enctype="multipart/form-data"之后,后台将无法经过 this.ctx.request.body来接收表单的数据。
须要使用egg-multipart
注意上传文件时候的csrf的写法
安装pump模块,防止module卡死
上传文件的stream示例
FileStream { _readableState: ReadableState { objectMode: false, highWaterMark: 16384, buffer: BufferList { head: [Object], tail: [Object], length: 1 }, length: 63967, pipes: null, pipesCount: 0, flowing: null, ended: false, endEmitted: false, reading: false, sync: true, needReadable: false, emittedReadable: false, readableListening: false, resumeScheduled: false, paused: true, emitClose: true, destroyed: false, defaultEncoding: 'utf8', awaitDrain: 0, readingMore: false, decoder: null, encoding: null }, readable: true, _events: [Object: null prototype] { end: [Function] }, _eventsCount: 1, _maxListeners: undefined, truncated: false, _read: [Function], //重点部分 fieldname: 'focus_img', filename: 's3.png', encoding: '7bit', transferEncoding: '7bit', mime: 'image/png', mimeType: 'image/png' }
能够将除了文件的其它字段提取到 parts 的 filed 中
getFileStream, 上传且只能上传一个文件
//注意在循环读取多个文件的时候使用continue时要把stream消费掉 //上传到本地模式 //循环读取多个文件 while ((stream = await parts()) != null) { if (!stream.filename) { //await pump(stream, writeStream); //写入并销毁当前流 (egg demo提供的) //continue; 销毁 break; } // 表单中的file类型的name let fieldname = stream.fieldname; // 图片上传的目录 let dir = await this.service.tools.getUploadFile(stream.filename); let target = dir.uploadDir; let writeStream = fs.createWriteStream(target); await pump(stream, writeStream); //写入并销毁当前流 (egg demo提供的) files = Object.assign(files, { [fieldname]: dir.saveDir }); } //上传到oss模式 //上传文件到oss async doAdd() { let parts = this.ctx.multipart({ autoFields: true }); //autoFileds能够将除了文件的其它字段提取到 parts 的 filed 中 let files = {}; let stream; console.log('---------fieldname--------'); //循环读取多个文件 while ((stream = await parts()) != null) { if (!stream.filename) { break; } // 表单中的file类型的name let fieldname = stream.fieldname; let d = await this.service.tools.getTime(); let name ='goods/' +d+ path.extname(stream.filename); const result = await this.ctx.oss.put(name, stream); files = Object.assign(files, { [fieldname]: result.url }); } // 写入数据库 let goods = new this.ctx.model.Goods(Object.assign(files, parts.field)); await goods.save(); console.log('------files--------------'); console.log(Object.assign(files, parts.field)); await this.success('/admin/goods', '添加号卡成功'); }
注意: 在/service和/model中多个单词的文件命名使用下划线隔开,调用时使用大驼峰
而控制器中使用小驼峰命名,使用时用小驼峰。
goodsTypeAttribute中的cate_id关联到商品类型(手机、电视)
attr_type属性的录入方式,check_box,
设置iframe的高度,在/view/main/index.html
多个组件的事件触发
$(function(){ $("input[name='attr_type']").change(function(){ console.log($(this).val()); if($(this).val()==3){ $('#attr_value').attr('disabled',false) }else{ $('#attr_value').attr('disabled',true) } }); })
3. iframe找不到 ### L62 商品类型属性的修改 //添加分类 **注意**checked==true的用法, /view/goodsTypeAttribute/edit.html ### l63 商品分类增删改查 1. 商品分类和商品关联,用于商品的筛选 2. 上传图片 3. string类型和objectid 类型转换 4. **jimp包动态生成缩略图** - npm i --save jimp - 引入· ```js //上传图片成功之后生成缩略图 Jimp.read(target, (err, lenna) => { if (err) throw err; lenna .resize(200, 200) // resize .quality(90) // set JPEG quality .write(target + '_200x200' + path.extname(target)); // save });
商品分类的修改
注意busboy的坑,multipart上传上传时候有表单field数量的限制,注意
在config.default.js中进行配置
//配置表单数量 exports.multipart = { fields: '50' };
商品界面布局:
添加商品属性的后台界面编写
'注意:'默认列表合并 /pulic/admin/js/base.js
toggleAside
$('.aside>li:nth-child(1) ul, .aside>li:nth-childe(2) ul, .aside>li:nth-childe(3) ul').hide();
bootstrap tab切换 bootstrap tabs
在page_header中引入bootstrap.min.js
须要本身增长颜色的增删改查
$(function) $.get() $().hide()
404 路由错误
403 csrf错误
500 服务器错误
mongodb分页
db.表名.find().skip((page-1)*pageSize).limit(pageSize)
#规定每页 8 条数据的查询方式 #第一页 查询0-7条数据 db.表名.find().skip(0).limit(8) #查询第二页(page=2): db.表名.find().skip(8).limit(8) #查询第四页(page=4): db.表名.find().skip(24).limit(8)
mongoose分页 https://mongoosejs.com/docs/queries.html
Person. find({ occupation: /host/, 'name.last': 'Ghost', age: { $gt: 17, $lt: 66 }, likes: { $in: ['vaporizing', 'talking'] } }). limit(10). sort({ occupation: -1 }). select({ name: 1, occupation: 1 }). exec(callback); // Using query builder Person. find({ occupation: /host/ }). where('name.last').equals('Ghost'). where('age').gt(17).lt(66). where('likes').in(['vaporizing', 'talking']). limit(10). sort('-occupation'). select('name occupation'). exec(callback);
jqPaginator
引入jQuery
定义一个div,div的class为pagination
jqPaginator初始化
<script src="/public/sell/js/jqPaginator.js"></script> <div id="page" class="pagination"></div> $("#page").jqpaginatork({ totalPages: 100, visiblePages:10, currentPage:1, onPagechange:function(num, type){ $('#text').html('当前第'+num+'页'); }});
聚合管道+计数
async index() { let proxy_id = this.app.mongoose.Types.ObjectId(this.ctx.request.query.id); let proxy = await this.ctx.model.Proxy.find({ _id: proxy_id }); let page = this.ctx.request.query.page || 1; //每一页数量 let pageSize = 10; //获取数据总数量 let totalNum = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'goods', localField: 'goods_id', foreignField: '_id', as: 'items' } }, { $match: { proxy_id } }, { //计数 $group: { _id: proxy_id, count: { $sum: 1 } } } ]); if(totalNum[0] != null) { totalNum = totalNum[0].count; } else { totalNum = 0; } let result = await this.ctx.model.Order.aggregate([ { $lookup: { from: 'goods', localField: 'goods_id', foreignField: '_id', as: 'items' } }, { $match: { proxy_id } }, { //跳过 $skip: (page-1)*pageSize }, { //限制 $limit: pageSize } ]); // console.log(JSON.stringify(result); await this.ctx.render('sell/order/index', { list: result, proxy: proxy[0], proxy_id, totalPages: Math.ceil(totalNum / pageSize), page }); }
https://a.itying.com/api1/newslist 或 https://a.itying.com/api2/newslist
egg-cors 容许跨域
npm i egg-cors --save //plug.js cors = { enable: true, package: 'egg-cors' } //在egg.config.js中配置 //和容许的请求方法 config.cors = { origin: '*', allowMethos: 'GET, HEAD, PUT, DELETE, POST, PATCH' } //容许的域名 config.security ={ //csrf 忽略某些url casrf: { ignore: ctx=> { if(ctx.request.url.indexOf('/api') != -1) { return true; } else { return false; } } } //不知道这个有啥用? csrf白名单,若是没有配置或者为空则默认csrf放过全部域名 domainWhiteList: ['http://localhost:8080'] }
//config.default.js exports.cors = { credentials: true; origin: '*', //cookie须要修改成具体地址 allowMethods: 'GET, PUT, POST, DELETE', credentials: true //cookies跨域 } 前端: {credentials: true}