เมื่อไม่นานมานี้ ฉันกำลังคุยเรื่องเส้นทางอาชีพของเขากับเพื่อนร่วมงานในบริษัทที่เปลี่ยนจากเทคโนโลยีมาเป็นผลิตภัณฑ์ และเขาพูดบางอย่างที่ฉันเชื่ออย่างลึกซึ้ง:
"อย่าจำกัดตัวเองอยู่เฉพาะบางสาขา การเปลี่ยนแปลงจากเทคโนโลยีสู่ผลิตภัณฑ์คือการเปลี่ยนแปลงทางความคิดเป็นประการแรก คุณทำงานที่ส่วนหน้า เมื่อโต้ตอบกับข้อมูล คุณเพียงรู้วิธีป้อนข้อมูล แต่คุณ ไม่รู้ว่าออกมาเป็นยังไง นี่คือข้อจำกัด”
มันเหมือนกับการรู้แจ้ง ตอนที่ฉันเรียน Vue ฉันเห็นการลงทะเบียนและล็อกอินโปรเจ็กต์ ฉันเพียงทำตามและเริ่มโปรเจ็กต์ Vue แนะนำ koa และ mongodb และใช้กระบวนการรับส่งข้อมูลกลับของเซิร์ฟเวอร์การส่งไคลเอ็นต์ .
โปรเจ็กต์นี้สร้างขึ้นจาก vue-cli ใช้วิธีโทเค็นเพื่อตรวจสอบการเข้าสู่ระบบของผู้ใช้ และใช้ฟังก์ชันต่างๆ เช่น การลงทะเบียนในฐานข้อมูล การอ่านผู้ใช้ และการลบผู้ใช้ บทความนี้สันนิษฐานว่าผู้อ่านมีรากฐานที่แน่นอนในโหนดและ vue ดังนั้นส่วนพื้นฐานจะไม่ได้รับการอธิบายโดยละเอียด
สภาพแวดล้อมของระบบ: MacOS 10.13.3
ติดตั้งโดยใช้กระจก Taobao
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
จากนั้นเปลี่ยน การติดตั้ง npm ทั้งหมดเป็นการ ติดตั้ง cnpm
เพื่อให้แนวคิดโครงการและเทคโนโลยีที่เลือกชัดเจนยิ่งขึ้น จึงได้มีการวาดแผนภาพเพื่อให้เข้าใจได้ง่าย
1.เริ่มต้นโครงการ
$ npm install
2. เริ่มโครงการ
$ npm run dev
3. เริ่ม MongoDB
$ mongod --dbpath XXX
xxx เป็นเส้นทางไปยังโฟลเดอร์ ข้อมูล ในโปรเจ็กต์ (คุณสามารถสร้างใหม่ได้โดยใช้ฐานข้อมูลเพื่อจัดเก็บข้อมูล) หรือคุณสามารถลากไปยังเทอร์มินัลได้โดยตรง
4. เริ่มเซิร์ฟเวอร์
$ node server.js
ฉันเลือก Element-UI ของ Ele.me เป็นไลบรารี UI ที่ฉัน ต้องการ สำหรับ vue
$ npm i element-ui -S
//在项目里的mian.js里增加下列代码
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
ใช้การสลับแท็บใน UI เพื่อสลับระหว่างอินเทอร์เฟซการลงทะเบียนและการเข้าสู่ระบบ ใช้ส่วนประกอบการเข้าสู่ระบบเป็นอินเทอร์เฟซหลักของระบบการเข้าสู่ระบบทั้งหมด และใช้ส่วนประกอบการลงทะเบียนเป็นส่วนประกอบอิสระ โปรดดูเว็บไซต์อย่างเป็นทางการสำหรับวิธีการองค์ประกอบของ Element-UI การตรวจสอบแบบฟอร์ม และ API อื่นๆ
//login组件
<template>
<div class="login">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="登录" name="first">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="名称" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="注册" name="second">
<register></register>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import register from '@/components/register'
export default {
data() {
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
return {
activeName: 'first',
ruleForm: {
name: '',
pass: '',
checkPass: '',
},
rules: {
name: [
{ required: true, message: '请输入您的名称', trigger: 'blur' },
{ min: 2, max: 5, message: '长度在 2 到 5 个字符', trigger: 'blur' }
],
pass: [
{ required: true, validator: validatePass, trigger: 'blur' }
]
},
};
},
methods: {
//选项卡切换
handleClick(tab, event) {
},
//重置表单
resetForm(formName) {
this.$refs[formName].resetFields();
},
//提交表单
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$message({
type: 'success',
message: '登录成功'
});
this.$router.push('HelloWorld');
} else {
console.log('error submit!!');
return false;
}
});
},
},
components: {
register
}
}
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
width: 400px;
margin: 0 auto;
}
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.el-tabs__item {
text-align: center;
width: 60px;
}
</style>
ถัดไปคือการลงทะเบียนส่วนประกอบ
//register组件
<template>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="名称" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="ruleForm.checkPass" auto-complete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">注册</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
activeName: 'second',
ruleForm: {
name: '',
pass: '',
checkPass: '',
},
rules: {
name: [
{ required: true, message: '请输入您的名称', trigger: 'blur' },
{ min: 2, max: 5, message: '长度在 2 到 5 个字符', trigger: 'blur' }
],
pass: [
{ required: true, validator: validatePass, trigger: 'blur' }
],
checkPass: [
{ required: true, validator: validatePass2, trigger: 'blur' }
],
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$message({
type: 'success',
message: '注册成功'
});
// this.activeName: 'first',
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
vue-router เป็นแกนหลักของ Vue ในการสร้างโปรเจ็กต์หน้าเดียว แอปพลิเคชันสามารถประกอบด้วยการรวมส่วนประกอบต่างๆ เข้าด้วยกัน สิ่งที่เราต้องทำคือการแมปส่วนประกอบต่างๆ กับเส้นทาง แล้วบอก vue-router ว่าจะเรนเดอร์พวกมันไปที่ใด โค้ดข้างต้นเกี่ยวข้องกับการสลับเส้นทางอยู่แล้ว มาปรับปรุงการกำหนดเส้นทางกันดีกว่า:
$ cnpm i vue-router
import Router from 'vue-router'
Vue.use(Router)
สร้างเราเตอร์ใหม่ (โฟลเดอร์)/index.js ภายใต้โฟลเดอร์ src เราได้แนะนำองค์ประกอบสามประการ:
หน้าแสดง HelloWorld หลังจากเข้าสู่ระบบ
เข้าสู่ระบบเข้าสู่ระบบอินเทอร์เฟซหลัก
ลงทะเบียนส่วนประกอบการลงทะเบียน
ใช้ router.beforeEach routing guard เพื่อตั้งค่าเพจที่ต้องล็อกอินก่อน ใช้ฟิลด์ needAuth เพื่อตรวจสอบว่าเส้นทางที่ต้องมีสิทธิ์ในการเข้าสู่ระบบจะถูกดักจับหรือไม่ จากนั้นจะถูกพิจารณาว่ามีโทเค็นหรือไม่ (โทเค็นจะกล่าวถึงด้านล่าง) หากไม่มี ให้เข้าสู่ระบบโดยตรง ข้ามไปที่หน้าเข้าสู่ระบบ
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import login from '@/components/login'
import register from '@/components/register'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [{
path: '/',
name: 'home',
component: HelloWorld,
meta: {
requiresAuth: true
}
},
{
path: '/HelloWorld',
name: 'HelloWorld',
component: HelloWorld,
},
{
path: '/login',
name: 'login',
component: login,
},
{
path: '/register',
name: 'register',
component: register,
},
]
});
//注册全局钩子用来拦截导航
router.beforeEach((to, from, next) => {
//获取store里面的token
let token = store.state.token;
//判断要去的路由有没有requiresAuth
if (to.meta.requiresAuth) {
if (token) {
next();
} else {
next({
path: '/login',
query: { redirect: to.fullPath } // 将刚刚要去的路由path作为参数,方便登录成功后直接跳转到该路由
});
}
} else {
next();
}
});
export default router;
เราจะเห็นว่าโทเค็นในการป้องกันการกำหนดเส้นทางนั้นได้รับจากร้านค้า ซึ่งหมายความว่าเราจัดเก็บสถานะต่างๆ ของโทเค็นไว้ในร้านค้าและดำเนินการต่างๆ เช่น การดึงข้อมูล การอัปเดต และการลบ ซึ่งจำเป็นต้องมีการจัดการสถานะ vuex .
อธิบายว่าเหตุใดหน้าการลงทะเบียนและเข้าสู่ระบบอย่างง่ายจึงต้องใช้ vuex: โดยทั่วไปแล้ว การดำเนินการของแต่ละส่วนประกอบของเราในโครงการจำเป็นต้องได้รับโทเค็นสำหรับการตรวจสอบ หากส่วนประกอบ A เก็บโทเค็น การได้มาของโทเค็นของส่วนประกอบ B จะเกี่ยวข้องกับการสื่อสารส่วนประกอบ ซึ่งจะน่าเบื่อมาก ด้วยการเปิดตัว vuex มันไม่ใช่การสื่อสารระหว่างส่วนประกอบอีกต่อไป แต่เป็นการสื่อสารระหว่างส่วนประกอบและร้านค้า ซึ่งง่ายและสะดวก
$ cnpm i vuex --S
แนะนำ store ใน main.js และเพิ่ม store ให้กับ vue instance
//引入store
import store from './store'
แล้วมาแนะนำในส่วนของส่วนประกอบที่ต้องใช้ vuex
//store index.js
import Vuex from 'vuex'
Vue.use(Vuex)
สร้างร้านค้าใหม่ (โฟลเดอร์)/index.js ภายใต้โฟลเดอร์ src
//store index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
//初始化时用sessionStore.getItem('token'),这样子刷新页面就无需重新登录
const state = {
token: window.sessionStorage.getItem('token'),
username: ''
};
const mutations = {
LOGIN: (state, data) => {
//更改token的值
state.token = data;
window.sessionStorage.setItem('token', data);
},
LOGOUT: (state) => {
//登出的时候要清除token
state.token = null;
window.sessionStorage.removeItem('token');
},
USERNAME: (state, data) => {
//把用户名存起来
state.username = data;
window.sessionStorage.setItem('username', data);
}
};
const actions = {
UserLogin({ commit }, data){
commit('LOGIN', data);
},
UserLogout({ commit }){
commit('LOGOUT');
},
UserName({ commit }, data){
commit('USERNAME', data);
}
};
export default new Vuex.Store({
state,
mutations,
actions
});
คุณจะเห็นว่าเราได้ส่งการเปลี่ยนแปลงผ่านการดำเนินการ โทเค็นการเปลี่ยนแปลง โทเค็นที่ชัดเจน และจัดเก็บชื่อผู้ใช้
เมื่อคุณเริ่มโครงการในเวลานี้ คุณจะเห็นการลงทะเบียนเบื้องต้นและอินเทอร์เฟซการเข้าสู่ระบบ คลิกปุ่มการลงทะเบียนหรือเข้าสู่ระบบเพื่อสลับไปยังอินเทอร์เฟซที่เกี่ยวข้อง และจะมีการตรวจสอบแบบฟอร์มพื้นฐานหลังจากเข้าสู่ระบบ คุณจะเข้าสู่หน้า HelloWorld
เราได้เขียนอินเทอร์เฟซพื้นฐานแล้ว และขั้นตอนต่อไปคือการส่งข้อมูลแบบฟอร์มไปยังพื้นหลังและดำเนินการประมวลผลเป็นชุด ไม่สำคัญว่าจะยังไม่มีอินเทอร์เฟซส่วนหลัง มาเขียนคำขอ axios ส่วนหน้าก่อน
การสื่อสารของ Vue เคยใช้ vue-resource มาก่อน และมีข้อผิดพลาดมากมาย จนกว่า vue2.0 จะมาถึง ให้ละทิ้ง vue-resource และใช้ axios
สรุป ajax ใช้ในการส่งคำขอและรับข้อมูลแบบอะซิงโครนัส ไคลเอนต์ HTTP ตามสัญญา เหมาะสำหรับ: เบราว์เซอร์และ node.js
คำอธิบาย API เฉพาะเป็นภาษาจีน: https://www.kancloud.cn/yunye/axios/234845
$ cnpm i -S axios
import axios from 'axios'
ในส่วนของการตั้งค่า vue-router นั้น จะมีการเพิ่มตัวป้องกันการกำหนดเส้นทางเพื่อสกัดกั้นเส้นทางที่ต้องมีการเข้าสู่ระบบ แต่วิธีนี้เป็นเพียงการควบคุมการกำหนดเส้นทางส่วนหน้าแบบง่ายๆ และไม่สามารถป้องกันผู้ใช้จากการเข้าถึงเส้นทางที่ต้องใช้สิทธิ์ในการเข้าสู่ระบบได้จริงๆ เมื่อโทเค็นหมดอายุ โทเค็นจะยังคงถูกบันทึกไว้ในเครื่อง ในเวลานี้ เมื่อคุณเข้าถึงเส้นทางที่ต้องมีสิทธิ์ในการเข้าสู่ระบบ คุณควรขอให้ผู้ใช้เข้าสู่ระบบอีกครั้ง ในขณะนี้ จำเป็นต้องมี ตัวสกัดกั้น + รหัสสถานะ http ที่ส่งคืนโดยอินเทอร์เฟซส่วนหลังเพื่อตัดสิน
สร้าง axios.js ใหม่ภายใต้โฟลเดอร์ src (ระดับเดียวกับ App.vue)
//axios.js
import axios from 'axios'
import store from './store'
import router from './router'
//创建axios实例
var instance = axios.create({
timeout: 5000, //请求超过5秒即超时返回错误
headers: { 'Content-Type': 'application/json;charset=UTF-8' },
});
//request拦截器
instance.interceptors.request.use(
config => {
//判断是否存在token,如果存在的话,则每个http header都加上token
if (store.state.token) {
config.headers.Authorization = `token ${store.state.token}`;
}
return config;
}
);
//respone拦截器
instance.interceptors.response.use(
response => {
return response;
},
error => { //默认除了2XX之外的都是错误的,就会走这里
if (error.response) {
switch (error.response.status) {
case 401:
router.replace({ //跳转到登录页面
path: 'login',
query: { redirect: router.currentRoute.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由
});
}
}
return Promise.reject(error.response);
}
);
export default {
//用户注册
userRegister(data){
return instance.post('/api/register', data);
},
//用户登录
userLogin(data){
return instance.post('/api/login', data);
},
//获取用户
getUser(){
return instance.get('/api/user');
},
//删除用户
delUser(data){
return instance.post('/api/delUser', data);
}
}
ในที่สุดโค้ดจะแสดงวิธีการร้องขอสี่วิธี ซึ่งสอดคล้องกับการลงทะเบียน เข้าสู่ระบบ รับ (ผู้ใช้) และลบผู้ใช้ (delUser) และทั้งหมดนี้อยู่ภายใต้ /api อินเทอร์เฟซคำขอทั้งสี่คือ:
http://localhost:8080/api/login
http://localhost:8080/api/register
http://localhost:8080/api/user
http://localhost:8080/api/delUser
หลังจากนั้นเราจะใช้สี่วิธีนี้เพื่อเขียนอินเทอร์เฟซแบ็กเอนด์ที่เกี่ยวข้อง
บทความนี้เริ่มต้นจากที่นี่ทางฝั่งเซิร์ฟเวอร์ เนื่องจากฝั่งเซิร์ฟเวอร์จำเป็นต้องสร้างร่วมกับฐานข้อมูลและการสื่อสารที่ปลอดภัย http (jwt) โปรดอ่านหัวข้อนี้ร่วมกับฐานข้อมูลและบท jwt ด้านล่าง
koa2 สามารถใช้ไวยากรณ์ async/await เพื่อหลีกเลี่ยงการซ้อนฟังก์ชันโทรกลับที่ซ้ำซ้อนและยุ่งยาก และใช้ ctx เพื่อเข้าถึงอ็อบเจ็กต์บริบท
ตอนนี้เราใช้ koa2 เพื่อเขียนอินเทอร์เฟซบริการ API ของโครงการ
$ cnpm i koa
$ cnpm i koa-router -S //koa路由中间件
$ cnpm i koa-bodyparser -S //处理post请求,并把koa2上下文的表单数据解析到ctx.request.body中
const Koa = require('koa');
สร้าง server.js ใหม่ภายใต้ไดเร็กทอรีรากของโปรเจ็กต์เป็นรายการเริ่มต้นสำหรับเซิร์ฟเวอร์ทั้งหมด
//server.js
const Koa = require('koa');
const app = new Koa();
//router
const Router = require('koa-router');
//父路由
const router = new Router();
//bodyparser:该中间件用于post请求的数据
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
//引入数据库操作方法
const UserController = require('./server/controller/user.js');
//checkToken作为中间件存在
const checkToken = require('./server/token/checkToken.js');
//登录
const loginRouter = new Router();
loginRouter.post('/login', UserController.Login);
//注册
const registerRouter = new Router();
registerRouter.post('/register', UserController.Reg);
//获取所有用户
const userRouter = new Router();
userRouter.get('/user', checkToken, UserController.GetAllUsers);
//删除某个用户
const delUserRouter = new Router();
delUserRouter.post('/delUser', checkToken, UserController.DelUser);
//装载上面四个子路由
router.use('/api',loginRouter.routes(),loginRouter.allowedMethods());
router.use('/api',registerRouter.routes(),registerRouter.allowedMethods());
router.use('/api',userRouter.routes(),userRouter.allowedMethods());
router.use('/api',delUserRouter.routes(),delUserRouter.allowedMethods());
//加载路由中间件
app.use(router.routes()).use(router.allowedMethods());
app.listen(8888, () => {
console.log('The server is running at http://localhost:' + 8888);
});
ดังที่เห็นในโค้ด ทั้งการรับผู้ใช้และการลบผู้ใช้จำเป็นต้องมีโทเค็นการตรวจสอบ (ดูรายละเอียดในบท jwt ด้านล่าง) และเราได้แขวนอินเทอร์เฟซทั้งสี่ไว้บน /api ซึ่งสอดคล้องกับเส้นทางคำขอก่อนหน้าของ axios
นอกจากนี้ เนื่องจากพอร์ตเริ่มต้นโปรเจ็กต์ของเราคือ 8080 และพอร์ตที่ตรวจสอบโดยอินเทอร์เฟซ koa คือ 8888 เราจึงต้องเพิ่มสิ่งต่อไปนี้ในการกำหนดค่า dev ในไฟล์ config/index.js:
proxyTable: {
'/api': {
target: 'http://localhost:8888',
changeOrigin: true
}
},
JWT สามารถช่วยเราดำเนินการตรวจสอบตัวตนระหว่างการสื่อสาร HTTP
สำหรับรายละเอียด API เฉพาะ โปรดดู: https://segmentfault.com/a/1190000009494020
1. ลูกค้าล็อกอินเข้าสู่เซิร์ฟเวอร์ผ่านชื่อผู้ใช้และรหัสผ่าน
2. เซิร์ฟเวอร์ตรวจสอบตัวตนของลูกค้า
3. เซิร์ฟเวอร์สร้างโทเค็นสำหรับผู้ใช้และส่งกลับไปยังไคลเอนต์
4. ไคลเอนต์บันทึกโทเค็นลงในเบราว์เซอร์ในเครื่อง ซึ่งโดยปกติจะอยู่ในคุกกี้ (บทความนี้ใช้ sessionStorage ขึ้นอยู่กับสถานการณ์)
5. เมื่อลูกค้าเริ่มต้นคำขอ ลูกค้าจะต้องมีโทเค็น
6. หลังจากได้รับคำขอแล้ว เซิร์ฟเวอร์จะตรวจสอบโทเค็นก่อนแล้วจึงส่งคืนข้อมูล เซิร์ฟเวอร์ไม่จำเป็นต้องบันทึกโทเค็น เพียงแต่ต้องตรวจสอบข้อมูลที่มีอยู่ในโทเค็นเท่านั้น ไม่ว่าไคลเอนต์จะเข้าถึงเซิร์ฟเวอร์ใดในเบื้องหลัง ตราบใดที่สามารถผ่านการตรวจสอบข้อมูลผู้ใช้ได้
ในโฟลเดอร์เซิร์ฟเวอร์ ให้สร้าง /token (โฟลเดอร์) ใหม่ด้านล่าง และเพิ่ม checkToken.js และ createToken.js เพื่อวางวิธีการตรวจสอบและเพิ่มโทเค็นตามลำดับ
$ cnpm i jsonwebtoken -S
const jwt = require('jsonwebtoken');
module.exports = function(user_id){
const token = jwt.sign({user_id: user_id}, 'zhangzhongjie', {expiresIn: '60s'
});
return token;
};
เมื่อสร้างโทเค็น เราจะใช้ชื่อผู้ใช้เป็นแอตทริบิวต์ของ JWT Payload ตั้งค่าคีย์เป็น 'zhangzhongjie' และตั้งเวลาหมดอายุของโทเค็นเป็น 60 วินาที ซึ่งหมายความว่าหลังจากเข้าสู่ระบบแล้ว การรีเฟรชหน้าเว็บภายใน 60 วินาทีไม่จำเป็นต้องเข้าสู่ระบบอีกครั้ง
const jwt = require('jsonwebtoken');
//检查token是否过期
module.exports = async ( ctx, next ) => {
//拿到token
const authorization = ctx.get('Authorization');
if (authorization === '') {
ctx.throw(401, 'no token detected in http headerAuthorization');
}
const token = authorization.split(' ')[1];
let tokenContent;
try {
tokenContent = await jwt.verify(token, 'zhangzhongjie');//如果token过期或验证失败,将抛出错误
} catch (err) {
ctx.throw(401, 'invalid token');
}
await next();
};
รับโทเค็นก่อน จากนั้นใช้ jwt.verify เพื่อตรวจสอบ โปรดทราบว่าคีย์จะต้องสอดคล้องกับคีย์ 'zhangzhongjie' ของ createToken.js หากโทเค็นว่างเปล่า หมดอายุ หรือไม่ผ่านการตรวจสอบ ข้อผิดพลาด 401 จะถูกส่งออกไป ซึ่งคุณจะต้องเข้าสู่ระบบอีกครั้ง
MongoDB เป็นระบบจัดการฐานข้อมูลเชิงเอกสารที่ออกแบบมาเพื่อมอบโซลูชันการจัดเก็บข้อมูลประสิทธิภาพสูงที่ปรับขนาดได้สำหรับแอปพลิเคชันเว็บ การใช้โหนดเชื่อมต่อกับ MongoDB นั้นสะดวกมาก
$ cnpm i mongoose -S
มีหลายวิธีในการเชื่อมต่อกับ MongoDB ที่นี่เราใช้การเชื่อมต่อ การเชื่อมต่อเป็นการอ้างอิงเริ่มต้นของโมดูลพังพอนและส่งกลับวัตถุการเชื่อมต่อ
สร้าง db.js ใหม่ในโฟลเดอร์เซิร์ฟเวอร์เป็นรายการการเชื่อมต่อฐานข้อมูล
//db.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/vue-login');
let db = mongoose.connection;
// 防止Mongoose: mpromise 错误
mongoose.Promise = global.Promise;
db.on('error', function(){
console.log('数据库连接出错!');
});
db.on('open', function(){
console.log('数据库连接成功!');
});
//声明schema
const userSchema = mongoose.Schema({
username: String,
password: String,
token: String,
create_time: Date
});
//根据schema生成model
const User = mongoose.model('User', userSchema)
module.exports = User;
นอกจาก การเชื่อมต่อ ที่เราใช้แล้ว ยังมีวิธีการเชื่อมต่อ *connect() และ createConnection()* อีกด้วย
สคีมากำหนดเทมเพลตของตารางเพื่อให้เอกสารประเภทนี้มีองค์ประกอบเฉพาะและโหมดการจัดเก็บข้อมูลในฐานข้อมูล แต่เพียงกำหนดลักษณะของเอกสารเท่านั้น สำหรับการสร้างเอกสารและการดำเนินการต่างๆ บนเอกสาร (การเพิ่ม การลบ การแก้ไข และตรวจสอบ) จะทำผ่านโมเดลที่เกี่ยวข้อง จากนั้น เราจำเป็นต้องแปลง userSchema ให้เป็นบางอย่าง เราสามารถใช้ได้ กล่าวคือ โมเดลคือที่จับที่เราสามารถใช้งานได้
หลังจากรวบรวมโมเดลแล้ว เราจะได้โมเดลชื่อ User
โปรดใส่ใจกับตารางสคีมาที่คุณกำหนดที่นี่ พื้นที่จัดเก็บข้อมูลจะต้องสอดคล้องกับตารางนี้เมื่อเขียนและลงทะเบียนลงในฐานข้อมูลในภายหลัง
สร้างคอนโทรลเลอร์ใหม่ (โฟลเดอร์)/user.js ภายใต้โฟลเดอร์เซิร์ฟเวอร์เพื่อจัดเก็บวิธีการทำงานของฐานข้อมูล
ติดตั้งปลั๊กอินที่ใช้งานได้บางอย่างก่อน
$ cnpm i moment -s //用于生成时间
$ cnpm i objectid-to-timestamp -s //用于生成时间
$ cnpm i sha1 -s //安全哈希算法,用于密码加密
//user.js
const User = require('../db.js').User;
//下面这两个包用来生成时间
const moment = require('moment');
const objectIdToTimestamp = require('objectid-to-timestamp');
//用于密码加密
const sha1 = require('sha1');
//createToken
const createToken = require('../token/createToken.js');
//数据库的操作
//根据用户名查找用户
const findUser = (username) => {
return new Promise((resolve, reject) => {
User.findOne({ username }, (err, doc) => {
if(err){
reject(err);
}
resolve(doc);
});
});
};
//找到所有用户
const findAllUsers = () => {
return new Promise((resolve, reject) => {
User.find({}, (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 Login = async ( ctx ) => {
//拿到账号和密码
let username = ctx.request.body.name;
let password = sha1(ctx.request.body.pass);//解密
let doc = await findUser(username);
if(!doc){
console.log('检查到用户名不存在');
ctx.status = 200;
ctx.body = {
info: false
}
}else if(doc.password === password){
console.log('密码一致!');
//生成一个新的token,并存到数据库
let token = createToken(username);
console.log(token);
doc.token = token;
await new Promise((resolve, reject) => {
doc.save((err) => {
if(err){
reject(err);
}
resolve();
});
});
ctx.status = 200;
ctx.body = {
success: true,
username,
token, //登录成功要创建一个新的token,应该存入数据库
create_time: doc.create_time
};
}else{
console.log('密码错误!');
ctx.status = 200;
ctx.body = {
success: false
};
}
};
//注册
const Reg = async ( ctx ) => {
let user = new User({
username: ctx.request.body.name,
password: sha1(ctx.request.body.pass), //加密
token: createToken(this.username), //创建token并存入数据库
create_time: moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss'),//将objectid转换为用户创建时间
});
//将objectid转换为用户创建时间(可以不用)
user.create_time = moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss');
let doc = await findUser(user.username);
if(doc){
console.log('用户名已经存在');
ctx.status = 200;
ctx.body = {
success: false
};
}else{
await new Promise((resolve, reject) => {
user.save((err) => {
if(err){
reject(err);
}
resolve();
});
});
console.log('注册成功');
ctx.status = 200;
ctx.body = {
success: true
}
}
};
//获得所有用户信息
const GetAllUsers = async( ctx ) => {
//查询所有用户信息
let doc = await findAllUsers();
ctx.status = 200;
ctx.body = {
succsess: '成功',
result: doc
};
};
//删除某个用户
const DelUser = async( ctx ) => {
//拿到要删除的用户id
let id = ctx.request.body.id;
await delUser(id);
ctx.status = 200;
ctx.body = {
success: '删除成功'
};
};
module.exports = {
Login,
Reg,
GetAllUsers,
DelUser
};
วิธีการข้างต้นเป็นแกนหลักของการดำเนินงานฐานข้อมูลในโครงการ มาวิเคราะห์กัน
ขั้นแรก มีการกำหนดวิธีการพื้นฐานทั่วไปสามวิธี: findUser, findAllUsers และ delUser ในหมู่พวกเขา findUser จำเป็นต้องส่งผ่านพารามิเตอร์ ชื่อผู้ใช้ และ delUser จำเป็นต้องส่งผ่านพารามิเตอร์ id
รับข้อมูลแบบฟอร์มที่ส่งโดยโพสต์ผู้ใช้ ผู้ใช้ใหม่ที่ได้รับการออกแบบก่อนหน้านี้ตามฐานข้อมูลและรวบรวมเป็นแบบจำลอง และชื่อผู้ใช้ รหัสผ่านที่ได้รับ (ต้องเข้ารหัสด้วยแฮช sha1) โทเค็น (โดยใช้ที่สร้างขึ้นก่อนหน้านี้ วิธี createToken และใช้ชื่อผู้ใช้เป็นพารามิเตอร์ payload ของ jwt) และประหยัดเวลาในการสร้าง
ในขณะนี้ คุณต้องค้นหาฐานข้อมูลเพื่อดูว่ามีชื่อผู้ใช้อยู่หรือไม่ หากมี จะส่งคืนความล้มเหลว มิฉะนั้น ผู้ใช้จะถูกจัดเก็บไว้ในฐานข้อมูลและจะส่งคืนความสำเร็จ
รับข้อมูลแบบฟอร์มของโพสต์ ชื่อผู้ใช้ และรหัสผ่านของผู้ใช้ (การลงทะเบียนถูกแฮชและจำเป็นต้องถอดรหัสในขณะนี้) ค้นหาชื่อผู้ใช้จากฐานข้อมูลเพื่อดูว่ามีชื่อผู้ใช้อยู่หรือไม่ ให้ตรวจสอบว่ารหัสผ่านที่จัดเก็บไว้ในฐานข้อมูลสอดคล้องกับรหัสผ่านที่ผู้ใช้ส่งมาหรือไม่ หากตรงกัน โทเค็นใหม่จะถูกสร้างขึ้น ผู้ใช้และจัดเก็บไว้ในฐานข้อมูลสำเร็จ
เพียงสรุปเมธอด findAllUsers สาธารณะด้านบนแล้วใส่ข้อมูลในผลลัพธ์ เพื่อให้เพจ helloworld สามารถรับข้อมูลนี้และแสดงในภายหลัง
โปรดทราบว่าก่อนอื่นคุณต้องได้รับ ID ผู้ใช้ที่ต้องลบและส่งผ่านเป็นพารามิเตอร์
หลังจากเขียนวิธีการเหล่านี้แล้ว คุณสามารถปรับปรุงฟังก์ชั่นการลงทะเบียนและการเข้าสู่ระบบที่ไม่เคยสมบูรณ์แบบมาก่อนได้
เมื่อเราลงทะเบียนเสร็จแล้วและข้อมูลถูกเก็บไว้ในฐานข้อมูล เราต้องการตรวจสอบข้อมูลที่เพิ่งลงทะเบียนและจัดเก็บไว้ในฐานข้อมูล และเราจำเป็นต้องใช้เครื่องมือสร้างภาพฐานข้อมูล ฉันใช้ MongoBooster ซึ่งใช้งานง่าย
ดังที่คุณเห็นจากรูปด้านล่าง ข้อมูลสองชิ้นที่ลงทะเบียนในตัวอย่างประกอบด้วย id, ชื่อผู้ใช้, รหัสผ่าน, โทเค็น และเวลา รหัสผ่านชุดยาวนั้นถูกรวบรวมเนื่องจากการเข้ารหัสแฮช
เพิ่มรหัสต่อไปนี้หลังจากการตรวจสอบแบบฟอร์มใน register.vue
//register.vue
if (valid) {
axios.userRegister(this.ruleForm)
.then(({}) => {
if (data.success) {
this.$message({
type: 'success',
message: '注册成功'
});
} else {
this.$message({
type: 'info',
message: '用户名已经存在'
});
}
})
}
เราไม่ได้ส่งข้อมูลใด ๆ มาก่อนในองค์ประกอบการเข้าสู่ระบบ ตอนนี้เราได้เพิ่มชุดวิธีการเพื่อดำเนินการเข้าสู่ระบบให้เสร็จสิ้นหลังจากการตรวจสอบยืนยันสำเร็จ: ขอแนะนำ axios
import axios from '../axios.js'
จากนั้นเพิ่มรหัสต่อไปนี้หลังจากยืนยันแบบฟอร์มใน login.vue
//login.vue
if (valid) {
axios.userLogin(this.ruleForm)
.then(({ data }) => {
//账号不存在
if (data.info === false) {
this.$message({
type: 'info',
message: '账号不存在'
});
return;
}
//账号存在
if (data.success) {
this.$message({
type: 'success',
message: '登录成功'
});
//拿到返回的token和username,并存到store
let token = data.token;
let username = data.username;
this.$store.dispatch('UserLogin', token);
this.$store.dispatch('UserName', username);
//跳到目标页
this.$router.push('HelloWorld');
}
});
}
ส่งข้อมูลแบบฟอร์มไปที่พื้นหลัง คืนสถานะข้อมูล และพิจารณาว่ามีบัญชีอยู่หรือไม่ หลังจากเข้าสู่ระบบสำเร็จ คุณจะต้องได้รับโทเค็นและชื่อผู้ใช้ที่ส่งคืน บันทึกไว้ในร้านค้า และข้ามไปยังหน้า HelloWorld เป้าหมาย
หลังจากลงทะเบียนและเข้าสู่ระบบสำเร็จแล้ว ในที่สุดเราก็มาถึงหน้าแสดงผลจริง Helloworld!
มาปรับปรุงส่วนประกอบนี้เพื่อแสดงชื่อผู้ใช้ที่ลงทะเบียนในปัจจุบันทั้งหมดและให้ปุ่มลบ
//Helloworld.vue
<template>
<div class="hello">
<ul>
<li v-for="(item,index) in users" :key="item._id">
{{ index + 1 }}.{{ item.username }}
<el-button @click="del_user(index)">删除</el-button>
</li>
</ul>
<el-button type="primary" @click="logout()">注销</el-button>
</div>
</template>
<script>
import axios from '../axios.js'
export default {
name: 'HelloWorld',
data () {
return {
users:''
}
},
created(){
axios.getUser().then((response) => {
if(response.status === 401){
//不成功跳转回登录页
this.$router.push('/login');
//并且清除掉这个token
this.$store.dispatch('UserLogout');
}else{
//成功了就把data.result里的数据放入users,在页面展示
this.users = response.data.result;
}
})
},
methods:{
del_user(index, event){
let thisID = {
id:this.users[index]._id
}
axios.delUser(thisID)
.then(response => {
this.$message({
type: 'success',
message: '删除成功'
});
//移除节点
this.users.splice(index, 1);
}).catch((err) => {
console.log(err);
});
},
logout(){
//清除token
this.$store.dispatch('UserLogout');
if (!this.$store.state.token) {
this.$router.push('/login')
this.$message({
type: 'success',
message: '注销成功'
})
} else {
this.$message({
type: 'info',
message: '注销失败'
})
}
},
}
}
</script>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.hello {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
width: 400px;
margin: 60px auto 0 auto;
}
</style>
หน้าผลลัพธ์ค่อนข้างง่าย ต่อไปนี้เป็นประเด็นสำคัญบางประการ:
1. ต้องร้องขออินเทอร์เฟซ getUser() ทันทีหลังจากสร้างอินสแตนซ์ ( create() ) หากคำขอล้มเหลว โทเค็นจะต้องถูกล้าง หากคำขอสำเร็จ จะต้องใส่ข้อมูลที่ส่งคืนลงในหน้าผู้ใช้ การแสดงผล
2. ID นี้ จะต้องเขียนในรูปแบบวัตถุ มิฉะนั้นข้อผิดพลาดจะถูกรายงาน
3. ล้างโทเค็นเมื่อออกจากระบบ
การเปลี่ยนความคิดผู้คนเป็นสิ่งที่ยากที่สุดจริงๆ ตามกระบวนการ koa ควรออกแบบอินเทอร์เฟซก่อน จากนั้นส่วนหน้าจะสร้างคำขอตามอินเทอร์เฟซนี้ แต่ในทางกลับกัน ฉันเขียนคำขอส่วนหน้าก่อน จากนั้นจึงกำหนดอินเทอร์เฟซตามคำขอนี้
แน่นอนว่าฉันยังประสบปัญหามากมาย: เมื่อฉันเสร็จสิ้นหน้าจอแสดงผลและ axios ถูกเขียน ฉันติดอยู่กับการใช้ koa ในการเขียนอินเทอร์เฟซเป็นเวลานานฉันไม่มีความคิดเลย ดังที่กล่าวไว้ในคำนำ , "ฉันรู้แค่วิธีการป้อนข้อมูล ไม่ใช่วิธีการป้อนข้อมูล" จากนั้นฉันพบข้อผิดพลาดของอินเทอร์เฟซ 500 และแก้ไขมันเป็นเวลานาน เหตุผลหลักคือฉันไม่มีแนวคิดในการดีบักอินเทอร์เฟซ ในท้ายที่สุด Langya หัวหน้าของบริษัทก็ช่วยแก้ปัญหาได้