这是一个基于 RPC 微服务的 token 管理项目,实现了对 token 的创建、过滤、刷新和查询的操作。协议定义和方法调用通过 thrift 构建,token 缓存在 redis 中,有效期在业务中定义。日志写入 kafka,由其他组件消费后写入 clickhouse 后再通过 superset 汇总展示。打点操作写入 prometheus 系统,由 grafana 汇总展示。开发语言为 golang,代码为原创分享给大家。
thrift 定义
// tames.thrift
namespace go tames_thrift
namespace py tames_thrift
namespace java com.glb.tames.thrift
const string __VERSION__ = "v0.0.1"
// token 常量
const i64 EXPIRATION = 7200
// token 异常
const string ERR_LOGIN_STATUS_EXPIRED = "登录状态过期"
const string ERR_TOKEN_VERIFICATION_FAILED = "令牌验证失败"
const string ERR_TOKEN_EXPIRATION_OR_VALIDATION_FAILURE = "令牌过期或验证错误"
enum TamesStatus {
FAILURE = -1
SUCCESS = 0
}
exception TamesError {
1: TamesStatus status // 错误编码
2: string message // 错误信息
}
struct Token {
1: required string uuid // 唯一标识
2: required i64 user_id // 用户 ID
3: required string user_name // 用户名称
4: required string user_addr // 用户地址
}
service TamesService {
bool filter_token(1: string token) throws (1: TamesError err);
string create_token(1: Token token) throws (1: TamesError err);
void refresh_token(1: Token token) throws (1: TamesError err);
string select_token(1: string token) throws (1: TamesError err);
}
数据结构
package macro
import "github.com/dgrijalva/jwt-go"
/**
* Author: gonglibin
* Description: 用户结构
*/
const (
UserKey string = "user_key"
DetailsUserID string = "user_id"
DetailsUserName string = "username"
LoginTokenKey string = "login_tokens:"
Secret string = "abcdefghijklmnopqrstuvwxyz"
)
type LoginUser struct {
jwt.StandardClaims
UUID string `json:"uuid"` // 唯一标识
UserID int64 `json:"uid"` // 用户 ID
UserName string `json:"user_name"` // 用户名称
UserAddr string `json:"user_addr"` // 用户地址
LoginTime int64 `json:"login_time"` // 登录时间
ExpireTime int64 `json:"expire_time"` // 过期时间
}
常量定义
package macro
const (
CtxTimer string = "ctx:timer"
CtxStart string = "ctx:start"
CtxUserID string = "ctx:userid"
CtxCToken string = "ctx:c:token"
CtxSToken string = "ctx:s:token"
)
接口定义
package api
import (
"context"
)
/**
* Author: gonglibin
* Description: API 接口定义
*/
type Api interface {
Post(ctx context.Context, log string)
Pre(ctx context.Context) context.Context
Run(ctx context.Context) (interface{}, error)
PreCustom(ctx context.Context, val interface{}) context.Context
}
创建 token
package impl
import (
"context"
"github.com/dgrijalva/jwt-go"
"github.com/prometheus/client_golang/prometheus"
"token/pkg/api"
"token/pkg/log"
"token/pkg/macro"
tJedis "token/pkg/utils/jedis"
tKafka "token/pkg/utils/kafka"
tPrometheus "token/pkg/utils/prometheus"
)
/**
* Author: gonglibin
* Description: CreateTokenAPIImpl
*/
type CreateTokenAPIImpl struct {
*APIImpl
}
var _ api.Api = (*CreateTokenAPIImpl)(nil)
var (
CreateTokenAPI api.Api
CreateTokenTimer prometheus.Gauge
CreateTokenRequest prometheus.Counter
CreateTokenResponse prometheus.Counter
)
func init() {
CreateTokenAPI = NewCreateTokenAPI()
CreateTokenTimer = tPrometheus.NewPrometheusGauge("create_token_timer", "Create Token 耗时")
CreateTokenRequest = tPrometheus.NewPrometheusCounter("create_token_request_counter", "Create Token Request 数量")
CreateTokenResponse = tPrometheus.NewPrometheusCounter("create_token_response_counter", "Create Token Response 数量")
tPrometheus.DefaultPrometheus.Collectors(
CreateTokenTimer,
CreateTokenRequest,
CreateTokenResponse,
)
}
func NewCreateTokenAPI() *CreateTokenAPIImpl {
return &CreateTokenAPIImpl{
APIImpl: &APIImpl{
jedis: tJedis.DefaultJedis,
producer: tKafka.DefaultProducer,
prometheus: tPrometheus.DefaultPrometheus,
},
}
}
func (a *CreateTokenAPIImpl) PreCustom(ctx context.Context, val interface{}) context.Context {
ctx = context.WithValue(ctx, macro.CtxTimer, CreateTokenTimer)
return context.WithValue(ctx, macro.CtxCToken, val)
}
func (a *CreateTokenAPIImpl) Run(ctx context.Context) (interface{}, error) {
tamesLog := log.TamesLog{}
loginUser := a.getLoginUserByToken(ctx)
tPrometheus.DefaultPrometheus.Inc(CreateTokenRequest)
defer tPrometheus.DefaultPrometheus.Inc(CreateTokenResponse)
tkn, err := a.getClaims(loginUser)
go a.Post(ctx, tamesLog.GetTamesLog(loginUser, "create_token"))
return tkn, err
}
func (a *CreateTokenAPIImpl) getClaims(loginUser *macro.LoginUser) (string, error) {
tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, loginUser)
return tkn.SignedString([]byte(macro.Secret))
}
过滤 token
package impl
import (
"context"
"errors"
"github.com/prometheus/client_golang/prometheus"
tThrift "tames/gen-go/tames_thrift"
"token/pkg/api"
"token/pkg/log"
"token/pkg/macro"
tJedis "token/pkg/utils/jedis"
tKafka "token/pkg/utils/kafka"
tPrometheus "token/pkg/utils/prometheus"
"time"
)
/**
* Author: gonglibin
* Description: FilterTokenAPIImpl
*/
type FilterTokenAPIImpl struct {
*APIImpl
}
var _ api.Api = (*FilterTokenAPIImpl)(nil)
var (
FilterTokenAPI api.Api
FilterTokenTimer prometheus.Gauge
FilterTokenRequest prometheus.Counter
FilterTokenResponse prometheus.Counter
)
func init() {
FilterTokenAPI = NewFilterTokenAPI()
FilterTokenTimer = tPrometheus.NewPrometheusGauge("filter_token_timer", "Filter Token 耗时")
FilterTokenRequest = tPrometheus.NewPrometheusCounter("filter_token_request_counter", "Filter Token Request 数量")
FilterTokenResponse = tPrometheus.NewPrometheusCounter("filter_token_response_counter", "Filter Token Response 数量")
tPrometheus.DefaultPrometheus.Collectors(
FilterTokenTimer,
FilterTokenRequest,
FilterTokenResponse,
)
}
func NewFilterTokenAPI() *FilterTokenAPIImpl {
return &FilterTokenAPIImpl{
APIImpl: &APIImpl{
jedis: tJedis.DefaultJedis,
producer: tKafka.DefaultProducer,
prometheus: tPrometheus.DefaultPrometheus,
},
}
}
func (a *FilterTokenAPIImpl) PreCustom(ctx context.Context, val interface{}) context.Context {
ctx = context.WithValue(ctx, macro.CtxTimer, FilterTokenTimer)
return context.WithValue(ctx, macro.CtxSToken, val)
}
func (a *FilterTokenAPIImpl) Run(ctx context.Context) (interface{}, error) {
tamesLog := log.TamesLog{}
loginUser, _ := a.getLoginUserByString(ctx)
tPrometheus.DefaultPrometheus.Inc(FilterTokenRequest)
defer tPrometheus.DefaultPrometheus.Inc(FilterTokenResponse)
if loginUser == nil {
return false, errors.New(tThrift.ERR_TOKEN_EXPIRATION_OR_VALIDATION_FAILURE)
}
if loginUser.ExpireTime < time.Now().Unix() {
return false, errors.New(tThrift.ERR_LOGIN_STATUS_EXPIRED)
}
if loginUser.UserID == 0 || loginUser.UserName == "" {
return false, errors.New(tThrift.ERR_TOKEN_VERIFICATION_FAILED)
}
go a.Post(ctx, tamesLog.GetTamesLog(loginUser, "filter_token"))
return true, nil
}
刷新 token
package impl
import (
"context"
"encoding/json"
"github.com/prometheus/client_golang/prometheus"
"strconv"
tThrift "tames/gen-go/tames_thrift"
"token/pkg/api"
"token/pkg/log"
"token/pkg/macro"
tJedis "token/pkg/utils/jedis"
tKafka "token/pkg/utils/kafka"
tPrometheus "token/pkg/utils/prometheus"
"time"
)
/**
* Author: gonglibin
* Description: RefreshTokenAPIImpl
*/
type RefreshTokenAPIImpl struct {
*APIImpl
}
var _ api.Api = (*RefreshTokenAPIImpl)(nil)
var (
RefreshTokenAPI api.Api
RefreshTokenTimer prometheus.Gauge
RefreshTokenRequest prometheus.Counter
RefreshTokenResponse prometheus.Counter
)
func init() {
RefreshTokenAPI = NewRefreshTokenAPI()
RefreshTokenTimer = tPrometheus.NewPrometheusGauge("refresh_token_timer", "Refresh Token 耗时")
RefreshTokenRequest = tPrometheus.NewPrometheusCounter("refresh_token_request_counter", "Refresh Token Request 数量")
RefreshTokenResponse = tPrometheus.NewPrometheusCounter("refresh_token_response_counter", "Refresh Token Response 数量")
tPrometheus.DefaultPrometheus.Collectors(
RefreshTokenTimer,
RefreshTokenRequest,
RefreshTokenResponse,
)
}
func NewRefreshTokenAPI() *RefreshTokenAPIImpl {
return &RefreshTokenAPIImpl{
APIImpl: &APIImpl{
jedis: tJedis.DefaultJedis,
producer: tKafka.DefaultProducer,
prometheus: tPrometheus.DefaultPrometheus,
},
}
}
func (a *RefreshTokenAPIImpl) PreCustom(ctx context.Context, val interface{}) context.Context {
ctx = context.WithValue(ctx, macro.CtxTimer, RefreshTokenTimer)
return context.WithValue(ctx, macro.CtxCToken, val)
}
func (a *RefreshTokenAPIImpl) Run(ctx context.Context) (interface{}, error) {
tamesLog := log.TamesLog{}
loginUser := a.getLoginUserByToken(ctx)
tPrometheus.DefaultPrometheus.Inc(RefreshTokenRequest)
defer tPrometheus.DefaultPrometheus.Inc(RefreshTokenResponse)
a.setToken(loginUser)
go a.Post(ctx, tamesLog.GetTamesLog(loginUser, "refresh_token"))
return nil, nil
}
func (a *RefreshTokenAPIImpl) setToken(loginUser *macro.LoginUser) {
val, _ := json.Marshal(loginUser)
key := macro.LoginTokenKey + strconv.FormatInt(loginUser.UserID, 10)
a.jedis.Client.Set(
key,
val,
time.Duration(tThrift.EXPIRATION)*time.Second,
)
}
查询 token
package impl
import (
"context"
"encoding/json"
"github.com/prometheus/client_golang/prometheus"
"token/pkg/api"
"token/pkg/log"
"token/pkg/macro"
tJedis "token/pkg/utils/jedis"
tKafka "token/pkg/utils/kafka"
tPrometheus "token/pkg/utils/prometheus"
)
/**
* Author: gonglibin
* Description: SelectTokenAPIImpl
*/
type SelectTokenAPIImpl struct {
*APIImpl
}
var _ api.Api = (*SelectTokenAPIImpl)(nil)
var (
SelectTokenAPI api.Api
SelectTokenTimer prometheus.Gauge
SelectTokenRequest prometheus.Counter
SelectTokenResponse prometheus.Counter
)
func init() {
SelectTokenAPI = NewSelectTokenAPI()
SelectTokenTimer = tPrometheus.NewPrometheusGauge("select_token_timer", "Select Token 耗时")
SelectTokenRequest = tPrometheus.NewPrometheusCounter("select_token_request_counter", "Select Token Request 数量")
SelectTokenResponse = tPrometheus.NewPrometheusCounter("select_token_response_counter", "Select Token Response 数量")
tPrometheus.DefaultPrometheus.Collectors(
SelectTokenTimer,
SelectTokenRequest,
SelectTokenResponse,
)
}
func NewSelectTokenAPI() *SelectTokenAPIImpl {
return &SelectTokenAPIImpl{
APIImpl: &APIImpl{
jedis: tJedis.DefaultJedis,
producer: tKafka.DefaultProducer,
prometheus: tPrometheus.DefaultPrometheus,
},
}
}
func (a *SelectTokenAPIImpl) PreCustom(ctx context.Context, val interface{}) context.Context {
ctx = context.WithValue(ctx, macro.CtxTimer, SelectTokenTimer)
return context.WithValue(ctx, macro.CtxUserID, val)
}
func (a *SelectTokenAPIImpl) Run(ctx context.Context) (interface{}, error) {
tamesLog := log.TamesLog{}
uid := ctx.Value(macro.CtxUserID).(string)
tPrometheus.DefaultPrometheus.Inc(SelectTokenRequest)
defer tPrometheus.DefaultPrometheus.Inc(SelectTokenResponse)
val := a.getToken(uid)
loginUser := ¯o.LoginUser{}
json.Unmarshal([]byte(val), loginUser)
go a.Post(ctx, tamesLog.GetTamesLog(loginUser, "select_token"))
return val, nil
}
func (a *SelectTokenAPIImpl) getToken(uid string) string {
return a.jedis.Client.Get(macro.LoginTokenKey + uid).Val()
}
server 定义
package service
import (
"context"
"fmt"
"github.com/getsentry/raven-go"
tThrift "tames/gen-go/tames_thrift"
"tames/pkg/api"
"tames/pkg/api/impl"
)
type TamesServiceImpl struct {
DefaultCreateToken api.Api
DefaultFilterToken api.Api
DefaultSelectToken api.Api
DefaultRefreshToken api.Api
}
var _ tThrift.TamesService = (*TamesServiceImpl)(nil)
func NewTamesService() *TamesServiceImpl {
return &TamesServiceImpl{
DefaultCreateToken: impl.CreateTokenAPI,
DefaultFilterToken: impl.FilterTokenAPI,
DefaultSelectToken: impl.SelectTokenAPI,
DefaultRefreshToken: impl.RefreshTokenAPI,
}
}
func (p *TamesServiceImpl) FilterToken(ctx context.Context, tkn string) (bool, error) {
defer func() {
if r := recover(); r != nil {
raven.CaptureError(fmt.Errorf("FilterToken panic: %v", r), nil)
}
}()
ctx = p.DefaultFilterToken.Pre(ctx)
ctx = p.DefaultFilterToken.PreCustom(ctx, tkn)
rst, err := p.DefaultFilterToken.Run(ctx)
return rst.(bool), err
}
func (p *TamesServiceImpl) CreateToken(ctx context.Context, tkn *tThrift.Token) (string, error) {
defer func() {
if r := recover(); r != nil {
raven.CaptureError(fmt.Errorf("CreateToken panic: %v", r), nil)
}
}()
ctx = p.DefaultCreateToken.Pre(ctx)
ctx = p.DefaultCreateToken.PreCustom(ctx, tkn)
rst, err := p.DefaultCreateToken.Run(ctx)
return rst.(string), err
}
func (p *TamesServiceImpl) RefreshToken(ctx context.Context, tkn *tThrift.Token) error {
defer func() {
if r := recover(); r != nil {
raven.CaptureError(fmt.Errorf("RefreshToken panic: %v", r), nil)
}
}()
ctx = p.DefaultRefreshToken.Pre(ctx)
ctx = p.DefaultRefreshToken.PreCustom(ctx, tkn)
p.DefaultRefreshToken.Run(ctx)
return nil
}
func (p *TamesServiceImpl) SelectToken(ctx context.Context, tkn string) (string, error) {
defer func() {
if r := recover(); r != nil {
raven.CaptureError(fmt.Errorf("RefreshToken panic: %v", r), nil)
}
}()
ctx = p.DefaultSelectToken.Pre(ctx)
ctx = p.DefaultSelectToken.PreCustom(ctx, tkn)
rst, err := p.DefaultSelectToken.Run(ctx)
return rst.(string), err
}
主程入口
package main
import (
"fmt"
"github.com/apache/thrift/lib/go/thrift"
"os"
tThrift "tames/gen-go/tames_thrift"
"tames/pkg/service"
)
func main() {
listen := "0.0.0.0:8202"
tamesService := service.NewTamesService()
serviceProcessor := tThrift.NewTamesServiceProcessor(tamesService)
serverSocket, err := thrift.NewTServerSocket(listen)
if err != nil {
fmt.Println("启动失败:", err)
os.Exit(1)
}
server := thrift.NewTSimpleServer2(serviceProcessor, serverSocket)
if err := server.Serve(); err != nil {
panic(err)
}
}
以上为核心功能代码,仅供参考。