EGG.JS筆記

Egg.js

注意:

小米商城:須要修改的東西javascript

  1. md5加密
  2. tools工具方法的書寫位置
  3. 中間件的文件與函數方法命名
  4. mongoose數據庫命名規範
  5. 後臺登錄的時候須要在前端後臺進行作表單數據驗證用戶名和密碼的判斷
  6. 只查詢一條數據的能夠用findOne 替代find
  7. 注意修改管理員密碼的時候須要前臺驗證,重複輸入
  8. input readonly的樣式
  9. 未分配角色bug,沒法訪問bug
  10. 菜單表示右側的菜單
  11. 操做表示沒有界面的操做
  12. 文件上個大小的錯誤警告
  13. 時間格式化插件: silly-datetime
  14. egg-view-ejs egg-mongoose

1、基礎部分

建立

//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

調試

  1. 打斷點
  2. 先啓動npm run debug
  3. 再 打開vscode調試選擇 egg debug 文件啓動
  4. 發送請求便可

app目錄結構

/app
/controller
/public 靜態資源,css,img
/view 視圖層
/service 模型層次
/middleware 中間件(例如權限判斷)
/extend 擴展寫法
/config 配置文件css

編寫過程:MODEL->ROUTER->CONTROLLER->VIEWhtml

基礎語法

  1. 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);
  2. 獲取get傳值:
    let query = this.ctx.query;node

  3. 動態路由(帶參數路由)jquery

  4. router.get('/newslist/:id', )linux

  5. 加載模板引擎webpack

  6. egg-view-ejsgit

  7. L6 extend寫法對內置對象進行擴展,silly-datetime格式化時間,在模板中進行調用

  8. 所有配置屬性

    module.exports = appInfo => {
      return {
        logger: {
          dir: path.join(appInfo.baseDir, 'logs'),
        },
      };
    };
  9. 中間件 記得await next();

  10. post 路由的頁面不能直接用this.ctx.body返回,須要使用頁面跳轉this.ctx.redirect

  11. 字符串和objectid 的類型轉換this.app.mongoose.Types.ObjectId(module_id);

  12. config的default.js配置及獲取

    const userConfig = {
            proxy: {
                pageSize: 10
            }
      }
    
      //控制器非路由方法中獲取
      const appkey = this.app.config.proxy.pageSize;
      //控制器路由方法
      let pageSize = this.config.proxy.pageSize;

CSRF防範

方法一

// 方法一
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:

關於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. fieldname: type=file的字段名
  2. filename: 本地文件的文件名
  3. parts.fields 除文件外的其餘字段組成的數組
  4. parts.field 除文件外的其餘字段組成的對象

模板引擎

1.ejs
2.art-template

L7 利用中間件屏蔽指定IP

some方法

L9 cookies session

cookies保存在客戶端

