diff --git a/api/app.json b/api/app.json index e7cfeb9..bd06ba9 100644 --- a/api/app.json +++ b/api/app.json @@ -92,34 +92,6 @@ } } }, - "/logout": { - "post": { - "description": "登出", - "tags": [ - "user" - ], - "summary": "登出", - "operationId": "Logout", - "parameters": [ - { - "name": "body", - "in": "body", - "schema": { - "type": "object", - "$ref": "#/definitions/IDReq" - } - } - ], - "responses": { - "200": { - "description": "BaseDataInfo", - "schema": { - "$ref": "#/definitions/BaseDataInfo" - } - } - } - } - }, "/register": { "post": { "description": "Register | 注册", @@ -206,11 +178,10 @@ }, "/user/info": { "post": { - "description": "@ Get UserInfo detail By Token | 获取用户信息", + "description": "Get UserInfo detail By Token | 获取用户信息", "tags": [ "user" ], - "summary": "@ Get UserInfo detail By Token | 获取用户信息", "operationId": "GetUserInfoByToken", "responses": { "200": { @@ -250,6 +221,24 @@ } } }, + "/user/logout": { + "post": { + "description": "Logout | 退出登录 不传参数,默认使用请求头的token", + "tags": [ + "user" + ], + "summary": "Logout | 退出登录 不传参数,默认使用请求头的token", + "operationId": "Logout", + "responses": { + "200": { + "description": "BaseMsgResp", + "schema": { + "$ref": "#/definitions/BaseMsgResp" + } + } + } + } + }, "/user/oauthAuthorize": { "post": { "description": "OauthAuthorize | 第三方登录接口", diff --git a/api/desc/app/user.api b/api/desc/app/user.api index a3cf331..04b9925 100644 --- a/api/desc/app/user.api +++ b/api/desc/app/user.api @@ -22,7 +22,6 @@ type ( CaptchaCode string `json:"captchaCode,optional"` // 过期时间 ExpireTime uint32 `json:"expireTime,optional"` - } RegisterReq { // 账户注册类型:手机或邮箱 @@ -31,89 +30,83 @@ type ( Value string `json:"value,optional"` // 昵称 NickName string `json:"nickName,optional"` - // 性别 - Sex uint8 `json:"sex,optional"` - //生日 - Birthday uint32 `json:"birthday,optional"` - // 密码加密后的值 - PasswordHash *string `json:"passwordHash,optional"` - //验证码 - Captcha string `json:"captcha,optional"` - // 验证码ID - CaptchaId string `json:"captchaId,optional"` - //VerificationType 类型 1 手机 2 邮箱 - VerificationType *uint32 `json:"verificationType,optional"` - // Gender male or female or other , 男,女,其他 - Gender *string `json:"gender,optional"` - // 注册来源 app,pc - RegisterSource *string `json:"registerSource,optional"` + // 性别 + Sex uint8 `json:"sex,optional"` + //生日 + Birthday uint32 `json:"birthday,optional"` + // 密码加密后的值 + PasswordHash *string `json:"passwordHash,optional"` + //验证码 + Captcha string `json:"captcha,optional"` + // 验证码ID + CaptchaId string `json:"captchaId,optional"` + //VerificationType 类型 1 手机 2 邮箱 + VerificationType *uint32 `json:"verificationType,optional"` + // Gender male or female or other , 男,女,其他 + Gender *string `json:"gender,optional"` + // 注册来源 app,pc + RegisterSource *string `json:"registerSource,optional"` } - - - RegisterResp{ - // token信息 - AuthToken *AuthToken `json:"authToken,optional"` - User *UserInfo `json:"userInfo,optional"` + RegisterResp { + // token信息 + AuthToken *AuthToken `json:"authToken,optional"` + User *UserInfo `json:"userInfo,optional"` } - - AuthToken{ - // access_token - AccessToken string `json:"accessToken,optional"` - // refresh_token - RefreshToken string `json:"refreshToken,optional"` - // token_type 类型 - TokenType string `json:"tokenType,optional"` - //access_token_expires - AccessTokenExpires int64 `json:"accessTokenExpires,optional"` - //refresh_token_expires - RefreshTokenExpires int64 `json:"refreshTokenExpires,optional"` + AuthToken { + // access_token + AccessToken string `json:"accessToken,optional"` + // refresh_token + RefreshToken string `json:"refreshToken,optional"` + // token_type 类型 + TokenType string `json:"tokenType,optional"` + //access_token_expires + AccessTokenExpires int64 `json:"accessTokenExpires,optional"` + //refresh_token_expires + RefreshTokenExpires int64 `json:"refreshTokenExpires,optional"` } - - UserInfo{ - BaseIDInfo - // 昵称 - NickName string `json:"nickName,optional"` - //生日 - Birthday *int64 `json:"birthday,optional"` - // 头像 - Avatar *string `json:"avatar,optional"` - // Gender male or female or other , 男,女,其他 - Gender *string `json:"gender,optional"` - // 账户注册类型: - // 1 手机 2 邮箱 - RegisterType *uint32 `json:"registerType,optional"` - // 手机号 - Mobile *string `json:"mobile,optional"` - // 用户名 - Username *string `json:"username,optional"` - //邮箱 - Email *string `json:"email,optional"` - } - - LoginReq{ - // UserName or Email or Mobile - UserName string `json:"userName,optional"` - // Password - Password string `json:"password"` - // ClientIP - ClientIP string `json:"clientIp"` - // 登录方式 - LoginType string `json:"loginType"` - // 登录系统 - LoginPlatform string `json:"loginPlatform"` - } - LoginResp{ - BaseMsgResp - AuthToken *AuthToken - UserInfo *UserInfo - } - UserInfoResp{ + UserInfo { BaseIDInfo - // UserInfo information | UserInfo信息数据 - Data UserInfo `json:"data"` + // 昵称 + NickName string `json:"nickName,optional"` + //生日 + Birthday *int64 `json:"birthday,optional"` + // 头像 + Avatar *string `json:"avatar,optional"` + // Gender male or female or other , 男,女,其他 + Gender *string `json:"gender,optional"` + // 账户注册类型: + // 1 手机 2 邮箱 + RegisterType *uint32 `json:"registerType,optional"` + // 手机号 + Mobile *string `json:"mobile,optional"` + // 用户名 + Username *string `json:"username,optional"` + //邮箱 + Email *string `json:"email,optional"` } - - UserListReq{ + LoginReq { + // UserName or Email or Mobile + UserName string `json:"userName,optional"` + // Password + Password string `json:"password"` + // ClientIP + ClientIP string `json:"clientIp"` + // 登录方式 + LoginType string `json:"loginType"` + // 登录系统 + LoginPlatform string `json:"loginPlatform"` + } + LoginResp { + BaseMsgResp + AuthToken *AuthToken + UserInfo *UserInfo + } + UserInfoResp { + BaseIDInfo + // UserInfo information | UserInfo信息数据 + Data UserInfo `json:"data"` + } + UserListReq { // Page PageInfo // UserName or Email or Mobile @@ -125,89 +118,88 @@ type ( // Status 1: normal 2: ban | 状态 1 正常 2 禁用 Status *string `json:"status,optional"` } - - // The response data of user list | User信息列表数据 + // The response data of user list | User信息列表数据 UserListResp { BaseDataInfo // The device list data | USer信息列表数据 Data UserListInfo `json:"data"` } - // The device list data | User信息列表数据 - UserListInfo { - BaseListInfo - // The device list data | User信息列表数据 - Data []UserInfo `json:"data"` - } - OauthAuthorizeReq { + UserListInfo { + BaseListInfo + // The device list data | User信息列表数据 + Data []UserInfo `json:"data"` + } + OauthAuthorizeReq {} + + + OauthAuthorizeResp { + BaseMsgResp + } + PassWordResetReq { + // UserName or Email or Mobile + UserName string `json:"userName,optional"` + // Password + Password string `json:"password"` + } + - } - OauthAuthorizeResp { - BaseMsgResp - } - PassWordResetReq { - // UserName or Email or Mobile - UserName string `json:"userName,optional"` - // Password - Password string `json:"password"` - } ) @server ( - group: user_public + group: user_public ) - service App { // GetVerifyCode | 获取验证码 @handler getVerifyCode post /get/verifyCode (VerifyCodeReq) returns (VerifyCodeResp) + // Register | 注册 @handler register - post /register (RegisterReq) returns (RegisterResp) - // Login | 登录 - @handler login - post /login (LoginReq) returns (LoginResp) -} + post /register (RegisterReq) returns (RegisterResp) + // Login | 登录 + @handler login + post /login (LoginReq) returns (LoginResp) +} @server ( group: user middleware: Authority ) - service App { - // Get userInfo detail by ID | 获取用户信息 + // Get userInfo detail by ID | 获取用户信息 @handler getUserInfoById post /user (IDReq) returns (UserInfo) // Get UserInfo detail By Token | 获取用户信息 @handler getUserInfoByToken - post /user/info () returns (UserInfo) - + post /user/info returns (UserInfo) + //List userInfo | 获取用户列表 @handler listUserInfo post /user/list (UserListReq) returns (UserListResp) //Update userInfo | 更新用户信息 @handler updateUserInfo - post /user/update (UserInfo) returns (BaseDataInfo) + post /user/update (UserInfo) returns (BaseMsgResp) - // Logout | 退出登录 + // Logout | 退出登录 不传参数,默认使用请求头的token @handler logout - post /user/logout (IDReq) returns (BaseDataInfo) + post /user/logout () returns (BaseMsgResp) - // PassWordReset | 修改密码 - @handler passWordReset - post /user/passWordReset (PassWordResetReq) returns (BaseDataInfo) + // PassWordReset | 修改密码 + @handler passWordReset + post /user/passWordReset (PassWordResetReq) returns (BaseDataInfo) - // CheckLogin | 检测登录状态 - @handler checkLogin - post /user/checkLogin (IDReq) returns (BaseDataInfo) + // CheckLogin | 检测登录状态 + @handler checkLogin + post /user/checkLogin (IDReq) returns (BaseDataInfo) - //OauthAuthorize | 第三方登录接口 - @handler oauthAuthorize - post /user/oauthAuthorize (OauthAuthorizeReq) returns (OauthAuthorizeResp) + //OauthAuthorize | 第三方登录接口 + @handler oauthAuthorize + post /user/oauthAuthorize (OauthAuthorizeReq) returns (OauthAuthorizeResp) } diff --git a/api/etc/app.yaml b/api/etc/app.yaml index df31f01..24cdcfe 100644 --- a/api/etc/app.yaml +++ b/api/etc/app.yaml @@ -33,4 +33,7 @@ AppRpc: Hosts: - 192.168.201.58:2379 # 共享etcd地址 Key: app.rpc - Enabled: true \ No newline at end of file + Enabled: true + NonBlock: true + Timeout: 3000 + RetryCount: 1 # 重试次数,如果为1,则最多调用2次(包括第一次) \ No newline at end of file diff --git a/api/internal/handler/user/get_user_info_by_token_handler.go b/api/internal/handler/user/get_user_info_by_token_handler.go index ebc8495..b87316c 100644 --- a/api/internal/handler/user/get_user_info_by_token_handler.go +++ b/api/internal/handler/user/get_user_info_by_token_handler.go @@ -11,9 +11,8 @@ import ( // swagger:route post /user/info user GetUserInfoByToken // -//@ Get UserInfo detail By Token | 获取用户信息 // -//@ Get UserInfo detail By Token | 获取用户信息 +// Get UserInfo detail By Token | 获取用户信息 // // Responses: // 200: UserInfo diff --git a/api/internal/handler/user/logout_handler.go b/api/internal/handler/user/logout_handler.go index a0966dd..cdbfb8a 100644 --- a/api/internal/handler/user/logout_handler.go +++ b/api/internal/handler/user/logout_handler.go @@ -7,34 +7,21 @@ import ( "mingyang-admin-app-api/internal/logic/user" "mingyang-admin-app-api/internal/svc" - "mingyang-admin-app-api/internal/types" ) -// swagger:route post /logout user Logout +// swagger:route post /user/logout user Logout // -//登出 +// Logout | 退出登录 不传参数,默认使用请求头的token // -//登出 -// -// Parameters: -// + name: body -// require: true -// in: body -// type: IDReq +// Logout | 退出登录 不传参数,默认使用请求头的token // // Responses: -// 200: BaseDataInfo +// 200: BaseMsgResp func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req types.IDReq - if err := httpx.Parse(r, &req, true); err != nil { - httpx.ErrorCtx(r.Context(), w, err) - return - } - l := user.NewLogoutLogic(r.Context(), svcCtx) - resp, err := l.Logout(&req) + resp, err := l.Logout() if err != nil { err = svcCtx.Trans.TransError(r.Context(), err) httpx.ErrorCtx(r.Context(), w, err) diff --git a/api/internal/logic/user/logout_logic.go b/api/internal/logic/user/logout_logic.go index 98d217f..957e6bd 100644 --- a/api/internal/logic/user/logout_logic.go +++ b/api/internal/logic/user/logout_logic.go @@ -1,10 +1,17 @@ package user import ( + "bytes" "context" - + "github.com/saas-mingyang/mingyang-admin-common/i18n" "mingyang-admin-app-api/internal/svc" "mingyang-admin-app-api/internal/types" + "mingyang-admin-app-rpc/types/app" + "net/http" + "runtime" + "strconv" + "strings" + "time" "github.com/zeromicro/go-zero/core/logx" ) @@ -23,8 +30,96 @@ func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogi } } -func (l *LogoutLogic) Logout(req *types.IDReq) (resp *types.BaseDataInfo, err error) { - // todo: add your logic here and delete this line +// Logout 在API层添加调用栈信息 +func (l *LogoutLogic) Logout() (resp *types.BaseMsgResp, err error) { + // 打印调用栈 + buf := make([]byte, 1024) + n := runtime.Stack(buf, false) + l.Logger.Infof("Logout call stack:\n%s", buf[:n]) - return + token := l.getTokenFromContext() + l.Infof("Starting Logout RPC call at: %v", time.Now()) + + // 记录goroutine ID + goID := getGoroutineID() + l.Logger.Infof("Goroutine ID: %d", goID) + + _, err = l.svcCtx.AppRpc.LogoutUser(l.ctx, &app.UserToken{ + AccessToken: token, + UserId: l.getUserIDFromContext(), + }) + + l.Logger.Infof("Finished Logout RPC call at: %v", time.Now()) + + if err != nil { + return nil, err + } + return &types.BaseMsgResp{Msg: l.svcCtx.Trans.Trans(l.ctx, i18n.Success)}, nil +} + +// 获取goroutine ID +func getGoroutineID() uint64 { + b := make([]byte, 64) + b = b[:runtime.Stack(b, false)] + b = bytes.TrimPrefix(b, []byte("goroutine ")) + b = b[:bytes.IndexByte(b, ' ')] + n, _ := strconv.ParseUint(string(b), 10, 64) + return n +} + +// 从上下文中获取 Token +func (l *LogoutLogic) getTokenFromContext() string { + // 尝试不同的 key 从上下文中获取 Token + keys := []string{"token", "access_token", "jwt_token", "auth_token"} + + for _, key := range keys { + if token, ok := l.ctx.Value(key).(string); ok && token != "" { + return l.cleanToken(token) + } + } + return "" +} + +// 清理 Token 字符串,移除 "Bearer " 前缀 +func (l *LogoutLogic) cleanToken(token string) string { + if token == "" { + return "" + } + token = strings.TrimSpace(token) + // 移除 "Bearer " 前缀 + if strings.HasPrefix(strings.ToLower(token), "bearer ") { + token = token[7:] + } + return token +} + +// 从 Authorization Header 中提取 Token +func (l *LogoutLogic) extractTokenFromAuthHeader(ctx context.Context) string { + // 尝试从请求上下文获取 Request 对象 + if req, ok := ctx.Value("http_request").(*http.Request); ok { + authHeader := req.Header.Get("Authorization") + if authHeader != "" { + return l.cleanToken(authHeader) + } + } + return "" +} + +// 从上下文中获取用户ID +func (l *LogoutLogic) getUserIDFromContext() uint64 { + // 在AuthMiddleware中,我们将用户ID放入了context + if userID, ok := l.ctx.Value("userId").(uint64); ok { + return userID + } + return 0 +} + +// 获取 Token 过期时间 +func (l *LogoutLogic) getTokenExpireTime(token string) (time.Time, error) { + // 使用 JWTManager 验证并获取 Claims + claims, err := l.svcCtx.AppRpc.AuthToken(l.ctx, &app.AuthReq{Token: token}) + if err != nil { + return time.Time{}, err + } + return claims.Claims.ExpiresAt.Timestamp.AsTime(), nil } diff --git a/api/internal/middleware/authority_middleware.go b/api/internal/middleware/authority_middleware.go index 17e3d6d..f07798b 100644 --- a/api/internal/middleware/authority_middleware.go +++ b/api/internal/middleware/authority_middleware.go @@ -7,6 +7,9 @@ import ( "fmt" "github.com/golang-jwt/jwt/v4" "github.com/redis/go-redis/v9" + "github.com/saas-mingyang/mingyang-admin-common/config" + jwt2 "github.com/saas-mingyang/mingyang-admin-common/utils/jwt" + "github.com/zeromicro/go-zero/rest/enum" "mingyang-admin-app-api/internal/types" "mingyang-admin-app-rpc/appclient" "mingyang-admin-app-rpc/types/app" @@ -16,12 +19,6 @@ import ( ) // 定义上下文键类型 -type contextKey string - -const ( - UserIDKey contextKey = "user_id" - UserInfoKey contextKey = "user_info" -) type AuthorityMiddleware struct { Rds redis.UniversalClient @@ -61,36 +58,21 @@ func (m *AuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { writeError(w, 401, "Authorization header is required") return } - - // 2. 验证 Bearer Token 格式 - if !strings.HasPrefix(authHeader, "Bearer ") { - writeError(w, 401, "Invalid authorization format, must be 'Bearer '") - return - } - - // 3. 提取 Token - tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer")) - if tokenString == "" { - writeError(w, 401, "Token cannot be empty") - return - } - - // 4. 检查 Redis 缓存(可选) + fromToken := jwt2.StripBearerPrefixFromToken(r.Header.Get("Authorization")) if m.Rds != nil { - cacheKey := fmt.Sprintf("token:%s", tokenString) + cacheKey := config.RedisTokenPrefix + fromToken + fmt.Printf("cacheKey: %s\n", cacheKey) cachedUserID, err := m.Rds.Get(r.Context(), cacheKey).Result() if err == nil && cachedUserID != "" { // 从缓存中获取到用户ID - ctx := context.WithValue(r.Context(), UserIDKey, cachedUserID) + ctx := context.WithValue(r.Context(), enum.UserIdRpcCtxKey, cachedUserID) r = r.WithContext(ctx) next(w, r) return } } - // 5. 调用 RPC 验证 Token - fmt.Printf("Validating token: %s\n", tokenString) - token, err := m.AppRpc.AuthToken(r.Context(), &app.AuthReq{Token: tokenString}) + token, err := m.AppRpc.AuthToken(r.Context(), &app.AuthReq{Token: fromToken}) if err != nil { fmt.Printf("Error validating token: %v\n", err) // 根据错误类型返回不同的错误信息 @@ -120,15 +102,30 @@ func (m *AuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { // 7. 缓存到 Redis(可选) if m.Rds != nil { - cacheKey := fmt.Sprintf("token:%s", tokenString) + cacheKey := fmt.Sprintf("token:%s", fromToken) // 设置缓存,过期时间30分钟 m.Rds.Set(r.Context(), cacheKey, id, 30*time.Minute) } - // 8. 设置到上下文 + // 创建新的上下文,包含 Token 和用户信息 ctx := r.Context() - ctx = context.WithValue(ctx, UserIDKey, id) - ctx = context.WithValue(ctx, UserInfoKey, token) + ctx = context.WithValue(ctx, "token", fromToken) + ctx = context.WithValue(ctx, "userId", token.UserId) + ctx = context.WithValue(ctx, "tokenClaims", token) + + // 获取客户端 IP + clientIP := getClientIP(r) + ctx = context.WithValue(ctx, "client_ip", clientIP) + + // 获取 User-Agent + userAgent := r.UserAgent() + ctx = context.WithValue(ctx, "user_agent", userAgent) + + // 将新上下文设置到请求中 + r = r.WithContext(ctx) + + // 调用下一个处理器 + next(w, r) // 9. 记录请求日志(可选) fmt.Printf("[%s] %s - UserID: %d - Duration: %v\n", @@ -136,9 +133,25 @@ func (m *AuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { r.URL.Path, id, time.Since(startTime)) - - // 10. 继续处理请求 - r = r.WithContext(ctx) - next(w, r) } } + +// 获取客户端 IP +func getClientIP(r *http.Request) string { + // 尝试从 X-Forwarded-For 获取 + if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" { + // 可能有多个 IP,取第一个 + ips := strings.Split(forwarded, ",") + if len(ips) > 0 { + return strings.TrimSpace(ips[0]) + } + } + + // 从 RemoteAddr 获取 + ip := r.RemoteAddr + if colonIndex := strings.LastIndex(ip, ":"); colonIndex != -1 { + ip = ip[:colonIndex] + } + + return ip +} diff --git a/rpc/app.proto b/rpc/app.proto index 6c4aad3..06880d9 100644 --- a/rpc/app.proto +++ b/rpc/app.proto @@ -95,6 +95,10 @@ message LoginResponse { AuthToken auth_token = 2; } +message LogoutUserRequest { + string access_token = 1; +} + // NumericDate 使用 timestamp 表示 JWT 中的时间字段 message NumericDate { google.protobuf.Timestamp timestamp = 1; @@ -191,6 +195,11 @@ message UserInfo { optional int64 updated_at = 24; } +message UserToken { + string access_token = 1; + uint64 user_id = 2; +} + message VerifyCodeReq { VerifyCodeType type = 1; AccountType account_type = 2; @@ -219,6 +228,9 @@ service App { // 分页查询用户信息 // group: user rpc ListUsers(PageUserRequest) returns (PageUserResponse); + // 用户退出登录 + // group: user + rpc LogoutUser(UserToken) returns (LogoutUserRequest); // group: base rpc InitDatabase(Empty) returns (BaseResp); } diff --git a/rpc/appclient/app.go b/rpc/appclient/app.go index 4f3e6e2..2f5f7f2 100644 --- a/rpc/appclient/app.go +++ b/rpc/appclient/app.go @@ -26,6 +26,7 @@ type ( IDsReq = app.IDsReq LoginRequest = app.LoginRequest LoginResponse = app.LoginResponse + LogoutUserRequest = app.LogoutUserRequest NumericDate = app.NumericDate PageInfoReq = app.PageInfoReq PageUserRequest = app.PageUserRequest @@ -36,6 +37,7 @@ type ( UUIDReq = app.UUIDReq UUIDsReq = app.UUIDsReq UserInfo = app.UserInfo + UserToken = app.UserToken VerifyCodeReq = app.VerifyCodeReq VerifyCodeResp = app.VerifyCodeResp @@ -50,6 +52,8 @@ type ( LoginUser(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) // 分页查询用户信息 ListUsers(ctx context.Context, in *PageUserRequest, opts ...grpc.CallOption) (*PageUserResponse, error) + // 用户退出登录 + LogoutUser(ctx context.Context, in *UserToken, opts ...grpc.CallOption) (*LogoutUserRequest, error) InitDatabase(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*BaseResp, error) } @@ -94,6 +98,12 @@ func (m *defaultApp) ListUsers(ctx context.Context, in *PageUserRequest, opts .. return client.ListUsers(ctx, in, opts...) } +// 用户退出登录 +func (m *defaultApp) LogoutUser(ctx context.Context, in *UserToken, opts ...grpc.CallOption) (*LogoutUserRequest, error) { + client := app.NewAppClient(m.cli.Conn()) + return client.LogoutUser(ctx, in, opts...) +} + func (m *defaultApp) InitDatabase(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*BaseResp, error) { client := app.NewAppClient(m.cli.Conn()) return client.InitDatabase(ctx, in, opts...) diff --git a/rpc/desc/app/user.proto b/rpc/desc/app/user.proto index 9b9da60..9a09e3a 100644 --- a/rpc/desc/app/user.proto +++ b/rpc/desc/app/user.proto @@ -84,8 +84,13 @@ message PageUserResponse { uint64 total = 1; repeated UserInfo data = 2; } - - +message UserToken { + string access_token = 1; + uint64 user_id = 2; +} +message LogoutUserRequest { + string access_token = 1; +} // App 服务定义 @@ -99,4 +104,7 @@ service App { // 分页查询用户信息 // group: user rpc ListUsers(PageUserRequest) returns (PageUserResponse); + // 用户退出登录 + // group: user + rpc LogoutUser(UserToken) returns (LogoutUserRequest); } \ No newline at end of file diff --git a/rpc/internal/jwt_manager/jwt_manager.go b/rpc/internal/jwt_manager/jwt_manager.go index b1c6041..29ba17d 100644 --- a/rpc/internal/jwt_manager/jwt_manager.go +++ b/rpc/internal/jwt_manager/jwt_manager.go @@ -1,111 +1,100 @@ package jwt_manager import ( + "context" + "crypto/md5" + "encoding/hex" "errors" "fmt" - "github.com/golang-jwt/jwt/v5" "mingyang-admin-app-rpc/internal/config" "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/redis/go-redis/v9" ) -type JWTManager struct { - accessTokenSecret []byte - refreshTokenSecret []byte - accessTokenExpiry time.Duration - refreshTokenExpiry time.Duration - issuer string -} +// TokenType 令牌类型 +type TokenType string +const ( + AccessToken TokenType = "access" + RefreshToken TokenType = "refresh" +) + +// JWTConfig JWT 配置 + +// Claims JWT Claims 结构体 type Claims struct { - UserID uint64 `json:"user_id"` - Type string `json:"type"` // "access" or "refresh" + UserID uint64 `json:"user_id"` + Type TokenType `json:"type"` jwt.RegisteredClaims } -func NewJWTManager(config *config.JWTConfig) *JWTManager { +// TokenPair 令牌对 +type TokenPair struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + AccessTokenExpiresAt time.Time `json:"access_token_expires_at"` + RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at"` + TokenType string `json:"token_type"` +} + +// JWTManager JWT 管理器 +type JWTManager struct { + accessSecret []byte + refreshSecret []byte + accessExpiry time.Duration + refreshExpiry time.Duration + issuer string + redis redis.UniversalClient + prefix string // Redis 键前缀 + ctx context.Context +} + +// NewJWTManager 创建新的 JWT 管理器 +func NewJWTManager(config *config.JWTConfig, redisClient redis.UniversalClient) *JWTManager { + if config.AccessTokenSecret == "" || config.RefreshTokenSecret == "" { + panic("JWT 配置错误") + } return &JWTManager{ - accessTokenSecret: []byte(config.AccessTokenSecret), - refreshTokenSecret: []byte(config.RefreshTokenSecret), - accessTokenExpiry: config.AccessTokenExpiry, - refreshTokenExpiry: config.RefreshTokenExpiry, - issuer: config.Issuer, + accessSecret: []byte(config.AccessTokenSecret), + refreshSecret: []byte(config.RefreshTokenSecret), + accessExpiry: config.AccessTokenExpiry, + refreshExpiry: config.RefreshTokenExpiry, + issuer: config.Issuer, + redis: redisClient, + prefix: "jwt:", + ctx: context.Background(), } } -// GenerateAccessToken 生成访问令牌 -func (m *JWTManager) GenerateAccessToken(userID uint64) (string, *Claims, error) { - return m.generateToken(userID, "access", m.accessTokenExpiry, m.accessTokenSecret) +// WithContext 设置上下文 +func (m *JWTManager) WithContext(ctx context.Context) *JWTManager { + m.ctx = ctx + return m } -// GenerateRefreshToken 生成刷新令牌 -func (m *JWTManager) GenerateRefreshToken(userID uint64) (string, *Claims, error) { - return m.generateToken(userID, "refresh", m.refreshTokenExpiry, m.refreshTokenSecret) -} +// ==================== Token 生成方法 ==================== -func (m *JWTManager) generateToken(userID uint64, tokenType string, expiry time.Duration, secret []byte) (string, *Claims, error) { - fmt.Printf("userID: %d, tokenType: %s, expiry: %s, secret: %s\n", userID, tokenType, expiry, secret) - claims := &Claims{ - UserID: userID, - Type: tokenType, - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)), - IssuedAt: jwt.NewNumericDate(time.Now()), - Issuer: m.issuer, - Subject: fmt.Sprint(userID), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, err := token.SignedString(secret) - if err != nil { - return "", nil, err - } - - return tokenString, claims, nil -} - -// VerifyAccessToken 验证访问令牌 -func (m *JWTManager) VerifyAccessToken(tokenString string) (*Claims, error) { - return m.verifyToken(tokenString, m.accessTokenSecret, "access") -} - -// VerifyRefreshToken 验证刷新令牌 -func (m *JWTManager) VerifyRefreshToken(tokenString string) (*Claims, error) { - return m.verifyToken(tokenString, m.refreshTokenSecret, "refresh") -} - -func (m *JWTManager) verifyToken(tokenString string, secret []byte, expectedType string) (*Claims, error) { - token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return secret, nil - }) - - if err != nil { - return nil, err - } - - if claims, ok := token.Claims.(*Claims); ok && token.Valid { - if claims.Type != expectedType { - return nil, errors.New("invalid token type") - } - return claims, nil - } - - return nil, errors.New("invalid token") -} - -// GenerateTokenPair 生成令牌对 +// GenerateTokenPair 生成访问令牌和刷新令牌对 func (m *JWTManager) GenerateTokenPair(userID uint64) (*TokenPair, error) { - accessToken, accessClaims, err := m.GenerateAccessToken(userID) + // 生成访问令牌 + accessToken, accessClaims, err := m.generateToken(userID, AccessToken, m.accessSecret, m.accessExpiry) + fmt.Printf("accessToken: %v\n", accessToken) if err != nil { - return nil, err + return nil, fmt.Errorf("生成访问令牌失败: %w", err) } - refreshToken, refreshClaims, err := m.GenerateRefreshToken(userID) + // 生成刷新令牌 + refreshToken, refreshClaims, err := m.generateToken(userID, RefreshToken, m.refreshSecret, m.refreshExpiry) if err != nil { - return nil, err + return nil, fmt.Errorf("生成刷新令牌失败: %w", err) + } + + // 存储令牌到 Redis(用于追踪和管理) + if m.redis != nil { + m.storeToken(userID, accessToken, accessClaims.ExpiresAt.Time) + m.storeToken(userID, refreshToken, refreshClaims.ExpiresAt.Time) } return &TokenPair{ @@ -117,10 +106,484 @@ func (m *JWTManager) GenerateTokenPair(userID uint64) (*TokenPair, error) { }, nil } -type TokenPair struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - AccessTokenExpiresAt time.Time `json:"access_token_expires_at"` - RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at"` - TokenType string `json:"token_type"` +// GenerateAccessToken 生成访问令牌 +func (m *JWTManager) GenerateAccessToken(userID uint64) (string, *Claims, error) { + token, claims, err := m.generateToken(userID, AccessToken, m.accessSecret, m.accessExpiry) + if err != nil { + return "", nil, err + } + + // 存储到 Redis + if m.redis != nil { + m.storeToken(userID, token, claims.ExpiresAt.Time) + } + + return token, claims, nil +} + +// GenerateRefreshToken 生成刷新令牌 +func (m *JWTManager) GenerateRefreshToken(userID uint64) (string, *Claims, error) { + token, claims, err := m.generateToken(userID, RefreshToken, m.refreshSecret, m.refreshExpiry) + if err != nil { + return "", nil, err + } + + // 存储到 Redis + if m.redis != nil { + m.storeToken(userID, token, claims.ExpiresAt.Time) + } + + return token, claims, nil +} + +// generateToken 内部方法:生成令牌 +func (m *JWTManager) generateToken(userID uint64, tokenType TokenType, secret []byte, expiry time.Duration) (string, *Claims, error) { + now := time.Now() + expireAt := now.Add(expiry) + + claims := &Claims{ + UserID: userID, + Type: tokenType, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expireAt), + IssuedAt: jwt.NewNumericDate(now), + Issuer: m.issuer, + Subject: fmt.Sprintf("%d", userID), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString(secret) + if err != nil { + return "", nil, fmt.Errorf("签名令牌失败: %w", err) + } + + return tokenString, claims, nil +} + +// ==================== Token 验证方法 ==================== + +// VerifyAccessToken 验证访问令牌(包含黑名单检查) +func (m *JWTManager) VerifyAccessToken(tokenString string) (*Claims, error) { + return m.verifyToken(tokenString, AccessToken, m.accessSecret) +} + +// VerifyRefreshToken 验证刷新令牌(包含黑名单检查) +func (m *JWTManager) VerifyRefreshToken(tokenString string) (*Claims, error) { + return m.verifyToken(tokenString, RefreshToken, m.refreshSecret) +} + +// verifyToken 内部方法:验证令牌 +func (m *JWTManager) verifyToken(tokenString string, expectedType TokenType, secret []byte) (*Claims, error) { + // 1. 检查令牌是否在黑名单中 + if m.redis != nil { + blacklisted, err := m.isTokenBlacklisted(tokenString, expectedType) + if err != nil { + return nil, fmt.Errorf("检查黑名单失败: %w", err) + } + if blacklisted { + return nil, errors.New("令牌已失效,请重新登录") + } + } + + // 2. 验证 JWT + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("不支持的签名方法: %v", token.Header["alg"]) + } + return secret, nil + }) + + if err != nil { + return nil, fmt.Errorf("解析令牌失败: %w", err) + } + + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + // 3. 检查令牌类型 + if claims.Type != expectedType { + return nil, errors.New("令牌类型不匹配") + } + + // 4. 检查令牌是否过期 + if claims.ExpiresAt.Time.Before(time.Now()) { + return nil, errors.New("令牌已过期") + } + + return claims, nil + } + + return nil, errors.New("无效的令牌") +} + +// ==================== 黑名单管理 ==================== + +// BlacklistToken 将令牌加入黑名单 +func (m *JWTManager) BlacklistToken(tokenString string) error { + if m.redis == nil { + return errors.New("redis 客户端未初始化") + } + + // 尝试解析令牌 + claims, err := m.parseTokenWithoutBlacklistCheck(tokenString) + if err != nil { + return fmt.Errorf("无法识别令牌: %w", err) + } + + return m.addToBlacklist(tokenString, claims.Type, claims.ExpiresAt.Time) +} + +// BlacklistAccessToken 将访问令牌加入黑名单 +func (m *JWTManager) BlacklistAccessToken(tokenString string) error { + if m.redis == nil { + return errors.New("redis 客户端未初始化") + } + + claims, err := m.VerifyAccessToken(tokenString) + if err != nil { + return fmt.Errorf("无效的访问令牌: %w", err) + } + + return m.addToBlacklist(tokenString, AccessToken, claims.ExpiresAt.Time) +} + +// BlacklistRefreshToken 将刷新令牌加入黑名单 +func (m *JWTManager) BlacklistRefreshToken(tokenString string) error { + if m.redis == nil { + return errors.New("redis 客户端未初始化") + } + + claims, err := m.VerifyRefreshToken(tokenString) + if err != nil { + return fmt.Errorf("无效的刷新令牌: %w", err) + } + + return m.addToBlacklist(tokenString, RefreshToken, claims.ExpiresAt.Time) +} + +// addToBlacklist 内部方法:将令牌加入黑名单 +func (m *JWTManager) addToBlacklist(tokenString string, tokenType TokenType, expireTime time.Time) error { + remaining := time.Until(expireTime) + if remaining <= 0 { + // 令牌已过期,不需要加入黑名单 + return nil + } + + tokenHash := m.hashToken(tokenString) + blacklistKey := m.getBlacklistKey(tokenHash, tokenType) + + // 设置黑名单,过期时间等于令牌剩余有效期 + err := m.redis.Set(m.ctx, blacklistKey, "1", remaining).Err() + if err != nil { + return fmt.Errorf("设置黑名单失败: %w", err) + } + + // 记录用户与令牌的关联(用于按用户清理) + if claims, err := m.parseTokenWithoutBlacklistCheck(tokenString); err == nil { + userTokenKey := m.getUserTokenKey(claims.UserID, tokenHash) + m.redis.Set(m.ctx, userTokenKey, tokenString, remaining) + } + + return nil +} + +// IsTokenBlacklisted 检查令牌是否在黑名单中 +func (m *JWTManager) IsTokenBlacklisted(tokenString string) (bool, error) { + if m.redis == nil { + return false, errors.New("redis 客户端未初始化") + } + + // 检查所有类型的黑名单 + tokenHash := m.hashToken(tokenString) + + // 检查访问令牌黑名单 + accessBlacklistKey := m.getBlacklistKey(tokenHash, AccessToken) + exists, err := m.redis.Exists(m.ctx, accessBlacklistKey).Result() + if err != nil { + return false, fmt.Errorf("检查访问令牌黑名单失败: %w", err) + } + if exists > 0 { + return true, nil + } + + // 检查刷新令牌黑名单 + refreshBlacklistKey := m.getBlacklistKey(tokenHash, RefreshToken) + exists, err = m.redis.Exists(m.ctx, refreshBlacklistKey).Result() + if err != nil { + return false, fmt.Errorf("检查刷新令牌黑名单失败: %w", err) + } + + return exists > 0, nil +} + +// isTokenBlacklisted 内部方法:检查令牌是否在黑名单中 +func (m *JWTManager) isTokenBlacklisted(tokenString string, tokenType TokenType) (bool, error) { + if m.redis == nil { + return false, nil + } + + tokenHash := m.hashToken(tokenString) + blacklistKey := m.getBlacklistKey(tokenHash, tokenType) + + exists, err := m.redis.Exists(m.ctx, blacklistKey).Result() + if err != nil { + return false, err + } + + return exists > 0, nil +} + +// RemoveFromBlacklist 从黑名单中移除令牌 +func (m *JWTManager) RemoveFromBlacklist(tokenString string) error { + if m.redis == nil { + return errors.New("redis 客户端未初始化") + } + + tokenHash := m.hashToken(tokenString) + + // 尝试移除两种类型的黑名单 + accessBlacklistKey := m.getBlacklistKey(tokenHash, AccessToken) + refreshBlacklistKey := m.getBlacklistKey(tokenHash, RefreshToken) + + err := m.redis.Del(m.ctx, accessBlacklistKey, refreshBlacklistKey).Err() + return err +} + +// RevokeUserTokens 撤销用户所有令牌 +func (m *JWTManager) RevokeUserTokens(userID uint64) error { + if m.redis == nil { + return errors.New("redis 客户端未初始化") + } + + // 获取用户的所有令牌 + tokens, err := m.GetUserTokens(userID) + if err != nil { + return fmt.Errorf("获取用户令牌失败: %w", err) + } + + // 将所有令牌加入黑名单 + for _, token := range tokens { + m.BlacklistToken(token) + } + + // 清理用户令牌集合 + userTokenSetKey := m.getUserTokenSetKey(userID) + err = m.redis.Del(m.ctx, userTokenSetKey).Err() + if err != nil { + return fmt.Errorf("清理用户令牌集合失败: %w", err) + } + + return nil +} + +// ==================== 用户令牌管理 ==================== + +// storeToken 存储用户令牌 +func (m *JWTManager) storeToken(userID uint64, tokenString string, expireTime time.Time) { + if m.redis == nil { + return + } + + // 将令牌添加到用户的令牌集合 + userTokenSetKey := m.getUserTokenSetKey(userID) + err := m.redis.SAdd(m.ctx, userTokenSetKey, tokenString).Err() + if err != nil { + // 记录错误但继续执行 + fmt.Printf("存储用户令牌失败: %v\n", err) + return + } + + // 设置集合的过期时间(比令牌晚 1 小时) + expireAt := expireTime.Add(time.Hour) + err = m.redis.ExpireAt(m.ctx, userTokenSetKey, expireAt).Err() + if err != nil { + fmt.Printf("设置集合过期时间失败: %v\n", err) + } +} + +// GetUserTokens 获取用户的所有令牌 +func (m *JWTManager) GetUserTokens(userID uint64) ([]string, error) { + if m.redis == nil { + return nil, errors.New("redis 客户端未初始化") + } + + userTokenSetKey := m.getUserTokenSetKey(userID) + return m.redis.SMembers(m.ctx, userTokenSetKey).Result() +} + +// RemoveUserToken 移除用户的特定令牌 +func (m *JWTManager) RemoveUserToken(userID uint64, tokenString string) error { + if m.redis == nil { + return errors.New("redis 客户端未初始化") + } + + userTokenSetKey := m.getUserTokenSetKey(userID) + err := m.redis.SRem(m.ctx, userTokenSetKey, tokenString).Err() + return err +} + +// ==================== 令牌刷新 ==================== + +// RefreshTokens 使用刷新令牌获取新的令牌对 +func (m *JWTManager) RefreshTokens(refreshToken string) (*TokenPair, error) { + // 验证刷新令牌 + claims, err := m.VerifyRefreshToken(refreshToken) + if err != nil { + return nil, fmt.Errorf("刷新令牌验证失败: %w", err) + } + + // 将旧的刷新令牌加入黑名单 + if err := m.BlacklistRefreshToken(refreshToken); err != nil { + // 记录日志但继续执行 + fmt.Printf("警告:将旧刷新令牌加入黑名单失败: %v\n", err) + } + + // 生成新的令牌对 + return m.GenerateTokenPair(claims.UserID) +} + +// ==================== 辅助方法 ==================== + +// parseTokenWithoutBlacklistCheck 解析令牌但不检查黑名单 +func (m *JWTManager) parseTokenWithoutBlacklistCheck(tokenString string) (*Claims, error) { + // 尝试作为访问令牌解析 + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return m.accessSecret, nil + }) + + if err == nil && token.Valid { + if claims, ok := token.Claims.(*Claims); ok { + return claims, nil + } + } + + // 尝试作为刷新令牌解析 + token, err = jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return m.refreshSecret, nil + }) + + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + + return nil, errors.New("无法解析令牌") +} + +// hashToken 生成令牌的哈希值 +func (m *JWTManager) hashToken(token string) string { + hash := md5.Sum([]byte(token)) + return hex.EncodeToString(hash[:]) +} + +// getBlacklistKey 获取黑名单键名 +func (m *JWTManager) getBlacklistKey(tokenHash string, tokenType TokenType) string { + return fmt.Sprintf("%sblacklist:%s:%s", m.prefix, tokenType, tokenHash) +} + +// getUserTokenSetKey 获取用户令牌集合键名 +func (m *JWTManager) getUserTokenSetKey(userID uint64) string { + return fmt.Sprintf("%suser:tokens:%d", m.prefix, userID) +} + +// getUserTokenKey 获取用户令牌键名 +func (m *JWTManager) getUserTokenKey(userID uint64, tokenHash string) string { + return fmt.Sprintf("%suser:%d:token:%s", m.prefix, userID, tokenHash) +} + +// ==================== 工具方法 ==================== + +// GetTokenInfo 获取令牌信息 +func (m *JWTManager) GetTokenInfo(tokenString string) (map[string]interface{}, error) { + claims, err := m.parseTokenWithoutBlacklistCheck(tokenString) + if err != nil { + return nil, err + } + + info := map[string]interface{}{ + "user_id": claims.UserID, + "token_type": claims.Type, + "issued_at": claims.IssuedAt.Time.Format(time.RFC3339), + "expires_at": claims.ExpiresAt.Time.Format(time.RFC3339), + "issuer": claims.Issuer, + "subject": claims.Subject, + "remaining": time.Until(claims.ExpiresAt.Time).String(), + } + + // 检查是否在黑名单中 + if m.redis != nil { + blacklisted, _ := m.IsTokenBlacklisted(tokenString) + info["blacklisted"] = blacklisted + } + + return info, nil +} + +// ValidateAndExtract 验证令牌并提取用户ID +func (m *JWTManager) ValidateAndExtract(tokenString string) (uint64, error) { + claims, err := m.VerifyAccessToken(tokenString) + if err != nil { + return 0, err + } + return claims.UserID, nil +} + +// Cleanup 清理过期的令牌数据 +func (m *JWTManager) Cleanup() error { + if m.redis == nil { + return nil + } + + // Redis 会自动清理过期的键 + return nil +} + +// GetBlacklistStats 获取黑名单统计信息 +func (m *JWTManager) GetBlacklistStats() (map[string]interface{}, error) { + if m.redis == nil { + return nil, errors.New("redis 客户端未初始化") + } + + stats := make(map[string]interface{}) + + // 获取访问令牌黑名单数量 + accessPattern := fmt.Sprintf("%sblacklist:access:*", m.prefix) + accessKeys, err := m.redis.Keys(m.ctx, accessPattern).Result() + if err == nil { + stats["access_token_blacklist_count"] = len(accessKeys) + } else { + stats["access_token_blacklist_count"] = 0 + } + + // 获取刷新令牌黑名单数量 + refreshPattern := fmt.Sprintf("%sblacklist:refresh:*", m.prefix) + refreshKeys, err := m.redis.Keys(m.ctx, refreshPattern).Result() + if err == nil { + stats["refresh_token_blacklist_count"] = len(refreshKeys) + } else { + stats["refresh_token_blacklist_count"] = 0 + } + + return stats, nil +} + +// Ping 检查 Redis 连接 +func (m *JWTManager) Ping() error { + if m.redis == nil { + return errors.New("redis 客户端未初始化") + } + + _, err := m.redis.Ping(m.ctx).Result() + return err +} + +// Close 关闭 Redis 连接 +func (m *JWTManager) Close() error { + if m.redis == nil { + return nil + } + + return m.redis.Close() } diff --git a/rpc/internal/logic/auth/auth_token_logic.go b/rpc/internal/logic/auth/auth_token_logic.go index ffc1213..9a8cdf6 100644 --- a/rpc/internal/logic/auth/auth_token_logic.go +++ b/rpc/internal/logic/auth/auth_token_logic.go @@ -23,7 +23,7 @@ func NewAuthTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *TokenLo ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), - jwtManager: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf), + jwtManager: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf, svcCtx.Redis), } } @@ -36,7 +36,7 @@ func (l *TokenLogic) AuthToken(in *app.AuthReq) (*app.AuthInfoResp, error) { } return &app.AuthInfoResp{ UserId: token.UserID, - Type: token.Type, + Type: string(token.Type), Claims: &app.RegisteredClaims{ IssuedAt: &app.NumericDate{ Timestamp: ×tamppb.Timestamp{ diff --git a/rpc/internal/logic/user/login_user_logic.go b/rpc/internal/logic/user/login_user_logic.go index 27556bc..39956d4 100644 --- a/rpc/internal/logic/user/login_user_logic.go +++ b/rpc/internal/logic/user/login_user_logic.go @@ -34,7 +34,7 @@ func NewLoginUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginUs ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), - jwt: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf), + jwt: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf, nil), } } diff --git a/rpc/internal/logic/user/logout_user_logic.go b/rpc/internal/logic/user/logout_user_logic.go new file mode 100644 index 0000000..239313f --- /dev/null +++ b/rpc/internal/logic/user/logout_user_logic.go @@ -0,0 +1,50 @@ +package user + +import ( + "context" + "mingyang-admin-app-rpc/internal/jwt_manager" + "mingyang-admin-app-rpc/internal/util/dberrorhandler" + "time" + + "mingyang-admin-app-rpc/internal/svc" + "mingyang-admin-app-rpc/types/app" + + "github.com/zeromicro/go-zero/core/logx" +) + +type LogoutUserLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger + jwtManager *jwt_manager.JWTManager +} + +func NewLogoutUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutUserLogic { + return &LogoutUserLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + jwtManager: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf, svcCtx.Redis), + } +} + +// LogoutUser 用户退出登录 +func (l *LogoutUserLogic) LogoutUser(in *app.UserToken) (*app.LogoutUserRequest, error) { + // 记录开始时间 + start := time.Now() + l.Logger.Info("LogoutUser started token =", in.AccessToken) + accessToken := in.GetAccessToken() + _, err := l.jwtManager.VerifyAccessToken(accessToken) + if err != nil { + l.Logger.Error("Verify access token failed", "error", err, "time", time.Since(start)) + return nil, dberrorhandler.DefaultEntError(l.Logger, err, in) + } + err = l.jwtManager.BlacklistToken(accessToken) + if err != nil { + l.Logger.Error("Blacklist token failed", "error", err, "time", time.Since(start)) + return nil, dberrorhandler.DefaultEntError(l.Logger, err, in) + } + return &app.LogoutUserRequest{ + AccessToken: accessToken, + }, nil +} diff --git a/rpc/internal/logic/user/register_user_logic.go b/rpc/internal/logic/user/register_user_logic.go index 33ea012..871b6b9 100644 --- a/rpc/internal/logic/user/register_user_logic.go +++ b/rpc/internal/logic/user/register_user_logic.go @@ -30,7 +30,7 @@ func NewRegisterUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Regi return &RegisterUserLogic{ ctx: ctx, svcCtx: svcCtx, - jwtManager: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf), + jwtManager: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf, svcCtx.Redis), Logger: logx.WithContext(ctx), cacheRepo: cacherepo.NewCacheRepository(ctx, svcCtx), } diff --git a/rpc/internal/server/app_server.go b/rpc/internal/server/app_server.go index 4f1714c..a3231f2 100644 --- a/rpc/internal/server/app_server.go +++ b/rpc/internal/server/app_server.go @@ -55,6 +55,12 @@ func (s *AppServer) ListUsers(ctx context.Context, in *app.PageUserRequest) (*ap return l.ListUsers(in) } +// 用户退出登录 +func (s *AppServer) LogoutUser(ctx context.Context, in *app.UserToken) (*app.LogoutUserRequest, error) { + l := user.NewLogoutUserLogic(ctx, s.svcCtx) + return l.LogoutUser(in) +} + func (s *AppServer) InitDatabase(ctx context.Context, in *app.Empty) (*app.BaseResp, error) { l := base.NewInitDatabaseLogic(ctx, s.svcCtx) return l.InitDatabase(in) diff --git a/rpc/internal/service/user_service.go b/rpc/internal/service/user_service.go deleted file mode 100644 index 6d43c33..0000000 --- a/rpc/internal/service/user_service.go +++ /dev/null @@ -1 +0,0 @@ -package service diff --git a/rpc/types/app/app.pb.go b/rpc/types/app/app.pb.go index c9b889a..428598c 100644 --- a/rpc/types/app/app.pb.go +++ b/rpc/types/app/app.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.10 -// protoc v3.21.11 +// protoc v3.19.4 // source: app.proto package app @@ -821,6 +821,50 @@ func (x *LoginResponse) GetAuthToken() *AuthToken { return nil } +type LogoutUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogoutUserRequest) Reset() { + *x = LogoutUserRequest{} + mi := &file_app_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogoutUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutUserRequest) ProtoMessage() {} + +func (x *LogoutUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_app_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogoutUserRequest.ProtoReflect.Descriptor instead. +func (*LogoutUserRequest) Descriptor() ([]byte, []int) { + return file_app_proto_rawDescGZIP(), []int{13} +} + +func (x *LogoutUserRequest) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + // NumericDate 使用 timestamp 表示 JWT 中的时间字段 type NumericDate struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -831,7 +875,7 @@ type NumericDate struct { func (x *NumericDate) Reset() { *x = NumericDate{} - mi := &file_app_proto_msgTypes[13] + mi := &file_app_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -843,7 +887,7 @@ func (x *NumericDate) String() string { func (*NumericDate) ProtoMessage() {} func (x *NumericDate) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[13] + mi := &file_app_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -856,7 +900,7 @@ func (x *NumericDate) ProtoReflect() protoreflect.Message { // Deprecated: Use NumericDate.ProtoReflect.Descriptor instead. func (*NumericDate) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{13} + return file_app_proto_rawDescGZIP(), []int{14} } func (x *NumericDate) GetTimestamp() *timestamppb.Timestamp { @@ -876,7 +920,7 @@ type PageInfoReq struct { func (x *PageInfoReq) Reset() { *x = PageInfoReq{} - mi := &file_app_proto_msgTypes[14] + mi := &file_app_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -888,7 +932,7 @@ func (x *PageInfoReq) String() string { func (*PageInfoReq) ProtoMessage() {} func (x *PageInfoReq) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[14] + mi := &file_app_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -901,7 +945,7 @@ func (x *PageInfoReq) ProtoReflect() protoreflect.Message { // Deprecated: Use PageInfoReq.ProtoReflect.Descriptor instead. func (*PageInfoReq) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{14} + return file_app_proto_rawDescGZIP(), []int{15} } func (x *PageInfoReq) GetPage() uint64 { @@ -933,7 +977,7 @@ type PageUserRequest struct { func (x *PageUserRequest) Reset() { *x = PageUserRequest{} - mi := &file_app_proto_msgTypes[15] + mi := &file_app_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -945,7 +989,7 @@ func (x *PageUserRequest) String() string { func (*PageUserRequest) ProtoMessage() {} func (x *PageUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[15] + mi := &file_app_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -958,7 +1002,7 @@ func (x *PageUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PageUserRequest.ProtoReflect.Descriptor instead. func (*PageUserRequest) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{15} + return file_app_proto_rawDescGZIP(), []int{16} } func (x *PageUserRequest) GetPage() uint64 { @@ -1020,7 +1064,7 @@ type PageUserResponse struct { func (x *PageUserResponse) Reset() { *x = PageUserResponse{} - mi := &file_app_proto_msgTypes[16] + mi := &file_app_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1032,7 +1076,7 @@ func (x *PageUserResponse) String() string { func (*PageUserResponse) ProtoMessage() {} func (x *PageUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[16] + mi := &file_app_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1045,7 +1089,7 @@ func (x *PageUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PageUserResponse.ProtoReflect.Descriptor instead. func (*PageUserResponse) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{16} + return file_app_proto_rawDescGZIP(), []int{17} } func (x *PageUserResponse) GetTotal() uint64 { @@ -1080,7 +1124,7 @@ type RegisterUserRequest struct { func (x *RegisterUserRequest) Reset() { *x = RegisterUserRequest{} - mi := &file_app_proto_msgTypes[17] + mi := &file_app_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1092,7 +1136,7 @@ func (x *RegisterUserRequest) String() string { func (*RegisterUserRequest) ProtoMessage() {} func (x *RegisterUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[17] + mi := &file_app_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1105,7 +1149,7 @@ func (x *RegisterUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterUserRequest.ProtoReflect.Descriptor instead. func (*RegisterUserRequest) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{17} + return file_app_proto_rawDescGZIP(), []int{18} } func (x *RegisterUserRequest) GetEmail() string { @@ -1190,7 +1234,7 @@ type RegisterUserResponse struct { func (x *RegisterUserResponse) Reset() { *x = RegisterUserResponse{} - mi := &file_app_proto_msgTypes[18] + mi := &file_app_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1202,7 +1246,7 @@ func (x *RegisterUserResponse) String() string { func (*RegisterUserResponse) ProtoMessage() {} func (x *RegisterUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[18] + mi := &file_app_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1215,7 +1259,7 @@ func (x *RegisterUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterUserResponse.ProtoReflect.Descriptor instead. func (*RegisterUserResponse) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{18} + return file_app_proto_rawDescGZIP(), []int{19} } func (x *RegisterUserResponse) GetUser() *UserInfo { @@ -1269,7 +1313,7 @@ type RegisteredClaims struct { func (x *RegisteredClaims) Reset() { *x = RegisteredClaims{} - mi := &file_app_proto_msgTypes[19] + mi := &file_app_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1281,7 +1325,7 @@ func (x *RegisteredClaims) String() string { func (*RegisteredClaims) ProtoMessage() {} func (x *RegisteredClaims) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[19] + mi := &file_app_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1294,7 +1338,7 @@ func (x *RegisteredClaims) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisteredClaims.ProtoReflect.Descriptor instead. func (*RegisteredClaims) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{19} + return file_app_proto_rawDescGZIP(), []int{20} } func (x *RegisteredClaims) GetIssuer() string { @@ -1355,7 +1399,7 @@ type UUIDReq struct { func (x *UUIDReq) Reset() { *x = UUIDReq{} - mi := &file_app_proto_msgTypes[20] + mi := &file_app_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1367,7 +1411,7 @@ func (x *UUIDReq) String() string { func (*UUIDReq) ProtoMessage() {} func (x *UUIDReq) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[20] + mi := &file_app_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1380,7 +1424,7 @@ func (x *UUIDReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UUIDReq.ProtoReflect.Descriptor instead. func (*UUIDReq) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{20} + return file_app_proto_rawDescGZIP(), []int{21} } func (x *UUIDReq) GetId() string { @@ -1399,7 +1443,7 @@ type UUIDsReq struct { func (x *UUIDsReq) Reset() { *x = UUIDsReq{} - mi := &file_app_proto_msgTypes[21] + mi := &file_app_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1411,7 +1455,7 @@ func (x *UUIDsReq) String() string { func (*UUIDsReq) ProtoMessage() {} func (x *UUIDsReq) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[21] + mi := &file_app_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1424,7 +1468,7 @@ func (x *UUIDsReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UUIDsReq.ProtoReflect.Descriptor instead. func (*UUIDsReq) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{21} + return file_app_proto_rawDescGZIP(), []int{22} } func (x *UUIDsReq) GetIds() []string { @@ -1464,7 +1508,7 @@ type UserInfo struct { func (x *UserInfo) Reset() { *x = UserInfo{} - mi := &file_app_proto_msgTypes[22] + mi := &file_app_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1476,7 +1520,7 @@ func (x *UserInfo) String() string { func (*UserInfo) ProtoMessage() {} func (x *UserInfo) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[22] + mi := &file_app_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1489,7 +1533,7 @@ func (x *UserInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use UserInfo.ProtoReflect.Descriptor instead. func (*UserInfo) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{22} + return file_app_proto_rawDescGZIP(), []int{23} } func (x *UserInfo) GetId() uint64 { @@ -1646,6 +1690,58 @@ func (x *UserInfo) GetUpdatedAt() int64 { return 0 } +type UserToken struct { + state protoimpl.MessageState `protogen:"open.v1"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token"` + UserId uint64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UserToken) Reset() { + *x = UserToken{} + mi := &file_app_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UserToken) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserToken) ProtoMessage() {} + +func (x *UserToken) ProtoReflect() protoreflect.Message { + mi := &file_app_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserToken.ProtoReflect.Descriptor instead. +func (*UserToken) Descriptor() ([]byte, []int) { + return file_app_proto_rawDescGZIP(), []int{24} +} + +func (x *UserToken) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *UserToken) GetUserId() uint64 { + if x != nil { + return x.UserId + } + return 0 +} + type VerifyCodeReq struct { state protoimpl.MessageState `protogen:"open.v1"` Type VerifyCodeType `protobuf:"varint,1,opt,name=type,proto3,enum=app.VerifyCodeType" json:"type"` @@ -1657,7 +1753,7 @@ type VerifyCodeReq struct { func (x *VerifyCodeReq) Reset() { *x = VerifyCodeReq{} - mi := &file_app_proto_msgTypes[23] + mi := &file_app_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1669,7 +1765,7 @@ func (x *VerifyCodeReq) String() string { func (*VerifyCodeReq) ProtoMessage() {} func (x *VerifyCodeReq) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[23] + mi := &file_app_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1682,7 +1778,7 @@ func (x *VerifyCodeReq) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyCodeReq.ProtoReflect.Descriptor instead. func (*VerifyCodeReq) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{23} + return file_app_proto_rawDescGZIP(), []int{25} } func (x *VerifyCodeReq) GetType() VerifyCodeType { @@ -1716,7 +1812,7 @@ type VerifyCodeResp struct { func (x *VerifyCodeResp) Reset() { *x = VerifyCodeResp{} - mi := &file_app_proto_msgTypes[24] + mi := &file_app_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1728,7 +1824,7 @@ func (x *VerifyCodeResp) String() string { func (*VerifyCodeResp) ProtoMessage() {} func (x *VerifyCodeResp) ProtoReflect() protoreflect.Message { - mi := &file_app_proto_msgTypes[24] + mi := &file_app_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1741,7 +1837,7 @@ func (x *VerifyCodeResp) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyCodeResp.ProtoReflect.Descriptor instead. func (*VerifyCodeResp) Descriptor() ([]byte, []int) { - return file_app_proto_rawDescGZIP(), []int{24} + return file_app_proto_rawDescGZIP(), []int{26} } func (x *VerifyCodeResp) GetCaptchaCode() string { @@ -1809,7 +1905,9 @@ const file_app_proto_rawDesc = "" + "\rLoginResponse\x12!\n" + "\x04user\x18\x01 \x01(\v2\r.app.UserInfoR\x04user\x12-\n" + "\n" + - "auth_token\x18\x02 \x01(\v2\x0e.app.AuthTokenR\tauthToken\"G\n" + + "auth_token\x18\x02 \x01(\v2\x0e.app.AuthTokenR\tauthToken\"6\n" + + "\x11LogoutUserRequest\x12!\n" + + "\faccess_token\x18\x01 \x01(\tR\vaccessToken\"G\n" + "\vNumericDate\x128\n" + "\ttimestamp\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\">\n" + "\vPageInfoReq\x12\x12\n" + @@ -1923,7 +2021,10 @@ const file_app_proto_rawDesc = "" + "\x14_registration_sourceB\x0e\n" + "\f_birthday_atB\r\n" + "\v_created_atB\r\n" + - "\v_updated_at\"\x83\x01\n" + + "\v_updated_at\"G\n" + + "\tUserToken\x12!\n" + + "\faccess_token\x18\x01 \x01(\tR\vaccessToken\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x04R\x06userId\"\x83\x01\n" + "\rVerifyCodeReq\x12'\n" + "\x04type\x18\x01 \x01(\x0e2\x13.app.VerifyCodeTypeR\x04type\x123\n" + "\faccount_type\x18\x02 \x01(\x0e2\x10.app.AccountTypeR\vaccountType\x12\x14\n" + @@ -1947,13 +2048,15 @@ const file_app_proto_rawDesc = "" + "\fUPDATE_PHONE\x10\x05\x12\x10\n" + "\fUPDATE_EMAIL\x10\x06\x12\f\n" + "\bWITHDRAW\x10\a\x12\x17\n" + - "\x13CHANGE_PAY_PASSWORD\x10\b2\xcb\x02\n" + + "\x13CHANGE_PAY_PASSWORD\x10\b2\x81\x03\n" + "\x03App\x12,\n" + "\tAuthToken\x12\f.app.AuthReq\x1a\x11.app.AuthInfoResp\x128\n" + "\rGetVerifyCode\x12\x12.app.VerifyCodeReq\x1a\x13.app.VerifyCodeResp\x12C\n" + "\fRegisterUser\x12\x18.app.RegisterUserRequest\x1a\x19.app.RegisterUserResponse\x122\n" + "\tLoginUser\x12\x11.app.LoginRequest\x1a\x12.app.LoginResponse\x128\n" + - "\tListUsers\x12\x14.app.PageUserRequest\x1a\x15.app.PageUserResponse\x12)\n" + + "\tListUsers\x12\x14.app.PageUserRequest\x1a\x15.app.PageUserResponse\x124\n" + + "\n" + + "LogoutUser\x12\x0e.app.UserToken\x1a\x16.app.LogoutUserRequest\x12)\n" + "\fInitDatabase\x12\n" + ".app.Empty\x1a\r.app.BaseRespB\aZ\x05./appb\x06proto3" @@ -1970,7 +2073,7 @@ func file_app_proto_rawDescGZIP() []byte { } var file_app_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_app_proto_msgTypes = make([]protoimpl.MessageInfo, 25) +var file_app_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_app_proto_goTypes = []any{ (AccountType)(0), // 0: app.AccountType (VerifyCodeType)(0), // 1: app.VerifyCodeType @@ -1987,53 +2090,57 @@ var file_app_proto_goTypes = []any{ (*IDsReq)(nil), // 12: app.IDsReq (*LoginRequest)(nil), // 13: app.LoginRequest (*LoginResponse)(nil), // 14: app.LoginResponse - (*NumericDate)(nil), // 15: app.NumericDate - (*PageInfoReq)(nil), // 16: app.PageInfoReq - (*PageUserRequest)(nil), // 17: app.PageUserRequest - (*PageUserResponse)(nil), // 18: app.PageUserResponse - (*RegisterUserRequest)(nil), // 19: app.RegisterUserRequest - (*RegisterUserResponse)(nil), // 20: app.RegisterUserResponse - (*RegisteredClaims)(nil), // 21: app.RegisteredClaims - (*UUIDReq)(nil), // 22: app.UUIDReq - (*UUIDsReq)(nil), // 23: app.UUIDsReq - (*UserInfo)(nil), // 24: app.UserInfo - (*VerifyCodeReq)(nil), // 25: app.VerifyCodeReq - (*VerifyCodeResp)(nil), // 26: app.VerifyCodeResp - (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp - (*structpb.Struct)(nil), // 28: google.protobuf.Struct + (*LogoutUserRequest)(nil), // 15: app.LogoutUserRequest + (*NumericDate)(nil), // 16: app.NumericDate + (*PageInfoReq)(nil), // 17: app.PageInfoReq + (*PageUserRequest)(nil), // 18: app.PageUserRequest + (*PageUserResponse)(nil), // 19: app.PageUserResponse + (*RegisterUserRequest)(nil), // 20: app.RegisterUserRequest + (*RegisterUserResponse)(nil), // 21: app.RegisterUserResponse + (*RegisteredClaims)(nil), // 22: app.RegisteredClaims + (*UUIDReq)(nil), // 23: app.UUIDReq + (*UUIDsReq)(nil), // 24: app.UUIDsReq + (*UserInfo)(nil), // 25: app.UserInfo + (*UserToken)(nil), // 26: app.UserToken + (*VerifyCodeReq)(nil), // 27: app.VerifyCodeReq + (*VerifyCodeResp)(nil), // 28: app.VerifyCodeResp + (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 30: google.protobuf.Struct } var file_app_proto_depIdxs = []int32{ - 21, // 0: app.AuthInfoResp.claims:type_name -> app.RegisteredClaims - 27, // 1: app.AuthReq.time:type_name -> google.protobuf.Timestamp - 27, // 2: app.AuthToken.access_token_expires:type_name -> google.protobuf.Timestamp - 27, // 3: app.AuthToken.refresh_token_expires:type_name -> google.protobuf.Timestamp - 24, // 4: app.LoginResponse.user:type_name -> app.UserInfo + 22, // 0: app.AuthInfoResp.claims:type_name -> app.RegisteredClaims + 29, // 1: app.AuthReq.time:type_name -> google.protobuf.Timestamp + 29, // 2: app.AuthToken.access_token_expires:type_name -> google.protobuf.Timestamp + 29, // 3: app.AuthToken.refresh_token_expires:type_name -> google.protobuf.Timestamp + 25, // 4: app.LoginResponse.user:type_name -> app.UserInfo 4, // 5: app.LoginResponse.auth_token:type_name -> app.AuthToken - 27, // 6: app.NumericDate.timestamp:type_name -> google.protobuf.Timestamp - 24, // 7: app.PageUserResponse.data:type_name -> app.UserInfo - 24, // 8: app.RegisterUserResponse.user:type_name -> app.UserInfo + 29, // 6: app.NumericDate.timestamp:type_name -> google.protobuf.Timestamp + 25, // 7: app.PageUserResponse.data:type_name -> app.UserInfo + 25, // 8: app.RegisterUserResponse.user:type_name -> app.UserInfo 4, // 9: app.RegisterUserResponse.auth_token:type_name -> app.AuthToken 9, // 10: app.RegisteredClaims.audience:type_name -> app.ClaimStrings - 15, // 11: app.RegisteredClaims.expires_at:type_name -> app.NumericDate - 15, // 12: app.RegisteredClaims.not_before:type_name -> app.NumericDate - 15, // 13: app.RegisteredClaims.issued_at:type_name -> app.NumericDate - 28, // 14: app.UserInfo.metadata:type_name -> google.protobuf.Struct + 16, // 11: app.RegisteredClaims.expires_at:type_name -> app.NumericDate + 16, // 12: app.RegisteredClaims.not_before:type_name -> app.NumericDate + 16, // 13: app.RegisteredClaims.issued_at:type_name -> app.NumericDate + 30, // 14: app.UserInfo.metadata:type_name -> google.protobuf.Struct 1, // 15: app.VerifyCodeReq.type:type_name -> app.VerifyCodeType 0, // 16: app.VerifyCodeReq.account_type:type_name -> app.AccountType 3, // 17: app.App.AuthToken:input_type -> app.AuthReq - 25, // 18: app.App.GetVerifyCode:input_type -> app.VerifyCodeReq - 19, // 19: app.App.RegisterUser:input_type -> app.RegisterUserRequest + 27, // 18: app.App.GetVerifyCode:input_type -> app.VerifyCodeReq + 20, // 19: app.App.RegisterUser:input_type -> app.RegisterUserRequest 13, // 20: app.App.LoginUser:input_type -> app.LoginRequest - 17, // 21: app.App.ListUsers:input_type -> app.PageUserRequest - 10, // 22: app.App.InitDatabase:input_type -> app.Empty - 2, // 23: app.App.AuthToken:output_type -> app.AuthInfoResp - 26, // 24: app.App.GetVerifyCode:output_type -> app.VerifyCodeResp - 20, // 25: app.App.RegisterUser:output_type -> app.RegisterUserResponse - 14, // 26: app.App.LoginUser:output_type -> app.LoginResponse - 18, // 27: app.App.ListUsers:output_type -> app.PageUserResponse - 7, // 28: app.App.InitDatabase:output_type -> app.BaseResp - 23, // [23:29] is the sub-list for method output_type - 17, // [17:23] is the sub-list for method input_type + 18, // 21: app.App.ListUsers:input_type -> app.PageUserRequest + 26, // 22: app.App.LogoutUser:input_type -> app.UserToken + 10, // 23: app.App.InitDatabase:input_type -> app.Empty + 2, // 24: app.App.AuthToken:output_type -> app.AuthInfoResp + 28, // 25: app.App.GetVerifyCode:output_type -> app.VerifyCodeResp + 21, // 26: app.App.RegisterUser:output_type -> app.RegisterUserResponse + 14, // 27: app.App.LoginUser:output_type -> app.LoginResponse + 19, // 28: app.App.ListUsers:output_type -> app.PageUserResponse + 15, // 29: app.App.LogoutUser:output_type -> app.LogoutUserRequest + 7, // 30: app.App.InitDatabase:output_type -> app.BaseResp + 24, // [24:31] is the sub-list for method output_type + 17, // [17:24] is the sub-list for method input_type 17, // [17:17] is the sub-list for extension type_name 17, // [17:17] is the sub-list for extension extendee 0, // [0:17] is the sub-list for field type_name @@ -2045,16 +2152,16 @@ func file_app_proto_init() { return } file_app_proto_msgTypes[11].OneofWrappers = []any{} - file_app_proto_msgTypes[15].OneofWrappers = []any{} - file_app_proto_msgTypes[17].OneofWrappers = []any{} - file_app_proto_msgTypes[22].OneofWrappers = []any{} + file_app_proto_msgTypes[16].OneofWrappers = []any{} + file_app_proto_msgTypes[18].OneofWrappers = []any{} + file_app_proto_msgTypes[23].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_proto_rawDesc), len(file_app_proto_rawDesc)), NumEnums: 2, - NumMessages: 25, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, diff --git a/rpc/types/app/app_grpc.pb.go b/rpc/types/app/app_grpc.pb.go index bc1dd11..c889048 100644 --- a/rpc/types/app/app_grpc.pb.go +++ b/rpc/types/app/app_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.6.0 -// - protoc v3.21.11 +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.19.4 // source: app.proto package app @@ -24,6 +24,7 @@ const ( App_RegisterUser_FullMethodName = "/app.App/RegisterUser" App_LoginUser_FullMethodName = "/app.App/LoginUser" App_ListUsers_FullMethodName = "/app.App/ListUsers" + App_LogoutUser_FullMethodName = "/app.App/LogoutUser" App_InitDatabase_FullMethodName = "/app.App/InitDatabase" ) @@ -48,6 +49,9 @@ type AppClient interface { // 分页查询用户信息 // group: user ListUsers(ctx context.Context, in *PageUserRequest, opts ...grpc.CallOption) (*PageUserResponse, error) + // 用户退出登录 + // group: user + LogoutUser(ctx context.Context, in *UserToken, opts ...grpc.CallOption) (*LogoutUserRequest, error) // group: base InitDatabase(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*BaseResp, error) } @@ -110,6 +114,16 @@ func (c *appClient) ListUsers(ctx context.Context, in *PageUserRequest, opts ... return out, nil } +func (c *appClient) LogoutUser(ctx context.Context, in *UserToken, opts ...grpc.CallOption) (*LogoutUserRequest, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LogoutUserRequest) + err := c.cc.Invoke(ctx, App_LogoutUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *appClient) InitDatabase(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*BaseResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(BaseResp) @@ -141,6 +155,9 @@ type AppServer interface { // 分页查询用户信息 // group: user ListUsers(context.Context, *PageUserRequest) (*PageUserResponse, error) + // 用户退出登录 + // group: user + LogoutUser(context.Context, *UserToken) (*LogoutUserRequest, error) // group: base InitDatabase(context.Context, *Empty) (*BaseResp, error) mustEmbedUnimplementedAppServer() @@ -154,22 +171,25 @@ type AppServer interface { type UnimplementedAppServer struct{} func (UnimplementedAppServer) AuthToken(context.Context, *AuthReq) (*AuthInfoResp, error) { - return nil, status.Error(codes.Unimplemented, "method AuthToken not implemented") + return nil, status.Errorf(codes.Unimplemented, "method AuthToken not implemented") } func (UnimplementedAppServer) GetVerifyCode(context.Context, *VerifyCodeReq) (*VerifyCodeResp, error) { - return nil, status.Error(codes.Unimplemented, "method GetVerifyCode not implemented") + return nil, status.Errorf(codes.Unimplemented, "method GetVerifyCode not implemented") } func (UnimplementedAppServer) RegisterUser(context.Context, *RegisterUserRequest) (*RegisterUserResponse, error) { - return nil, status.Error(codes.Unimplemented, "method RegisterUser not implemented") + return nil, status.Errorf(codes.Unimplemented, "method RegisterUser not implemented") } func (UnimplementedAppServer) LoginUser(context.Context, *LoginRequest) (*LoginResponse, error) { - return nil, status.Error(codes.Unimplemented, "method LoginUser not implemented") + return nil, status.Errorf(codes.Unimplemented, "method LoginUser not implemented") } func (UnimplementedAppServer) ListUsers(context.Context, *PageUserRequest) (*PageUserResponse, error) { - return nil, status.Error(codes.Unimplemented, "method ListUsers not implemented") + return nil, status.Errorf(codes.Unimplemented, "method ListUsers not implemented") +} +func (UnimplementedAppServer) LogoutUser(context.Context, *UserToken) (*LogoutUserRequest, error) { + return nil, status.Errorf(codes.Unimplemented, "method LogoutUser not implemented") } func (UnimplementedAppServer) InitDatabase(context.Context, *Empty) (*BaseResp, error) { - return nil, status.Error(codes.Unimplemented, "method InitDatabase not implemented") + return nil, status.Errorf(codes.Unimplemented, "method InitDatabase not implemented") } func (UnimplementedAppServer) mustEmbedUnimplementedAppServer() {} func (UnimplementedAppServer) testEmbeddedByValue() {} @@ -182,7 +202,7 @@ type UnsafeAppServer interface { } func RegisterAppServer(s grpc.ServiceRegistrar, srv AppServer) { - // If the following call panics, it indicates UnimplementedAppServer was + // If the following call pancis, it indicates UnimplementedAppServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -282,6 +302,24 @@ func _App_ListUsers_Handler(srv interface{}, ctx context.Context, dec func(inter return interceptor(ctx, in, info, handler) } +func _App_LogoutUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UserToken) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AppServer).LogoutUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: App_LogoutUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AppServer).LogoutUser(ctx, req.(*UserToken)) + } + return interceptor(ctx, in, info, handler) +} + func _App_InitDatabase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { @@ -327,6 +365,10 @@ var App_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListUsers", Handler: _App_ListUsers_Handler, }, + { + MethodName: "LogoutUser", + Handler: _App_LogoutUser_Handler, + }, { MethodName: "InitDatabase", Handler: _App_InitDatabase_Handler,