全棧項目|小書架|服務器開發-用戶模塊設計(用戶表設計,註冊登陸,退出登陸)

寫系列文章最容易出現的狀況是寫到一半,而後寫不下去了。一個因素是人變懶了;一個因素是這幾天在整理用戶模塊這部分的知識,發現有太多的內容要寫,而深刻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

以上的用戶表的設計能夠參考: 用戶系統設計與實現服務器

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的方式實現退出登陸。

其餘的處理方式參考:


諮詢請加微信:輕撩便可。
在這裏插入圖片描述

相關文章
相關標籤/搜索