session保存在服務端,當瀏覽器訪問服務器併發送第一次請求時,服務器端會建立一個session對象,生成一個相似於key,value的鍵值對,而後將key(cookie)返回到瀏覽器(客戶)端,瀏覽器下次再訪問時,攜帶key(cookie,找到對應的session(value)。

cookie和session的必須爲字符串

cookies

this.ctx.cookies.set('username', 'zhangsj');
    this.ctx.cookies.get('username');
  1. 實現同一個瀏覽器訪問同一個域的時候,不一樣頁面之間的數據共享

  2. 實現數據的持久化

  3. 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!
  4. cookie用JSON.stringify和JSON.parse能夠存儲對象

  5. 清除cookie

    async loginOut() {
        //cookies 置空
        this.ctx.cookies.set('unserinfo', null);
         //或者設置過時時間爲0
        this.ctx.cookies.set('username', null, {
            maxAge: 0
        });
        this.ctx.rediredc('/news'); //路由跳轉
      }

session

當瀏覽器訪問服務器併發送第一次請求時,服務器端會建立一個session對象,生成一個相似於key,value的鍵值對,而後將key(cookie)返回到瀏覽器(客戶)端,瀏覽器下次再訪問時,攜帶key(cookie,找到對應的session(value)。

要訪問session必須藉助本地保存的cookies

  1. 使用

    // 設置
      this.ctx.session.userinfo = {
        name: '張三',
        age: '20'
      },
    
      // 獲取
      var userinfo = this.ctx.session.username;
    
      // 
      this.ctx.session.username = "";
  2. 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; //秒
  3. url重寫 http://www.javashuo.com/article/p-fjebwwgg-q.html

L8 中間件 middleware

  1. 全局中間

    mdoule.exports=(options, app)=>{
        return asycn function auth(ctx, next) {
          //xxxx
          await next();
        }
      }
    
      //配置:
      config.middleware=['auth']
      //傳參
      config.auth={
        title: 'dfsfsf'
      }
  2. 路由中間件

    • 在/middleware下定義中間件
    • 在router.js下使用中間件
    /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);
      };
  3. 框架默認中間節

    //config.default.js

    config.bodyParser={
      jsonLimit: '10mb' //Default is 1mb.
    }
  4. 使用koa

    • 規範的(標準的)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  協議,主機帶端口,不帶端口,完整域名
  1. 通用中間件配置

    //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
    }
  2. 中間件配合項目 LESSON13

中間件命名, auth_api.js => authApi

路由配置的

  • 路由命名
  • 路由重定向
  1. 外部重定向
this.ctx.status = 301;
this.ctx.redirect('\nnn');
  1. 內部重定向
//302 301重定向
// 有利於seo優化
router.redirect('\news', '\', 302);
  1. 路由分組
//新建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);
};

BaseControllerde,兼容寫法

content特殊用法

  1. 過幾秒跳轉到某個網頁
    <meta http-equiv="refresh" content="3:url=/">

  2. 在/app/core/base.js中定義base基類, 繼承

  3. 控制器的兼容性寫法

    'use strict';
    const Controller = require('egg').Controller;
      class HomeController extends Controller {
      //把ctx當作參數傳入 等同於在函數內使用 this.ctx
      async index(ctx) {
        await ctx.render('home');
      }
    }
    module.exports = HomeController;

定時任務schedule--爬蟲

定時執行某些任務,定時清理緩存,定時報告

/app/schedule
watchfile.js

1.繼承schedule寫法
2.導出對象寫法
3.導出函數,函數返回對象

cherrio 模塊爬蟲

  1. 安裝cnpm i cheerio --save
  2. 加載要解析的內容
    const $=cheerio.load('<h2 class="title">Hello world</h2>")
  3. 用法
    $('title').html()獲取了要匹配的標題的內容
  4. 抓取網站內容
    var url="http://news.baidu.com//";
  5. 解析數據

MongoDB L18-32

2. mongodb簡單使用

  1. 安裝插件
    npm i egg-mongo-native --save

  2. 聚合管道:關聯查詢與數據統計
    $project 增長、刪除、重命名字段 投影篩選
    $match 條件匹配。只知足條件的文檔才能進入下
    $limit 限制結果的數量
    $skip 跳過文檔的數量
    $sort 條件排序
    $group 條件組合結果 統計

    $lookup 用以引入其它集合的數據 (表關聯查詢)

  3. scheme-》表-》集合

  4. 定義model-》操做數據庫

3. L31 在egg中使用mongoose

  1. 安裝npm i egg-mongoose --save

  2. 在/app/model中操做數據庫

  3. 字符串和objectid 的類型轉換this.app.mongoose.Types.ObjectId(module_id);

  4. schema表結構

    1. 特殊類型goods_id: { type: type: Schema.Types.ObjectId }
  5. 關聯查詢:對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
               }
           }
       ]);
  6. 增刪改查

    //增
    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
            }
          })
        }
  7. 篩選查詢

    // 注意篩選查詢的順序 $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,
        }     
    ]);
  8. mongodb查詢時間 https://stackoverflow.com/questions/11973304/mongodb-mongoose-querying-at-a-specific-date http://www.javashuo.com/article/p-qkhrcdhg-nr.html

