Version: 0.9.66.dev.260504

后端:
1. 阶段 2 user/auth 服务边界落地,新增 `cmd/userauth` go-zero zrpc 服务、`services/userauth` 核心实现、gateway user API/zrpc client 与 shared contracts/ports,迁移注册、登录、刷新 token、登出、JWT、黑名单和 token 额度治理
2. gateway 与启动装配切流,`cmd/all` 只保留边缘路由、鉴权和轻量组合,通过 userauth zrpc 访问核心用户能力;拆分 MySQL/Redis 初始化与 AutoMigrate 边界,`userauth` 自迁 `users` 和 token 记账幂等表,`all` 不再迁用户表
3. 清退 Gin 单体旧 user/auth DAO、model、service、router、middleware 和 JWT handler,并同步调整 agent/schedule/cache/outbox 相关调用依赖
4. 补齐 refresh token 防并发重放、MySQL 幂等 token 记账、额度 `>=` 拦截和 RPC 错误映射,避免重复记账与内部错误透出

文档:
1. 新增《学习计划论坛与Token商店PRD》
This commit is contained in:
Losita
2026-05-04 15:20:47 +08:00
parent 9902ca3563
commit b08ee17893
58 changed files with 3754 additions and 1510 deletions

View File

@@ -0,0 +1,86 @@
package rpc
import (
"errors"
"log"
"strings"
"github.com/LoveLosita/smartflow/backend/respond"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const userAuthErrorDomain = "smartflow.userauth"
// grpcErrorFromServiceError 负责把 user/auth 内部错误收口成 gRPC status。
//
// 职责边界:
// 1. 只负责把本服务内部的 respond.Response / 普通 error 转成 gRPC 可传输错误;
// 2. 不负责决定 HTTP 语义,也不负责写回前端响应体;
// 3. 上层 handler 只要直接 return 这个结果,就能让 client 侧按 `res, err :=` 的方式接收。
func grpcErrorFromServiceError(err error) error {
if err == nil {
return nil
}
var resp respond.Response
if errors.As(err, &resp) {
return grpcErrorFromResponse(resp)
}
log.Printf("userauth rpc internal error: %v", err)
return status.Error(codes.Internal, "userauth service internal error")
}
// grpcErrorFromResponse 负责把项目内业务响应映射成 gRPC status。
//
// 职责边界:
// 1. 只处理 user/auth 这组响应码到 gRPC code 的映射;
// 2. 业务码和业务文案通过 ErrorInfo 附带,方便 gateway 再反解回 respond.Response
// 3. 失败时退化为普通 gRPC status不阻断请求链路。
func grpcErrorFromResponse(resp respond.Response) error {
code := grpcCodeFromRespondStatus(resp.Status)
message := strings.TrimSpace(resp.Info)
if message == "" {
message = strings.TrimSpace(resp.Status)
}
st := status.New(code, message)
detail := &errdetails.ErrorInfo{
Domain: userAuthErrorDomain,
Reason: resp.Status,
Metadata: map[string]string{
"info": resp.Info,
},
}
withDetails, err := st.WithDetails(detail)
if err != nil {
return st.Err()
}
return withDetails.Err()
}
func grpcCodeFromRespondStatus(statusValue string) codes.Code {
switch strings.TrimSpace(statusValue) {
case respond.InvalidName.Status:
return codes.AlreadyExists
case respond.WrongName.Status:
return codes.NotFound
case respond.WrongPwd.Status, respond.WrongUsernameOrPwd.Status:
return codes.Unauthenticated
case respond.MissingToken.Status, respond.InvalidTokenSingingMethod.Status, respond.InvalidToken.Status,
respond.InvalidClaims.Status, respond.ErrUnauthorized.Status, respond.InvalidRefreshToken.Status,
respond.WrongTokenType.Status, respond.UserLoggedOut.Status:
return codes.Unauthenticated
case respond.TokenUsageExceedsLimit.Status:
return codes.ResourceExhausted
case respond.MissingParam.Status, respond.WrongParamType.Status, respond.ParamTooLong.Status,
respond.WrongGender.Status, respond.WrongUserID.Status:
return codes.InvalidArgument
}
if strings.HasPrefix(strings.TrimSpace(statusValue), "5") {
return codes.Internal
}
return codes.InvalidArgument
}

View File

@@ -0,0 +1,177 @@
package rpc
import (
"context"
"errors"
"time"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/LoveLosita/smartflow/backend/services/userauth/rpc/pb"
userauthsv "github.com/LoveLosita/smartflow/backend/services/userauth/sv"
contracts "github.com/LoveLosita/smartflow/backend/shared/contracts/userauth"
)
type Handler struct {
pb.UnimplementedUserAuthServer
svc *userauthsv.Service
}
func NewHandler(svc *userauthsv.Service) *Handler {
return &Handler{svc: svc}
}
// Register 负责把 user/auth 的注册请求从 gRPC 协议转成内部服务调用。
//
// 职责边界:
// 1. 只做 transport -> service 的参数搬运,不碰 DAO/Redis/JWT 细节;
// 2. 业务错误统一转成 gRPC status让 client 侧继续使用 `res, err :=`
// 3. 成功时只回传业务数据,不再在 payload 里塞 status/info。
func (h *Handler) Register(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("userauth service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.Register(ctx, contracts.RegisterRequest{
Username: req.Username,
Password: req.Password,
PhoneNumber: req.PhoneNumber,
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.RegisterResponse{Id: uint64(resp.ID)}, nil
}
func (h *Handler) Login(ctx context.Context, req *pb.LoginRequest) (*pb.TokensResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("userauth service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.Login(ctx, contracts.LoginRequest{
Username: req.Username,
Password: req.Password,
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.TokensResponse{
AccessToken: resp.AccessToken,
RefreshToken: resp.RefreshToken,
}, nil
}
func (h *Handler) RefreshToken(ctx context.Context, req *pb.RefreshTokenRequest) (*pb.TokensResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("userauth service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.RefreshToken(ctx, contracts.RefreshTokenRequest{
RefreshToken: req.RefreshToken,
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.TokensResponse{
AccessToken: resp.AccessToken,
RefreshToken: resp.RefreshToken,
}, nil
}
func (h *Handler) Logout(ctx context.Context, req *pb.LogoutRequest) (*pb.StatusResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("userauth service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingToken)
}
if err := h.svc.LogoutByAccessToken(ctx, req.AccessToken); err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.StatusResponse{}, nil
}
func (h *Handler) ValidateAccessToken(ctx context.Context, req *pb.ValidateAccessTokenRequest) (*pb.ValidateAccessTokenResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("userauth service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingToken)
}
resp, err := h.svc.ValidateAccessToken(ctx, contracts.ValidateAccessTokenRequest{
AccessToken: req.AccessToken,
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.ValidateAccessTokenResponse{
Valid: resp.Valid,
UserId: int64(resp.UserID),
TokenType: resp.TokenType,
Jti: resp.JTI,
ExpiresAtUnixNano: timeToUnixNano(resp.ExpiresAt),
}, nil
}
func (h *Handler) CheckTokenQuota(ctx context.Context, req *pb.CheckTokenQuotaRequest) (*pb.CheckTokenQuotaResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("userauth service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.ErrUnauthorized)
}
resp, err := h.svc.CheckTokenQuota(ctx, contracts.CheckTokenQuotaRequest{
UserID: int(req.UserId),
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.CheckTokenQuotaResponse{
Allowed: resp.Allowed,
TokenLimit: int64(resp.TokenLimit),
TokenUsage: int64(resp.TokenUsage),
LastResetAtUnixNano: timeToUnixNano(resp.LastResetAt),
}, nil
}
func (h *Handler) AdjustTokenUsage(ctx context.Context, req *pb.AdjustTokenUsageRequest) (*pb.CheckTokenQuotaResponse, error) {
if h == nil || h.svc == nil {
return nil, grpcErrorFromServiceError(errors.New("userauth service dependency not initialized"))
}
if req == nil {
return nil, grpcErrorFromServiceError(respond.MissingParam)
}
resp, err := h.svc.AdjustTokenUsage(ctx, contracts.AdjustTokenUsageRequest{
EventID: req.EventId,
UserID: int(req.UserId),
TokenDelta: int(req.TokenDelta),
})
if err != nil {
return nil, grpcErrorFromServiceError(err)
}
return &pb.CheckTokenQuotaResponse{
Allowed: resp.Allowed,
TokenLimit: int64(resp.TokenLimit),
TokenUsage: int64(resp.TokenUsage),
LastResetAtUnixNano: timeToUnixNano(resp.LastResetAt),
}, nil
}
func timeToUnixNano(value time.Time) int64 {
if value.IsZero() {
return 0
}
return value.UnixNano()
}

View File

@@ -0,0 +1,151 @@
package pb
import proto "github.com/golang/protobuf/proto"
var _ = proto.Marshal
const _ = proto.ProtoPackageIsVersion3
type RegisterRequest struct {
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
PhoneNumber string `protobuf:"bytes,3,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RegisterRequest) Reset() { *m = RegisterRequest{} }
func (m *RegisterRequest) String() string { return proto.CompactTextString(m) }
func (*RegisterRequest) ProtoMessage() {}
type RegisterResponse struct {
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RegisterResponse) Reset() { *m = RegisterResponse{} }
func (m *RegisterResponse) String() string { return proto.CompactTextString(m) }
func (*RegisterResponse) ProtoMessage() {}
type LoginRequest struct {
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LoginRequest) Reset() { *m = LoginRequest{} }
func (m *LoginRequest) String() string { return proto.CompactTextString(m) }
func (*LoginRequest) ProtoMessage() {}
type TokensResponse struct {
AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *TokensResponse) Reset() { *m = TokensResponse{} }
func (m *TokensResponse) String() string { return proto.CompactTextString(m) }
func (*TokensResponse) ProtoMessage() {}
type RefreshTokenRequest struct {
RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RefreshTokenRequest) Reset() { *m = RefreshTokenRequest{} }
func (m *RefreshTokenRequest) String() string { return proto.CompactTextString(m) }
func (*RefreshTokenRequest) ProtoMessage() {}
type LogoutRequest struct {
AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LogoutRequest) Reset() { *m = LogoutRequest{} }
func (m *LogoutRequest) String() string { return proto.CompactTextString(m) }
func (*LogoutRequest) ProtoMessage() {}
type StatusResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *StatusResponse) Reset() { *m = StatusResponse{} }
func (m *StatusResponse) String() string { return proto.CompactTextString(m) }
func (*StatusResponse) ProtoMessage() {}
type ValidateAccessTokenRequest struct {
AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ValidateAccessTokenRequest) Reset() { *m = ValidateAccessTokenRequest{} }
func (m *ValidateAccessTokenRequest) String() string { return proto.CompactTextString(m) }
func (*ValidateAccessTokenRequest) ProtoMessage() {}
type ValidateAccessTokenResponse struct {
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"`
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
TokenType string `protobuf:"bytes,3,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"`
Jti string `protobuf:"bytes,4,opt,name=jti,proto3" json:"jti,omitempty"`
ExpiresAtUnixNano int64 `protobuf:"varint,5,opt,name=expires_at_unix_nano,json=expiresAtUnixNano,proto3" json:"expires_at_unix_nano,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ValidateAccessTokenResponse) Reset() { *m = ValidateAccessTokenResponse{} }
func (m *ValidateAccessTokenResponse) String() string { return proto.CompactTextString(m) }
func (*ValidateAccessTokenResponse) ProtoMessage() {}
type CheckTokenQuotaRequest struct {
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CheckTokenQuotaRequest) Reset() { *m = CheckTokenQuotaRequest{} }
func (m *CheckTokenQuotaRequest) String() string { return proto.CompactTextString(m) }
func (*CheckTokenQuotaRequest) ProtoMessage() {}
type AdjustTokenUsageRequest struct {
EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
TokenDelta int64 `protobuf:"varint,3,opt,name=token_delta,json=tokenDelta,proto3" json:"token_delta,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *AdjustTokenUsageRequest) Reset() { *m = AdjustTokenUsageRequest{} }
func (m *AdjustTokenUsageRequest) String() string { return proto.CompactTextString(m) }
func (*AdjustTokenUsageRequest) ProtoMessage() {}
type CheckTokenQuotaResponse struct {
Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"`
TokenLimit int64 `protobuf:"varint,2,opt,name=token_limit,json=tokenLimit,proto3" json:"token_limit,omitempty"`
TokenUsage int64 `protobuf:"varint,3,opt,name=token_usage,json=tokenUsage,proto3" json:"token_usage,omitempty"`
LastResetAtUnixNano int64 `protobuf:"varint,4,opt,name=last_reset_at_unix_nano,json=lastResetAtUnixNano,proto3" json:"last_reset_at_unix_nano,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CheckTokenQuotaResponse) Reset() { *m = CheckTokenQuotaResponse{} }
func (m *CheckTokenQuotaResponse) String() string { return proto.CompactTextString(m) }
func (*CheckTokenQuotaResponse) ProtoMessage() {}

View File

@@ -0,0 +1,307 @@
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
const (
UserAuth_Register_FullMethodName = "/smartflow.userauth.UserAuth/Register"
UserAuth_Login_FullMethodName = "/smartflow.userauth.UserAuth/Login"
UserAuth_RefreshToken_FullMethodName = "/smartflow.userauth.UserAuth/RefreshToken"
UserAuth_Logout_FullMethodName = "/smartflow.userauth.UserAuth/Logout"
UserAuth_ValidateAccessToken_FullMethodName = "/smartflow.userauth.UserAuth/ValidateAccessToken"
UserAuth_CheckTokenQuota_FullMethodName = "/smartflow.userauth.UserAuth/CheckTokenQuota"
UserAuth_AdjustTokenUsage_FullMethodName = "/smartflow.userauth.UserAuth/AdjustTokenUsage"
)
type UserAuthClient interface {
Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error)
Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*TokensResponse, error)
RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*TokensResponse, error)
Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*StatusResponse, error)
ValidateAccessToken(ctx context.Context, in *ValidateAccessTokenRequest, opts ...grpc.CallOption) (*ValidateAccessTokenResponse, error)
CheckTokenQuota(ctx context.Context, in *CheckTokenQuotaRequest, opts ...grpc.CallOption) (*CheckTokenQuotaResponse, error)
AdjustTokenUsage(ctx context.Context, in *AdjustTokenUsageRequest, opts ...grpc.CallOption) (*CheckTokenQuotaResponse, error)
}
type userAuthClient struct {
cc grpc.ClientConnInterface
}
func NewUserAuthClient(cc grpc.ClientConnInterface) UserAuthClient {
return &userAuthClient{cc}
}
func (c *userAuthClient) Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterResponse, error) {
out := new(RegisterResponse)
err := c.cc.Invoke(ctx, UserAuth_Register_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userAuthClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*TokensResponse, error) {
out := new(TokensResponse)
err := c.cc.Invoke(ctx, UserAuth_Login_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userAuthClient) RefreshToken(ctx context.Context, in *RefreshTokenRequest, opts ...grpc.CallOption) (*TokensResponse, error) {
out := new(TokensResponse)
err := c.cc.Invoke(ctx, UserAuth_RefreshToken_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userAuthClient) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
out := new(StatusResponse)
err := c.cc.Invoke(ctx, UserAuth_Logout_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userAuthClient) ValidateAccessToken(ctx context.Context, in *ValidateAccessTokenRequest, opts ...grpc.CallOption) (*ValidateAccessTokenResponse, error) {
out := new(ValidateAccessTokenResponse)
err := c.cc.Invoke(ctx, UserAuth_ValidateAccessToken_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userAuthClient) CheckTokenQuota(ctx context.Context, in *CheckTokenQuotaRequest, opts ...grpc.CallOption) (*CheckTokenQuotaResponse, error) {
out := new(CheckTokenQuotaResponse)
err := c.cc.Invoke(ctx, UserAuth_CheckTokenQuota_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userAuthClient) AdjustTokenUsage(ctx context.Context, in *AdjustTokenUsageRequest, opts ...grpc.CallOption) (*CheckTokenQuotaResponse, error) {
out := new(CheckTokenQuotaResponse)
err := c.cc.Invoke(ctx, UserAuth_AdjustTokenUsage_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
type UserAuthServer interface {
Register(context.Context, *RegisterRequest) (*RegisterResponse, error)
Login(context.Context, *LoginRequest) (*TokensResponse, error)
RefreshToken(context.Context, *RefreshTokenRequest) (*TokensResponse, error)
Logout(context.Context, *LogoutRequest) (*StatusResponse, error)
ValidateAccessToken(context.Context, *ValidateAccessTokenRequest) (*ValidateAccessTokenResponse, error)
CheckTokenQuota(context.Context, *CheckTokenQuotaRequest) (*CheckTokenQuotaResponse, error)
AdjustTokenUsage(context.Context, *AdjustTokenUsageRequest) (*CheckTokenQuotaResponse, error)
}
type UnimplementedUserAuthServer struct{}
func (UnimplementedUserAuthServer) Register(context.Context, *RegisterRequest) (*RegisterResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Register not implemented")
}
func (UnimplementedUserAuthServer) Login(context.Context, *LoginRequest) (*TokensResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
}
func (UnimplementedUserAuthServer) RefreshToken(context.Context, *RefreshTokenRequest) (*TokensResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshToken not implemented")
}
func (UnimplementedUserAuthServer) Logout(context.Context, *LogoutRequest) (*StatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented")
}
func (UnimplementedUserAuthServer) ValidateAccessToken(context.Context, *ValidateAccessTokenRequest) (*ValidateAccessTokenResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ValidateAccessToken not implemented")
}
func (UnimplementedUserAuthServer) CheckTokenQuota(context.Context, *CheckTokenQuotaRequest) (*CheckTokenQuotaResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckTokenQuota not implemented")
}
func (UnimplementedUserAuthServer) AdjustTokenUsage(context.Context, *AdjustTokenUsageRequest) (*CheckTokenQuotaResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AdjustTokenUsage not implemented")
}
func RegisterUserAuthServer(s grpc.ServiceRegistrar, srv UserAuthServer) {
s.RegisterService(&UserAuth_ServiceDesc, srv)
}
func _UserAuth_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RegisterRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserAuthServer).Register(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserAuth_Register_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserAuthServer).Register(ctx, req.(*RegisterRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserAuth_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoginRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserAuthServer).Login(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserAuth_Login_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserAuthServer).Login(ctx, req.(*LoginRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserAuth_RefreshToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RefreshTokenRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserAuthServer).RefreshToken(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserAuth_RefreshToken_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserAuthServer).RefreshToken(ctx, req.(*RefreshTokenRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserAuth_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LogoutRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserAuthServer).Logout(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserAuth_Logout_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserAuthServer).Logout(ctx, req.(*LogoutRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserAuth_ValidateAccessToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ValidateAccessTokenRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserAuthServer).ValidateAccessToken(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserAuth_ValidateAccessToken_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserAuthServer).ValidateAccessToken(ctx, req.(*ValidateAccessTokenRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserAuth_CheckTokenQuota_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckTokenQuotaRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserAuthServer).CheckTokenQuota(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserAuth_CheckTokenQuota_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserAuthServer).CheckTokenQuota(ctx, req.(*CheckTokenQuotaRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserAuth_AdjustTokenUsage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AdjustTokenUsageRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserAuthServer).AdjustTokenUsage(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserAuth_AdjustTokenUsage_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserAuthServer).AdjustTokenUsage(ctx, req.(*AdjustTokenUsageRequest))
}
return interceptor(ctx, in, info, handler)
}
var UserAuth_ServiceDesc = grpc.ServiceDesc{
ServiceName: "smartflow.userauth.UserAuth",
HandlerType: (*UserAuthServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Register",
Handler: _UserAuth_Register_Handler,
},
{
MethodName: "Login",
Handler: _UserAuth_Login_Handler,
},
{
MethodName: "RefreshToken",
Handler: _UserAuth_RefreshToken_Handler,
},
{
MethodName: "Logout",
Handler: _UserAuth_Logout_Handler,
},
{
MethodName: "ValidateAccessToken",
Handler: _UserAuth_ValidateAccessToken_Handler,
},
{
MethodName: "CheckTokenQuota",
Handler: _UserAuth_CheckTokenQuota_Handler,
},
{
MethodName: "AdjustTokenUsage",
Handler: _UserAuth_AdjustTokenUsage_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "services/userauth/rpc/userauth.proto",
}

View File

@@ -0,0 +1,72 @@
package rpc
import (
"errors"
"log"
"strings"
"time"
"github.com/LoveLosita/smartflow/backend/services/userauth/rpc/pb"
userauthsv "github.com/LoveLosita/smartflow/backend/services/userauth/sv"
"github.com/zeromicro/go-zero/core/service"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
const (
defaultListenOn = "0.0.0.0:9081"
defaultTimeout = 2 * time.Second
)
type ServerOptions struct {
ListenOn string
Timeout time.Duration
Service *userauthsv.Service
}
// Start 启动 user/auth zrpc 服务。
//
// 职责边界:
// 1. 只负责装配 gozero zrpc server 和注册 protobuf service
// 2. 不创建 DB/Redis 连接,这些依赖由 cmd/userauth 入口注入;
// 3. 阻塞直到进程收到退出信号,保持一个服务一个独立进程的迁移方向。
func Start(opts ServerOptions) {
server, listenOn, err := NewServer(opts)
if err != nil {
log.Fatalf("failed to build userauth zrpc server: %v", err)
}
defer server.Stop()
log.Printf("userauth zrpc service starting on %s", listenOn)
server.Start()
}
func NewServer(opts ServerOptions) (*zrpc.RpcServer, string, error) {
if opts.Service == nil {
return nil, "", errors.New("userauth service dependency not initialized")
}
listenOn := strings.TrimSpace(opts.ListenOn)
if listenOn == "" {
listenOn = defaultListenOn
}
timeout := opts.Timeout
if timeout <= 0 {
timeout = defaultTimeout
}
server, err := zrpc.NewServer(zrpc.RpcServerConf{
ServiceConf: service.ServiceConf{
Name: "userauth.rpc",
Mode: service.DevMode,
},
ListenOn: listenOn,
Timeout: int64(timeout / time.Millisecond),
}, func(grpcServer *grpc.Server) {
pb.RegisterUserAuthServer(grpcServer, NewHandler(opts.Service))
})
if err != nil {
return nil, "", err
}
return server, listenOn, nil
}

View File

@@ -0,0 +1,75 @@
syntax = "proto3";
package smartflow.userauth;
option go_package = "github.com/LoveLosita/smartflow/backend/services/userauth/rpc/pb";
service UserAuth {
rpc Register(RegisterRequest) returns (RegisterResponse);
rpc Login(LoginRequest) returns (TokensResponse);
rpc RefreshToken(RefreshTokenRequest) returns (TokensResponse);
rpc Logout(LogoutRequest) returns (StatusResponse);
rpc ValidateAccessToken(ValidateAccessTokenRequest) returns (ValidateAccessTokenResponse);
rpc CheckTokenQuota(CheckTokenQuotaRequest) returns (CheckTokenQuotaResponse);
rpc AdjustTokenUsage(AdjustTokenUsageRequest) returns (CheckTokenQuotaResponse);
}
message RegisterRequest {
string username = 1;
string password = 2;
string phone_number = 3;
}
message RegisterResponse {
uint64 id = 1;
}
message LoginRequest {
string username = 1;
string password = 2;
}
message TokensResponse {
string access_token = 1;
string refresh_token = 2;
}
message RefreshTokenRequest {
string refresh_token = 1;
}
message LogoutRequest {
string access_token = 1;
}
message StatusResponse {
}
message ValidateAccessTokenRequest {
string access_token = 1;
}
message ValidateAccessTokenResponse {
bool valid = 1;
int64 user_id = 2;
string token_type = 3;
string jti = 4;
int64 expires_at_unix_nano = 5;
}
message CheckTokenQuotaRequest {
int64 user_id = 1;
}
message AdjustTokenUsageRequest {
string event_id = 1;
int64 user_id = 2;
int64 token_delta = 3;
}
message CheckTokenQuotaResponse {
bool allowed = 1;
int64 token_limit = 2;
int64 token_usage = 3;
int64 last_reset_at_unix_nano = 4;
}