前言:
作为一个Coder虽然主要在做前端方面的开发,但是为了可以更好的协作开发,还是很有必要学习后端的一些知识,最起码你可以了解到什么东西是真的实现不了😏。
技术栈:
- 基础项目:eggjs
- 时间处理:dayjs
- 数据加密:md5
- UID生成:uuid
- 鉴权处理:jsonwebtoken
- 参数校验:egg-validate
- 跨域限制:egg-cors
- 数据存储:egg-mongoose
选择Eggjs原因:
“Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。”
我们可以通过eggjs提供的脚手架生成一套完整的项目结构,这对于我们快速学习将是非常有必要的,接下来我们就一起了解一下eggjs基础项目的的结构,对于初次使用我们就只关注如下的目录即可。
了解第一个Controller:
- Controller意为控制器,我们主要的后端逻辑处理的地方(当然过多的通用逻辑应该抽取到Service层),我们通过
this
指针结构到ctx
上下文对象,并将要返回的内容赋值给body
,接着我们在router.js
中增加router.get('/', controller.home.index);
就可以启动服务后在浏览器访问IP:PORD
得到3号标题的内容了。
'use strict'; const Controller = require('egg').Controller; class HomeController extends Controller { async index() { const { ctx } = this; ctx.body = '<h3>欢迎使用可追溯查询数据提供服务</h3>'; } } module.exports = HomeController; 复制代码
接下来试着实现用户的基本操作:
- 这次我们先定义好如下三个路由,分别对应用户的登录,信息获取,登出三种操作。
router.post('/dev-api/user/login', controller.user.login); router.get('/dev-api/user/info', controller.user.info); router.post('/dev-api/user/logout', controller.user.logout); 复制代码
- 因为会涉及到数据存储,鉴权,跨域,我们将先配置好中间件来避免后续的麻烦,具体的包自行安装就好。
// server\config\plugin.js exports.mongoose = { enable: true, package: 'egg-mongoose', }; exports.validate = { enable: true, package: 'egg-validate', }; exports.cors = { enable: true, package: 'egg-cors', }; 复制代码
// server\config\config.default.js module.exports = appInfo => { ... config.cors = { origin: '*', // 表示允许的源 allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH', // 表示允许的http请求方式 }; return { ... security: { csrf: { enable: false, }, }, bodyParser: { jsonLimit: '5mb', // 允许传输内容的大小限制 formLimit: '5mb', }, mongoose: { client: { url: 'mongodb://<这块有时间单独说,各位先百度也行>', options: { autoReconnect: true, reconnectTries: Number.MAX_VALUE, bufferMaxEntries: 0, }, }, }, }; }; 复制代码
- MongoDB对应的用户和Token模型定义:
module.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const UserSchema = new Schema({ username: { type: String, required: true }, password: { type: String, required: true }, roles: { type: Array, required: true }, introduction: { type: String, required: true }, avatar: { type: String, required: true }, name: { type: String, required: true }, }, { timestamps: true }); return mongoose.model('User', UserSchema); }; module.exports = app => { const mongoose = app.mongoose; const Schema = mongoose.Schema; const TokenSchema = new Schema({ token: { type: String, required: true }, }, { timestamps: true }); return mongoose.model('Token', TokenSchema); }; 复制代码
- 还准备了一个公共的
BaseController
,将统一处理接口的返回格式:
'use strict'; const Controller = require('egg').Controller; class BaseController extends Controller { success(data) { this.ctx.body = { code: 20000, data, }; } message(message) { this.ctx.body = { code: 20000, message, }; } error(message, code = -1, errors = {}) { this.ctx.body = { code, message, errors, }; } } module.exports = BaseController; 复制代码
- 总算要开始我们的用户
Controller
的编写了,继承自BaseController
,并导入了加密、鉴权,也定义了操作校验的对象loginUserRule
:
'use strict'; const BaseController = require('./base'); const KEY = require('../key'); const jwt = require('jsonwebtoken'); const md5 = require('md5'); const loginUserRule = { username: { type: 'string' }, password: { type: 'string' }, }; class UserController extends BaseController { async login() { const { ctx } = this; } async info() { const { ctx } = this; } async logout() { const { ctx } = this; } } module.exports = UserController; 复制代码
- 参数校验统一处理参照如下:
const { ctx } = this; try { ctx.validate(loginUserRule); } catch (e) { return this.error('参数校验失败', -1, e.errors); } 复制代码
- 登录接口编写
- 首先通过
request
对象的body
属性得到请求中的用户名和密码; - 通过用户名在MongoDB中查找用户,成功找到说明用户名正常;
- 通过将密码进行md5加密与存储的密码比对,成功则说明密码正常;
- 使用jwt将用户名写入并生成token,存储到MongoDB中;
- token成功存储后成功响应前端接口数据。
const { username, password } = ctx.request.body; const ret = await ctx.model.User.findOne({ username }); if (ret.password && ret.password === md5(password)) { const token = jwt.sign({ username }, KEY.secretOrPrivateKey); const tokenRet = await ctx.model.Token.create({ token, }); if (tokenRet._id) { this.success({ token }); } } else { this.error('用户名或密码错误'); } 复制代码
- 用户信息获取接口编写
- 获取用户信息的接口将只需要传递token即可;
- 我们通过将接收到的token进行Mongo查询,成功查询说明Token正常;
- 通过验证token正确性得到被写入的用户名;
- 我们在通过用户名查询Mongo中对应的详细信息,成功查询后相应前端接口数据。
const token = ctx.request.header['x-token']; const ret = await ctx.model.Token.findOne({ token }); if (ret) { const { username } = jwt.verify(token, KEY.secretOrPrivateKey); const userRet = await ctx.model.User.findOne({ username }); if (userRet) { this.success(userRet); } } 复制代码
- 登出接口编写
- 同样通过获取token并查询,成功查询说明token正常;
- 这时候我们只需要删除token,成功响应前端接口数据即可。
const token = ctx.request.header['x-token']; const ret = await ctx.model.Token.findOne({ token }); if (ret) { const tokenRet = await ctx.model.Token.deleteOne({ token }); if (tokenRet && tokenRet.ok === 1) { this.success('success'); } } else { this.error('服务器暂无在线记录'); } 复制代码
至此我们就已经实现了一个最简单的App中用户的基本操作(登录,信息获取,登出)的功能,当然在实际的业务中将更为复杂。
总结:
这个流程下来,其实涉及的知识点还不少,比如说MongoDB的存取操作,JWT的生成验证,还有统一个数据结构应用的必要等,没有为自己App提供过服务的Coder们,一起来试试吧。