文件下載

  1. excel的mime類型: https://stackoverflow.com/questions/4212861/what-is-a-correct-mime-type-for-docx-pptx-etc

  2. https://github.com/eggjs/examples 文件下載實例

  3. 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;

2、EGG實踐筆記

egg-jwt的用法

https://github.com/okoala/egg-jwt/blob/master/test/fixtures/apps/jwt-app.jwt/app/controller/success.js

  1. 用jsonwebtoken對application進行了extend進而實現egg-jwt

  2. 用法:

    1. npm 安裝

    2. plgin.js

      // {app_root}/config/plugin.js
      exports.jwt = {
        enable: true,
        package: "egg-jwt"
      };
    3. config.js

      // {app_root}/config/config.default.js
      exports.jwt = {
        secret: "123456"
      };
    4. 在路由中使用,在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';
      //?????

html生成pdf

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 # 刷新字體緩存

3、小米商城

  1. 驗證碼: svg-captcha 插件

L39登錄功能

  1. 全局指定中間件登錄路由

  2. 權限判斷:

    • 獲取表單提交的數據
    • 判斷驗證碼是否正確
      • 驗證碼正確
        1. 密碼md5加密
          1. 安裝 md5模塊 npm install md5 -S
          2. 引入 const md5 = require('md5');
          3. 使用 md5(str)
        2. 在用戶集合 中查詢當前用戶是否存在
          • egg-mongoose 安裝
          • 建立數據庫
          • 安裝數據庫
        3. 若是數據庫有用戶信息: 保存用戶信息到session 跳轉到後臺
      • 驗證碼錯誤 : 跳轉到登陸頁面 提示驗證碼錯誤
  3. 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;

RBAC權限管理模型

L40 RBAC管理模型概述

  1. 用戶
  2. 角色
  3. 權限

用戶屬於某種角色角,色擁有權限

L41-42 RBAC角色管理 增刪改

L42 用戶管理用戶增刪改

  1. 定義model
  2. 注意修改管理員密碼的時候須要前臺驗證,重複輸入

L45 權限管理

  1. 菜單 與 操做
  2. /model/access.js
  3. 模塊名稱,例如,管理員管理,角色管理,權限管理
  4. 操做名稱:例如 管理員的增刪改查
  5. 節點類型:
  6. url:操做地址
  7. module_id 和access 自關聯的表 若是module=0 表示爲模塊,
  8. 數據庫mogoose的mixed 混合類型
  9. 在權限表access中,moduleid 要保存爲objectid類型 /access/doAdd
  10. 注意權限列表 本身和本身關聯

L46 權限修改

權限的級聯刪除,級聯更新(模塊修改以後,屬於模塊的操做將沒法顯示)

L47 受權

  1. 一次傳送多個checkbox, 使用數組[]
  2. model的文件命名含有下劃線則訪問model的時候要使用駝峯式命名

L51公共的ajax方法---特別注意

注意幾個點: dom中的this,jquery中的$(function)

  1. es6屬性名錶達式
  2. dom this
  3. dom $init()

L52單文件上傳

  1. form 表單中必須加 enctype="multipart/form-data"。表單默認提交數據的方式是
    application/x-www-form-urlencoded 不是不能上傳文件,是隻能上傳文本格式的文件,
    multipart/form-data 是將文件以二進制的形式上傳,這樣能夠實現多種類型的文件上傳。

  2. enctype="multipart/form-data"之後,後臺將無法經過 this.ctx.request.body來接收表單的數據。

  3. 須要使用egg-multipart

  4. 注意上傳文件時候的csrf的寫法

  5. 安裝pump模塊,防止module卡死

  6. 上傳文件的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' }

L53多文件上傳

  1. 能夠將除了文件的其它字段提取到 parts 的 filed 中

  2. 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', '添加號卡成功');
    }

