얼마 전 기술에서 제품으로 전환한 회사 동료와 그의 진로에 대해 논의하던 중, 그는 내가 깊이 믿었던 다음과 같은 말을 했습니다.
"특정 분야에만 국한되지 마세요. 기술에서 제품으로의 전환은 우선 생각의 변화입니다. 프론트 엔드에서 일해 오셨습니다. 데이터와 상호 작용할 때는 입력하는 방법만 알지만, 어떻게 나오는지 모르겠습니다.”
깨달음 같았어요 Vue를 배울 때 등록과 로그인 프로젝트를 보고 그냥 그대로 따라 Vue 프로젝트를 시작하고 koa와 mongodb를 도입하고 클라이언트 제출-서버 수신 반환-입력 전체 프로세스를 구현했습니다. .
이 프로젝트는 vue-cli를 기반으로 구축되었으며 토큰 방식을 사용하여 사용자 로그인을 확인하고 데이터베이스에 등록, 사용자 읽기, 사용자 삭제 등의 기능을 구현합니다. 이 글에서는 독자들이 node와 vue에 대해 어느 정도 기초를 갖고 있다고 가정하고 있으므로 기본적인 부분은 자세히 설명하지 않겠습니다.
시스템 환경: MacOS 10.13.3
타오바오 미러를 이용해 설치하기
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
그런 다음 모든 npm 설치를 cnpm 설치 로 변경하십시오.
프로젝트 아이디어와 선정된 기술을 보다 명확하게 하기 위해 이해하기 쉽도록 도표를 작성하였습니다.
1. 프로젝트 초기화
$ npm install
2. 프로젝트 시작
$ npm run dev
3. 몽고DB 시작
$ mongod --dbpath XXX
xxx는 프로젝트의 데이터 폴더에 대한 경로입니다(새 폴더를 만들 수도 있고, 데이터베이스는 데이터를 저장하는 데 사용됨). 또는 터미널로 직접 끌어서 놓을 수도 있습니다.
4. 서버 시작
$ node server.js
나는 vue에 대해 선호하는 UI 라이브러리로 Ele.me의 Element-UI를 선택했습니다. 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);
UI의 탭 전환을 사용하여 등록 인터페이스와 로그인 인터페이스 사이를 전환하고, 로그인 구성 요소를 전체 로그인 시스템의 기본 인터페이스로 사용하고, 등록 구성 요소를 독립 구성 요소로 사용합니다. Element-UI의 구성방법, Form Validation, 기타 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)
src 폴더 아래에 새 라우터(폴더)/index.js를 만듭니다. 다음 세 가지 구성 요소를 도입했습니다.
로그인 후 HelloWorld 표시 페이지
로그인 로그인 메인 인터페이스
등록 등록 구성 요소
먼저 로그인해야 하는 페이지를 설정하려면 router.beforeEach 라우팅 가드를 사용하세요. 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에 store를 도입하고 vue 인스턴스에 store를 추가하세요.
//引入store
import store from './store'
그런 다음 vuex를 사용해야 하는 구성 요소에 도입하세요.
//store index.js
import Vuex from 'vuex'
Vue.use(Vuex)
src 폴더 아래에 새 스토어(폴더)/index.js를 생성합니다.
//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를 캡슐화합니다. Promise 기반 HTTP 클라이언트는 브라우저 및 node.js에 적합합니다.
중국어로 된 특정 API 설명: https://www.kancloud.cn/yunye/axios/234845
$ cnpm i -S axios
import axios from 'axios'
vue-router를 설정하는 과정에서 로그인이 필요한 경로를 가로채기 위해 라우팅 가드가 추가되지만, 이 방법은 단순한 프런트엔드 라우팅 제어일 뿐 실제로 사용자가 로그인 권한이 필요한 경로에 액세스하는 것을 막을 수는 없습니다. 토큰이 만료되더라도 토큰은 여전히 로컬에 저장됩니다. 이때, 로그인 권한이 필요한 경로에 접근할 때에는 실제로 사용자에게 다시 로그인하도록 요청해야 합니다. 이때 판단에는 인터셉터 + 백엔드 인터페이스에서 반환된 http 상태 코드가 필요합니다.
src 폴더 아래에 새 axios.js를 만듭니다(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를 사용하여 Context 객체에 액세스할 수 있습니다.
이제 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 장 참조), 이전 axios 요청 경로와 일치하는 /api에 4개의 인터페이스를 걸어 두었습니다.
또한 프로젝트 시작 포트가 8080이고 koa 인터페이스에서 모니터링하는 포트가 8888이므로 config/index.js 파일의 dev 구성에 다음을 추가해야 합니다.
proxyTable: {
'/api': {
target: 'http://localhost:8888',
changeOrigin: true
}
},
JWT는 HTTP 통신 중에 ID 인증을 수행하는 데 도움이 될 수 있습니다.
구체적인 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 페이로드의 속성으로 사용하고 키를 '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를 사용하여 확인하세요. 키는 createToken.js의 'zhangzhongjie' 키와 일치해야 합니다. 토큰이 비어 있거나 만료되거나 확인에 실패하면 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는 username 매개변수를 전달해야 하고, delUser는 id 매개변수를 전달해야 합니다.
사용자 게시물이 제출한 양식 정보, 이전에 데이터베이스에 따라 설계되어 모델로 컴파일된 새 사용자, 획득한 사용자 이름, 비밀번호(sha1 해시로 암호화해야 함), 토큰(이전에 생성된 사용)을 가져옵니다. createToken 메소드, 그리고 사용자 이름을 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. 인스턴스가 생성된 후( created() ) 즉시 getUser() 인터페이스를 요청해야 합니다. 요청이 실패하면 토큰을 지워야 하며, 요청이 성공하면 페이지에 대한 반환 데이터를 사용자에게 입력해야 합니다. 표현.
2. thisID는 객체 형식으로 작성되어야 합니다. 그렇지 않으면 오류가 보고됩니다.
3. 로그아웃 시 토큰 삭제
사람들의 생각을 바꾸는 것은 실제로 가장 어려운 일입니다. 프로세스에 따르면 Koa는 인터페이스를 먼저 디자인한 다음 프런트엔드에서 이 인터페이스를 기반으로 요청을 작성해야 합니다. 그러나 반대로 저는 프런트엔드 요청을 먼저 작성한 다음 이 요청을 기반으로 인터페이스를 구성합니다.
물론, 나 또한 많은 어려움에 직면했다. 프론트엔드 디스플레이 페이지를 완성하고 axios를 작성했을 때, 서문에서 언급했듯이 koa를 사용하여 인터페이스를 작성하는 데 오랜 시간이 걸렸다. , "나는 데이터를 입력하는 방법만 알고, 나가는 방법을 알지 못합니다." 그러다가 500이라는 인터페이스 오류가 발생해서 오랫동안 디버깅을 했는데, 인터페이스 디버깅에 대한 개념이 없었기 때문이었는데요. 결국 회사 사장님께서 문제 해결에 도움을 주셨네요.