01.前语

我们好,我是Baird~

之前做了许多项目,基本上每个项目都要完成一遍用户的登陆认证,功率极低,还简单遗漏一些场景。网上有许多登陆认证的例子,但都是很根底的共享,要能做为商业运用,还需求自己进一步改造打磨。

考虑到登陆模块的通用性,我打算做一个可复用的登陆认证模块,方便后续项目快速开发,防止重复造轮子

适合同学: 微服务开发、Go、go-zero、正在开发登陆模块的同学、感兴趣的朋友

02.用户中心规划

登陆认证模块,主要是用户行为,咱们这儿一致将模块命名为用户中心

2.1. 挑选开发语言和框架

考虑到可复用性,咱们选用微服务开发的方法,独自打造用户中心, 而微服务这款go-zero现在是不二之选,故⬇️
开发语言:Go
框架: go-zero
环境搭建请参阅官方文档

2.3. 需求规划

用户中心的需求主要有以下几个方面:(这儿考虑通用需求)

  • 用户注册
  • 登陆认证
    分为三种:
    1.手机号+暗码登陆
    2.手机短信验证登陆
    3.第三方授权登陆(现在只考虑微信小程序授权)
  • 找回/修正暗码
  • 修正用户信息
  • 计算在线用户

2.2. 数据库规划