L54輪播輪上傳

  1. 封裝函數,以時間爲單位存儲輪播圖
  2. /service/tools
  3. silly-datetime
  4. 建立文件夾的庫 mz-module/mkdir
  5. https://github.com/node-modules/mz-modules
  6. path.extname(filename)獲取文件後綴名稱
  7. ObjectAssign的用法

L56單擊數量方法

  1. 注意es6屬性表達式
  2. jquery時間連綴寫法
  3. jquery的基本操做
  4. 在瀏覽器中爲了兼容性儘可能不要使用es6的語法

L57 helper擴展修改日期格式

  1. css手風琴收縮特效
  2. css箭頭方向變換特效 /public/admin/css/basic.css
  3. css命名規範base.js toogleAside $().find()
  4. iframe重構後臺菜單結構
  5. iframe target
  6. $(window).resize

L59商品表概覽

  1. 實現商品類型的增刪改查
  2. 實現商品類型屬性的增刪改查,並實現類型和類型對應屬性的關聯
  3. 實現商品分類的增刪改查,並實現商品分類表的自關聯
  4. 實現商品模塊的增刪改查、而且實現商品和商品分類、商品類型、顏色等其餘表的關聯

注意: 在/service和/model中多個單詞的文件命名使用下劃線隔開,調用時使用大駝峯
而控制器中使用小駝峯命名,使用時用小駝峯。

L60 商品類型屬性

goodsTypeAttribute中的cate_id關聯到商品類型(手機、電視)
attr_type屬性的錄入方式,check_box,

L61 商品類型屬性的添加

  1. 設置iframe的高度,在/view/main/index.html

  2. 多個組件的事件觸發

    $(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
        });

L64商品分類的增長**

  1. 注意二級列表,能夠進行無限級列表

L65 goods_cate的編輯

  1. 商品分類的修改

  2. 注意busboy的坑,multipart上傳上傳時候有表單field數量的限制,注意
    在config.default.js中進行配置

    //配置表單數量
     exports.multipart = {
       fields: '50'
     };

L66添加商品佈局


商品界面佈局:

  1. 添加商品屬性的後臺界面編寫

  2. '注意:'默認列表合併 /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();

  3. bootstrap tab切換 bootstrap tabs

  4. 在page_header中引入bootstrap.min.js

  5. 須要本身增長顏色的增刪改查

  6. $(function) $.get() $().hide()

L67商品顏色、類型屬性、相冊


  1. 顏色
  2. 規格

L68-L69 商品描述wysiwyg editor


  1. 針對某些地址關閉csrf驗證

404 路由錯誤
403 csrf錯誤
500 服務器錯誤

L70 商品相冊批量上傳


L75 數據分頁+jqPaginator


  1. 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)
  2. 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);
  3. jqPaginator

    1. 引入jQuery

    2. 定義一個div,div的class爲pagination

    3. 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+'頁');
       }});
  4. 聚合管道+計數

    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
        });
    }

L121先後端分離跨域

  1. 版本號區分
    1. https://a.itying.com/api1/newslist 或 https://a.itying.com/api2/newslist
  2. 在 RESTful 架構中,每一個網址表明一種資源(resource),因此網址中建議不能有動詞,只能有名詞,並且所用的名詞每每與數據庫的表格名對應。通常來講,數據庫中的表都是同種記錄的"集合"(collection),因此 API 中的名詞也應該使用複數
  3. http請求數據方式:
    1. GET(SELECT):從服務器出資源(一項或多項)。
    2. POST(CREATE):在服務器新建一個資源。
    3. PUT(UPDATE):在服務器更新資源(客戶端提供改變後的完整資源)。
    4. DELETE(DELETE):從服務器刪除資源。
    5. 不經常使用三個
    6. HEAD:獲取資源的元數據。
    7. OPTIONS:獲取信息,關於資源的哪些屬性是客戶端能夠改變的。
    8. PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。
  4. 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}

