用户管理模块

一、配置前后端路由

  • 界面路由
router.get('/login', async (ctx, next) => {
    await ctx.render('login', {})
})

router.get('/register', async (ctx, next) => {
    await ctx.render('register', {})
})
  • 后端接口路由
router.prefix('/api/user')

//注册
router.post('/register', async (ctx, next) => {
    const { userName, password, gender } = ctx.request.body
    //controller
    ctx.body = await register({ userName, password, gender })
})

//登录
router.post('/login', async (ctx, next) => {
    const { userName, password } = ctx.request.body
    //controller
    ctx.body = await login(ctx, userName, password) 
})

//用户名是否存在
router.post('/isExist', async (ctx, next) => {
    const { userName } = ctx.request.body
    console.log("username",userName)
    //controller
    ctx.body = await isExist(userName)
})

注:
(1)还要注册路由接口,这里没有贴图忽略
(2)前端页面采用ejs模板,后面会分享出来
(3)这里主要涉及的是后端的配置

在这里插入图片描述
在这里插入图片描述

二、功能一:用户名是否存在

  • 功能流程图

在这里插入图片描述

  • controller 层
/**
 * 
 * @param {String} userName 用户名
 */
async function isExist(userName) {
  //调用 services 获取数据
  const userInfo = await getUserInfo(userName)
  if (userInfo) {
      //已存在
      return new SuccessModel()
  } else {
     //不存在
     return new ErrorModel(registerUserNameNotExistInfo)
  }
}
  • services 层
async function getUserInfo(userName, password) {
   //查询条件
   const whereOpt = {
       userName
   }
   if (password) {
       //ES6 对象语法  拷贝对象
       Object.assign(whereOpt, {password})
    }

    //查询
    const result = await User.findOne({
        attributes:[
            'id',
            'userName',
            'nickName',
            'picture',
            'city'
        ],
        where:whereOpt
    })

    if (result == null) {
        //未找到
        return result
    }
    //格式化
    const formaRes = formatUser(result.dataValues) 
    return formaRes
}
  • 功能测试
    在这里插入图片描述

  • 涉及的知识模块:
    (1)sequeline框架对数据库的操作(重点)不懂点这里
    (2)ES6语法(对象解构)不懂点这里
    (3)分层实现理念

三、功能二:用户注册

  • 功能流程图跟第二点一样(采用分层的思想)
  • controller 层
async function register({ userName, password, gender }) {
    //用户是否存在
    const userInfo = await getUserInfo(userName)
    if (userInfo) {
        //用户名已存在
        return ErrorModel(registerUserNameExistInfo)
    }

    //注册 service
    try {
        await createUser({
            userName,
            password: doCrypto(password),//md5加密
            gender
        })
        return new SuccessModel()
    } catch (ex) {
        console.error(ex.message, ex.stack)
        return new ErrorModel(registerFailInfo)
    }
}
  • service 层
async function createUser({ userName, password, gender = 3, nickName}){
   const result = User.create({
       userName,
       password,
       nickName: nickName ? nickName :userName,
       gender
   })
   return result.dataValues
}
  • 功能测试
    在这里插入图片描述

  • 涉及的知识点:
    (1)上面的三点知识模块
    (2)md5 加密不懂点这里

四、前端发送用户信息对其格式校验

  • 安装 ajv 插件

npm i ajv – save

  • 配置用户信息格式
// 校验规则  user.js
const SCHEMA = {
    type: 'object',
    properties: {
        userName: {
            type: 'string',
            // 字母开头,字母数字下划线
            pattern: '^[a-zA-Z][a-zA-Z0-9_]+$', 
            maxLength: 255,
            minLength: 2
        },
        password: {
            type: 'string',
            maxLength: 255,
            minLength: 3
        },
        newPassword: {
            type: 'string',
            maxLength: 255,
            minLength: 3
        },
        nickName: {
            type: 'string',
            maxLength: 255
        },
        picture: {
            type: 'string',
            maxLength: 255
        },
        city: {
            type: 'string',
            maxLength: 255,
            minLength: 2
        },
        gender: {
            type: 'number',
            minimum: 1,
            maximum: 3
        }
    }
}
  • 封装校验方法
//validate.js
const Ajv = require('ajv').default
const ajv = new Ajv()

/**
 * json schema 校验
 * @param {Object} schema json schema 规则
 * @param {Object} data 待校验的数据
 */
function validate(schema, data = {}) {
    const valid = ajv.validate(schema, data)
    if (!valid) {
        return ajv.errors[0]
    }
}

module.exports = validate

主: 代码中导入 ajv 包后面需要加 default,否则程序会报 new Ajv() 不是一个构造器

  • 调用封装的方法
    在这里插入图片描述

  • 将 user.js 中的 UserValidate 封装成中间件


const userValidate = require("../validator/user");
/**
 * 生成 json schema 验证的中间件
 * @param {function} validatorFn 验证函数
 * @returns 
 */
