全栈项目|小书架|服务器开发-用户模块设计(用户表设计,注册登陆,退出登陆)

写系列文章最容易出现的状况是写到一半,而后写不下去了。一个因素是人变懒了;一个因素是这几天在整理用户模块这部分的知识,发现有太多的内容要写,而深刻Google以后又发现好多内容是本身未知的,写着写着就不知道该如何写了。linux

常见的和用户模块相关的操做有:注册登陆、信息修改、退出登陆ios

用户表设计

一个系统中,用户模块是最基础也是最重要的。若是只考虑一种登陆方式,那用户表能够用一张users表搞定一切。可是现实状况是登陆方式有不少种,好比:帐号密码登陆微信、QQ、微博登陆手机号、邮箱登陆。并且一个用户可使用帐号密码登陆以后再关联微信、QQ、微博,以后能够直接使用微信、QQ、微博登陆;那么如何保证设计用户表将变得相当重要。数据库

参考:axios

  1. 可扩展的用户表设计
  2. 设计一个可扩展的用户登陆系统 (1)
  3. 用户系统设计与实现
  4. 多平台统一用户系统设计

经过以上部分博文的介绍,咱们能够将用户表设计为:用户基础表:user_base用户受权表:user_auth用户扩展表:user_extends小程序

用户基础表:uid、user_role、user_name、avatar、password、signature、birthday、gender等segmentfault

用户受权表:id、uid、identity_type、identifier、certificate等微信小程序

用户扩展表:id、uid、device_name、device_id、vendor、client_name、client_version等api

以上的用户表的设计能够参考: 用户系统设计与实现bash

ps:我这个项目的用户表设计当时没有考虑那么多,只考虑了微信小程序端的设计,先给本身留个坑之后再将项目完善吧。先介绍目前的实现方式。服务器

我目前的实现步骤以下:

app/models包下建立user.js:用户表的相关字段以及用户表的操做

User.init({
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    nickname: Sequelize.STRING,
    email: {
        type: Sequelize.STRING(128),
        // 添加惟一(unique)约束后插入重复值会报错
        unique: true
    },
    password: {
        type: Sequelize.STRING,
        set(val) {
        	// 使用 bcryptjs 库对密码加密处理
            const salt = bcrypt.genSaltSync(10)
            const psw = bcrypt.hashSync(val, salt)
            // 设置 password 的值
            this.setDataValue('password', psw)
        }
    },
    openid: {
        type: Sequelize.STRING(64),
        unique: true
    },
    gender: Sequelize.INTEGER,
    balance: Sequelize.INTEGER,
    avatar_url: Sequelize.STRING,
    city: Sequelize.STRING,
    country: Sequelize.STRING,
    province: Sequelize.STRING
}, {
    sequelize,
    tableName: 'users'
})

module.exports = {
    User
}
复制代码

注册登陆

注册

微信小程序端不须要注册,直接使用微信受权登陆便可。

这里的注册是指用户在Web、App 端使用邮箱、用户名、密码完成注册。

app/api/v1包下建立user.js

/**
*	用户名密码注册
*/
router.post('/register', async (ctx) => {
    const v = await new RegisterValidator().validate(ctx)
    // 令牌获取 颁布令牌
    const user = {
        email: v.get('body.email'),
        password: v.get('body.password2'),
        nickname: v.get('body.nickname')
    }

    // 使用 Sequlize 保存到数据库
    await User.create(user)
    success()
})

module.exports = router

复制代码

调用:http://192.168.*.***:3000/v1/user/register,在表单中填写邮箱、用户名、密码,而后将结果传递给api便可

登陆

使用帐号(邮箱)密码登陆

/**
 * 邮箱登陆:用户不存在会提示帐户不存在,用户存在则验证用户信息,登陆成功以后返回 token
 * @param {帐号} account 
 * @param {密码} secret 
 */
async function emailLogin(account, secret) {
    const user = await User.verifyEmailPassword(account, secret)
    return token = generateToken(user.id, Auth.USER)
}
复制代码

微信小程序使用token登陆app/service包下建立wx.js:获取登陆的token

class WXManager {
    /**
     * 小程序登陆
     * @param {小程序中传递的 code} code 
     * @param {小程序中公开的用户信息} userInfo
     */
    static async codeToToken(code, userInfo) {
        const url = util.format(global.config.wx.loginUrl,
            global.config.wx.appId,
            global.config.wx.appSecret,
            code)

        // 调用微信提供的接口    
        const result = await axios.get(url)
        if (result.status !== 200) {
            throw new global.errs.AuthFailed('openid获取失败')
        }
        // 微信中最终判断是经过 errcode 判断
        const errcode = result.data.errcode
        const errmsg = result.data.errmsg
        if (errcode){
            throw new global.errs.AuthFailed('openid获取失败:'+errmsg)
        }
        // openid
        // 档案 user uid openid 长
        // openid 
        // 用户是否存在,例如 token 过时的状况
        let user = await User.getUserByOpenid(result.data.openid)
        if(!user){
            user = await User.registerByOpenid(result.data.openid, userInfo)
        }
        return generateToken(user.id, Auth.USER)
    }
}

module.exports = {
    WXManager
}
复制代码

(1)帐号密码登陆调用:http://192.168.*.***:3000/v1/token,在表单中填写邮箱、密码,而后将结果传递给api便可;(2)小程序登陆调用:先进行小程序登陆,而后调用http://192.168.*.***:3000/v1/token,将小程序提供的code发送给api便可

无论是帐号密码登陆仍是微信小程序登陆,都返回了token主要是用于API 鉴权、登陆状态保持core包下建立util.js:生成token的工具类

/**
 * 使用 JWT 生成 token
 * @param {用户 id} uid 
 * @param {用户权限} scope 
 */
const generateToken = function (uid, scope) {
    const secretKey = global.config.security.secretKey
    const expiresIn = global.config.security.expiresIn
    
    // 在 token 中写入 uid、scope 数据
    const token = jwt.sign({
        uid,
        scope
    }, secretKey, {
        expiresIn: expiresIn
    })
    return token
}

module.exports = {
    generateToken,
}
复制代码

关于JWT的介绍能够查看我以前写的文章:全栈项目|小书架|服务器开发-JWT 详解,或者这篇文章:JWT 超详细分析

修改信息

用户信息的修改须要调用update 信息便可,若是是修改密码还须要将本地的登陆状态清除,从新登陆。

具体的修改信息,后续接口实现了再补上。

退出登陆

这篇文章介绍的不错:How to log out when using JWT

文中描述的主要方式有下面几种:

  • 为令牌设置合理的过时时间
  • 退出登陆后,客户端删除 token
  • 数据库存储再也不有效且在过时时间内的token
  • 在服务端使用 Redis-维基百科 维护黑名单列表

维护黑名单的方式有人认为会致使黑名单列表过长,这里能够经过判断token是否过时,过时则自动删除。

本项目只是从客户端删除token的方式实现退出登陆。

其余的处理方式参考:


咨询请加微信:轻撩便可。

在这里插入图片描述
相关文章
相关标签/搜索