Egg.js

注意:

小米商城:須要修改的東西

  1. md5加密
  2. tools工具方法的書寫位置
  3. 中間件的文件與函數方法命名
  4. mongoose數據庫命名規範
  5. 後臺登錄的時候須要在前端後臺進行作表單數據驗證用戶名和密碼的判斷
  6. 只查詢一條數據的能夠用findOne 替代find
  7. 注意修改管理員密碼的時候須要前臺驗證,重複輸入
  8. input readonly的樣式
  9. 未分配角色bug,沒法訪問bug
  10. 菜單表示右側的菜單
  11. 操做表示沒有界面的操做
  12. 文件上個大小的錯誤警告
  13. 時間格式化插件: silly-datetime
  14. egg-view-ejs egg-mongoose

1、基礎部分

建立

//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

調試

  1. 打斷點
  2. 先啓動npm run debug
  3. 再 打開vscode調試選擇 egg debug 文件啓動
  4. 發送請求便可

app目錄結構

/app
/controller
/public 靜態資源,css,img
/view 視圖層
/service 模型層次
/middleware 中間件(例如權限判斷)
/extend 擴展寫法
/config 配置文件

編寫過程:MODEL->ROUTER->CONTROLLER->VIEW

基礎語法

  1. 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);
  2. 獲取get傳值:
    let query = this.ctx.query;

  3. 動態路由(帶參數路由)

  4. router.get('/newslist/:id', )

  5. 加載模板引擎

  6. egg-view-ejs

  7. L6 extend寫法對內置對象進行擴展,silly-datetime格式化時間,在模板中進行調用

  8. 所有配置屬性

    module.exports = appInfo => {
      return {
        logger: {
          dir: path.join(appInfo.baseDir, 'logs'),
        },
      };
    };
  9. 中間件 記得await next();

  10. post 路由的頁面不能直接用this.ctx.body返回,須要使用頁面跳轉this.ctx.redirect

  11. 字符串和objectid 的類型轉換this.app.mongoose.Types.ObjectId(module_id);

  12. config的default.js配置及獲取

    const userConfig = {
            proxy: {
                pageSize: 10
            }
      }
    
      //控制器非路由方法中獲取
      const appkey = this.app.config.proxy.pageSize;
      //控制器路由方法
      let pageSize = this.config.proxy.pageSize;

CSRF防範

方法一

// 方法一
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:

關於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. fieldname: type=file的字段名
  2. filename: 本地文件的文件名
  3. parts.fields 除文件外的其餘字段組成的數組
  4. parts.field 除文件外的其餘字段組成的對象

模板引擎

1.ejs
2.art-template

L7 利用中間件屏蔽指定IP

some方法

L9 cookies session

cookies保存在客戶端