function genValidator(validatorFn) {
    //定义中间件函数
    async function validator(ctx, next) {
        //校验
        const data = ctx.request.body
        const error = validatorFn(data)
        if (error) {
            ctx.body = new ErrorModel(jsonSchemaFileInfo)
            return
        }
        await next()
    }
    //返回中间件函数
    return validator
}

module.exports = {
    genValidator
}
  • 路由层api中,使用该中间件
const { genValidator } =require('../../middlewares/validator')
const userValidate = require('../../validator/user')
//注册
router.post('/register',genValidator(userValidate), async (ctx, next) => {
    const { userName, password, gender } = ctx.request.body
    //controller
    ctx.body = await register({ userName, password, gender })
})

五、功能三:用户登录

  • router 层 和 controller 层 跟前面俩个功能模块类似,不细讲
  • service 层是直接调用判断用户是否存在的方法(getUserInfo),只是要在传递一个密码值即可。

六、封装登录验证的中间件(页面、api)

  • 中间件
/**
 * API 登录验证
 * @param {Object} ctx 
 * @param {function} next 
 */
async function loginCheck(ctx, next){
    if (ctx.session && ctx.session.userInfo) {
        await next()
        return
    }
    //未登录
    ctx.body = new ErrorModel(loginCheckFailInfo)
}
/**
 * 页面登录验证 
 * @param {Object} ctx 
 * @param {function} next 
 */
async function loginRedirect(ctx, next){
     if (ctx.seession && ctx.session.userInfo) {
         await next()
     }
     
     const curUrl = ctx.url
     ctx.redirect('/login?url=' + encodeURIComponent(curUrl))
}

module.exports = {
    loginRedirect,
    loginCheck,
}
  • 在要使用登录验证的路由中使用即可 如:
//删除
router.post('/del', loginCheck, async (ctx, next) => {
    if (isTest) {
        //测试环境下,测试账号登录之后,删除自己
        const { userName } = ctx.session.userInfo
        //调用 controller
        ctx.body = await deleteCurUser(userName)
    }
})

七、进行单元测试

/**
 * @description jest server
 * @author 浩
 */

const request = require('supertest')
const server = require('../src/app').callback()

module.exports = request(server)
/**
 * @description user api test
 * @author 浩
 */

const server = require('../server')

// 用户信息
const userName = `u_${Date.now()}`
const password = `u_${Date.now()}`
const testUser = {
    userName,
    password,
    nickName:userName,
    gender:1,
}

// 存储cookie
let   COOKIE = ''

// 注册
test('注册一个用户,应该成功', async() => {
    const res = await server
    .post('/api/user/register').send(testUser) 
    expect(res.body.errno).toBe(0)
})

// 重复注册
test("重复注册,应该失败", async () => {
    const res = await server
    .post('/api/user/register').send(testUser) 
    expect(res.body.errno).not.toBe(0)
})

// 查询注册的用户名是否存在
test("查询注册的用户名,应该存在", async () => {
   const res = await server
   .post('/api/user/isExist')
   .send({userName})
   expect(res.body.errno).toBe(0)
})

// json schame 校验
test("json schame 检测,非法的格式,注册应该失败", async () => {
    const res = await server
    .post('/api/user/register')
    .send({
        userName: '123', //用户名不是字母(或下划线)开头
        password: 'a', // 最小的长度不是 3
        gender: 'mail' // 不是数字
    })
    expect(res.body.errno).not.toBe(0)
})

//登录
test('登录,应该成功', async () => {
    const res = await server
    .post('/api/user/login')
    .send({
        userName,
        password
    })
    expect(res.body.errno).toBe(0)

    //获取cookie
    COOKIE = res.headers['set-cookie'].join(';')
})

// 删除
test('删除用户,应该成功', async () => {
    const res = await server
    .post('/api/user/del')
    .set('Cookie', COOKIE)
   expect(res.body.errno).toBe(0)
   
}) 

// 再次查询注册的用户是否存在,应该不存在
test('再次查询用户,应该不存在', async () => {
    const res = await server
    .post('/api/user/isExist')
    .send({
        userName
    })
    expect(res.body.errno).not.toBe(0)
})

  • 这部分与上面的信息格式校验类似,不同的是:前者是上线后一直都会校验的,而后者是在开发过程中,为了防止误删某个属性,程序员不知道。
const { User } = require('../../src/db/model/index')

test('User 模型的各个属性,符合预期', () => {
    // build 会构建一个内存的 User 实例,但不会提交到数据库中
    const user = User.build({
        userName: 'zhangsan',
        password: 'p123123',
        nickName: '张三',
        picture: '/xxx.png',
        city:  '汕头',
       // gender: 1
    })
    //验证属性
    expect(user.userName).toBe('zhangsan')
    expect(user.password).toBe('p123123')
    expect(user.nickName).toBe('张三')
    expect(user.picture).toBe("/xxx.png")
    expect(user.city).toBe('汕头')
    expect(user.gender).toBe(3)
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海面有风

您的鼓励将是我前进的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值