zoukankan      html  css  js  c++  java
  • 全栈项目|小书架|服务器开发-用户模块设计(用户表设计,注册登录,退出登录)

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

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

    用户表设计

    一个系统中,用户模块是最基础也是最重要的。如果只考虑一种登录方式,那用户表可以用一张users表搞定一切。但是现实情况是登录方式有很多种,比如:账号密码登录微信、QQ、微博登录手机号、邮箱登录。而且一个用户可以使用账号密码登录之后再关联微信、QQ、微博,之后可以直接使用微信、QQ、微博登录;那么如何保证设计用户表将变得至关重要。

    参考:

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

    通过以上部分博文的介绍,我们可以将用户表设计为:用户基础表:user_base用户授权表:user_auth用户扩展表:user_extends

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

    用户授权表:id、uid、identity_type、identifier、certificate等

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

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

    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的方式实现退出登录。

    其他的处理方式参考:


    咨询请加微信:轻撩即可。
    在这里插入图片描述

  • 相关阅读:
    C# Lambda表达式
    C# LINQ用法
    C# XML介绍及基本操作
    C# 装箱和拆箱
    C# 堆与栈
    C#中ref和out的区别
    C#中16进制string字符串的转16byte互转
    C#中把一个Struct结构转换成Byte[]的方法
    SqlServer中查询操作记录的方法
    asp.net中后台获取Post参数(Json)最简单的一种方法。
  • 原文地址:https://www.cnblogs.com/gdragon/p/11924044.html
Copyright © 2011-2022 走看看