session保存在服務端,當瀏覽器訪問服務器併發送第一次請求時,服務器端會建立一個session對象,生成一個相似於key,value的鍵值對,而後將key(cookie)返回到瀏覽器(客戶)端,瀏覽器下次再訪問時,攜帶key(cookie,找到對應的session(value)。

cookie和session的必須爲字符串

cookies

this.ctx.cookies.set('username', 'zhangsj');
    this.ctx.cookies.get('username');
  1. 實現同一個瀏覽器訪問同一個域的時候,不一樣頁面之間的數據共享

  2. 實現數據的持久化

  3. 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!
  4. cookie用JSON.stringify和JSON.parse能夠存儲對象

  5. 清除cookie

    async loginOut() {
        //cookies 置空
        this.ctx.cookies.set('unserinfo', null);
         //或者設置過時時間爲0
        this.ctx.cookies.set('username', null, {
            maxAge: 0
        });
        this.ctx.rediredc('/news'); //路由跳轉
      }

session

當瀏覽器訪問服務器併發送第一次請求時,服務器端會建立一個session對象,生成一個相似於key,value的鍵值對,而後將key(cookie)返回到瀏覽器(客戶)端,瀏覽器下次再訪問時,攜帶key(cookie,找到對應的session(value)。

要訪問session必須藉助本地保存的cookies

  1. 使用

    // 設置
      this.ctx.session.userinfo = {
        name: '張三',
        age: '20'
      },
    
      // 獲取
      var userinfo = this.ctx.session.username;
    
      // 
      this.ctx.session.username = "";
  2. 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; //秒
  3. url重寫 http://www.javashuo.com/article/p-fjebwwgg-q.html

L8 中間件 middleware

  1. 全局中間

    mdoule.exports=(options, app)=>{
        return asycn function auth(ctx, next) {
          //xxxx
          await next();
        }
      }
    
      //配置:
      config.middleware=['auth']
      //傳參
      config.auth={
        title: 'dfsfsf'
      }
  2. 路由中間件

    • 在/middleware下定義中間件
    • 在router.js下使用中間件
    /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);
      };
  3. 框架默認中間節

    //config.default.js

    config.bodyParser={
      jsonLimit: '10mb' //Default is 1mb.
    }
  4. 使用koa

    • 規範的(標準的)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  協議,主機帶端口,不帶端口,完整域名
  1. 通用中間件配置

    //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
    }
  2. 中間件配合項目 LESSON13

中間件命名, auth_api.js => authApi

路由配置的

  • 路由命名
  • 路由重定向
  1. 外部重定向
this.ctx.status = 301;
this.ctx.redirect('\nnn');
  1. 內部重定向
//302 301重定向
// 有利於seo優化
router.redirect('\news', '\', 302);
  1. 路由分組
//新建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);
};

BaseControllerde,兼容寫法

content特殊用法

  1. 過幾秒跳轉到某個網頁
    <meta http-equiv="refresh" content="3:url=/">

  2. 在/app/core/base.js中定義base基類, 繼承

  3. 控制器的兼容性寫法

    'use strict';
    const Controller = require('egg').Controller;
      class HomeController extends Controller {
      //把ctx當作參數傳入 等同於在函數內使用 this.ctx
      async index(ctx) {
        await ctx.render('home');
      }
    }
    module.exports = HomeController;

定時任務schedule--爬蟲

定時執行某些任務,定時清理緩存,定時報告

/app/schedule
watchfile.js

1.繼承schedule寫法
2.導出對象寫法
3.導出函數,函數返回對象

cherrio 模塊爬蟲

  1. 安裝cnpm i cheerio --save
  2. 加載要解析的內容
    const $=cheerio.load('<h2 class="title">Hello world</h2>")
  3. 用法
    $('title').html()獲取了要匹配的標題的內容
  4. 抓取網站內容
    var url="http://news.baidu.com//";
  5. 解析數據

MongoDB L18-32

2. mongodb簡單使用

  1. 安裝插件
    npm i egg-mongo-native --save

  2. 聚合管道:關聯查詢與數據統計
    $project 增長、刪除、重命名字段 投影篩選
    $match 條件匹配。只知足條件的文檔才能進入下
    $limit 限制結果的數量
    $skip 跳過文檔的數量
    $sort 條件排序
    $group 條件組合結果 統計

    $lookup 用以引入其它集合的數據 (表關聯查詢)

  3. scheme-》表-》集合

  4. 定義model-》操做數據庫

3. L31 在egg中使用mongoose

  1. 安裝npm i egg-mongoose --save

  2. 在/app/model中操做數據庫

  3. 字符串和objectid 的類型轉換this.app.mongoose.Types.ObjectId(module_id);

  4. schema表結構

    1. 特殊類型goods_id: { type: type: Schema.Types.ObjectId }
  5. 關聯查詢:對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
               }
           }
       ]);
  6. 增刪改查

    //增
    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
            }
          })
        }
  7. 篩選查詢

    // 注意篩選查詢的順序 $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,
        }     
    ]);
  8. mongodb查詢時間 https://stackoverflow.com/questions/11973304/mongodb-mongoose-querying-at-a-specific-date http://www.javashuo.com/article/p-qkhrcdhg-nr.html

