منذ بعض الوقت، كنت أناقش مساره المهني مع زميل في الشركة تحول من التكنولوجيا إلى المنتج، وقال شيئًا أؤمن به بشدة:
"لا تقصر نفسك على مجال معين. إن التحول من التكنولوجيا إلى المنتج هو في المقام الأول تغيير في التفكير. لقد كنت تعمل على الواجهة الأمامية. عند التفاعل مع البيانات، فأنت تعرف فقط كيفية إدخالها، لكنك لا أعرف كيف يخرج هذا القيد.
كان الأمر بمثابة التنوير عندما كنت أتعلم Vue، ورأيت مشروعًا للتسجيل وتسجيل الدخول، وتبعت ذلك ببساطة وبدأت مشروع Vue، وقدمت koa وmongodb، ونفذت عملية إدخال واستقبال إرسال العميل إلى قاعدة البيانات بأكملها .
تم بناء هذا المشروع على أساس vue-cli، ويستخدم طريقة الرمز المميز للتحقق من تسجيل دخول المستخدم، وينفذ وظائف مثل التسجيل في قاعدة البيانات، وقراءة المستخدمين، وحذف المستخدمين. تفترض المقالة أن القراء لديهم أساس معين في العقدة و vue، لذلك لن يتم وصف الأجزاء الأساسية بالتفصيل.
بيئة النظام: ماك 10.13.3
التثبيت باستخدام مرآة تاوباو
$ 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 كمكتبة واجهة المستخدم المفضلة لدي لـ vue. لا يبدو أن البرامج الأخرى مثل iview و vue-strap شاملة مثل ele.
$ npm i element-ui -S
//在项目里的mian.js里增加下列代码
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
استخدم تبديل علامات التبويب في واجهة المستخدم للتبديل بين واجهات التسجيل وتسجيل الدخول، واستخدم مكون تسجيل الدخول كواجهة رئيسية لنظام تسجيل الدخول بالكامل، ومكون التسجيل كمكون مستقل. يرجى الرجوع إلى الموقع الرسمي للتعرف على طريقة تكوين Element-UI والتحقق من صحة النموذج وواجهات برمجة التطبيقات الأخرى.
//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 بمكان تقديمها. يتضمن الكود أعلاه بالفعل بعض تبديل التوجيه، فلنحسن التوجيه الآن:
$ cnpm i vue-router
import Router from 'vue-router'
Vue.use(Router)
قم بإنشاء جهاز توجيه (مجلد)/index.js جديد ضمن المجلد src وقد قدمنا ثلاثة مكونات:
عرض صفحة HelloWorld بعد تسجيل الدخول
تسجيل الدخول تسجيل الدخول الواجهة الرئيسية
سجل مكون التسجيل
استخدم router.beforeEach routing Guard لتعيين الصفحة التي يجب تسجيل الدخول إليها أولاً. استخدم الحقل requireAuth لتحديد ما إذا كان المسار يتطلب أذونات تسجيل الدخول سيتم اعتراضها، ثم سيتم تحديد ما إذا كان هناك رمز مميز (ستتم مناقشة الرمز المميز أدناه). انتقل إلى صفحة تسجيل الدخول.
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
قم بتقديم المتجر في main.js، وأضف المتجر إلى مثيل vue.
//引入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 من قبل، وكانت هناك العديد من المخاطر. حتى يأتي vue2.0، ما عليك سوى التخلي عن vue-resource واستخدام axios .
تغليف اياكس، يستخدم لإرسال الطلبات والحصول على البيانات بشكل غير متزامن. عميل HTTP قائم على الوعد، ومناسب لـ: المتصفح وnode.js.
وصف محدد لواجهة برمجة التطبيقات باللغة الصينية: 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 استخدام بناء الجملة غير المتزامن/الانتظار لتجنب تداخل وظيفة رد الاتصال المتكررة والمرهقة، واستخدام 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.
للحصول على تفاصيل محددة عن واجهة برمجة التطبيقات، يرجى الاطلاع على: 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، ونضبط المفتاح على "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()*.
يحدد المخطط قالب الجدول بحيث يكون لهذا النوع من المستندات تكوين محدد ووضع تخزين في قاعدة البيانات. ولكنه يحدد فقط شكل المستند. أما بالنسبة لإنشاء المستند وتنفيذ العمليات المختلفة على المستند (الإضافة والحذف والتعديل والفحص)، فسيتم ذلك من خلال النموذج المقابل، ثم نحتاج إلى تحويل مخطط المستخدم إلى شيء ما يمكننا استخدام النموذج، أي أن النموذج هو المقبض الذي يمكننا العمل عليه.
بعد تجميع النموذج، نحصل على نموذج اسمه المستخدم .
انتبه إلى جدول المخطط الذي تحدده هنا. يجب أن يتوافق تخزين البيانات مع هذا الجدول عند كتابته وتسجيله في قاعدة البيانات لاحقًا.
قم بإنشاء وحدة تحكم (مجلد)/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، واستخدم اسم المستخدم كمعلمة الحمولة لـ jwt)، واحفظ وقت الإنشاء.
في هذا الوقت، يجب عليك أولاً البحث في قاعدة البيانات لمعرفة ما إذا كان اسم المستخدم موجودًا أم لا، فسيُرجع الفشل. وإلا فسيتم تخزين المستخدم في قاعدة البيانات وإرجاع النجاح.
احصل على معلومات النموذج الخاصة بمنشور المستخدم واسم المستخدم وكلمة المرور (يتم تجزئة التسجيل ويجب فك تشفيره في هذا الوقت). ابحث في اسم المستخدم من قاعدة البيانات لتحديد ما إذا كان اسم المستخدم موجودًا، وإذا لم يكن هناك خطأ في الإرجاع، فحدد ما إذا كانت كلمة المرور المخزنة في قاعدة البيانات متوافقة مع كلمة المرور التي أرسلها المستخدم، وإذا كانت متسقة، فسيتم إنشاء رمز مميز جديد لها المستخدم والمخزنة في قاعدة البيانات يعود النجاح.
ما عليك سوى تغليف طريقة findAllUsers العامة أعلاه ووضع المعلومات في النتيجة، حتى تتمكن صفحة helloworld من الحصول على هذه البيانات وعرضها لاحقًا.
لاحظ أنه يجب عليك أولاً الحصول على معرف المستخدم الذي يجب حذفه وتمريره كمعلمة.
بعد كتابة هذه الطرق، يمكنك تحسين وظائف التسجيل وتسجيل الدخول التي لم تكن مثالية من قبل.
عندما ننتهي من التسجيل ويتم تخزين البيانات في قاعدة البيانات، نريد التحقق من البيانات المسجلة والمخزنة للتو في قاعدة البيانات، ونحتاج إلى استخدام أدوات تصور قاعدة البيانات. أستخدم MongoBooster ، وهو سهل التشغيل.
كما ترون من الشكل أدناه، فإن قطعتي البيانات المسجلتين في المثال تتضمن المعرف واسم المستخدم وكلمة المرور والرمز المميز والوقت. يتم تجميع هذه السلسلة الطويلة من كلمات المرور بسبب تشفير التجزئة.
أضف الكود التالي بعد التحقق من صحة النموذج في 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'
ثم قم بإضافة الكود التالي بعد التحقق من النموذج في تسجيل الدخول.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() مباشرة بعد إنشاء المثيل ( تم إنشاؤه() ). إذا فشل الطلب، فيجب مسح الرمز المميز، وإذا نجح الطلب، فيجب وضع بيانات الإرجاع في المستخدم للصفحة تقديم.
2. يجب كتابة هذا المعرف بتنسيق كائن، وإلا سيتم الإبلاغ عن خطأ
3. قم بمسح الرمز المميز عند تسجيل الخروج
إن تغيير تفكير الناس هو في الواقع أصعب شيء. وفقًا للعملية، يجب على koa تصميم الواجهة أولاً، ثم ستقوم الواجهة الأمامية بتقديم الطلبات بناءً على هذه الواجهة، لكن على العكس من ذلك، أكتب طلبات الواجهة الأمامية أولاً، ثم أقوم بصياغة الواجهة بناءً على هذا الطلب.
بالطبع، واجهت أيضًا العديد من الصعوبات: عندما انتهيت من كتابة صفحة العرض الأمامية وكتابة المحاور، كنت عالقًا لفترة طويلة في استخدام koa لكتابة الواجهة، ولم يكن لدي أي فكرة على الإطلاق "أنا أعرف فقط كيفية إدخال البيانات، وليس كيفية إدخالها." ثم واجهت خطأً في الواجهة قدره 500 وقمت بتصحيحه لفترة طويلة، والسبب الرئيسي هو أنه لم يكن لدي أي فكرة عن تصحيح أخطاء الواجهة، وفي النهاية، ساعدني رئيس الشركة لانجيا في حل المشكلة.