vue koa2 mongodb 從零開始做個人博客(二) 登錄註冊功能後端部分

来源:https://www.cnblogs.com/wangzirui98/archive/2020/01/19/12209082.html
-Advertisement-
Play Games

博客項目登錄註冊功能後端,涉及到mongodb,redis,vuex,nuxt.js,mongoose,koa-redis,smtp使用的基本操作 ...


0.效果演示

插入視頻插不進來,就很煩。可以出門右拐去優酷看下(點我!)。 

1.後端搭建

1.1項目結構

首先看一下後端的server目錄

 挨個解釋一下

  • 首先dbs文件夾顧名思義,操作資料庫的,modules就是操作資料庫的mongoose模型。
  • config.js是為了方便修改資料庫數據。
  • interface就是介面文件夾,utils就是工具的意思唄,介面需要用到的axios和賬號集權的passport都在這裡修改(passport是啥待會兒再細說)。
  • 和utils同級的就是users.js 就是user介面的路由,具體的邏輯就在這個文件里
  • index.js和dbs,interface文件夾同級。是整個server目錄的入口文件。

 

1.2後端配置

首先看一下config.js,直接放代碼。

 1 // 導出相應的配置,然後可以方便的用下麵的數據,改的話也比較方便修改。
 2 export default {
 3   //dbs 表示需要連接的伺服器
 4   dbs: "mongodb://127.0.0.1:27017/myblog2",
 5   //redis對象是提供redis的信息
 6   redis: {
 7     get host() {
 8       return "127.0.0.1";
 9     },
10     get port() {
11       return 6379;
12     }
13   },
14   // smtp對象是利用郵箱來發送驗證碼的
15     smtp: {
16     get host() {
17       return "stmp.qq.com";
18     },
19     get user() {
20       return "[email protected]";
21     },
22     // 這個pass碼是qq郵箱給提供的。下麵是隨機打的不是真實的。
23     get pass() {
24       return "pfpeqwddadadasdaf";
25     },
26     // 製造一個隨機的驗證碼
27     get code() {
28       return () => {
29         return Math.random()
30           .toString(16)
31           .slice(2, 6)
32           .toUpperCase();
33       };
34     },
35     // 創建一個時間,時間就是發送郵箱的時間。
36     get expire() {
37       return () => {
38         return new Date().getTime() + 60 * 60 * 1000;
39       };
40     }
41   }
42 };

針對於smtp協議,就是你可以利用它來發送驗證碼的。至於如何獲取qq郵箱的pass碼?

↓首先打開郵箱點擊設置:

↓點擊上方navbar的賬號按鈕

↓滑到下麵,找到smtp選項 

 

1.3創建數據模型

鋪墊性的東西太多,建議都學完再來看。