文件下載

  1. excel的mime類型: https://stackoverflow.com/questions/4212861/what-is-a-correct-mime-type-for-docx-pptx-etc

  2. https://github.com/eggjs/examples 文件下載實例

  3. 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;

2、EGG實踐筆記

egg-jwt的用法

https://github.com/okoala/egg-jwt/blob/master/test/fixtures/apps/jwt-app.jwt/app/controller/success.js

  1. 用jsonwebtoken對application進行了extend進而實現egg-jwt

  2. 用法:

    1. npm 安裝

    2. plgin.js

      // {app_root}/config/plugin.js
      exports.jwt = {
        enable: true,
        package: "egg-jwt"
      };
    3. config.js

      // {app_root}/config/config.default.js
      exports.jwt = {
        secret: "123456"
      };
    4. 在路由中使用,在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';
      //?????

html生成pdf

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 # 刷新字體緩存

3、小米商城

  1. 驗證碼: svg-captcha 插件

L39登錄功能

  1. 全局指定中間件登錄路由

  2. 權限判斷:

    • 獲取表單提交的數據
    • 判斷驗證碼是否正確
      • 驗證碼正確
        1. 密碼md5加密
          1. 安裝 md5模塊 npm install md5 -S
          2. 引入 const md5 = require('md5');
          3. 使用 md5(str)
        2. 在用戶集合 中查詢當前用戶是否存在
          • egg-mongoose 安裝
          • 建立數據庫
          • 安裝數據庫
        3. 若是數據庫有用戶信息: 保存用戶信息到session 跳轉到後臺
      • 驗證碼錯誤 : 跳轉到登陸頁面 提示驗證碼錯誤
  3. 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;

RBAC權限管理模型

L40 RBAC管理模型概述

  1. 用戶
  2. 角色
  3. 權限

用戶屬於某種角色角,色擁有權限

L41-42 RBAC角色管理 增刪改

L42 用戶管理用戶增刪改

  1. 定義model
  2. 注意修改管理員密碼的時候須要前臺驗證,重複輸入

L45 權限管理

  1. 菜單 與 操做
  2. /model/access.js
  3. 模塊名稱,例如,管理員管理,角色管理,權限管理
  4. 操做名稱:例如 管理員的增刪改查
  5. 節點類型:
  6. url:操做地址
  7. module_id 和access 自關聯的表 若是module=0 表示爲模塊,
  8. 數據庫mogoose的mixed 混合類型
  9. 在權限表access中,moduleid 要保存爲objectid類型 /access/doAdd
  10. 注意權限列表 本身和本身關聯

L46 權限修改

權限的級聯刪除,級聯更新(模塊修改以後,屬於模塊的操做將沒法顯示)

L47 受權

  1. 一次傳送多個checkbox, 使用數組[]
  2. model的文件命名含有下劃線則訪問model的時候要使用駝峯式命名

L51公共的ajax方法---特別注意

注意幾個點: dom中的this,jquery中的$(function)

  1. es6屬性名錶達式
  2. dom this
  3. dom $init()

L52單文件上傳

  1. form 表單中必須加 enctype="multipart/form-data"。表單默認提交數據的方式是
    application/x-www-form-urlencoded 不是不能上傳文件,是隻能上傳文本格式的文件,
    multipart/form-data 是將文件以二進制的形式上傳,這樣能夠實現多種類型的文件上傳。

  2. enctype="multipart/form-data"之後,後臺將無法經過 this.ctx.request.body來接收表單的數據。

  3. 須要使用egg-multipart

  4. 注意上傳文件時候的csrf的寫法

  5. 安裝pump模塊,防止module卡死

  6. 上傳文件的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' }

L53多文件上傳

  1. 能夠將除了文件的其它字段提取到 parts 的 filed 中

  2. 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', '添加號卡成功');
    }