数据库规划,考虑规划两张表,一张是用户表,一张是用户授权表
用户表:保存用户信息
用户授权表:保存用户登陆类型和登陆信息,如微信登陆,手机认证登陆

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `delete_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `del_state` tinyint NOT NULL DEFAULT '0',
  `version` bigint NOT NULL DEFAULT '0' COMMENT '版别号',
  `mobile` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `sex` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别 0:男 1:女',
  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
-- ----------------------------
-- Table structure for user_auth
-- ----------------------------
DROP TABLE IF EXISTS `user_auth`;
CREATE TABLE `user_auth` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `delete_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `del_state` tinyint NOT NULL DEFAULT '0',
  `version` bigint NOT NULL DEFAULT '0' COMMENT '版别号',
  `user_id` bigint NOT NULL DEFAULT '0',
  `auth_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '渠道唯一id',
  `auth_type` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '渠道类型',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_type_key` (`auth_type`,`auth_key`) USING BTREE,
  UNIQUE KEY `idx_userId_key` (`user_id`,`auth_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户授权表';

2.3.用户中心整体架构

拒绝重复造轮子,gozero登陆认证模块分享

现在用户中心总共分为四部分
1.API模块: 用户中心API
2.RPC模块: 用户中心 和 短信模块
3.服务发现模块: 选用etcd,用于RPC模块的注册和API发现RPC模块
4.数据库: 选用mysql 和 redis

2.4.用户中心处理流程

拒绝重复造轮子,gozero登陆认证模块分享

整体流程如上图所示

  1. 发动根底服务,mysql redis etcd
  2. RPC模块发动,注册到etcd
  3. API模块发动
  4. API请求->API模块->服务发现->RPC请求到RPC模块
  5. 短信RPC模块需求对接到短信云服务,如腾讯云短信服务

2.5 API和Proto规划

syntax = "v1"
info(
	title: "用户中心服务"
	desc: "用户中心服务"
	author: "GoGeekBaird"
	email: "gogeek2022@163.com"
	version: "v1"
)
import (
	"user/user.api"
)
//============================> usercenter v1 <============================
//no need login
@server(
	prefix: usercenter/v1
	group: usercenter
)
service usercenter {
	@doc(
		summary: "register"
	)
	@handler register
	post /user/register (RegisterReq) returns (RegisterResp)
	@doc(
		summary: "login"
	)
	@handler login
	post /user/login (LoginReq) returns (LoginResp)
	@doc(
		summary: "wechat mini auth"
	)
	@handler wxMiniAuth
	post /user/wxmini-auth (WXMiniAuthReq) returns (WXMiniAuthResp)
	@doc(
		summary: "get message code"
	)
	@handler messagecode
	post /user/messagecode (MsgCodeReq)
	@doc(
		summary: "get captcha code"
	)
	@handler getCaptcha
	get /user/get-captcha returns(GetCaptchaResp)
	@doc(
		summary: "verfy captcha code"
	)
	@handler verfyCaptcha
	post /user/verfy-captcha(VerfyCaptchaReq) returns(VerfyCaptchaResp)
	@doc(
		summary: "change password"
	)
	@handler changePwd
	post /user/change-pwd(ChangePwdReq)
}
//need login
@server(
	prefix: usercenter/v1
	group: usercenter
	jwt: JwtAuth
	middleware: OnlineStatus // 路由中间件声明
)
service usercenter {
	@doc(
		summary: "get user info"
	)
	@handler detail
	get /user/detail returns (UserInfoResp)
	@doc(
		summary: "update user"
	)
	@handler updateUser
	post /user/update-user(UpdateUserReq)
	@doc(
		summary: "update user"
	)
	@handler deleteUser
	post /user/delete-user(DeleteUserReq)
	@doc(
		summary: "get online user list"
	)
	@handler getOnlineUser
	get /user/online-user returns(GetOnlineUserResp)
}

API规划选用 gozero API语法,运用goctl能够自动生成代码
Proto规划,同样运用goctl能够自动生成代码 (proto文件太长就不贴啦,能够检查项目)

goctl 选用1.4版别,模版代码见项目地址,能够自行替换
goctl 具体运用,请参阅gozero官方文档

03.具体介绍

好,大体的规划按照上述进行即可,接下来咱们分部分来对用户进行的功能开发做具体介绍。

3.1. 用户登陆

用户登陆,现在选用三种方法登陆:
1.手机号+暗码登陆
2.手机短信验证登陆
3.第三方授权登陆(现在只考虑微信小程序授权)

登陆成功认证方法同一选用JWT

JWT (JSON Web Token) 是现在最盛行的跨域认证解决方案,是一种根据 Token 的认证授权机制。 从 JWT 的全称能够看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。
JWT 本身包含了身份验证所需求的一切信息,因此,咱们的服务器不需求存储 Session 信息。这显然添加了系统的可用性和伸缩性,大大减轻了服务端的压力。
能够看出,JWT 更符合规划 RESTful API 时的「Stateless(无状况)」原则
并且, 运用 JWT 认证能够有效防止 CSRF 进犯,因为 JWT 一般是存在在 localStorage 中,运用 JWT 进行身份验证的过程中是不会涉及到 Cookie 的。

第一种:手机号+暗码登陆
这种方法比较简单,是咱们最常见的登陆方法。这种方法需求用户先注册,生成暗码后再运用。 经过手机号找到数据库中暗码的HASH和输入的暗码HASH比较持平即可认证经过。 经过后回来生成JWT。
这儿需求注意一点便是,为防止用户爆炸输入,即重试屡次用户名和暗码。能够添加图形校验码验证。
图形校验码生成接口如下:
1.getCaptcha
2.verfyCaptcha
图形校验库选用: github.com/wenlng/go-captcha

第二种:手机号+短信验证码方法
这种方法也是咱们比较常用的方法: 需求用短信云服务器,这儿选用的是腾讯云,需求可自己注册,个人有100条免费短信。 经过腾讯云的SDK文档开发接口。
短信验证码用redis缓存,记住校验完删除redis缓存即可。

第三种:第三方授权登陆。
常见的有微信授权登陆、付出宝授权登陆等。 这种方法按需进行开发,最近开发微信小程序比较多,所以挑选的是微信小程序授权接口。
能够参阅微信开发文档:wx.login

这儿微信开发,选用了比较热门的微信API库:github.com/silenceper/wechat/v2
其他登陆方法,我们能够按需自行开发。

除上述三种还有其他的登陆方法,如APP获取手机号,这种方法我接触不多,有待学习补充。

3.2 JWT处理流程

上一节介绍了JWT,JWT是无状况的和咱们之前用的session、cookie方法有所不同。怎样改写JWT,怎样判别JWT是否过期,是登陆认证要考虑清楚的,否则登陆的安全性就会受到影响。

本项目选用的 github.com/golang-jwt/jwt/v4 库生成和验证JWT

这儿我给出我处理JWT的流程思路供我们参阅,有可能考虑不全,也请我们指出。

1.首先回来给前端的数据有三个

  • Token String (Token字符串)
  • Token Expire (Token过期时刻)
  • Token RefreshAfter (Token重改写时刻点)

前端将数据存储到localStorage

2.前端登陆处理流程

  1. 没有Token –> 登陆
  2. 有Token,但已过期 –>登陆
  3. 有Token,没过期,但超过了RefreshAfter时刻 -> 1⃣️ 免登陆 2⃣️获取新Token 3⃣️发送请求带上新Token
  4. 有Token,没过期,RefreshAfter时刻没到 -> 1⃣️免登陆,运用原来的Token
  5. 用户自动刊出,前端自动铲除本地的Token数据

3.例子:完成登陆后7天免登陆,过程中继续有操作无需从头登陆,7天无操作则强制登陆

  • Token XXX (Token字符串)
  • Token 4233600 (Token过期时刻)
  • Token 4233600/2 (Token重改写时刻点)

按照上述前端登陆处理流程,编写逻辑即可。

3.3 用户注册

针对手机号+暗码登陆方法,需求进行用户自注册。
用户注册逻辑比较简单,填写相应的用户信息即可。
如有需求能够添加短信验证码验证手机,经过即可注册成功。

3.4 找回/修正暗码

这也是必要的的手法,暗码忘记了,需求供给方法让用户能找回暗码。
这儿选用便是手机验证码验证,输入新暗码,保存成功即可。
这儿能够添加图形校验码验证,起必定防爆炸效果。

3.5 修正用户信息

这个比较简单的,供给接口答应用户修正头像和简介等基本信息。

3.6 计算在线用户

用户中心一个需求就有要计算在线的用户数量,以作为运营数据,优化业务和相关流程等等
所以这儿需求考虑三点

  1. 用户在线状况需求保存
  2. 用户在线状况需求改写或判别过期
  3. 获取用户在线数量

针对第一点,这儿可能有朋友现已发现了,这个和Session的功能很像,服务端保存 Session就能完成类似的功能。
是的,要完成记录用户在线状况,咱们就必须得保存一个用户唯一ID,作为用户在线记录。
所以,我选用用户数据库id,做为Key保存用户在线数据到redis。

针对第二点,咱们能够选用路由中间件的方法,使得每个用户登陆后的操作,去改写Key

package middleware
import (
	"github/community-online/app/usercenter/cmd/rpc/usercenter"
	"github/community-online/common/ctxdata"
	"net/http"
)
type OnlineStatusMiddleware struct {
	UsercenterRpc usercenter.Usercenter
}
func NewOnlineStatusMiddleware(uRpc usercenter.Usercenter) *OnlineStatusMiddleware {
	return &OnlineStatusMiddleware{UsercenterRpc: uRpc}
}
func (m *OnlineStatusMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// 改写用户在线状况
		userId := ctxdata.GetUidFromCtx(r.Context())
		m.UsercenterRpc.FreshUserOnlineStatus(r.Context(), &usercenter.FreshUserOnlineStatusReq{
			UserId: userId,
		})
		// Passthrough to next handler if need
		next(w, r)
	}
}

gozero路由中间件生成方法,能够参阅官方文档

第三点,就比较显着了。咱们只要把redis中相关的Key,query出来回来对运用户id列表即可。

以上便是整个用户中心具体介绍,文字较多,想具体了解的同学还需求结合代码过一遍。

04.项目地址

Github项目地址

老规矩,先star再看

代码有不当之处,欢迎PR

05.跋文

项目还有一些能够进一步完善的地方:
1.数据校验现在还没添加,添加数据校验有助提交安全性。

后边计划会再做一个关于通用付出模块,把微信和付出宝付出做一次一致,还是相同

防止重复造轮子!!!

欢迎我们点赞、谈论、转发,感谢支持~