1、mongodb和mongoose不詳細展開講了,mongodb可參考我之前寫的入門教程(點我!

2、mongoose大體流程是引入,創建shcema ,創建model。然後進行操作。詳情進入官網(點我!)。針對於這個順序先不引入,引入是index.js里的,現在先講創建schema和創建model這個部分的。

3、用了些async await和解構賦值的語法,如果有不太理解的可以自動跳轉去學習一下新版本的js,推薦阮一峰的《ECMAScript 6 入門教程》(直接點書名!)

 

不多說,直接放代碼:

 1 // server/dbs/modules/user.js
 2 // 導入Monogoose
 3 import mongoose from "mongoose";
 4 // 創建schema
 5 const Schema = mongoose.Schema;
 6 // 創建userSchema
 7 const UserSchema = new Schema({
 8   username: {
 9     type: String,
10     unique: true,
11     require: true
12   },
13   password: {
14     type: String,
15     require: true
16   },
17   email: {
18     type: String,
19     require: true
20   }
21 });
22 // 導出user模型
23 export default mongoose.model("User", UserSchema);

現在登錄註冊需要存入庫中的只需要這些,驗證方面的緩存數據統一存到redis里。reids的邏輯統一在users的介面里講。

 

1.4創建users介面 

可能大家沒註意到我沒說utils里的內容,axios和passport.js 。因為現在時機未到。

現在先配置一下user介面,支起來個架子,然後再談邏輯

// koa-router必引的,不多解釋
import Router from "koa-router";
// 發送驗證碼用redis,因為可能需求量會很大。redis效率較高
import Redis from "koa-redis";
// 用nodeMailer插件來發送郵件。
import nodeMailer from "nodemailer";
// USer模型,來操作mongodb
import User from "../dbs/modules/users";
// 用來發郵件的配置參數
import Email from "../dbs/config";
// axios來請求數據
import axios from "./utils/axios";
//來引入passprot中間件
import Passport from "./utils/passport";

// 創建一個路由,他的最開始用/users
let router = new Router({ prefix: "/users" });

// 創建一個redis的倉庫。
let Store = new Redis().client;

// 導出router
export default router;

 redis的邏輯是引入koa-redis插件,然後新建store對象。然後我們針對store對象進行相應的操作。詳細介紹請去npm看(點我!

 

1.4.1 註冊驗證碼介面

首先第一個任務那就是註冊,註冊的邏輯是填寫郵箱和用戶名,確定密碼,然後發送驗證碼郵件進行驗證。

那麼首先配置的就是發送驗證碼的路由,因為都是線性的代碼,所以直接放代碼,代碼如下:

 1 // 發送驗證碼
 2 router.post("/verify", async ctx => {
 3   //獲取username
 4   let username = ctx.request.body.username;
 5 
 6   //可以不看6-16行,看到結尾再回來看。
 7   //獲得驗證碼的有效時間
 8   const saveExpire = await Store.hget(`nodemail:${username}`, "expire");
 9   //如果驗證碼的有效時間太短,就不能再發次發送。
10   if (saveExpire && new Date().getTime() - saveExpire < 0) {
11     ctx.body = {
12       code: -1,
13       msg: "驗證請求過於頻繁,1分鐘內1次"
14     };
15     return false;
16   }
17   //然後用nodeMailer創建一個transport
18   let transporter = nodeMailer.createTransport({
19     // server名稱
20     service: "qq",
21     // user的名稱和他的pass
22     auth: {
23       user: Email.smtp.user,
24       pass: Email.smtp.pass
25     }
26   });
27   //獲取到驗證碼和時間,還有用戶輸入的郵箱和用戶名
28   let ko = {
29     code: Email.smtp.code(),
30     expire: Email.smtp.expire(),
31     email: ctx.request.body.email,
32     user: ctx.request.body.username
33   };
34   // 郵件的配置文件
35   let mailOptions = {
36     from: `" 博客註冊認證郵件" <${Email.smtp.user}>`,
37     to: ko.email,
38     subject: "王梓瑞的博客註冊驗證碼",
39     html: `驗證碼是${ko.code},請儘快完成註冊!`
40   };
41   await transporter.sendMail(mailOptions, (error, info) => {
42     if (error) {
43       return console.log(error);
44     } else {
45       // 當郵件發送成功了,就將數據保存起來,以後可以拿來用。
46       Store.hmset(
47         `nodemail:${ko.user}`,
48         "code",
49         ko.code,
50         "expire",
51         ko.expire,
52         "email",
53         ko.email
54       );
55     }
56   });
57   ctx.body = {
58     code: 0,
59     msg: "驗證碼已發送,可能會有延時,有效期1分鐘"
60   };
61 });

 

1.4.2註冊介面 

等發完驗證碼 ,我們就可以繼續進行註冊操作。

同樣直接放代碼,很好理解。

router.post("/signup", async ctx => {
  // 先獲取表單里的信息,這麼寫是es6的解構賦值語法
  const { username, password, email, code } = ctx.request.body;
  if (code) {
    const saveCode = Store.hget(`nodemail:${username}`, "code");
    const saveExpire = Store.hget(`nodemail:${username}`, "expire");

    if (saveCode == code) {
      if (new Date().getTime() - saveExpire > 0) {
        ctx.body = {
          code: -1,
          msg: "驗證碼已過期,請重新嘗試"
        };
        return false;
      }
    } else {
      ctx.body = {
        code: -1,
        msg: "請填寫正確的驗證碼"
      };
    }
  } else {
    ctx.body = {
      code: -1,
      msg: "未輸入驗證碼"
    };
  }
let user
= await User.find({ username }); if (user.length) { ctx.body = { code: -1, msg: "已被註冊" }; return; } let nuser = await User.create({ username, password, email }); console.log(nuser); if (nuser) { ctx.body = { code: 0, msg: "註冊成功" }; } else { ctx.body = { code: -1, msg: "註冊失敗" }; } });

 

 

1.4.3 登錄介面

進行到這裡就涉及到了passport,因為登陸的狀態是需要集中去進行管理的,那麼就涉及到Passport這個插件。如果需要快速上手的話,可以看看這篇簡書(點我!

我這裡直接就是講實戰了。不多說了,可以參考著上面的簡書加上我的代碼自行理解。

 1 // server/interface/utils/passport.js
 2 // 引入 passprot ,然後引入本地策略,就是驗證用戶是否成立,最後引入操作模型。
 3 import passport from "koa-passport";
 4 import LocalStrategy from "passport-local";
 5 import UserModel from "../../dbs/modules/users";
 6 
 7 // passport 載入策略中間件,然後通過新建location對象在裡面進行對用戶的鑒定。
 8 passport.use(
 9   //創建新的策略,然後三個參數分別是 用戶名密碼和回調
10   new LocalStrategy(async function(username, password, done) {
11     //此處用where是表示搜索的時候參數是一個對象
12     let where = {
13       username
14     };
15     // 用user的Mongoose的模型來搜索user在資料庫中的記錄,用res來接收
16     const res = await UserModel.findOne(where);
17     // 判斷res是否存在, 不存在就用策略的回調函數done返回一個用戶不存在的錯誤信息。
18     if (res != null) {
19       // 如果資料庫里的Password和輸入的password吻合,就返回一個res
20       if (res.password === password) {
21         return done(null, res);
22       } else {
23         // 不吻合就返回一個密碼錯誤。
24         return done(null, false, "密碼錯誤");
25       }
26     } else {
27       return done(null, false, "用戶不存在");
28     }
29   })
30 );
31 
32 // 序列化和反序列化,沒什麼大事。
33 passport.serializeUser(function(user, done) {
34   done(null, user);
35 });
36 
37 passport.deserializeUser(function(user, done) {
38   return done(null, user);
39 });
40 
41 // 導出passport集權控制中間件。
42 export default passport;

 

再放user.js介面里的代碼

 1 router.post("/signin", async (ctx, next) => {
 2   return Passport.authenticate("local", (err, user, info, status) => {
 3     if (err) {
 4       ctx.body = {
 5         code: -1,
 6         msg: err
 7       };
 8     } else {
 9       if (user) {
10         ctx.body = {
11           code: 0,
12           msg: "登錄成功",
13           user
14         };
15         return ctx.login(user);
16       } else {
17         ctx.body = {
18           code: 1,
19           msg: info
20         };
21       }
22     }
23   })(ctx, next);
24 });

 

這裡可能有人會有疑問,就說我的介面都沒有獲取到ctx裡面的username和password數據,怎麼直接就return了passport的對象呢?

問題就在必須在index.js讓koa2對象使用bodyParser的中間件。代碼如下:

 // bodyParser中的extendTypes必須要加,要不然passport就無法解析username和passport
  app.use(
    bodyParser({
      extendTypes: ["json", "form", "text"]
    })
  );

 

然後就可以解析到登錄過來的username和passport了。

 

1.4.4 退出和查詢用戶介面

直接放代碼,沒有難度

 1 router.get("/getUser", async ctx => {
 2   if (ctx.isAuthenticated()) {
 3     const { username, email } = ctx.session.passport.user;
 4     ctx.body = {
 5       user: username,
 6       email
 7     };
 8   } else {
 9     ctx.body = {
10       user: "",
11       email: ""
12     };
13   }
14 });

 

退出登錄

 1 router.get("/exit", async (ctx, next) => {
 2   await ctx.logout();
 3   if (!ctx.isAuthenticated()) {
 4     ctx.body = {
 5       code: 0
 6     };
 7   } else {
 8     ctx.body = {
 9       code: -1
10     };
11   }
12 });

 

如果你想獲取用戶數據的話還有第二種方法,從session獲取,session是什麼?大家自行百度下,簡單來說就是服務端的cookie。

然後運用vuex 配合 nuxt.js的nuxtServerInit方法。這個又需要理解vuex和nuxtServerInit,這個放上鏈接,vuexnuxtServerInit

項目結構

 

 

如果用vue-cli會有modules文件夾來放模型的,但是nuxt.js直接都放在平級了,詳情可參照nuxt.js文檔。(點我!)

 

同樣直接放代碼吧。

 1 //store/user.js
 2 const state = () => ({
 3   user: ""
 4 });
 5 const mutations = {
 6   setUser(state, param) {
 7     state.user = param;
 8   }
 9 };
10 const actions = {
11   setUser: ({ commit }, param) => {
12     commit("setUser", param);
13   }
14 };
15 
16 export default { state, mutations, actions };
 1 //store/index.js
 2 const actions = {
 3   async nuxtServerInit({ commit }, { req }) {
 4     if (req.user) {
 5       commit("user/setUser", req.user.username);
 6     }
 7   }
 8 };
 9 
10 export { actions };

 

 然後我們就可以在頁面的任何位置調用 $store.state.user.user 即可獲得用戶的用戶名了。可以省去非同步獲取的操作。

 

1.5 index.js文件

直接放代碼,之前解釋的都解釋過了,這個就是一個啟動後端的文件

 1 // server/index.js
 2 const Koa = require("koa");
 3 const consola = require("consola");
 4 const { Nuxt, Builder } = require("nuxt");
 5 
 6 import bodyParser from "koa-bodyparser"; // 這個一開始就要加,不加的話解析不出來request.body。post請求就白給。
 7 import json from "koa-json";
 8 import mongoose from "mongoose";
 9 import dbConfig from "./dbs/config";
10 import Redis from "koa-redis";
11 import session from "koa-generic-session";
12 import users from "./interface/users";
13 import passport from "./interface/utils/passport";
14 
15 const app = new Koa();
16 
17 // Import and Set Nuxt.js options
18 const config = require("../nuxt.config.js");
19 config.dev = app.env !== "production";
20 
21 async function start() {
22   // Instantiate nuxt.js
23   const nuxt = new Nuxt(config);
24 
25   const {
26     host = process.env.HOST || "127.0.0.1",
27     port = process.env.PORT || 3000
28   } = nuxt.options.server;
29 
30   //這個是加密用的
31   app.keys = ["my", "keyskeys"];
32   //是否設置代理
33   app.proxy = true;
34   //session的首碼
35   app.use(session({ key: "my", prefix: "my:uid", store: new Redis() }));
36   //mongoose鏈接Mongodb
37   mongoose.connect(dbConfig.dbs, {
38     useNewUrlParser: true
39   });
40   //初始化passport
41   app.use(passport.initialize());
42   //讓passport使用session
43   app.use(passport.session());
44 
45   // Build in development
46   if (config.dev) {
47     const builder = new Builder(nuxt);
48     await builder.build();
49   } else {
50     await nuxt.ready();
51   }
52   //解析json用的中間件
53   app.use(json());
54   // bodyParser中的extendTypes必須要加,要不然passport就無法解析username和passport
55   app.use(
56     bodyParser({
57       extendTypes: ["json", "form", "text"]
58     })
59   );
60   // 載入路由中間件
61   app.use(users.routes()).use(users.allowedMethods());
62 
63   app.use(ctx => {
64     ctx.status = 200;
65     ctx.respond = false; // Bypass Koa's built-in response handling
66     ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
67     nuxt.render(ctx.req, ctx.res);
68   });
69 
70   app.listen(port, host);
71   consola.ready({
72     message: `Server listening on http://${host}:${port}`,
73     badge: true
74   });
75 }
76 
77 start();

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • C#實現的從小到大的冒泡排序: public void BubbleSort(int[] array) { int length = array.Length; for (int i = 0; i < length - 1; i++) { for (int j = length - 1; j > i ...
  • C#實現(Delegate)的委托就不多說了,直接上代碼,看代碼中的註釋: namespace Delegate { delegate void DGSayiHi(string name);//聲明委托 delegate void DGDo(string name); class Program { ...
  • 在公司的電腦虛擬機上安裝了centos 6.5 ,然後我把他克隆下來用在家裡電腦的虛擬機上,打開後查看ip,發現只有迴環地址lo,沒有eth0, 於是重啟網路 輸入 service network restart 發現 報錯Bringing up interface eth0: Device eth ...
  • 參考 鏈接:https://gitbook.cn/books/5a33782c5778440a9d906017/index.html ...
  • 對長期奮戰在一線的後端開發人員來說,都知道redis有兩種持久化方式RDB和AOF,雖說大家都知道這兩種方式大概運作方式,但想必有實操瞭解得不會太多。 這裡是自己實操兩種持久化方式的一點點記錄。 先看以下摘錄自redis官網原文解釋(當然原文是English,這裡用google翻譯過了。) Redi ...
  • 參考鏈接:https://www.zhihu.com/question/333417513 https://www.oschina.net/p/hbase hadoop環境搭建:https://blog.csdn.net/hliq5399/article/details/78193113/ goog ...
  • ​ 若在傳統DBMS 關係型資料庫中查詢海量數據,特別是模糊查詢,一般我們都是使用like %查詢的值%,但這樣會導致無法應用索引,從而形成全表掃描效率低下,即使是在有索引的欄位精確值查找,面對海量數據,效率也是相對較低的,所以目前一般的互聯網公司或大型公司,若要查詢海量數據,最好的辦法就是使用搜索 ...
  • 一、a標簽的偽類選擇器註意點 (1)a標簽的偽類選擇器可以單獨出現,也可以一起出現。也就是可以設置多個狀態的樣式。 (2) a標簽的偽類選擇器如果一起出現,那麼有嚴格的順序要求,編寫的順序必須要遵守原則: (love\hate原則,即link\visited\hover\active)預設狀態、被訪 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...