小米商城:須要修改的東西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}