L54輪播輪上傳

  1. 封裝函數,以時間爲單位存儲輪播圖
  2. /service/tools
  3. silly-datetime
  4. 建立文件夾的庫 mz-module/mkdir
  5. https://github.com/node-modules/mz-modules
  6. path.extname(filename)獲取文件後綴名稱
  7. ObjectAssign的用法

L56單擊數量方法

  1. 注意es6屬性表達式
  2. jquery時間連綴寫法
  3. jquery的基本操做
  4. 在瀏覽器中爲了兼容性儘可能不要使用es6的語法

L57 helper擴展修改日期格式

  1. css手風琴收縮特效
  2. css箭頭方向變換特效 /public/admin/css/basic.css
  3. css命名規範base.js toogleAside $().find()
  4. iframe重構後臺菜單結構
  5. iframe target
  6. $(window).resize

L59商品表概覽

  1. 實現商品類型的增刪改查
  2. 實現商品類型屬性的增刪改查,並實現類型和類型對應屬性的關聯
  3. 實現商品分類的增刪改查,並實現商品分類表的自關聯
  4. 實現商品模塊的增刪改查、而且實現商品和商品分類、商品類型、顏色等其餘表的關聯

注意: 在/service和/model中多個單詞的文件命名使用下劃線隔開,調用時使用大駝峯
而控制器中使用小駝峯命名,使用時用小駝峯。

L60 商品類型屬性

goodsTypeAttribute中的cate_id關聯到商品類型(手機、電視)
attr_type屬性的錄入方式,check_box,

L61 商品類型屬性的添加

  1. 設置iframe的高度,在/view/main/index.html

  2. 多個組件的事件觸發

    $(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
        });

L64商品分類的增長**

  1. 注意二級列表,能夠進行無限級列表

L65 goods_cate的編輯

  1. 商品分類的修改

  2. 注意busboy的坑,multipart上傳上傳時候有表單field數量的限制,注意
    在config.default.js中進行配置

    //配置表單數量
     exports.multipart = {
       fields: '50'
     };

L66添加商品佈局


商品界面佈局:

  1. 添加商品屬性的後臺界面編寫

  2. '注意:'默認列表合併 /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();

  3. bootstrap tab切換 bootstrap tabs

  4. 在page_header中引入bootstrap.min.js

  5. 須要本身增長顏色的增刪改查

  6. $(function) $.get() $().hide()

L67商品顏色、類型屬性、相冊


  1. 顏色
  2. 規格

L68-L69 商品描述wysiwyg editor


  1. 針對某些地址關閉csrf驗證

404 路由錯誤
403 csrf錯誤
500 服務器錯誤

L70 商品相冊批量上傳


L75 數據分頁+jqPaginator


  1. 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)
  2. 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);
  3. jqPaginator

    1. 引入jQuery

    2. 定義一個div,div的class爲pagination

    3. 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+'頁');
       }});
  4. 聚合管道+計數

    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
        });
    }

L121先後端分離跨域

  1. 版本號區分
    1. https://a.itying.com/api1/newslist 或 https://a.itying.com/api2/newslist
  2. 在 RESTful 架構中,每一個網址表明一種資源(resource),因此網址中建議不能有動詞,只能有名詞,並且所用的名詞每每與數據庫的表格名對應。通常來講,數據庫中的表都是同種記錄的"集合"(collection),因此 API 中的名詞也應該使用複數
  3. http請求數據方式:
    1. GET(SELECT):從服務器出資源(一項或多項)。
    2. POST(CREATE):在服務器新建一個資源。
    3. PUT(UPDATE):在服務器更新資源(客戶端提供改變後的完整資源)。
    4. DELETE(DELETE):從服務器刪除資源。
    5. 不經常使用三個
    6. HEAD:獲取資源的元數據。
    7. OPTIONS:獲取信息,關於資源的哪些屬性是客戶端能夠改變的。
    8. PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。
  4. 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}
相關文章
相關標籤/搜索