vue koa2 mongodb 从零开始做个人博客(二) 登录注册功能后端部分
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,直接放代码。
// 导出相应的配置,然后可以方便的用下面的数据,改的话也比较方便修改。 export default { //dbs 表示需要连接的服务器 dbs: "mongodb://127.0.0.1:27017/myblog2", //redis对象是提供redis的信息 redis: { get host() { return "127.0.0.1"; }, get port() { return 6379; } }, // smtp对象是利用邮箱来发送验证码的 smtp: { get host() { return "stmp.qq.com"; }, get user() { return ""; }, // 这个pass码是qq邮箱给提供的。下面是随机打的不是真实的。 get pass() { return "pfpeqwddadadasdaf"; }, // 制造一个随机的验证码 get code() { return () => { return Math.random() .toString(16) .slice(2, 6) .toUpperCase(); }; }, // 创建一个时间,时间就是发送邮箱的时间。 get expire() { return () => { return new Date().getTime() + 60 * 60 * 1000; }; } } };
针对于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 入门教程》(直接点书名!)
不多说,直接放代码:
// server/dbs/modules/user.js // 导入Monogoose import mongoose from "mongoose"; // 创建schema const Schema = mongoose.Schema; // 创建userSchema const UserSchema = new Schema({ username: { type: String, unique: true, require: true }, password: { type: String, require: true }, email: { type: String, require: true } }); // 导出user模型 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 注册验证码接口
首先第一个任务那就是注册,注册的逻辑是填写邮箱和用户名,确定密码,然后发送验证码邮件进行验证。
那么首先配置的就是发送验证码的路由,因为都是线性的代码,所以直接放代码,代码如下:
// 发送验证码 router.post("/verify", async ctx => { //获取username let username = ctx.request.body.username; //可以不看6-16行,看到结尾再回来看。 //获得验证码的有效时间 const saveExpire = await Store.hget(`nodemail:${username}`, "expire"); //如果验证码的有效时间太短,就不能再发次发送。 if (saveExpire && new Date().getTime() - saveExpire < 0) { ctx.body = { code: -1, msg: "验证请求过于频繁,1分钟内1次" }; return false; } //然后用nodeMailer创建一个transport let transporter = nodeMailer.createTransport({ // server名称 service: "qq", // user的名称和他的pass auth: { user: Email.smtp.user, pass: Email.smtp.pass } }); //获取到验证码和时间,还有用户输入的邮箱和用户名 let ko = { code: Email.smtp.code(), expire: Email.smtp.expire(), email: ctx.request.body.email, user: ctx.request.body.username }; // 邮件的配置文件 let mailOptions = { from: `" 博客注册认证邮件" <${Email.smtp.user}>`, to: ko.email, subject: "王梓瑞的博客注册验证码", html: `验证码是${ko.code},请尽快完成注册!` }; await transporter.sendMail(mailOptions, (error, info) => { if (error) { return console.log(error); } else { // 当邮件发送成功了,就将数据保存起来,以后可以拿来用。 Store.hmset( `nodemail:${ko.user}`, "code", ko.code, "expire", ko.expire, "email", ko.email ); } }); ctx.body = { code: 0, msg: "验证码已发送,可能会有延时,有效期1分钟" }; });
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这个插件。如果需要快速上手的话,可以看看这篇简书(点我!)
我这里直接就是讲实战了。不多说了,可以参考着上面的简书加上我的代码自行理解。
// server/interface/utils/passport.js // 引入 passprot ,然后引入本地策略,就是验证用户是否成立,最后引入操作模型。 import passport from "koa-passport"; import LocalStrategy from "passport-local"; import UserModel from "../../dbs/modules/users"; // passport 加载策略中间件,然后通过新建location对象在里面进行对用户的鉴定。 passport.use( //创建新的策略,然后三个参数分别是 用户名密码和回调 new LocalStrategy(async function(username, password, done) { //此处用where是表示搜索的时候参数是一个对象 let where = { username }; // 用user的Mongoose的模型来搜索user在数据库中的记录,用res来接收 const res = await UserModel.findOne(where); // 判断res是否存在, 不存在就用策略的回调函数done返回一个用户不存在的错误信息。 if (res != null) { // 如果数据库里的Password和输入的password吻合,就返回一个res if (res.password === password) { return done(null, res); } else { // 不吻合就返回一个密码错误。 return done(null, false, "密码错误"); } } else { return done(null, false, "用户不存在"); } }) ); // 序列化和反序列化,没什么大事。 passport.serializeUser(function(user, done) { done(null, user); }); passport.deserializeUser(function(user, done) { return done(null, user); }); // 导出passport集权控制中间件。 export default passport;
再放user.js接口里的代码
router.post("/signin", async (ctx, next) => { return Passport.authenticate("local", (err, user, info, status) => { if (err) { ctx.body = { code: -1, msg: err }; } else { if (user) { ctx.body = { code: 0, msg: "登录成功", user }; return ctx.login(user); } else { ctx.body = { code: 1, msg: info }; } } })(ctx, next); });
这里可能有人会有疑问,就说我的接口都没有获取到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 退出和查询用户接口
直接放代码,没有难度
router.get("/getUser", async ctx => { if (ctx.isAuthenticated()) { const { username, email } = ctx.session.passport.user; ctx.body = { user: username, email }; } else { ctx.body = { user: "", email: "" }; } });
退出登录
router.get("/exit", async (ctx, next) => { await ctx.logout(); if (!ctx.isAuthenticated()) { ctx.body = { code: 0 }; } else { ctx.body = { code: -1 }; } });
如果你想获取用户数据的话还有第二种方法,从session获取,session是什么?大家自行百度下,简单来说就是服务端的cookie。
然后运用vuex 配合 nuxt.js的nuxtServerInit方法。这个又需要理解vuex和nuxtServerInit,这个放上链接,vuex,nuxtServerInit
项目结构
如果用vue-cli会有modules文件夹来放模型的,但是nuxt.js直接都放在平级了,详情可参照nuxt.js文档。(点我!)
同样直接放代码吧。
//store/user.js const state = () => ({ user: "" }); const mutations = { setUser(state, param) { state.user = param; } }; const actions = { setUser: ({ commit }, param) => { commit("setUser", param); } }; export default { state, mutations, actions };
//store/index.js const actions = { async nuxtServerInit({ commit }, { req }) { if (req.user) { commit("user/setUser", req.user.username); } } }; export { actions };
然后我们就可以在页面的任何位置调用 $store.state.user.user 即可获得用户的用户名了。可以省去异步获取的操作。
1.5 index.js文件
直接放代码,之前解释的都解释过了,这个就是一个启动后端的文件
// server/index.js const Koa = require("koa"); const consola = require("consola"); const { Nuxt, Builder } = require("nuxt"); import bodyParser from "koa-bodyparser"; // 这个一开始就要加,不加的话解析不出来request.body。post请求就白给。 import json from "koa-json"; import mongoose from "mongoose"; import dbConfig from "./dbs/config"; import Redis from "koa-redis"; import session from "koa-generic-session"; import users from "./interface/users"; import passport from "./interface/utils/passport"; const app = new Koa(); // Import and Set Nuxt.js options const config = require("../nuxt.config.js"); config.dev = app.env !== "production"; async function start() { // Instantiate nuxt.js const nuxt = new Nuxt(config); const { host = process.env.HOST || "127.0.0.1", port = process.env.PORT || 3000 } = nuxt.options.server; //这个是加密用的 app.keys = ["my", "keyskeys"]; //是否设置代理 app.proxy = true; //session的前缀 app.use(session({ key: "my", prefix: "my:uid", store: new Redis() })); //mongoose链接Mongodb mongoose.connect(dbConfig.dbs, { useNewUrlParser: true }); //初始化passport app.use(passport.initialize()); //让passport使用session app.use(passport.session()); // Build in development if (config.dev) { const builder = new Builder(nuxt); await builder.build(); } else { await nuxt.ready(); } //解析json用的中间件 app.use(json()); // bodyParser中的extendTypes必须要加,要不然passport就无法解析username和passport app.use( bodyParser({ extendTypes: ["json", "form", "text"] }) ); // 加载路由中间件 app.use(users.routes()).use(users.allowedMethods()); app.use(ctx => { ctx.status = 200; ctx.respond = false; // Bypass Koa‘s built-in response handling ctx.req.ctx = ctx; // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash nuxt.render(ctx.req, ctx.res); }); app.listen(port, host); consola.ready({ message: `Server listening on http://${host}:${port}`, badge: true }); } start();