本文共 14532 字,大约阅读时间需要 48 分钟。
作为一个前端,工作之中总离不开前后端的数据联调,奈何前端的数据一直只知道怎么用,却不知道是怎么出来的,正好借着放假的期间,琢磨琢磨后端的一些逻辑。先从最最基本的注册登录来看一个完整的数据交互流程,初步的窥探一下全栈的世界。
因为vue项目使用较多,前端项目直接使用了vue,避免造轮子,UI框架也直接使用了常用的Element-UI,后端使用koa2,数据库引入mongodb,都是前端熟悉的JavaScript和json。
引入Element-UI、router等
src目录下的main.js需加上:
import Vue from 'vue'import App from './App.vue'import router from './router'import store from './store'import ElementUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css'Vue.config.productionTip = falseVue.use(ElementUI)new Vue({ router, store, render: h => h(App)}).$mount('#app')复制代码
1. 登录界面
用户登录复制代码登录 还没有账号? 立即注册
2.注册界面
账号注册复制代码提交 重置 已有账号, 去登录
3. 首页部分
4. router部分
import Vue from 'vue'import VueRouter from 'vue-router'import Home from '../views/Home.vue'import store from '../store'Vue.use(VueRouter)const routes = [ { path: '/', name: 'home', component: Home, meta: { requiresAuth: true // 需要登录才能访问的页面 } }, { path: '/login', name: '/login', component: (resolve) => require([ '@/views/Login' ], resolve) }, { path: '/register', name: '/register', component: (resolve) => require([ '@/views/Register' ], resolve) }]const router = new VueRouter({ routes})// 注册全局钩子用来拦截导航router.beforeEach((to, from, next) => { let token = store.state.user.token console.log('token:::', token) if (to.meta.requiresAuth) { if (token) { next() } else { next({ path: '/login', query: { redirect: to.fullPath } // 将刚刚要去的路由path(却无权限)作为参数,方便登录成功后直接跳转到该路由 }) } } else { next() }})export default router复制代码
5. store部分
import Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({ state: { token: window.sessionStorage.getItem('token') || '', user: { _id: window.sessionStorage.getItem('_id') || '', user_name: window.sessionStorage.getItem('user_name') || '' } }, mutations: { LOGIN: (state, data) => { state.token = data window.sessionStorage.setItem('token', data) }, LOGOUT: (state) => { state.token = null window.sessionStorage.removeItem('token') }, USERINFOR: (state, data) => { state.user.user_name = data.user_name state.user._id = data._id window.sessionStorage.setItem('_id', data._id) window.sessionStorage.setItem('user_name', data.user_name) } }, actions: { UserLogin ({ commit }, data) { commit('LOGIN', data) }, UserLogout ({ commit }) { commit('LOGOUT') }, UserInfo ({ commit }, data) { commit('USERINFO', data) } }})复制代码
6. axios的封装
import axios from 'axios'import store from '../store'import router from '../router'// 全局设置axios.defaults.timeout = 10000 // 时间超时设置10saxios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'// 创建一个axios的实列const instance = axios.create()instance.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'axios.interceptors.request.use = instance.interceptors.request.use// request拦截器,每次发送请求的时候拦截下来instance.interceptors.request.use( config => { // 每次发送请求,检查 vuex 中是否有token,如果有放在headers中 if (store.state.user.token) { config.headers.Authorization = store.state.user.token } return config }, err => { return Promise.reject(err) })// respone拦截器instance.interceptors.response.use( response => { return response }, // 除了200以外的请求到这里来,,这里的200不是我们设置的那个code200,,我这里是,没有登录才会不返回200 error => { let { response } = error if (response != null) { // 这里为什么处理401错误,详见,server/untils/token check_token这个函数 if (response.status === 401) { let msg = response.data || '请重新登录!' alert(msg) store.commit('LOGOUT') // token过期,清除 router.replace({ // 跳转到登录页面 path: '/login', // 添加一个重定向后缀,等登录以后再到这里来 query: { redirect: router.currentRoute.fullPath } }) return Promise.reject(error.response) } } else { console.log(error) } })// 添加API请求export default { // 用户注册 userRegister (data) { return instance.post('/api/user/register', data) }, // 用户登录 userLogin (data) { return instance.post('/api/user/login', data) }, // 获取用户 getUser () { return instance.get('/api/user') }, // 删除用户 delUser (data) { return instance.post('/api/user/delete', data) }}复制代码
到目前为止,前端vue部分的工作做好准备了,现在开始服务端部分:
1. 入口文件
在项目根目录下面新建server文件夹,作为整个server端项目目录,server目录下初始化package.json文件,npm init -y
命令,并引入koa及其他中间件。
npm i koanpm i koa-router -Snpm i koa-bodyparser -S// koa-bodyparser: 支持x-www-form-urlencoded, application/json等格式的请求体,但不支持form-data的请求体, 用来解析body,比如通过post来传递表单、json或上传文件,数据不容易获取,通过koa-bodyparser解析之后,在koa中this.body就能获取到数据复制代码
index.js :
const Koa = require('koa');const bodyParser = require('koa-bodyparser'); // koa-bodyparser: 支持x-www-form-urlencoded, application/json等格式的请求体,但不支持form-data的请求体// 用来解析body,比如通过post来传递表单、json或上传文件,数据不容易获取,通过koa-bodyparser解析之后,在koa中this.body就能获取到数据const json = require('koa-json') // 告诉客户端『返回的是 JSON 数据』const logger = require('koa-logger') // 提供了输出请求日志的功能,包括请求的url、状态码、响应时间、响应体大小等信息const onerror = require('koa-onerror') // koa有error事件,当发生错误,可以通过error事件,对错误统一处理const staticServe = require('koa-static') // 用于koa的静态文件指定映射路径const {check_token} = require('./utils/token')const app = new Koa();onerror(app)app.use(bodyParser());app.use(json())app.use(logger())app.use(staticServe(__dirname + '/public'))// 添加token 验证中间件app.use(check_token);// loggerapp.use(async (ctx, next) => { const start = new Date() await next() const ms = new Date() - start console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)})// routesconst index = require('./routes/index')app.use(index.routes(), index.allowedMethods())// error-handlingapp.on('error', (err, ctx) => { console.error('server error', err, ctx) });app.listen(3000, () => { console.log('The server is running at http://localhost:' + 3000);});复制代码
2. 路由
新建一个文件夹,routes,路由文件写在index.js文件中
const router = require('koa-router')()const controller = require('../controller')router.get('/', async (ctx, next) => { ctx.body = "hello,armor"}).post("/api/user/register", controller.user.register) // 用户注册.post("/api/user/login", controller.user.login) // 用户登录.get('/api/user',controller.user.allUser) // 获取所有用户.post('/api/user/delete',controller.user.deleteUser) // 获取所有用户module.exports = router复制代码
3. JWT验证
const jwt = require('jsonwebtoken') // 用于签发、解析 tokenconst { TOKEN_ENCODE_STR, URL_YES_PASS } = require('./config')const User = require('../db').Usermodule.exports = { // 生成登录 token create_token (str) { const token = jwt.sign({ str }, TOKEN_ENCODE_STR, { expiresIn: '1h' }) return token }, /* 验证登录 token 是否正确 => 写成中间件 get 请求与设置的请求不拦截验证,其余均需登录 */ async check_token (ctx, next) { let url = ctx.url console.log('ctx.url:::', ctx.url); if (ctx.method != 'GET' && !URL_YES_PASS.includes(url)) { let token = ctx.get('Authorization') if (token === '') { ctx.response.status = 401 ctx.response.body = '你还没有登录,快去登录吧!' return } try { // 验证token是否过期 let { str = '' } = await jwt.verify(token, TOKEN_ENCODE_STR) // 验证token与账号是否匹配 let res = await User.find({ user_id: str, token }) if (res.length === 0) { ctx.response.status = 401 ctx.response.body = '登录过期,请重新登录!' return } // 保存用户的_id ctx._id = res[0]._id } catch (e) { ctx.response.status = 401 ctx.response.body = '登录已过期请重新登录!' return } } await next() }}复制代码
config.js:
module.exports = { // 用户密码加密字符串 PWD_ENCODE_STR: "pawn_user_encode_str", // token 加密字符串, TOKEN_ENCODE_STR: "pawn_token_encode_str", // 添加非get请求通过的连接 URL_YES_PASS: ['/api/user/login', '/api/user/register'] }复制代码
1. 建表
首先需要安装mongodb,可以在网上找到很多下载的方式,这里略过。
至于数据库可视化工具,我这里使用的是adminMongo,直接拉取GitHub上的代码就可以,
git clone https://github.com/mrvautin/adminMongo.git && cd adminMongo
npm install
npm start
or node app
建立db文件夹,作为连接数据库的入口,建立用户表:
const mongoose = require('mongoose');const db = mongoose.connect("mongodb://localhost:27017/test", {useNewUrlParser:true}, function(err){ if(err){ console.log(err) }else{ console.log("Connection success!") }})const Schema = mongoose.Schema; // 用户let userSchema = new Schema({ user_name: String, user_id: String, password: String, create_time: Date, token: { type: String, default: "" }})exports.User = mongoose.model('User', userSchema); 复制代码
2. 操作数据库
建立controller文件夹,主要对数据库进行操作
const User = require('../db').Userconst sha1 = require('sha1') // 用于密码加密const { PWD_ENCODE_STR } = require('../utils/config')const { create_token } = require('../utils/token')const xss = require('xss')//下面这两个包用来生成时间const moment = require('moment')const objectIdToTimestamp = require('objectid-to-timestamp')//根据用户名查找用户const findUser = (name) => { return new Promise((resolve, reject) => { User.findOne({ user_name: name }, (err, doc) => { if (err) { reject(err) } resolve(doc) }) })}//删除某个用户const delUser = function (id) { return new Promise((resolve, reject) => { User.findOneAndRemove({ _id: id }, (err) => { if (err) { reject(err) } console.log('删除用户成功') resolve() }) })}// 用户注册const register = async (ctx) => { console.log('注册:::,', ctx.request.body); let { name = '', pass = '' } = ctx.request.body try { if (name === '' || pass === '') { ctx.body = { code: 401, msg: '注册失败,请填写完整表单!' } return } if (pass.length < 3) { ctx.body = { code: 401, msg: '注册失败,密码最少为3位!' } return } let doc = await findUser(name) console.log('doc::', doc); if (doc) { ctx.body = { code: 409, msg: '注册失败,该用户名已经存在!' } return } else { pass = sha1(sha1(pass + PWD_ENCODE_STR)) // 防止xss攻击, 转义 name = xss(name) console.log('pass;::', pass); console.log('name;::', name); let token = create_token(name) let user = new User({ user_name: name, password: pass, token //创建token并存入数据库 }) user.create_time = moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss') // 将objectid转换为用户创建时间 console.log('user;::', user); await new Promise((resolve, reject) => { user.save((err) => { if (err) { reject(err) } resolve() }) }) console.log('注册成功') ctx.status = 200 ctx.body = { status: true, data: { id: user._id, token, user_name: name } } } } catch (e) { console.log(e) ctx.body = { code: 500, msg: '注册失败,服务器异常!' } }}// 用户登录const login = async (ctx) => { let { name = '', pass = '' } = ctx.request.body try { if (name === '' || pass === '') { ctx.body = { code: 401, msg: '登录失败,请输入登录账号或密码!' } return } // 解密 pass = sha1(sha1(pass + PWD_ENCODE_STR)) console.log('pass1::', pass); let res = await findUser(name) console.log('pass2::', res); if (!res) { ctx.body = { code: 401, msg: '登录失败,用户名或者密码错误!' } return } else if (pass === res.password) { let token = create_token(name) res.token = token await new Promise((resolve, reject) => { res.save((err) => { if (err) { reject(err) } resolve() }) }) ctx.status = 200 ctx.body = { status: true, data: { id: res._id, token, user_name: name } } } else { ctx.status = 200 ctx.body = { success: false } } } catch (e) { console.log(e) ctx.body = { code: 500, msg: '登录失败,服务器异常!' } }}const deleteUser = async (ctx) => { //拿到要删除的用户id let id = ctx.request.body.id await delUser(id) ctx.status = 200 ctx.body = { success: '删除成功' }}module.exports = { register, login, deleteUser}复制代码
完成后端以及数据库的开发之后,可以开始整合前端的页面,获取接口返回的响应数据,并进行相应的操作
如,注册页面,提交注册的逻辑如下:
submitForm (formName) { this.$refs[formName].validate(valid => { if (valid) { api.userRegister(this.ruleForm).then(res => { if (res.data.status) { this.$message({ message: '注册成功!', type: 'success' }) this.$store.dispatch('UserRegister', res.data.data) this.$router.push('/home') } else { this.$message({ message: '注册失败', type: 'error' }) } }) } else { return false } }) },复制代码
登录逻辑:
submitForm (formName) { this.$refs[formName].validate(valid => { if (valid) { api.userLogin(this.ruleForm).then(res => { if (res.data.status) { this.$message({ message: '登录成功!', type: 'success' }) this.$store.dispatch('UserLogin', res.data.data) this.$router.push('/home') } else { this.$message({ message: '登录失败', type: 'error' }) } }) } else { console.log('error submit!!') return false } }) },复制代码
现在可以完成首页的设计了,这里只设置了一个退出功能的按钮,具体内容可以继续完善。
首页:Home.vue
复制代码你好啊,现在是已经登录的状态
退出登录
至此,最简单的一个注册登录demo完成了,具备注册、登录、退出登录三个最基本的功能,技术不难,主要为熟悉整个流程,还不能完全用于实际项目中,还需要去更多的完善功能。
作者:Armor 链接:https://juejin.im/post/5e2fa4b7e51d451c7a436856 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。