做视频网站带宽要,动易网站内容管理系统,太原做网站,公司建官网找谁弄项目地址#xff1a;https://github.com/caochangkui/vue-element-responsive-demo/tree/login-register通过 vue-cli3.0 Element 构建项目前端#xff0c;Node.js Koa2 MongoDB Redis 实现数据库和接口设计#xff0c;包括邮箱验证码、用户注册、用户登录、查看删除用户…项目地址https://github.com/caochangkui/vue-element-responsive-demo/tree/login-register通过 vue-cli3.0 Element 构建项目前端Node.js Koa2 MongoDB Redis 实现数据库和接口设计包括邮箱验证码、用户注册、用户登录、查看删除用户等功能。1. 技术栈 前端初始化项目vue-cli3.0 组件库Element-ui路由控制/拦截Vue-router 状态管理Vuex 服务端运行环境Node.js后台开发框架Koa2路由中间件Koa-router发送邮件: nodemailer HTTP通讯接口请求/拦截AxiosToken认证jsonwebtoken 数据库MongoDB数据库操作Mongoose缓存工具Redis2. 项目依赖dependencies: {axios: ^0.18.0,crypto-js: ^3.1.9-1,element-ui: ^2.4.5,js-cookie: ^2.2.0,jsonwebtoken: ^8.5.0,koa: ^2.7.0,koa-bodyparser: ^4.2.1,koa-generic-session: ^2.0.1,koa-json: ^2.0.2,koa-redis: ^3.1.3,koa-router: ^7.4.0,mongoose: ^5.4.19,nodemailer: ^5.1.1,nodemon: ^1.18.10,vue: ^2.5.21,vue-router: ^3.0.1,vuex: ^3.0.1}3. 前端实现步骤3.1 登录注册页面通过 vue-cli3.0 Element 构建项目前端页面登录页/view/users/Login.vue注册页/view/users/Register.vue发送验证码前需要验证用户名和邮箱用户名必填邮箱格式需正确。用户设置页/view/users/setting/Setting.vue用户登录后可以进入用户设置页查看用户和删除用户3.2 Vuex 状态管理通过 vuex 实现保存或删除用户 token保存用户名等功能。由于使用单一状态树应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时store 对象就有可能变得相当臃肿。为了解决以上问题Vuex 允许我们将 store 分割成模块module。每个模块拥有自己的 state、mutation、action、getter。根目录下新建store文件夹创建modules/user.js:const user {state: {token: localStorage.getItem(token),username: localStorage.getItem(username)},mutations: {BIND_LOGIN: (state, data) {localStorage.setItem(token, data)state.token data},BIND_LOGOUT: (state) {localStorage.removeItem(token)state.token null},SAVE_USER: (state, data) {localStorage.setItem(username, data)state.username data}}
}export default user创建文件 getters.js 对数据进行处理输出:const getters {sidebar: state state.app.sidebar,device: state state.app.device,token: state state.user.token,username: state state.user.username}
export default getters创建文件 index.js 管理所有状态:import Vue from vue
import Vuex from vuex
import user from ./modules/user
import getters from ./gettersVue.use(Vuex)const store new Vuex.Store({modules: {user},getters
})export default store3.3 路由控制/拦截路由配置router.jsimport Vue from vue
import Router from vue-router
const Login () import(/* webpackChunkName: users */ /views/users/Login.vue)
const Register () import(/* webpackChunkName: users */ /views/users/Register.vue)
const Setting () import(/* webpackChunkName: tables */ /views/setting/Setting.vue)Vue.use(Router)const router new Router({base: process.env.BASE_URL,routes: [{path: /login,name: Login,component: Login,meta: {title: 登录}},{path: /register,name: Register,component: Register,meta: {title: 注册}},{path: /setting,name: Setting,component: Setting,meta: {breadcrumb: 设置,requireLogin: true},}]
})路由拦截:关于vue 路由拦截参考https://www.cnblogs.com/cckui/p/10319013.html// 页面刷新时重新赋值token
if (localStorage.getItem(token)) {store.commit(BIND_LOGIN, localStorage.getItem(token))
}// 全局导航钩子
router.beforeEach((to, from, next) {if (to.meta.title) { // 路由发生变化修改页面titledocument.title to.meta.title}if (to.meta.requireLogin) {if (store.getters.token) {if (Object.keys(from.query).length 0) { // 判断路由来源是否有query处理不是目的跳转的情况next()} else {let redirect from.query.redirect // 如果来源路由有queryif (to.path redirect) { // 避免 next 无限循环next()} else {next({ path: redirect }) // 跳转到目的路由}}} else {next({path: /login,query: { redirect: to.fullPath } // 将跳转的路由path作为参数登录成功后跳转到该路由})}} else {next()}
})export default router3.4 Axios 封装封装 Axios// axios 配置
import axios from axios
import store from ./store
import router from ./router//创建 axios 实例
let instance axios.create({timeout: 5000, // 请求超过5秒即超时返回错误headers: { Content-Type: application/json;charsetUTF-8 },
})instance.interceptors.request.use(config {if (store.getters.token) { // 若存在token则每个Http Header都加上tokenconfig.headers.Authorization token ${store.getters.token}console.log(拿到token)}console.log(request请求配置, config)return config},err {return Promise.reject(err)})// http response 拦截器
instance.interceptors.response.use(response {console.log(成功响应, response)return response},error {if (error.response) {switch (error.response.status) {case 401:// 返回 401 (未授权) 清除 token 并跳转到登录页面store.commit(BIND_LOGOUT)router.replace({path: /login,query: {redirect: router.currentRoute.fullPath}})breakdefault:console.log(服务器出错请稍后重试)alert(服务器出错请稍后重试)}}return Promise.reject(error.response) // 返回接口返回的错误信息}
)export default {// 发送验证码userVerify (data) {return instance.post(/api/verify, data)},// 注册userRegister (data) {return instance.post(/api/register, data)},// 登录userLogin (data) {return instance.post(/api/login, data)},// 获取用户列表getAllUser () {return instance.get(/api/alluser)},// 删除用户delUser (data) {return instance.post(/api/deluser, data)}
}4. 服务端和数据库实现在根目录下创建 server 文件夹存放服务端和数据库相关代码。4.1 MongoDB和Redis创建 /server/dbs/config.js 进行数据库和邮箱配置// mongo 连接地址
const dbs mongodb://127.0.0.1:27017/[数据库名称]// redis 地址和端口
const redis {get host() {return 127.0.0.1},get port() {return 6379}
}// qq邮箱配置
const smtp {get host() {return smtp.qq.com},get user() {return 1********qq.com // qq邮箱名},get pass() {return ***************** // qq邮箱授权码},// 生成邮箱验证码get code() {return () {return Math.random().toString(16).slice(2, 6).toUpperCase()}},// 定义验证码过期时间rules5分钟get expire() {return () {return new Date().getTime() 5 * 60 * 1000}}
}module.exports {dbs,redis,smtp
}使用 qq 邮箱发送验证码需要在“设置/账户”中打开POP3/SMTP服务和MAP/SMTP服务。4.2 Mongo 模型创建 /server/dbs/models/users.js:// users模型包括四个字段
const mongoose require(mongoose)
const Schema mongoose.Schema
const UserSchema new Schema({username: {type: String,unique: true,required: true},password: {type: String,required: true},email: {type: String,required: true},token: {type: String,required: true}
})module.exports {Users: mongoose.model(User, UserSchema)
}4.3 接口实现创建 /server/interface/user.js:const Router require(koa-router)
const Redis require(koa-redis) // key-value存储系统, 存储用户名验证每个用户名对应的验证码是否正确
const nodeMailer require(nodemailer) // 通过node发送邮件
const User require(../dbs/models/users).Users
const Email require(../dbs/config)// 创建和验证token, 参考4.4
const createToken require(../token/createToken.js) // 创建token
const checkToken require(../token/checkToken.js) // 验证token// 创建路由对象
const router new Router({prefix: /api // 接口的统一前缀
})// 获取redis的客户端
const Store new Redis().client// 接口 - 测试
router.get(/test, async ctx {ctx.body {code: 0,msg: 测试,}
})// 发送验证码 的接口
router.post(/verify, async (ctx, next) {const username ctx.request.body.usernameconst saveExpire await Store.hget(nodemail:${username}, expire) // 拿到过期时间console.log(ctx.request.body)console.log(当前时间:, new Date().getTime())console.log(过期时间, saveExpire)// 检验已存在的验证码是否过期以限制用户频繁发送验证码if (saveExpire new Date().getTime() - saveExpire 0) {ctx.body {code: -1,msg: 发送过于频繁请稍后再试}return}// QQ邮箱smtp服务权限校验const transporter nodeMailer.createTransport({/*** 端口465和587用于电子邮件客户端到电子邮件服务器通信 - 发送电子邮件。* 端口465用于smtps SSL加密在任何SMTP级别通信之前自动启动。* 端口587用于msa*/host: Email.smtp.host,port: 587,secure: false, // 为true时监听465端口为false时监听其他端口auth: {user: Email.smtp.user,pass: Email.smtp.pass}})// 邮箱需要接收的信息const ko {code: Email.smtp.code(),expire: Email.smtp.expire(),email: ctx.request.body.email,user: ctx.request.body.username}// 邮件中需要显示的内容const mailOptions {from: 认证邮件 ${Email.smtp.user}, // 邮件来自to: ko.email, // 邮件发往subject: 邀请码, // 邮件主题 标题html: 您正在注册****您的邀请码是${ko.code} // 邮件内容}// 执行发送邮件await transporter.sendMail(mailOptions, (err, info) {if (err) {return console.log(error)} else {Store.hmset(nodemail:${ko.user}, code, ko.code, expire, ko.expire, email, ko.email)}})ctx.body {code: 0,msg: 验证码已发送请注意查收可能会有延时有效期5分钟}
})// 接口 - 注册
router.post(/register, async ctx {const { username, password, email, code } ctx.request.body// 验证验证码if (code) {const saveCode await Store.hget(nodemail:${username}, code) // 拿到已存储的真实的验证码const saveExpire await Store.hget(nodemail:${username}, expire) // 过期时间console.log(ctx.request.body)console.log(redis中保存的验证码, saveCode)console.log(当前时间:, new Date().getTime())console.log(过期时间, saveExpire)// 用户提交的验证码是否等于已存的验证码if (code saveCode) {if (new Date().getTime() - saveExpire 0) {ctx.body {code: -1,msg: 验证码已过期请重新申请}return}} else {ctx.body {code: -1,msg: 请填写正确的验证码}return}} else {ctx.body {code: -1,msg: 请填写验证码}return}// 用户名是否已经被注册const user await User.find({ username })if (user.length) {ctx.body {code: -1,msg: 该用户名已被注册}return}// 如果用户名未被注册则写入数据库const newUser await User.create({username,password,email,token: createToken(this.username) // 生成一个token 存入数据库})// 如果用户名被成功写入数据库则返回注册成功if (newUser) {ctx.body {code: 0,msg: 注册成功,}} else {ctx.body {code: -1,msg: 注册失败}}
})// 接口 - 登录
router.post(/login, async (ctx, next) {const { username, password } ctx.request.bodylet doc await User.findOne({ username })if (!doc) {ctx.body {code: -1,msg: 用户名不存在}} else if (doc.password ! password) {ctx.body {code: -1,msg: 密码错误}} else if (doc.password password) {console.log(密码正确)let token createToken(username) // 生成tokendoc.token token // 更新mongo中对应用户名的tokentry {await doc.save() // 更新mongo中对应用户名的tokenctx.body {code: 0,msg: 登录成功,username,token}} catch (err) {ctx.body {code: -1,msg: 登录失败请重新登录}}}
})// 接口 - 获取所有用户 需要验证 token
router.get(/alluser, checkToken, async (ctx, next) {try {let result []let doc await User.find({})doc.map((val, index) {result.push({email: val.email,username: val.username,})})ctx.body {code: 0,msg: 查找成功,result}} catch (err) {ctx.body {code: -1,msg: 查找失败,result: err}}
})// 接口 - 删除用户 需要验证 token
router.post(/deluser, checkToken, async (ctx, next) {const { username } ctx.request.bodytry {await User.findOneAndRemove({username: username})ctx.body {code: 0,msg: 删除成功,}} catch (err) {ctx.body {code: -1,msg: 删除失败,}}
})module.exports {router
}上面实现了五个接口发送验证码至邮箱 router.post(/verify)注册router.post(/register)登录router.post(/login)获取用户列表router.get(/alluser)删除数据库中的某个用户router.post(/deluser)分别对应了前面 3.4 中 axios 中的5个请求地址4.4 JSON Web Token 认证JSON Web Token缩写 JWT是目前最流行的跨域认证解决方案。详情参考http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html分别创建 /server/token/createToken.js 和 /server/token/checkToken.js// 创建token
const jwt require(jsonwebtoken)module.exports function (id) {const token jwt.sign({id: id},cedric1990,{expiresIn: 300s})return token
}
// 验证token
const jwt require(jsonwebtoken)// 检查 token
module.exports async (ctx, next) {// 检验是否存在 token// axios.js 中设置了 authorizationconst authorization ctx.get(Authorization)if (authorization ) {ctx.throw(401, no token detected in http headerAuthorization)}const token authorization.split( )[1]// 检验 token 是否已过期try {await jwt.verify(token, cedric1990)} catch (err) {ctx.throw(401, invalid token)}await next()
}4.5 服务端入口根目录创建 server.js:// server端启动入口
const Koa require(koa)
const app new Koa();
const mongoose require(mongoose)
const bodyParser require(koa-bodyparser)
const session require(koa-generic-session)
const Redis require(koa-redis)
const json require(koa-json) // 美化json格式化
const dbConfig require(./server/dbs/config)const users require(./server/interface/user.js).router// 一些session和redis相关配置
app.keys [keys, keyskeys]
app.proxy true
app.use(session({store: new Redis()})
)app.use(bodyParser({extendTypes: [json, form, text]
}))app.use(json())// 连接数据库
mongoose.connect(dbConfig.dbs,{ useNewUrlParser: true }
)mongoose.set(useNewUrlParser, true)
mongoose.set(useFindAndModify, false)
mongoose.set(useCreateIndex, true)const db mongoose.connection
mongoose.Promise global.Promise // 防止Mongoose: mpromise 错误db.on(error, function () {console.log(数据库连接出错)
})db.on(open, function () {console.log(数据库连接成功)
})// 路由中间件
app.use(users.routes()).use(users.allowedMethods())app.listen(8888, () {console.log(This server is running at http://localhost: 8888)
})5. 跨域处理详情参考:https://www.cnblogs.com/cckui/p/10331432.htmlvue 前端启动端口9527 和 koa 服务端启动端口8888不同需要做跨域处理打开vue.config.js:devServer: {port: 9527,https: false,hotOnly: false,proxy: {/api: {target: http://127.0.0.1:8888/, // 接口地址changeOrigin: true,ws: true,pathRewrite: {^/: }}}}6. 接口对接import axios from ../../axios.js
import CryptoJS from crypto-js // 用于MD5加密处理发送验证码// 用户名不能为空并且验证邮箱格式
sendCode() {let email this.ruleForm2.emailif (this.checkEmail(email) this.ruleForm2.username) {axios.userVerify({username: encodeURIComponent(this.ruleForm2.username),email: this.ruleForm2.email}).then((res) {if (res.status 200 res.data res.data.code 0) {this.$notify({title: 成功,message: 验证码发送成功请注意查收。有效期5分钟,duration: 1000,type: success})let time 300this.buttonText 已发送this.isDisabled trueif (this.flag) {this.flag false;let timer setInterval(() {time--;this.buttonText time 秒if (time 0) {clearInterval(timer);this.buttonText 重新获取this.isDisabled falsethis.flag true;}}, 1000)}} else {this.$notify({title: 失败,message: res.data.msg,duration: 1000,type: error})}})}
}注册:submitForm(formName) {this.$refs[formName].validate(valid {if (valid) {axios.userRegister({username: encodeURIComponent(this.ruleForm2.username),password: CryptoJS.MD5(this.ruleForm2.pass).toString(),email: this.ruleForm2.email,code: this.ruleForm2.smscode}).then((res) {if (res.status 200) {if (res.data res.data.code 0) {this.$notify({title: 成功,message: 注册成功。,duration: 2000,type: success})setTimeout(() {this.$router.push({path: /login})}, 500)} else {this.$notify({title: 错误,message: res.data.msg,duration: 2000,type: error})}} else {this.$notify({title: 错误,message: 服务器请求出错 错误码${res.status},duration: 2000,type: error})}})} else {console.log(error submit!!);return false;}})
},登录login(formName) {this.$refs[formName].validate(valid {if (valid) {axios.userLogin({username: window.encodeURIComponent(this.ruleForm.name),password: CryptoJS.MD5(this.ruleForm.pass).toString()}).then((res) {if (res.status 200) {if (res.data res.data.code 0) {this.bindLogin(res.data.token)this.saveUser(res.data.username)this.$notify({title: 成功,message: 恭喜登录成功。,duration: 1000,type: success})setTimeout(() {this.$router.push({path: /})}, 500)} else {this.$notify({title: 错误,message: res.data.msg,duration: 1000,type: error})}} else {this.$notify({title: 错误,message: 服务器出错请稍后重试,duration: 1000,type: error})}})}})
},7. 启动项目 测试接口7.1 vue端$ npm run serve7.2 启动mogod$ mongod7.3 启动Redis$ redis-server7.4 启动服务端server.js安装 nodemon 热启动辅助工具$ npm i nodemon
$ nodemon server.js