add: user auth accomplished
This commit is contained in:
@@ -21,7 +21,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
var c config.Config
|
var c config.Config
|
||||||
conf.MustLoad(*configFile, &c)
|
conf.MustLoad(*configFile, &c, conf.UseEnv())
|
||||||
|
|
||||||
server := rest.MustNewServer(c.RestConf)
|
server := rest.MustNewServer(c.RestConf)
|
||||||
defer server.Stop()
|
defer server.Stop()
|
||||||
|
|||||||
@@ -2,18 +2,24 @@ Name: email-api
|
|||||||
Host: 0.0.0.0
|
Host: 0.0.0.0
|
||||||
Port: 8888
|
Port: 8888
|
||||||
|
|
||||||
|
Prometheus:
|
||||||
|
Host: 0.0.0.0
|
||||||
|
Port: 4001
|
||||||
|
Path: /metrics
|
||||||
|
|
||||||
CacheConf:
|
CacheConf:
|
||||||
- Host: "${REDIS_M_HOST}"
|
- Host: "${REDIS_M_HOST}"
|
||||||
Type: node
|
Type: node
|
||||||
Pass: "${REDIS_PASSWORD}"
|
Pass: "${REDIS_PASSWORD}"
|
||||||
User: "default"
|
User: "default"
|
||||||
- Host: "${REDIS_S_HOST}"
|
- Host: "${REDIS_S_HOST}"
|
||||||
Type: node
|
Type: node
|
||||||
Pass: "${REDIS_PASSWORD}"
|
Pass: "${REDIS_PASSWORD}"
|
||||||
User: "default"
|
User: "default"
|
||||||
|
|
||||||
Kmq:
|
Kmq:
|
||||||
Name: email-api
|
Name: email-api
|
||||||
Brokers:
|
Brokers:
|
||||||
- "${KAFKA_BROKER}"
|
- "${KAFKA_BROKER}"
|
||||||
Topic: "email-task"
|
Group: "email-api-group"
|
||||||
|
Topic: "email-task"
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ func (l *SendVerificationCodeLogic) SendVerificationCode(req *types.SendVerifica
|
|||||||
code := utils.GenCode()
|
code := utils.GenCode()
|
||||||
requestID := uuid.NewString()
|
requestID := uuid.NewString()
|
||||||
|
|
||||||
redisKey := fmt.Sprintf("%s:%s:%s", req.Email, code, req.Email)
|
redisKey := fmt.Sprintf("vcode:%s:%s:%s", requestID, req.Scene, req.Email)
|
||||||
if exists, getErr := l.svcCtx.RedisCluster.Get(l.ctx, redisKey).Result(); getErr == nil && exists != "" {
|
if exists, getErr := l.svcCtx.RedisCluster.Get(l.ctx, redisKey).Result(); getErr == nil && exists != "" {
|
||||||
return nil, fmt.Errorf("verification code already sent, please wait before requesting a new one")
|
return nil, fmt.Errorf("verification code already sent, please wait before requesting a new one")
|
||||||
}
|
}
|
||||||
if setErr := l.svcCtx.RedisCluster.Set(l.ctx, redisKey, req.Scene, 60*time.Second).Err(); setErr != nil {
|
if setErr := l.svcCtx.RedisCluster.Set(l.ctx, redisKey, code, 60*time.Second).Err(); setErr != nil {
|
||||||
return nil, setErr
|
return nil, setErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
var c config.Config
|
var c config.Config
|
||||||
conf.MustLoad(*configFile, &c)
|
conf.MustLoad(*configFile, &c, conf.UseEnv())
|
||||||
if err := c.SetUp(); err != nil {
|
if err := c.SetUp(); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Name: email-mq
|
|||||||
|
|
||||||
Prometheus:
|
Prometheus:
|
||||||
Host: 0.0.0.0
|
Host: 0.0.0.0
|
||||||
Port: 4003
|
Port: 4001
|
||||||
Path: /metrics
|
Path: /metrics
|
||||||
|
|
||||||
Kmq:
|
Kmq:
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Mqs(c config.Config) []service.Service {
|
func Mqs(c config.Config) []service.Service {
|
||||||
//svcContext := NewServiceContext
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
svcCtx := svc.NewServiceContext(c)
|
svcCtx := svc.NewServiceContext(c)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
Name: snowflake.rpc
|
Name: snowflake.rpc
|
||||||
ListenOn: 0.0.0.0:8080
|
ListenOn: 0.0.0.0:8080
|
||||||
#Etcd:
|
|
||||||
# Hosts:
|
|
||||||
# - 127.0.0.1:2379
|
|
||||||
# Key: snowflake.rpc
|
|
||||||
|
|
||||||
Snowflake:
|
Snowflake:
|
||||||
DatacenterId: 1
|
DatacenterId: 1
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ Prometheus:
|
|||||||
|
|
||||||
UsercenterRpcConf:
|
UsercenterRpcConf:
|
||||||
Target: k8s://juwan/user-rpc-svc:9001
|
Target: k8s://juwan/user-rpc-svc:9001
|
||||||
|
|
||||||
|
SnowflakeRpcConf:
|
||||||
|
Target: k8s://juwan/snowflake-svc:8080
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
rest.RestConf
|
rest.RestConf
|
||||||
UsercenterRpcConf zrpc.RpcClientConf
|
UsercenterRpcConf zrpc.RpcClientConf
|
||||||
|
SnowflakeRpcConf zrpc.RpcClientConf
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package contextx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithRequestId(c context.Context, requestId string) context.Context {
|
||||||
|
return context.WithValue(c, "request_id", requestId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequestIdFrom(c context.Context) (string, error) {
|
||||||
|
requestID, ok := c.Value("request_id").(string)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("request_id not found in context")
|
||||||
|
}
|
||||||
|
return requestID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithToken(c context.Context, token string) context.Context {
|
||||||
|
return context.WithValue(c, "token", token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TokenFrom(c context.Context) (string, error) {
|
||||||
|
token, ok := c.Value("token").(string)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("token not found in context")
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"juwan-backend/app/users/api/internal/logic/user"
|
"juwan-backend/app/users/api/internal/logic/user"
|
||||||
"juwan-backend/app/users/api/internal/svc"
|
"juwan-backend/app/users/api/internal/svc"
|
||||||
"juwan-backend/app/users/api/internal/types"
|
"juwan-backend/app/users/api/internal/types"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 用户登录接口
|
// 用户登录接口
|
||||||
@@ -23,6 +23,22 @@ func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|||||||
|
|
||||||
l := user.NewLoginLogic(r.Context(), svcCtx)
|
l := user.NewLoginLogic(r.Context(), svcCtx)
|
||||||
resp, err := l.Login(&req)
|
resp, err := l.Login(&req)
|
||||||
|
token := resp.Token
|
||||||
|
resp.Token = ""
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: "JToken",
|
||||||
|
Value: token,
|
||||||
|
Quoted: false,
|
||||||
|
Path: "/",
|
||||||
|
Domain: "",
|
||||||
|
RawExpires: "",
|
||||||
|
MaxAge: 691200,
|
||||||
|
Secure: false,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
Partitioned: false,
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.ErrorCtx(r.Context(), w, err)
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,29 +4,93 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"juwan-backend/app/users/api/internal/contextx"
|
||||||
|
"juwan-backend/common/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
|
||||||
"juwan-backend/app/users/api/internal/logic/user"
|
"juwan-backend/app/users/api/internal/logic/user"
|
||||||
"juwan-backend/app/users/api/internal/svc"
|
"juwan-backend/app/users/api/internal/svc"
|
||||||
"juwan-backend/app/users/api/internal/types"
|
"juwan-backend/app/users/api/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 用户注册接口
|
// 用户注册接口
|
||||||
func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := normalizeRegisterBody(r); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var req types.RegisterReq
|
var req types.RegisterReq
|
||||||
if err := httpx.Parse(r, &req); err != nil {
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
httpx.ErrorCtx(r.Context(), w, err)
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l := user.NewRegisterLogic(r.Context(), svcCtx)
|
requestId := r.Header.Get("X-Request-ID")
|
||||||
|
//regCtx := context.WithValue(r.Context(), "request_id", requestId)
|
||||||
|
regCtx := contextx.WithRequestId(r.Context(), requestId)
|
||||||
|
if requestId == "" {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, errors.New("bad request"))
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewRegisterLogic(regCtx, svcCtx)
|
||||||
resp, err := l.Register(&req)
|
resp, err := l.Register(&req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.ErrorCtx(r.Context(), w, err)
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
} else {
|
} else {
|
||||||
httpx.OkJsonCtx(r.Context(), w, resp)
|
httpx.OkJsonCtx(r.Context(), w, utils.NewErrorResp(400, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeRegisterBody(r *http.Request) error {
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
if len(body) == 0 {
|
||||||
|
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload map[string]any
|
||||||
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||||||
|
r.Body = io.NopCloser(bytes.NewReader(body))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vcode, exists := payload["vcode"]
|
||||||
|
if exists {
|
||||||
|
switch value := vcode.(type) {
|
||||||
|
case string:
|
||||||
|
parsed, convErr := strconv.Atoi(value)
|
||||||
|
if convErr != nil {
|
||||||
|
return fmt.Errorf("invalid vcode format")
|
||||||
|
}
|
||||||
|
payload["vcode"] = parsed
|
||||||
|
case float64:
|
||||||
|
payload["vcode"] = int(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Body = io.NopCloser(bytes.NewReader(normalized))
|
||||||
|
r.ContentLength = int64(len(normalized))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"juwan-backend/app/users/rpc/usercenter"
|
||||||
|
"juwan-backend/common/converter"
|
||||||
|
|
||||||
"juwan-backend/app/users/api/internal/svc"
|
"juwan-backend/app/users/api/internal/svc"
|
||||||
"juwan-backend/app/users/api/internal/types"
|
"juwan-backend/app/users/api/internal/types"
|
||||||
@@ -27,8 +30,21 @@ func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserInfoReq) (resp *types.UserInfo, err error) {
|
func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserInfoReq) (resp types.UserInfo, err error) {
|
||||||
// todo: add your logic here and delete this line
|
// todo: add your logic here and delete this line
|
||||||
|
pbUser, err := l.svcCtx.UserRpc.GetUsersById(l.ctx, &usercenter.GetUsersByIdReq{
|
||||||
|
Id: req.UserId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return types.UserInfo{}, errors.New("failed to get user info by userid")
|
||||||
|
}
|
||||||
|
user := types.UserInfo{}
|
||||||
|
err = converter.StructToStruct(&pbUser.Users, &user)
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("struct to user info failed, err:%v.", err)
|
||||||
|
return types.UserInfo{}, errors.New("failed to get user info by userid")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
//req.UserId
|
||||||
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"juwan-backend/app/users/api/internal/svc"
|
"juwan-backend/app/users/api/internal/svc"
|
||||||
"juwan-backend/app/users/api/internal/types"
|
"juwan-backend/app/users/api/internal/types"
|
||||||
|
"juwan-backend/app/users/rpc/usercenter"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
@@ -28,6 +30,24 @@ func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) {
|
func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err error) {
|
||||||
|
if len(req.Username) < 3 || len(req.Password) > 20 || len(req.Password) < 8 || len(req.Password) > 20 {
|
||||||
|
return nil, errors.New("the information is illegal")
|
||||||
|
}
|
||||||
|
|
||||||
return &types.LoginResp{}, nil
|
res, err := l.svcCtx.UserRpc.Login(l.ctx, &usercenter.LoginReq{
|
||||||
|
Username: req.Username,
|
||||||
|
Passwd: req.Password,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("rpc login err: %v", err)
|
||||||
|
return nil, errors.New("login fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.LoginResp{
|
||||||
|
UserId: res.Id,
|
||||||
|
Username: res.Username,
|
||||||
|
Email: res.Email,
|
||||||
|
Token: res.Token,
|
||||||
|
Expires: int64((7 * 24 * time.Hour).Seconds()),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"juwan-backend/app/users/api/internal/svc"
|
"juwan-backend/app/users/api/internal/svc"
|
||||||
"juwan-backend/app/users/api/internal/types"
|
"juwan-backend/app/users/api/internal/types"
|
||||||
|
"juwan-backend/app/users/rpc/usercenter"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
@@ -28,7 +30,14 @@ func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *LogoutLogic) Logout(req *types.LogoutReq) (resp *types.LogoutResp, err error) {
|
func (l *LogoutLogic) Logout(req *types.LogoutReq) (resp *types.LogoutResp, err error) {
|
||||||
// todo: add your logic here and delete this line
|
if req.UserId <= 0 {
|
||||||
|
return nil, errors.New("invalid userId")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
_, err = l.svcCtx.UserRpc.Logout(l.ctx, &usercenter.LogoutReq{UserId: req.UserId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.LogoutResp{Message: "logout success"}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ package user
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"juwan-backend/app/users/api/internal/contextx"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"juwan-backend/app/users/api/internal/svc"
|
"juwan-backend/app/users/api/internal/svc"
|
||||||
"juwan-backend/app/users/api/internal/types"
|
"juwan-backend/app/users/api/internal/types"
|
||||||
"juwan-backend/app/users/rpc/pb"
|
"juwan-backend/app/users/rpc/pb"
|
||||||
|
"juwan-backend/app/users/rpc/usercenter"
|
||||||
"juwan-backend/common/utils"
|
"juwan-backend/common/utils"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,40 +33,54 @@ func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Register
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var usernameRegex = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||||
|
|
||||||
func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) {
|
func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterResp, err error) {
|
||||||
// 检查用户是否已存在
|
|
||||||
existingUser, err := l.svcCtx.UserRpc.GetUserByUsername(l.ctx, &pb.GetUserByUsernameReq{
|
existingUser, err := l.svcCtx.UserRpc.GetUserByUsername(l.ctx, &pb.GetUserByUsernameReq{
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
})
|
})
|
||||||
|
if len(req.Username) < 3 {
|
||||||
|
return nil, errors.New("username must be at least 3 characters long")
|
||||||
|
}
|
||||||
|
if len(req.Username) > 20 {
|
||||||
|
return nil, errors.New("username must be at most 20 characters long")
|
||||||
|
}
|
||||||
|
if !usernameRegex.MatchString(req.Username) {
|
||||||
|
return nil, errors.New("username can only contain letters, numbers, and underscores")
|
||||||
|
}
|
||||||
if err == nil && existingUser != nil {
|
if err == nil && existingUser != nil {
|
||||||
return nil, errors.New("user already exists")
|
return nil, errors.New("user already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成用户ID
|
|
||||||
userId, err := uuid.NewRandom()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("generate user ID failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加密密码
|
|
||||||
hashedPassword, err := utils.HashPassword(req.Password)
|
hashedPassword, err := utils.HashPassword(req.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("hash password failed")
|
return nil, errors.New("hash password failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新用户
|
requestId, err := contextx.RequestIdFrom(l.ctx)
|
||||||
_res, err := l.svcCtx.UserRpc.AddUsers(l.ctx, &pb.AddUsersReq{
|
if err != nil {
|
||||||
UserId: userId.String(),
|
logx.Errorf("contextx.RequestIdFrom failed: %v", errjA)
|
||||||
Username: req.Username,
|
return nil, errors.New("contextx.RequestIdFrom failed")
|
||||||
Passwd: hashedPassword,
|
}
|
||||||
Phone: req.Phone,
|
|
||||||
State: true,
|
_, err = l.svcCtx.UserRpc.Register(l.ctx, &usercenter.RegisterReq{
|
||||||
|
Username: req.Username,
|
||||||
|
Passwd: hashedPassword,
|
||||||
|
Phone: req.Username,
|
||||||
|
Vcode: req.Vcode,
|
||||||
|
Email: req.Email,
|
||||||
|
RequestId: requestId,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("AddUsers failed: %v", err)
|
logx.Error("failed to register user: ", err)
|
||||||
return nil, errors.New("add user failed")
|
return nil, errors.New("failed to register user")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回响应
|
// 返回响应
|
||||||
return &types.RegisterResp{}, nil
|
return &types.RegisterResp{
|
||||||
|
UserId: 0,
|
||||||
|
Username: req.Username,
|
||||||
|
Email: req.Email,
|
||||||
|
Message: "register success",
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,28 @@
|
|||||||
package svc
|
package svc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"juwan-backend/app/snowflake/rpc/snowflake"
|
||||||
"juwan-backend/app/users/api/internal/config"
|
"juwan-backend/app/users/api/internal/config"
|
||||||
"juwan-backend/app/users/api/internal/middleware"
|
"juwan-backend/app/users/api/internal/middleware"
|
||||||
"juwan-backend/app/users/rpc/usercenter"
|
"juwan-backend/app/users/rpc/usercenter"
|
||||||
|
"juwan-backend/common/snowflakex"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest"
|
"github.com/zeromicro/go-zero/rest"
|
||||||
"github.com/zeromicro/go-zero/zrpc"
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
Logger rest.Middleware
|
Logger rest.Middleware
|
||||||
UserRpc usercenter.Usercenter
|
UserRpc usercenter.Usercenter
|
||||||
|
SnowflakeRpc snowflake.SnowflakeServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
return &ServiceContext{
|
return &ServiceContext{
|
||||||
Config: c,
|
Config: c,
|
||||||
Logger: middleware.NewLoggerMiddleware().Handle,
|
Logger: middleware.NewLoggerMiddleware().Handle,
|
||||||
UserRpc: usercenter.NewUsercenter(zrpc.MustNewClient(c.UsercenterRpcConf)),
|
UserRpc: usercenter.NewUsercenter(zrpc.MustNewClient(c.UsercenterRpcConf)),
|
||||||
|
SnowflakeRpc: snowflakex.NewClient(c.SnowflakeRpcConf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ type LoginResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LogoutReq struct {
|
type LogoutReq struct {
|
||||||
UserId int64 `path:"userId" binding:"required,gt=0"`
|
UserId int64 `path:"userId" binding:"required,gt=0"`
|
||||||
|
Token string `header:"Authorization" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogoutResp struct {
|
type LogoutResp struct {
|
||||||
@@ -38,6 +39,7 @@ type RegisterReq struct {
|
|||||||
Password string `json:"password" binding:"required,min=6,max=128"`
|
Password string `json:"password" binding:"required,min=6,max=128"`
|
||||||
Email string `json:"email,omitempty" binding:"omitempty,email"`
|
Email string `json:"email,omitempty" binding:"omitempty,email"`
|
||||||
Phone string `json:"phone,omitempty" binding:"omitempty,len=11"`
|
Phone string `json:"phone,omitempty" binding:"omitempty,len=11"`
|
||||||
|
Vcode int32 `json:"vcode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegisterResp struct {
|
type RegisterResp struct {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl 1.9.2
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"juwan-backend/app/users/api/internal/config"
|
||||||
|
"juwan-backend/app/users/api/internal/handler"
|
||||||
|
"juwan-backend/app/users/api/internal/svc"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configFile = flag.String("f", "etc/user-api.yaml", "the config file")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var c config.Config
|
||||||
|
conf.MustLoad(*configFile, &c)
|
||||||
|
|
||||||
|
server := rest.MustNewServer(c.RestConf)
|
||||||
|
defer server.Stop()
|
||||||
|
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
handler.RegisterHandlers(server, ctx)
|
||||||
|
|
||||||
|
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
|
||||||
|
server.Start()
|
||||||
|
}
|
||||||
@@ -2,9 +2,11 @@ package logic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"juwan-backend/app/users/rpc/internal/svc"
|
"juwan-backend/app/users/rpc/internal/svc"
|
||||||
|
utils2 "juwan-backend/app/users/rpc/internal/utils"
|
||||||
"juwan-backend/app/users/rpc/pb"
|
"juwan-backend/app/users/rpc/pb"
|
||||||
|
"juwan-backend/common/utils"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
@@ -24,7 +26,29 @@ func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *LoginLogic) Login(in *pb.LoginReq) (*pb.LoginResp, error) {
|
func (l *LoginLogic) Login(in *pb.LoginReq) (*pb.LoginResp, error) {
|
||||||
// todo: add your logic here and delete this line
|
user, err := l.svcCtx.UsersModelRO.FindOneByUsername(l.ctx, in.Username)
|
||||||
|
if err != nil {
|
||||||
|
logx.WithContext(l.ctx).Errorf("LoginLogic.Login error:%v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !utils.VerifyPassword(user.Passwd, in.Passwd) {
|
||||||
|
logx.WithContext(l.ctx).Errorf("User %s Login failed", user.Username)
|
||||||
|
return nil, errors.New("incorrect password")
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.LoginResp{}, nil
|
token, err := l.svcCtx.JwtManager.New(l.ctx, &utils2.TokenPayload{
|
||||||
|
UserId: user.UserId,
|
||||||
|
IsAdmin: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logx.Errorf("LoginLogic.Login gen jwt for user %v error:%v", user.UserId, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.LoginResp{
|
||||||
|
Token: token,
|
||||||
|
Username: user.Username,
|
||||||
|
Email: user.Email,
|
||||||
|
Id: user.UserId,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"juwan-backend/app/users/rpc/internal/svc"
|
||||||
|
"juwan-backend/app/users/rpc/pb"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogoutLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic {
|
||||||
|
return &LogoutLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LogoutLogic) Logout(in *pb.LogoutReq) (*pb.LogoutResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
err := l.svcCtx.JwtManager.Logout(l.ctx, in.UserId)
|
||||||
|
if err != nil {
|
||||||
|
logx.WithContext(l.ctx).Errorf("Logout failed: %s", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.LogoutResp{}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package logic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"juwan-backend/app/snowflake/rpc/snowflake"
|
||||||
|
"juwan-backend/app/users/rpc/internal/models"
|
||||||
|
"juwan-backend/app/users/rpc/internal/svc"
|
||||||
|
"juwan-backend/app/users/rpc/pb"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterLogic struct {
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
logx.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
|
||||||
|
return &RegisterLogic{
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustNewRandomNickname() string {
|
||||||
|
bytes := make([]byte, 5)
|
||||||
|
_, err := rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "NewUser"
|
||||||
|
}
|
||||||
|
nickname := strings.Builder{}
|
||||||
|
nickname.WriteString("user_")
|
||||||
|
nickname.WriteString(hex.EncodeToString(bytes))
|
||||||
|
return nickname.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RegisterLogic) Register(in *pb.RegisterReq) (*pb.RegisterResp, error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
if in.Phone == "" || in.Username == "" || in.Passwd == "" {
|
||||||
|
logx.Error("invalid input")
|
||||||
|
return nil, errors.New("invalid input")
|
||||||
|
}
|
||||||
|
|
||||||
|
redisKey := fmt.Sprintf("vcode:%s:%s:%s", in.RequestId, "register", in.Email)
|
||||||
|
vcode, err := l.svcCtx.RedisCluster.Get(l.ctx, redisKey).Result()
|
||||||
|
logx.Infof("vcode:%s, err:%v", vcode, err)
|
||||||
|
if err != nil {
|
||||||
|
logx.Error("invalid verification code")
|
||||||
|
return nil, errors.New("invalid verification code")
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := strconv.ParseInt(vcode, 10, 32)
|
||||||
|
if err != nil || int32(code) != in.Vcode {
|
||||||
|
logx.Error("invalid verification code")
|
||||||
|
return nil, errors.New("invalid verification code")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := l.svcCtx.Snowflake.NextId(l.ctx, &snowflake.NextIdReq{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("generate user ID failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
user := models.Users{
|
||||||
|
UserId: resp.Id,
|
||||||
|
Username: in.Username,
|
||||||
|
Nickname: mustNewRandomNickname(),
|
||||||
|
Passwd: in.Passwd,
|
||||||
|
Phone: in.Phone,
|
||||||
|
Email: in.Email,
|
||||||
|
RoleType: 0,
|
||||||
|
IsVerified: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = l.svcCtx.UsersModelRW.Insert(l.ctx, &user)
|
||||||
|
if err != nil {
|
||||||
|
logx.Error("failed to create user: ", err)
|
||||||
|
return nil, errors.New("failed to create user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.RegisterResp{
|
||||||
|
Res: "user registered successfully",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package logic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"juwan-backend/app/users/rpc/internal/svc"
|
"juwan-backend/app/users/rpc/internal/svc"
|
||||||
"juwan-backend/app/users/rpc/pb"
|
"juwan-backend/app/users/rpc/pb"
|
||||||
@@ -9,6 +10,8 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var USER_TOKEN_TEMP = "jwt:%v"
|
||||||
|
|
||||||
type ValidateTokenLogic struct {
|
type ValidateTokenLogic struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
svcCtx *svc.ServiceContext
|
svcCtx *svc.ServiceContext
|
||||||
@@ -24,7 +27,20 @@ func NewValidateTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Val
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *ValidateTokenLogic) ValidateToken(in *pb.ValidateTokenReq) (*pb.ValidateTokenResp, error) {
|
func (l *ValidateTokenLogic) ValidateToken(in *pb.ValidateTokenReq) (*pb.ValidateTokenResp, error) {
|
||||||
// todo: add your logic here and delete this line
|
redisKey := fmt.Sprintf(USER_TOKEN_TEMP, in.UserId)
|
||||||
|
_, err := l.svcCtx.JwtManager.Valid(l.ctx, redisKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
users, err := l.svcCtx.UsersModelRO.FindOne(l.ctx, in.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.ValidateTokenResp{}, nil
|
return &pb.ValidateTokenResp{
|
||||||
|
Valid: true,
|
||||||
|
Message: "OK",
|
||||||
|
UserId: in.UserId,
|
||||||
|
RoleType: users.RoleType,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-6
@@ -25,6 +25,7 @@ var (
|
|||||||
usersRowsWithPlaceHolder = builder.PostgreSqlJoin(stringx.Remove(usersFieldNames, "user_id", "create_at", "create_time", "created_at", "update_at", "update_time", "updated_at"))
|
usersRowsWithPlaceHolder = builder.PostgreSqlJoin(stringx.Remove(usersFieldNames, "user_id", "create_at", "create_time", "created_at", "update_at", "update_time", "updated_at"))
|
||||||
|
|
||||||
cachePublicUsersUserIdPrefix = "cache:public:users:userId:"
|
cachePublicUsersUserIdPrefix = "cache:public:users:userId:"
|
||||||
|
cachePublicUsersEmailPrefix = "cache:public:users:email:"
|
||||||
cachePublicUsersPhonePrefix = "cache:public:users:phone:"
|
cachePublicUsersPhonePrefix = "cache:public:users:phone:"
|
||||||
cachePublicUsersUsernamePrefix = "cache:public:users:username:"
|
cachePublicUsersUsernamePrefix = "cache:public:users:username:"
|
||||||
)
|
)
|
||||||
@@ -33,6 +34,7 @@ type (
|
|||||||
usersModel interface {
|
usersModel interface {
|
||||||
Insert(ctx context.Context, data *Users) (sql.Result, error)
|
Insert(ctx context.Context, data *Users) (sql.Result, error)
|
||||||
FindOne(ctx context.Context, userId int64) (*Users, error)
|
FindOne(ctx context.Context, userId int64) (*Users, error)
|
||||||
|
FindOneByEmail(ctx context.Context, email string) (*Users, error)
|
||||||
FindOneByPhone(ctx context.Context, phone string) (*Users, error)
|
FindOneByPhone(ctx context.Context, phone string) (*Users, error)
|
||||||
FindOneByUsername(ctx context.Context, username string) (*Users, error)
|
FindOneByUsername(ctx context.Context, username string) (*Users, error)
|
||||||
Update(ctx context.Context, data *Users) error
|
Update(ctx context.Context, data *Users) error
|
||||||
@@ -56,6 +58,7 @@ type (
|
|||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
UpdatedAt time.Time `db:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
DeletedAt sql.NullTime `db:"deleted_at"`
|
DeletedAt sql.NullTime `db:"deleted_at"`
|
||||||
|
Email string `db:"email"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,13 +75,14 @@ func (m *defaultUsersModel) Delete(ctx context.Context, userId int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publicUsersEmailKey := fmt.Sprintf("%s%v", cachePublicUsersEmailPrefix, data.Email)
|
||||||
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, data.Phone)
|
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, data.Phone)
|
||||||
publicUsersUserIdKey := fmt.Sprintf("%s%v", cachePublicUsersUserIdPrefix, userId)
|
publicUsersUserIdKey := fmt.Sprintf("%s%v", cachePublicUsersUserIdPrefix, userId)
|
||||||
publicUsersUsernameKey := fmt.Sprintf("%s%v", cachePublicUsersUsernamePrefix, data.Username)
|
publicUsersUsernameKey := fmt.Sprintf("%s%v", cachePublicUsersUsernamePrefix, data.Username)
|
||||||
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := fmt.Sprintf("delete from %s where user_id = $1", m.table)
|
query := fmt.Sprintf("delete from %s where user_id = $1", m.table)
|
||||||
return conn.ExecCtx(ctx, query, userId)
|
return conn.ExecCtx(ctx, query, userId)
|
||||||
}, publicUsersPhoneKey, publicUsersUserIdKey, publicUsersUsernameKey)
|
}, publicUsersEmailKey, publicUsersPhoneKey, publicUsersUserIdKey, publicUsersUsernameKey)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +103,26 @@ func (m *defaultUsersModel) FindOne(ctx context.Context, userId int64) (*Users,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *defaultUsersModel) FindOneByEmail(ctx context.Context, email string) (*Users, error) {
|
||||||
|
publicUsersEmailKey := fmt.Sprintf("%s%v", cachePublicUsersEmailPrefix, email)
|
||||||
|
var resp Users
|
||||||
|
err := m.QueryRowIndexCtx(ctx, &resp, publicUsersEmailKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
|
||||||
|
query := fmt.Sprintf("select %s from %s where email = $1 limit 1", usersRows, m.table)
|
||||||
|
if err := conn.QueryRowCtx(ctx, &resp, query, email); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp.UserId, nil
|
||||||
|
}, m.queryPrimary)
|
||||||
|
switch err {
|
||||||
|
case nil:
|
||||||
|
return &resp, nil
|
||||||
|
case sqlc.ErrNotFound:
|
||||||
|
return nil, ErrNotFound
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *defaultUsersModel) FindOneByPhone(ctx context.Context, phone string) (*Users, error) {
|
func (m *defaultUsersModel) FindOneByPhone(ctx context.Context, phone string) (*Users, error) {
|
||||||
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, phone)
|
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, phone)
|
||||||
var resp Users
|
var resp Users
|
||||||
@@ -140,13 +164,14 @@ func (m *defaultUsersModel) FindOneByUsername(ctx context.Context, username stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *defaultUsersModel) Insert(ctx context.Context, data *Users) (sql.Result, error) {
|
func (m *defaultUsersModel) Insert(ctx context.Context, data *Users) (sql.Result, error) {
|
||||||
|
publicUsersEmailKey := fmt.Sprintf("%s%v", cachePublicUsersEmailPrefix, data.Email)
|
||||||
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, data.Phone)
|
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, data.Phone)
|
||||||
publicUsersUserIdKey := fmt.Sprintf("%s%v", cachePublicUsersUserIdPrefix, data.UserId)
|
publicUsersUserIdKey := fmt.Sprintf("%s%v", cachePublicUsersUserIdPrefix, data.UserId)
|
||||||
publicUsersUsernameKey := fmt.Sprintf("%s%v", cachePublicUsersUsernamePrefix, data.Username)
|
publicUsersUsernameKey := fmt.Sprintf("%s%v", cachePublicUsersUsernamePrefix, data.Username)
|
||||||
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := fmt.Sprintf("insert into %s (%s) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)", m.table, usersRowsExpectAutoSet)
|
query := fmt.Sprintf("insert into %s (%s) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", m.table, usersRowsExpectAutoSet)
|
||||||
return conn.ExecCtx(ctx, query, data.UserId, data.Username, data.Passwd, data.Nickname, data.Phone, data.RoleType, data.IsVerified, data.State, data.DeletedAt)
|
return conn.ExecCtx(ctx, query, data.UserId, data.Username, data.Passwd, data.Nickname, data.Phone, data.RoleType, data.IsVerified, data.State, data.DeletedAt, data.Email)
|
||||||
}, publicUsersPhoneKey, publicUsersUserIdKey, publicUsersUsernameKey)
|
}, publicUsersEmailKey, publicUsersPhoneKey, publicUsersUserIdKey, publicUsersUsernameKey)
|
||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,13 +181,14 @@ func (m *defaultUsersModel) Update(ctx context.Context, newData *Users) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publicUsersEmailKey := fmt.Sprintf("%s%v", cachePublicUsersEmailPrefix, data.Email)
|
||||||
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, data.Phone)
|
publicUsersPhoneKey := fmt.Sprintf("%s%v", cachePublicUsersPhonePrefix, data.Phone)
|
||||||
publicUsersUserIdKey := fmt.Sprintf("%s%v", cachePublicUsersUserIdPrefix, data.UserId)
|
publicUsersUserIdKey := fmt.Sprintf("%s%v", cachePublicUsersUserIdPrefix, data.UserId)
|
||||||
publicUsersUsernameKey := fmt.Sprintf("%s%v", cachePublicUsersUsernamePrefix, data.Username)
|
publicUsersUsernameKey := fmt.Sprintf("%s%v", cachePublicUsersUsernamePrefix, data.Username)
|
||||||
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||||
query := fmt.Sprintf("update %s set %s where user_id = $1", m.table, usersRowsWithPlaceHolder)
|
query := fmt.Sprintf("update %s set %s where user_id = $1", m.table, usersRowsWithPlaceHolder)
|
||||||
return conn.ExecCtx(ctx, query, newData.UserId, newData.Username, newData.Passwd, newData.Nickname, newData.Phone, newData.RoleType, newData.IsVerified, newData.State, newData.DeletedAt)
|
return conn.ExecCtx(ctx, query, newData.UserId, newData.Username, newData.Passwd, newData.Nickname, newData.Phone, newData.RoleType, newData.IsVerified, newData.State, newData.DeletedAt, newData.Email)
|
||||||
}, publicUsersPhoneKey, publicUsersUserIdKey, publicUsersUsernameKey)
|
}, publicUsersEmailKey, publicUsersPhoneKey, publicUsersUserIdKey, publicUsersUsernameKey)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +59,11 @@ func (s *UsercenterServer) Login(ctx context.Context, in *pb.LoginReq) (*pb.Logi
|
|||||||
return l.Login(in)
|
return l.Login(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UsercenterServer) Register(ctx context.Context, in *pb.RegisterReq) (*pb.RegisterResp, error) {
|
||||||
|
l := logic.NewRegisterLogic(ctx, s.svcCtx)
|
||||||
|
return l.Register(in)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UsercenterServer) ValidateToken(ctx context.Context, in *pb.ValidateTokenReq) (*pb.ValidateTokenResp, error) {
|
func (s *UsercenterServer) ValidateToken(ctx context.Context, in *pb.ValidateTokenReq) (*pb.ValidateTokenResp, error) {
|
||||||
l := logic.NewValidateTokenLogic(ctx, s.svcCtx)
|
l := logic.NewValidateTokenLogic(ctx, s.svcCtx)
|
||||||
return l.ValidateToken(in)
|
return l.ValidateToken(in)
|
||||||
@@ -68,3 +73,8 @@ func (s *UsercenterServer) CheckPermission(ctx context.Context, in *pb.CheckPerm
|
|||||||
l := logic.NewCheckPermissionLogic(ctx, s.svcCtx)
|
l := logic.NewCheckPermissionLogic(ctx, s.svcCtx)
|
||||||
return l.CheckPermission(in)
|
return l.CheckPermission(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UsercenterServer) Logout(ctx context.Context, in *pb.LogoutReq) (*pb.LogoutResp, error) {
|
||||||
|
l := logic.NewLogoutLogic(ctx, s.svcCtx)
|
||||||
|
return l.Logout(in)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JWKS (JSON Web Key Set) 结构
|
|
||||||
type JWKSKey struct {
|
|
||||||
Kty string `json:"kty"`
|
|
||||||
Use string `json:"use"`
|
|
||||||
Kid string `json:"kid"`
|
|
||||||
N string `json:"n,omitempty"`
|
|
||||||
E string `json:"e,omitempty"`
|
|
||||||
K string `json:"k,omitempty"` // 对称密钥
|
|
||||||
Alg string `json:"alg"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWKS struct {
|
|
||||||
Keys []JWKSKey `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateJWKSFromSecret 从密钥生成 JWKS(用于对称加密 HS256)
|
|
||||||
func GenerateJWKSFromSecret(secretKey string, keyID string) *JWKS {
|
|
||||||
// 对于 HS256,将密钥进行 base64 编码
|
|
||||||
encodedSecret := base64.RawURLEncoding.EncodeToString([]byte(secretKey))
|
|
||||||
|
|
||||||
return &JWKS{
|
|
||||||
Keys: []JWKSKey{
|
|
||||||
{
|
|
||||||
Kty: "oct",
|
|
||||||
Use: "sig",
|
|
||||||
Kid: keyID,
|
|
||||||
K: encodedSecret,
|
|
||||||
Alg: "HS256",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateJWKSEndpoint 生成可以被 Envoy 使用的 JWKS JSON
|
|
||||||
// 此端点应在 user-rpc 中暴露,URL 为 /.well-known/jwks.json
|
|
||||||
func GenerateJWKSEndpoint(secretKey string, keyID string) (string, error) {
|
|
||||||
if secretKey == "" {
|
|
||||||
return "", fmt.Errorf("secret key cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
jwks := GenerateJWKSFromSecret(secretKey, keyID)
|
|
||||||
|
|
||||||
jsonData, err := json.MarshalIndent(jwks, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(jsonData), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenPayload 令牌负载
|
|
||||||
type TokenMetadata struct {
|
|
||||||
IssuedAt time.Time
|
|
||||||
ExpiresAt time.Time
|
|
||||||
Subject string // userId
|
|
||||||
Issuer string
|
|
||||||
Audience string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractTokenMetadata 从 token 中提取元数据(不验证签名)
|
|
||||||
func ExtractTokenMetadata(tokenString string) (*TokenMetadata, error) {
|
|
||||||
token, _, err := new(jwt.Parser).ParseUnverified(tokenString, &Claims{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, ok := token.Claims.(*Claims)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid token claims type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TokenMetadata{
|
|
||||||
IssuedAt: claims.IssuedAt.Time,
|
|
||||||
ExpiresAt: claims.ExpiresAt.Time,
|
|
||||||
Subject: claims.UserId,
|
|
||||||
Issuer: claims.Issuer,
|
|
||||||
Audience: "", // 如果需要,可以增加到 Claims 中
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TokenPayload struct {
|
type TokenPayload struct {
|
||||||
UserId string
|
UserId int64
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ var (
|
|||||||
errInvalidToken = errors.New("invalid token claims")
|
errInvalidToken = errors.New("invalid token claims")
|
||||||
errTokenNotInCache = errors.New("token not found in cache")
|
errTokenNotInCache = errors.New("token not found in cache")
|
||||||
errNoRedisClient = errors.New("redis client not configured")
|
errNoRedisClient = errors.New("redis client not configured")
|
||||||
|
errInvalidUserID = errors.New("invalid user id")
|
||||||
// errExpiredToken = errors.New("token expired")
|
// errExpiredToken = errors.New("token expired")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,8 +76,7 @@ func (m *JwtManager) New(ctx context.Context, payload *TokenPayload) (string, er
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储 token 到 Redis,TTL 为 30 天
|
userKey := tokenCachePrefixUser + strconv.FormatInt(claims.UserId, 10)
|
||||||
userKey := tokenCachePrefixUser + payload.UserId
|
|
||||||
tokenKey := tokenCachePrefixToken + tokenString
|
tokenKey := tokenCachePrefixToken + tokenString
|
||||||
|
|
||||||
tokenData, _ := json.Marshal(payload)
|
tokenData, _ := json.Marshal(payload)
|
||||||
@@ -105,12 +106,12 @@ func (m *JwtManager) Valid(ctx context.Context, tokenString string) (*TokenPaylo
|
|||||||
// 检查 token 是否在 Redis 中
|
// 检查 token 是否在 Redis 中
|
||||||
tokenKey := tokenCachePrefixToken + tokenString
|
tokenKey := tokenCachePrefixToken + tokenString
|
||||||
tokenData, err := m.redisCluster.Get(ctx, tokenKey).Result()
|
tokenData, err := m.redisCluster.Get(ctx, tokenKey).Result()
|
||||||
if err != nil && err != redis.Nil {
|
if err != nil && !errors.Is(err, redis.Nil) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload TokenPayload
|
var payload TokenPayload
|
||||||
if err == redis.Nil {
|
if errors.Is(err, redis.Nil) {
|
||||||
return nil, errTokenNotInCache
|
return nil, errTokenNotInCache
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +126,20 @@ func (m *JwtManager) Valid(ctx context.Context, tokenString string) (*TokenPaylo
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
if _, renewErr := m.Renew(ctx, tokenString); renewErr != nil {
|
||||||
|
return nil, renewErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if token != nil {
|
||||||
|
if claims, ok := token.Claims.(*Claims); ok {
|
||||||
|
return &claims.TokenPayload, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +161,7 @@ func (m *JwtManager) Renew(ctx context.Context, tokenString string) (string, err
|
|||||||
tokenKey := tokenCachePrefixToken + tokenString
|
tokenKey := tokenCachePrefixToken + tokenString
|
||||||
tokenData, err := m.redisCluster.Get(ctx, tokenKey).Result()
|
tokenData, err := m.redisCluster.Get(ctx, tokenKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == redis.Nil {
|
if errors.Is(err, redis.Nil) {
|
||||||
return "", errTokenNotInCache
|
return "", errTokenNotInCache
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
@@ -159,15 +174,15 @@ func (m *JwtManager) Renew(ctx context.Context, tokenString string) (string, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除旧 token 记录
|
// 删除旧 token 记录
|
||||||
userKey := tokenCachePrefixUser + payload.UserId
|
userKey := tokenCachePrefixUser + strconv.FormatInt(payload.UserId, 10)
|
||||||
m.redisCluster.Del(ctx, tokenKey, userKey)
|
m.redisCluster.Del(ctx, tokenKey, userKey)
|
||||||
|
|
||||||
// 生成新 token
|
// 生成新 token
|
||||||
return m.New(ctx, &payload)
|
return m.New(ctx, &payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract payload from token without validating expiration (used for auto-renewal)
|
// Extract payload from token without validating expiration (used for auto-renewal)
|
||||||
func (m *JwtManager) Extract(ctx context.Context, tokenString string) (*TokenPayload, error) {
|
func (m *JwtManager) Extract(_ context.Context, tokenString string) (*TokenPayload, error) {
|
||||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
return []byte(m.secretKey), nil
|
return []byte(m.secretKey), nil
|
||||||
})
|
})
|
||||||
@@ -184,7 +199,7 @@ func (m *JwtManager) Extract(ctx context.Context, tokenString string) (*TokenPay
|
|||||||
return &claims.TokenPayload, nil
|
return &claims.TokenPayload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if token exists in Redis (i.e. is valid and not revoked)
|
// Exists check if token exists in Redis (i.e. is valid and not revoked)
|
||||||
func (m *JwtManager) Exists(ctx context.Context, tokenString string) (bool, error) {
|
func (m *JwtManager) Exists(ctx context.Context, tokenString string) (bool, error) {
|
||||||
if m.redisCluster == nil {
|
if m.redisCluster == nil {
|
||||||
return false, errNoRedisClient
|
return false, errNoRedisClient
|
||||||
@@ -199,12 +214,12 @@ func (m *JwtManager) Exists(ctx context.Context, tokenString string) (bool, erro
|
|||||||
return exists > 0, nil
|
return exists > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract payload from JWT claims
|
// ClaimsToPayload extract payload from JWT claims
|
||||||
func (m *JwtManager) ClaimsToPayload(claims *Claims) *TokenPayload {
|
func (m *JwtManager) ClaimsToPayload(claims *Claims) *TokenPayload {
|
||||||
return &claims.TokenPayload
|
return &claims.TokenPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
// revoke token by deleting both user -> token and token -> payload keys from Redis
|
// Revoke revoke token by deleting both user -> token and token -> payload keys from Redis
|
||||||
func (m *JwtManager) Revoke(ctx context.Context, tokenString string) error {
|
func (m *JwtManager) Revoke(ctx context.Context, tokenString string) error {
|
||||||
if m.redisCluster == nil {
|
if m.redisCluster == nil {
|
||||||
return errNoRedisClient
|
return errNoRedisClient
|
||||||
@@ -215,7 +230,7 @@ func (m *JwtManager) Revoke(ctx context.Context, tokenString string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
userKey := tokenCachePrefixUser + payload.UserId
|
userKey := tokenCachePrefixUser + strconv.FormatInt(payload.UserId, 10)
|
||||||
tokenKey := tokenCachePrefixToken + tokenString
|
tokenKey := tokenCachePrefixToken + tokenString
|
||||||
|
|
||||||
pipe := m.redisCluster.Pipeline()
|
pipe := m.redisCluster.Pipeline()
|
||||||
@@ -225,19 +240,48 @@ func (m *JwtManager) Revoke(ctx context.Context, tokenString string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *JwtManager) GetUserToken(ctx context.Context, userID string) (string, error) {
|
func (m *JwtManager) GetUserToken(ctx context.Context, userID int64) (string, error) {
|
||||||
if m.redisCluster == nil {
|
if m.redisCluster == nil {
|
||||||
return "", errNoRedisClient
|
return "", errNoRedisClient
|
||||||
}
|
}
|
||||||
|
//userID, err := strconv.FormatInt(userID, 10)
|
||||||
|
id := strconv.FormatInt(userID, 10)
|
||||||
|
|
||||||
userKey := tokenCachePrefixUser + userID
|
userKey := tokenCachePrefixUser + id
|
||||||
token, err := m.redisCluster.Get(ctx, userKey).Result()
|
token, err := m.redisCluster.Get(ctx, userKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == redis.Nil {
|
if errors.Is(err, redis.Nil) {
|
||||||
return "", fmt.Errorf("user %s has no token", userID)
|
return "", fmt.Errorf("user %v has no token", userID)
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logout 按用户登出:删除 user->token 和 token->payload 两类缓存数据
|
||||||
|
func (m *JwtManager) Logout(ctx context.Context, userID int64) error {
|
||||||
|
if m.redisCluster == nil {
|
||||||
|
return errNoRedisClient
|
||||||
|
}
|
||||||
|
|
||||||
|
if userID <= 0 {
|
||||||
|
return errInvalidUserID
|
||||||
|
}
|
||||||
|
|
||||||
|
userKey := tokenCachePrefixUser + strconv.FormatInt(userID, 10)
|
||||||
|
tokenString, err := m.redisCluster.Get(ctx, userKey).Result()
|
||||||
|
if err != nil && !errors.Is(err, redis.Nil) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe := m.redisCluster.Pipeline()
|
||||||
|
pipe.Del(ctx, userKey)
|
||||||
|
if !errors.Is(err, redis.Nil) && tokenString != "" {
|
||||||
|
tokenKey := tokenCachePrefixToken + tokenString
|
||||||
|
pipe.Del(ctx, tokenKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, execErr := pipe.Exec(ctx)
|
||||||
|
return execErr
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"juwan-backend/app/users/rpc/internal/svc"
|
"juwan-backend/app/users/rpc/internal/svc"
|
||||||
"juwan-backend/app/users/rpc/pb"
|
"juwan-backend/app/users/rpc/pb"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
"github.com/zeromicro/go-zero/core/conf"
|
"github.com/zeromicro/go-zero/core/conf"
|
||||||
"github.com/zeromicro/go-zero/core/service"
|
"github.com/zeromicro/go-zero/core/service"
|
||||||
"github.com/zeromicro/go-zero/zrpc"
|
"github.com/zeromicro/go-zero/zrpc"
|
||||||
|
|||||||
+309
-51
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.36.9
|
// protoc-gen-go v1.36.11
|
||||||
// protoc v6.32.0
|
// protoc v5.29.6
|
||||||
// source: users.proto
|
// source: users.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
@@ -24,7 +24,7 @@ const (
|
|||||||
// --------------------------------users--------------------------------
|
// --------------------------------users--------------------------------
|
||||||
type Users struct {
|
type Users struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
||||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` //username
|
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` //username
|
||||||
Passwd string `protobuf:"bytes,3,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
Passwd string `protobuf:"bytes,3,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
||||||
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
||||||
@@ -69,11 +69,11 @@ func (*Users) Descriptor() ([]byte, []int) {
|
|||||||
return file_users_proto_rawDescGZIP(), []int{0}
|
return file_users_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Users) GetUserId() string {
|
func (x *Users) GetUserId() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UserId
|
return x.UserId
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Users) GetUsername() string {
|
func (x *Users) GetUsername() string {
|
||||||
@@ -148,7 +148,7 @@ func (x *Users) GetDeletedAt() int64 {
|
|||||||
|
|
||||||
type AddUsersReq struct {
|
type AddUsersReq struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
||||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` //username
|
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` //username
|
||||||
Passwd string `protobuf:"bytes,3,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
Passwd string `protobuf:"bytes,3,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
||||||
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
||||||
@@ -193,11 +193,11 @@ func (*AddUsersReq) Descriptor() ([]byte, []int) {
|
|||||||
return file_users_proto_rawDescGZIP(), []int{1}
|
return file_users_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddUsersReq) GetUserId() string {
|
func (x *AddUsersReq) GetUserId() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UserId
|
return x.UserId
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *AddUsersReq) GetUsername() string {
|
func (x *AddUsersReq) GetUsername() string {
|
||||||
@@ -308,7 +308,7 @@ func (*AddUsersResp) Descriptor() ([]byte, []int) {
|
|||||||
|
|
||||||
type UpdateUsersReq struct {
|
type UpdateUsersReq struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
||||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` //username
|
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` //username
|
||||||
Passwd string `protobuf:"bytes,3,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
Passwd string `protobuf:"bytes,3,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
||||||
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
Nickname string `protobuf:"bytes,4,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
||||||
@@ -353,11 +353,11 @@ func (*UpdateUsersReq) Descriptor() ([]byte, []int) {
|
|||||||
return file_users_proto_rawDescGZIP(), []int{3}
|
return file_users_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *UpdateUsersReq) GetUserId() string {
|
func (x *UpdateUsersReq) GetUserId() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UserId
|
return x.UserId
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *UpdateUsersReq) GetUsername() string {
|
func (x *UpdateUsersReq) GetUsername() string {
|
||||||
@@ -638,7 +638,7 @@ type SearchUsersReq struct {
|
|||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` //page
|
Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` //page
|
||||||
Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` //limit
|
Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` //limit
|
||||||
UserId string `protobuf:"bytes,3,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
UserId int64 `protobuf:"varint,3,opt,name=userId,proto3" json:"userId,omitempty"` //userId
|
||||||
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` //username
|
Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` //username
|
||||||
Passwd string `protobuf:"bytes,5,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
Passwd string `protobuf:"bytes,5,opt,name=passwd,proto3" json:"passwd,omitempty"` //passwd
|
||||||
Nickname string `protobuf:"bytes,6,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
Nickname string `protobuf:"bytes,6,opt,name=nickname,proto3" json:"nickname,omitempty"` //nickname
|
||||||
@@ -697,11 +697,11 @@ func (x *SearchUsersReq) GetLimit() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SearchUsersReq) GetUserId() string {
|
func (x *SearchUsersReq) GetUserId() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UserId
|
return x.UserId
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *SearchUsersReq) GetUsername() string {
|
func (x *SearchUsersReq) GetUsername() string {
|
||||||
@@ -961,6 +961,9 @@ func (x *LoginReq) GetPasswd() string {
|
|||||||
type LoginResp struct {
|
type LoginResp struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
|
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
|
||||||
|
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
|
||||||
|
Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
|
||||||
|
Id int64 `protobuf:"varint,4,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -1002,10 +1005,31 @@ func (x *LoginResp) GetToken() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LoginResp) GetUsername() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Username
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginResp) GetEmail() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Email
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LoginResp) GetId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
type ValidateTokenReq struct {
|
type ValidateTokenReq struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` // JWT token
|
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` // JWT token
|
||||||
UserId string `protobuf:"bytes,2,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID
|
UserId int64 `protobuf:"varint,2,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -1047,18 +1071,18 @@ func (x *ValidateTokenReq) GetToken() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ValidateTokenReq) GetUserId() string {
|
func (x *ValidateTokenReq) GetUserId() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UserId
|
return x.UserId
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidateTokenResp struct {
|
type ValidateTokenResp struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` // token 是否有效(不在黑名单中)
|
Valid bool `protobuf:"varint,1,opt,name=valid,proto3" json:"valid,omitempty"` // token 是否有效(不在黑名单中)
|
||||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 验证失败原因
|
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 验证失败原因
|
||||||
UserId string `protobuf:"bytes,3,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID
|
UserId int64 `protobuf:"varint,3,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID
|
||||||
RoleType int64 `protobuf:"varint,4,opt,name=roleType,proto3" json:"roleType,omitempty"` // 用户角色
|
RoleType int64 `protobuf:"varint,4,opt,name=roleType,proto3" json:"roleType,omitempty"` // 用户角色
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@@ -1108,11 +1132,11 @@ func (x *ValidateTokenResp) GetMessage() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ValidateTokenResp) GetUserId() string {
|
func (x *ValidateTokenResp) GetUserId() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UserId
|
return x.UserId
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ValidateTokenResp) GetRoleType() int64 {
|
func (x *ValidateTokenResp) GetRoleType() int64 {
|
||||||
@@ -1124,7 +1148,7 @@ func (x *ValidateTokenResp) GetRoleType() int64 {
|
|||||||
|
|
||||||
type CheckPermissionReq struct {
|
type CheckPermissionReq struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID
|
UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` // 用户ID
|
||||||
Resource string `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` // 资源 ID
|
Resource string `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` // 资源 ID
|
||||||
Action string `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"` // 操作类型: read/write/delete
|
Action string `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"` // 操作类型: read/write/delete
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
@@ -1161,11 +1185,11 @@ func (*CheckPermissionReq) Descriptor() ([]byte, []int) {
|
|||||||
return file_users_proto_rawDescGZIP(), []int{17}
|
return file_users_proto_rawDescGZIP(), []int{17}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CheckPermissionReq) GetUserId() string {
|
func (x *CheckPermissionReq) GetUserId() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UserId
|
return x.UserId
|
||||||
}
|
}
|
||||||
return ""
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CheckPermissionReq) GetResource() string {
|
func (x *CheckPermissionReq) GetResource() string {
|
||||||
@@ -1234,13 +1258,221 @@ func (x *CheckPermissionResp) GetMessage() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RegisterReq struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
|
||||||
|
Passwd string `protobuf:"bytes,2,opt,name=passwd,proto3" json:"passwd,omitempty"`
|
||||||
|
Phone string `protobuf:"bytes,3,opt,name=phone,proto3" json:"phone,omitempty"`
|
||||||
|
Vcode int32 `protobuf:"varint,4,opt,name=vcode,proto3" json:"vcode,omitempty"`
|
||||||
|
Email string `protobuf:"bytes,5,opt,name=email,proto3" json:"email,omitempty"`
|
||||||
|
RequestId string `protobuf:"bytes,6,opt,name=requestId,proto3" json:"requestId,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) Reset() {
|
||||||
|
*x = RegisterReq{}
|
||||||
|
mi := &file_users_proto_msgTypes[19]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RegisterReq) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RegisterReq) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_users_proto_msgTypes[19]
|
||||||
|
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 RegisterReq.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RegisterReq) Descriptor() ([]byte, []int) {
|
||||||
|
return file_users_proto_rawDescGZIP(), []int{19}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) GetUsername() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Username
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) GetPasswd() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Passwd
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) GetPhone() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Phone
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) GetVcode() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Vcode
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) GetEmail() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Email
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterReq) GetRequestId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RequestId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterResp struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Res string `protobuf:"bytes,1,opt,name=res,proto3" json:"res,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterResp) Reset() {
|
||||||
|
*x = RegisterResp{}
|
||||||
|
mi := &file_users_proto_msgTypes[20]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterResp) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RegisterResp) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RegisterResp) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_users_proto_msgTypes[20]
|
||||||
|
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 RegisterResp.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RegisterResp) Descriptor() ([]byte, []int) {
|
||||||
|
return file_users_proto_rawDescGZIP(), []int{20}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RegisterResp) GetRes() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Res
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogoutReq struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UserId int64 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogoutReq) Reset() {
|
||||||
|
*x = LogoutReq{}
|
||||||
|
mi := &file_users_proto_msgTypes[21]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogoutReq) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogoutReq) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogoutReq) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_users_proto_msgTypes[21]
|
||||||
|
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 LogoutReq.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogoutReq) Descriptor() ([]byte, []int) {
|
||||||
|
return file_users_proto_rawDescGZIP(), []int{21}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogoutReq) GetUserId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogoutResp struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogoutResp) Reset() {
|
||||||
|
*x = LogoutResp{}
|
||||||
|
mi := &file_users_proto_msgTypes[22]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LogoutResp) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LogoutResp) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LogoutResp) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_users_proto_msgTypes[22]
|
||||||
|
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 LogoutResp.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LogoutResp) Descriptor() ([]byte, []int) {
|
||||||
|
return file_users_proto_rawDescGZIP(), []int{22}
|
||||||
|
}
|
||||||
|
|
||||||
var File_users_proto protoreflect.FileDescriptor
|
var File_users_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_users_proto_rawDesc = "" +
|
const file_users_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\vusers.proto\x12\x02pb\"\xb1\x02\n" +
|
"\vusers.proto\x12\x02pb\"\xb1\x02\n" +
|
||||||
"\x05Users\x12\x16\n" +
|
"\x05Users\x12\x16\n" +
|
||||||
"\x06userId\x18\x01 \x01(\tR\x06userId\x12\x1a\n" +
|
"\x06userId\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" +
|
||||||
"\busername\x18\x02 \x01(\tR\busername\x12\x16\n" +
|
"\busername\x18\x02 \x01(\tR\busername\x12\x16\n" +
|
||||||
"\x06passwd\x18\x03 \x01(\tR\x06passwd\x12\x1a\n" +
|
"\x06passwd\x18\x03 \x01(\tR\x06passwd\x12\x1a\n" +
|
||||||
"\bnickname\x18\x04 \x01(\tR\bnickname\x12\x14\n" +
|
"\bnickname\x18\x04 \x01(\tR\bnickname\x12\x14\n" +
|
||||||
@@ -1255,7 +1487,7 @@ const file_users_proto_rawDesc = "" +
|
|||||||
" \x01(\x03R\tupdatedAt\x12\x1c\n" +
|
" \x01(\x03R\tupdatedAt\x12\x1c\n" +
|
||||||
"\tdeletedAt\x18\v \x01(\x03R\tdeletedAt\"\xb7\x02\n" +
|
"\tdeletedAt\x18\v \x01(\x03R\tdeletedAt\"\xb7\x02\n" +
|
||||||
"\vAddUsersReq\x12\x16\n" +
|
"\vAddUsersReq\x12\x16\n" +
|
||||||
"\x06userId\x18\x01 \x01(\tR\x06userId\x12\x1a\n" +
|
"\x06userId\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" +
|
||||||
"\busername\x18\x02 \x01(\tR\busername\x12\x16\n" +
|
"\busername\x18\x02 \x01(\tR\busername\x12\x16\n" +
|
||||||
"\x06passwd\x18\x03 \x01(\tR\x06passwd\x12\x1a\n" +
|
"\x06passwd\x18\x03 \x01(\tR\x06passwd\x12\x1a\n" +
|
||||||
"\bnickname\x18\x04 \x01(\tR\bnickname\x12\x14\n" +
|
"\bnickname\x18\x04 \x01(\tR\bnickname\x12\x14\n" +
|
||||||
@@ -1271,7 +1503,7 @@ const file_users_proto_rawDesc = "" +
|
|||||||
"\tdeletedAt\x18\v \x01(\x03R\tdeletedAt\"\x0e\n" +
|
"\tdeletedAt\x18\v \x01(\x03R\tdeletedAt\"\x0e\n" +
|
||||||
"\fAddUsersResp\"\xba\x02\n" +
|
"\fAddUsersResp\"\xba\x02\n" +
|
||||||
"\x0eUpdateUsersReq\x12\x16\n" +
|
"\x0eUpdateUsersReq\x12\x16\n" +
|
||||||
"\x06userId\x18\x01 \x01(\tR\x06userId\x12\x1a\n" +
|
"\x06userId\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" +
|
||||||
"\busername\x18\x02 \x01(\tR\busername\x12\x16\n" +
|
"\busername\x18\x02 \x01(\tR\busername\x12\x16\n" +
|
||||||
"\x06passwd\x18\x03 \x01(\tR\x06passwd\x12\x1a\n" +
|
"\x06passwd\x18\x03 \x01(\tR\x06passwd\x12\x1a\n" +
|
||||||
"\bnickname\x18\x04 \x01(\tR\bnickname\x12\x14\n" +
|
"\bnickname\x18\x04 \x01(\tR\bnickname\x12\x14\n" +
|
||||||
@@ -1296,7 +1528,7 @@ const file_users_proto_rawDesc = "" +
|
|||||||
"\x0eSearchUsersReq\x12\x12\n" +
|
"\x0eSearchUsersReq\x12\x12\n" +
|
||||||
"\x04page\x18\x01 \x01(\x03R\x04page\x12\x14\n" +
|
"\x04page\x18\x01 \x01(\x03R\x04page\x12\x14\n" +
|
||||||
"\x05limit\x18\x02 \x01(\x03R\x05limit\x12\x16\n" +
|
"\x05limit\x18\x02 \x01(\x03R\x05limit\x12\x16\n" +
|
||||||
"\x06userId\x18\x03 \x01(\tR\x06userId\x12\x1a\n" +
|
"\x06userId\x18\x03 \x01(\x03R\x06userId\x12\x1a\n" +
|
||||||
"\busername\x18\x04 \x01(\tR\busername\x12\x16\n" +
|
"\busername\x18\x04 \x01(\tR\busername\x12\x16\n" +
|
||||||
"\x06passwd\x18\x05 \x01(\tR\x06passwd\x12\x1a\n" +
|
"\x06passwd\x18\x05 \x01(\tR\x06passwd\x12\x1a\n" +
|
||||||
"\bnickname\x18\x06 \x01(\tR\bnickname\x12\x14\n" +
|
"\bnickname\x18\x06 \x01(\tR\bnickname\x12\x14\n" +
|
||||||
@@ -1318,24 +1550,40 @@ const file_users_proto_rawDesc = "" +
|
|||||||
"\x05users\x18\x01 \x01(\v2\t.pb.UsersR\x05users\">\n" +
|
"\x05users\x18\x01 \x01(\v2\t.pb.UsersR\x05users\">\n" +
|
||||||
"\bLoginReq\x12\x1a\n" +
|
"\bLoginReq\x12\x1a\n" +
|
||||||
"\busername\x18\x01 \x01(\tR\busername\x12\x16\n" +
|
"\busername\x18\x01 \x01(\tR\busername\x12\x16\n" +
|
||||||
"\x06passwd\x18\x02 \x01(\tR\x06passwd\"!\n" +
|
"\x06passwd\x18\x02 \x01(\tR\x06passwd\"c\n" +
|
||||||
"\tLoginResp\x12\x14\n" +
|
"\tLoginResp\x12\x14\n" +
|
||||||
"\x05token\x18\x01 \x01(\tR\x05token\"@\n" +
|
"\x05token\x18\x01 \x01(\tR\x05token\x12\x1a\n" +
|
||||||
|
"\busername\x18\x02 \x01(\tR\busername\x12\x14\n" +
|
||||||
|
"\x05email\x18\x03 \x01(\tR\x05email\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x04 \x01(\x03R\x02id\"@\n" +
|
||||||
"\x10ValidateTokenReq\x12\x14\n" +
|
"\x10ValidateTokenReq\x12\x14\n" +
|
||||||
"\x05token\x18\x01 \x01(\tR\x05token\x12\x16\n" +
|
"\x05token\x18\x01 \x01(\tR\x05token\x12\x16\n" +
|
||||||
"\x06userId\x18\x02 \x01(\tR\x06userId\"w\n" +
|
"\x06userId\x18\x02 \x01(\x03R\x06userId\"w\n" +
|
||||||
"\x11ValidateTokenResp\x12\x14\n" +
|
"\x11ValidateTokenResp\x12\x14\n" +
|
||||||
"\x05valid\x18\x01 \x01(\bR\x05valid\x12\x18\n" +
|
"\x05valid\x18\x01 \x01(\bR\x05valid\x12\x18\n" +
|
||||||
"\amessage\x18\x02 \x01(\tR\amessage\x12\x16\n" +
|
"\amessage\x18\x02 \x01(\tR\amessage\x12\x16\n" +
|
||||||
"\x06userId\x18\x03 \x01(\tR\x06userId\x12\x1a\n" +
|
"\x06userId\x18\x03 \x01(\x03R\x06userId\x12\x1a\n" +
|
||||||
"\broleType\x18\x04 \x01(\x03R\broleType\"`\n" +
|
"\broleType\x18\x04 \x01(\x03R\broleType\"`\n" +
|
||||||
"\x12CheckPermissionReq\x12\x16\n" +
|
"\x12CheckPermissionReq\x12\x16\n" +
|
||||||
"\x06userId\x18\x01 \x01(\tR\x06userId\x12\x1a\n" +
|
"\x06userId\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" +
|
||||||
"\bresource\x18\x02 \x01(\tR\bresource\x12\x16\n" +
|
"\bresource\x18\x02 \x01(\tR\bresource\x12\x16\n" +
|
||||||
"\x06action\x18\x03 \x01(\tR\x06action\"I\n" +
|
"\x06action\x18\x03 \x01(\tR\x06action\"I\n" +
|
||||||
"\x13CheckPermissionResp\x12\x18\n" +
|
"\x13CheckPermissionResp\x12\x18\n" +
|
||||||
"\aallowed\x18\x01 \x01(\bR\aallowed\x12\x18\n" +
|
"\aallowed\x18\x01 \x01(\bR\aallowed\x12\x18\n" +
|
||||||
"\amessage\x18\x02 \x01(\tR\amessage2\x87\x04\n" +
|
"\amessage\x18\x02 \x01(\tR\amessage\"\xa1\x01\n" +
|
||||||
|
"\vRegisterReq\x12\x1a\n" +
|
||||||
|
"\busername\x18\x01 \x01(\tR\busername\x12\x16\n" +
|
||||||
|
"\x06passwd\x18\x02 \x01(\tR\x06passwd\x12\x14\n" +
|
||||||
|
"\x05phone\x18\x03 \x01(\tR\x05phone\x12\x14\n" +
|
||||||
|
"\x05vcode\x18\x04 \x01(\x05R\x05vcode\x12\x14\n" +
|
||||||
|
"\x05email\x18\x05 \x01(\tR\x05email\x12\x1c\n" +
|
||||||
|
"\trequestId\x18\x06 \x01(\tR\trequestId\" \n" +
|
||||||
|
"\fRegisterResp\x12\x10\n" +
|
||||||
|
"\x03res\x18\x01 \x01(\tR\x03res\"#\n" +
|
||||||
|
"\tLogoutReq\x12\x16\n" +
|
||||||
|
"\x06userId\x18\x01 \x01(\x03R\x06userId\"\f\n" +
|
||||||
|
"\n" +
|
||||||
|
"LogoutResp2\xdf\x04\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"usercenter\x12-\n" +
|
"usercenter\x12-\n" +
|
||||||
"\bAddUsers\x12\x0f.pb.AddUsersReq\x1a\x10.pb.AddUsersResp\x126\n" +
|
"\bAddUsers\x12\x0f.pb.AddUsersReq\x1a\x10.pb.AddUsersResp\x126\n" +
|
||||||
@@ -1344,9 +1592,11 @@ const file_users_proto_rawDesc = "" +
|
|||||||
"\fGetUsersById\x12\x13.pb.GetUsersByIdReq\x1a\x14.pb.GetUsersByIdResp\x12H\n" +
|
"\fGetUsersById\x12\x13.pb.GetUsersByIdReq\x1a\x14.pb.GetUsersByIdResp\x12H\n" +
|
||||||
"\x11GetUserByUsername\x12\x18.pb.GetUserByUsernameReq\x1a\x19.pb.GetUserByUsernameResp\x126\n" +
|
"\x11GetUserByUsername\x12\x18.pb.GetUserByUsernameReq\x1a\x19.pb.GetUserByUsernameResp\x126\n" +
|
||||||
"\vSearchUsers\x12\x12.pb.SearchUsersReq\x1a\x13.pb.SearchUsersResp\x12$\n" +
|
"\vSearchUsers\x12\x12.pb.SearchUsersReq\x1a\x13.pb.SearchUsersResp\x12$\n" +
|
||||||
"\x05Login\x12\f.pb.LoginReq\x1a\r.pb.LoginResp\x12<\n" +
|
"\x05Login\x12\f.pb.LoginReq\x1a\r.pb.LoginResp\x12-\n" +
|
||||||
|
"\bRegister\x12\x0f.pb.RegisterReq\x1a\x10.pb.RegisterResp\x12<\n" +
|
||||||
"\rValidateToken\x12\x14.pb.ValidateTokenReq\x1a\x15.pb.ValidateTokenResp\x12B\n" +
|
"\rValidateToken\x12\x14.pb.ValidateTokenReq\x1a\x15.pb.ValidateTokenResp\x12B\n" +
|
||||||
"\x0fCheckPermission\x12\x16.pb.CheckPermissionReq\x1a\x17.pb.CheckPermissionRespB\x06Z\x04./pbb\x06proto3"
|
"\x0fCheckPermission\x12\x16.pb.CheckPermissionReq\x1a\x17.pb.CheckPermissionResp\x12'\n" +
|
||||||
|
"\x06Logout\x12\r.pb.LogoutReq\x1a\x0e.pb.LogoutRespB\x06Z\x04./pbb\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
file_users_proto_rawDescOnce sync.Once
|
file_users_proto_rawDescOnce sync.Once
|
||||||
@@ -1360,7 +1610,7 @@ func file_users_proto_rawDescGZIP() []byte {
|
|||||||
return file_users_proto_rawDescData
|
return file_users_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_users_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
|
var file_users_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
|
||||||
var file_users_proto_goTypes = []any{
|
var file_users_proto_goTypes = []any{
|
||||||
(*Users)(nil), // 0: pb.Users
|
(*Users)(nil), // 0: pb.Users
|
||||||
(*AddUsersReq)(nil), // 1: pb.AddUsersReq
|
(*AddUsersReq)(nil), // 1: pb.AddUsersReq
|
||||||
@@ -1381,6 +1631,10 @@ var file_users_proto_goTypes = []any{
|
|||||||
(*ValidateTokenResp)(nil), // 16: pb.ValidateTokenResp
|
(*ValidateTokenResp)(nil), // 16: pb.ValidateTokenResp
|
||||||
(*CheckPermissionReq)(nil), // 17: pb.CheckPermissionReq
|
(*CheckPermissionReq)(nil), // 17: pb.CheckPermissionReq
|
||||||
(*CheckPermissionResp)(nil), // 18: pb.CheckPermissionResp
|
(*CheckPermissionResp)(nil), // 18: pb.CheckPermissionResp
|
||||||
|
(*RegisterReq)(nil), // 19: pb.RegisterReq
|
||||||
|
(*RegisterResp)(nil), // 20: pb.RegisterResp
|
||||||
|
(*LogoutReq)(nil), // 21: pb.LogoutReq
|
||||||
|
(*LogoutResp)(nil), // 22: pb.LogoutResp
|
||||||
}
|
}
|
||||||
var file_users_proto_depIdxs = []int32{
|
var file_users_proto_depIdxs = []int32{
|
||||||
0, // 0: pb.GetUsersByIdResp.users:type_name -> pb.Users
|
0, // 0: pb.GetUsersByIdResp.users:type_name -> pb.Users
|
||||||
@@ -1393,19 +1647,23 @@ var file_users_proto_depIdxs = []int32{
|
|||||||
11, // 7: pb.usercenter.GetUserByUsername:input_type -> pb.GetUserByUsernameReq
|
11, // 7: pb.usercenter.GetUserByUsername:input_type -> pb.GetUserByUsernameReq
|
||||||
9, // 8: pb.usercenter.SearchUsers:input_type -> pb.SearchUsersReq
|
9, // 8: pb.usercenter.SearchUsers:input_type -> pb.SearchUsersReq
|
||||||
13, // 9: pb.usercenter.Login:input_type -> pb.LoginReq
|
13, // 9: pb.usercenter.Login:input_type -> pb.LoginReq
|
||||||
15, // 10: pb.usercenter.ValidateToken:input_type -> pb.ValidateTokenReq
|
19, // 10: pb.usercenter.Register:input_type -> pb.RegisterReq
|
||||||
17, // 11: pb.usercenter.CheckPermission:input_type -> pb.CheckPermissionReq
|
15, // 11: pb.usercenter.ValidateToken:input_type -> pb.ValidateTokenReq
|
||||||
2, // 12: pb.usercenter.AddUsers:output_type -> pb.AddUsersResp
|
17, // 12: pb.usercenter.CheckPermission:input_type -> pb.CheckPermissionReq
|
||||||
4, // 13: pb.usercenter.UpdateUsers:output_type -> pb.UpdateUsersResp
|
21, // 13: pb.usercenter.Logout:input_type -> pb.LogoutReq
|
||||||
6, // 14: pb.usercenter.DelUsers:output_type -> pb.DelUsersResp
|
2, // 14: pb.usercenter.AddUsers:output_type -> pb.AddUsersResp
|
||||||
8, // 15: pb.usercenter.GetUsersById:output_type -> pb.GetUsersByIdResp
|
4, // 15: pb.usercenter.UpdateUsers:output_type -> pb.UpdateUsersResp
|
||||||
12, // 16: pb.usercenter.GetUserByUsername:output_type -> pb.GetUserByUsernameResp
|
6, // 16: pb.usercenter.DelUsers:output_type -> pb.DelUsersResp
|
||||||
10, // 17: pb.usercenter.SearchUsers:output_type -> pb.SearchUsersResp
|
8, // 17: pb.usercenter.GetUsersById:output_type -> pb.GetUsersByIdResp
|
||||||
14, // 18: pb.usercenter.Login:output_type -> pb.LoginResp
|
12, // 18: pb.usercenter.GetUserByUsername:output_type -> pb.GetUserByUsernameResp
|
||||||
16, // 19: pb.usercenter.ValidateToken:output_type -> pb.ValidateTokenResp
|
10, // 19: pb.usercenter.SearchUsers:output_type -> pb.SearchUsersResp
|
||||||
18, // 20: pb.usercenter.CheckPermission:output_type -> pb.CheckPermissionResp
|
14, // 20: pb.usercenter.Login:output_type -> pb.LoginResp
|
||||||
12, // [12:21] is the sub-list for method output_type
|
20, // 21: pb.usercenter.Register:output_type -> pb.RegisterResp
|
||||||
3, // [3:12] is the sub-list for method input_type
|
16, // 22: pb.usercenter.ValidateToken:output_type -> pb.ValidateTokenResp
|
||||||
|
18, // 23: pb.usercenter.CheckPermission:output_type -> pb.CheckPermissionResp
|
||||||
|
22, // 24: pb.usercenter.Logout:output_type -> pb.LogoutResp
|
||||||
|
14, // [14:25] is the sub-list for method output_type
|
||||||
|
3, // [3:14] is the sub-list for method input_type
|
||||||
3, // [3:3] is the sub-list for extension type_name
|
3, // [3:3] is the sub-list for extension type_name
|
||||||
3, // [3:3] is the sub-list for extension extendee
|
3, // [3:3] is the sub-list for extension extendee
|
||||||
0, // [0:3] is the sub-list for field type_name
|
0, // [0:3] is the sub-list for field type_name
|
||||||
@@ -1422,7 +1680,7 @@ func file_users_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_users_proto_rawDesc), len(file_users_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_users_proto_rawDesc), len(file_users_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 19,
|
NumMessages: 23,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
// - protoc v6.32.0
|
// - protoc v5.29.6
|
||||||
// source: users.proto
|
// source: users.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
@@ -26,8 +26,10 @@ const (
|
|||||||
Usercenter_GetUserByUsername_FullMethodName = "/pb.usercenter/GetUserByUsername"
|
Usercenter_GetUserByUsername_FullMethodName = "/pb.usercenter/GetUserByUsername"
|
||||||
Usercenter_SearchUsers_FullMethodName = "/pb.usercenter/SearchUsers"
|
Usercenter_SearchUsers_FullMethodName = "/pb.usercenter/SearchUsers"
|
||||||
Usercenter_Login_FullMethodName = "/pb.usercenter/Login"
|
Usercenter_Login_FullMethodName = "/pb.usercenter/Login"
|
||||||
|
Usercenter_Register_FullMethodName = "/pb.usercenter/Register"
|
||||||
Usercenter_ValidateToken_FullMethodName = "/pb.usercenter/ValidateToken"
|
Usercenter_ValidateToken_FullMethodName = "/pb.usercenter/ValidateToken"
|
||||||
Usercenter_CheckPermission_FullMethodName = "/pb.usercenter/CheckPermission"
|
Usercenter_CheckPermission_FullMethodName = "/pb.usercenter/CheckPermission"
|
||||||
|
Usercenter_Logout_FullMethodName = "/pb.usercenter/Logout"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsercenterClient is the client API for Usercenter service.
|
// UsercenterClient is the client API for Usercenter service.
|
||||||
@@ -42,8 +44,10 @@ type UsercenterClient interface {
|
|||||||
GetUserByUsername(ctx context.Context, in *GetUserByUsernameReq, opts ...grpc.CallOption) (*GetUserByUsernameResp, error)
|
GetUserByUsername(ctx context.Context, in *GetUserByUsernameReq, opts ...grpc.CallOption) (*GetUserByUsernameResp, error)
|
||||||
SearchUsers(ctx context.Context, in *SearchUsersReq, opts ...grpc.CallOption) (*SearchUsersResp, error)
|
SearchUsers(ctx context.Context, in *SearchUsersReq, opts ...grpc.CallOption) (*SearchUsersResp, error)
|
||||||
Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error)
|
Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error)
|
||||||
|
Register(ctx context.Context, in *RegisterReq, opts ...grpc.CallOption) (*RegisterResp, error)
|
||||||
ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error)
|
ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error)
|
||||||
CheckPermission(ctx context.Context, in *CheckPermissionReq, opts ...grpc.CallOption) (*CheckPermissionResp, error)
|
CheckPermission(ctx context.Context, in *CheckPermissionReq, opts ...grpc.CallOption) (*CheckPermissionResp, error)
|
||||||
|
Logout(ctx context.Context, in *LogoutReq, opts ...grpc.CallOption) (*LogoutResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type usercenterClient struct {
|
type usercenterClient struct {
|
||||||
@@ -124,6 +128,16 @@ func (c *usercenterClient) Login(ctx context.Context, in *LoginReq, opts ...grpc
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *usercenterClient) Register(ctx context.Context, in *RegisterReq, opts ...grpc.CallOption) (*RegisterResp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(RegisterResp)
|
||||||
|
err := c.cc.Invoke(ctx, Usercenter_Register_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *usercenterClient) ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error) {
|
func (c *usercenterClient) ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
out := new(ValidateTokenResp)
|
out := new(ValidateTokenResp)
|
||||||
@@ -144,6 +158,16 @@ func (c *usercenterClient) CheckPermission(ctx context.Context, in *CheckPermiss
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *usercenterClient) Logout(ctx context.Context, in *LogoutReq, opts ...grpc.CallOption) (*LogoutResp, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(LogoutResp)
|
||||||
|
err := c.cc.Invoke(ctx, Usercenter_Logout_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UsercenterServer is the server API for Usercenter service.
|
// UsercenterServer is the server API for Usercenter service.
|
||||||
// All implementations must embed UnimplementedUsercenterServer
|
// All implementations must embed UnimplementedUsercenterServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -156,8 +180,10 @@ type UsercenterServer interface {
|
|||||||
GetUserByUsername(context.Context, *GetUserByUsernameReq) (*GetUserByUsernameResp, error)
|
GetUserByUsername(context.Context, *GetUserByUsernameReq) (*GetUserByUsernameResp, error)
|
||||||
SearchUsers(context.Context, *SearchUsersReq) (*SearchUsersResp, error)
|
SearchUsers(context.Context, *SearchUsersReq) (*SearchUsersResp, error)
|
||||||
Login(context.Context, *LoginReq) (*LoginResp, error)
|
Login(context.Context, *LoginReq) (*LoginResp, error)
|
||||||
|
Register(context.Context, *RegisterReq) (*RegisterResp, error)
|
||||||
ValidateToken(context.Context, *ValidateTokenReq) (*ValidateTokenResp, error)
|
ValidateToken(context.Context, *ValidateTokenReq) (*ValidateTokenResp, error)
|
||||||
CheckPermission(context.Context, *CheckPermissionReq) (*CheckPermissionResp, error)
|
CheckPermission(context.Context, *CheckPermissionReq) (*CheckPermissionResp, error)
|
||||||
|
Logout(context.Context, *LogoutReq) (*LogoutResp, error)
|
||||||
mustEmbedUnimplementedUsercenterServer()
|
mustEmbedUnimplementedUsercenterServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,31 +195,37 @@ type UsercenterServer interface {
|
|||||||
type UnimplementedUsercenterServer struct{}
|
type UnimplementedUsercenterServer struct{}
|
||||||
|
|
||||||
func (UnimplementedUsercenterServer) AddUsers(context.Context, *AddUsersReq) (*AddUsersResp, error) {
|
func (UnimplementedUsercenterServer) AddUsers(context.Context, *AddUsersReq) (*AddUsersResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AddUsers not implemented")
|
return nil, status.Error(codes.Unimplemented, "method AddUsers not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) UpdateUsers(context.Context, *UpdateUsersReq) (*UpdateUsersResp, error) {
|
func (UnimplementedUsercenterServer) UpdateUsers(context.Context, *UpdateUsersReq) (*UpdateUsersResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateUsers not implemented")
|
return nil, status.Error(codes.Unimplemented, "method UpdateUsers not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) DelUsers(context.Context, *DelUsersReq) (*DelUsersResp, error) {
|
func (UnimplementedUsercenterServer) DelUsers(context.Context, *DelUsersReq) (*DelUsersResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DelUsers not implemented")
|
return nil, status.Error(codes.Unimplemented, "method DelUsers not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) GetUsersById(context.Context, *GetUsersByIdReq) (*GetUsersByIdResp, error) {
|
func (UnimplementedUsercenterServer) GetUsersById(context.Context, *GetUsersByIdReq) (*GetUsersByIdResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUsersById not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetUsersById not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) GetUserByUsername(context.Context, *GetUserByUsernameReq) (*GetUserByUsernameResp, error) {
|
func (UnimplementedUsercenterServer) GetUserByUsername(context.Context, *GetUserByUsernameReq) (*GetUserByUsernameResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUserByUsername not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetUserByUsername not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) SearchUsers(context.Context, *SearchUsersReq) (*SearchUsersResp, error) {
|
func (UnimplementedUsercenterServer) SearchUsers(context.Context, *SearchUsersReq) (*SearchUsersResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method SearchUsers not implemented")
|
return nil, status.Error(codes.Unimplemented, "method SearchUsers not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) Login(context.Context, *LoginReq) (*LoginResp, error) {
|
func (UnimplementedUsercenterServer) Login(context.Context, *LoginReq) (*LoginResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
|
return nil, status.Error(codes.Unimplemented, "method Login not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedUsercenterServer) Register(context.Context, *RegisterReq) (*RegisterResp, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method Register not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) ValidateToken(context.Context, *ValidateTokenReq) (*ValidateTokenResp, error) {
|
func (UnimplementedUsercenterServer) ValidateToken(context.Context, *ValidateTokenReq) (*ValidateTokenResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ValidateToken not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ValidateToken not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) CheckPermission(context.Context, *CheckPermissionReq) (*CheckPermissionResp, error) {
|
func (UnimplementedUsercenterServer) CheckPermission(context.Context, *CheckPermissionReq) (*CheckPermissionResp, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CheckPermission not implemented")
|
return nil, status.Error(codes.Unimplemented, "method CheckPermission not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedUsercenterServer) Logout(context.Context, *LogoutReq) (*LogoutResp, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method Logout not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedUsercenterServer) mustEmbedUnimplementedUsercenterServer() {}
|
func (UnimplementedUsercenterServer) mustEmbedUnimplementedUsercenterServer() {}
|
||||||
func (UnimplementedUsercenterServer) testEmbeddedByValue() {}
|
func (UnimplementedUsercenterServer) testEmbeddedByValue() {}
|
||||||
@@ -206,7 +238,7 @@ type UnsafeUsercenterServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RegisterUsercenterServer(s grpc.ServiceRegistrar, srv UsercenterServer) {
|
func RegisterUsercenterServer(s grpc.ServiceRegistrar, srv UsercenterServer) {
|
||||||
// If the following call pancis, it indicates UnimplementedUsercenterServer was
|
// If the following call panics, it indicates UnimplementedUsercenterServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
@@ -342,6 +374,24 @@ func _Usercenter_Login_Handler(srv interface{}, ctx context.Context, dec func(in
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Usercenter_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RegisterReq)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UsercenterServer).Register(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Usercenter_Register_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UsercenterServer).Register(ctx, req.(*RegisterReq))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _Usercenter_ValidateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Usercenter_ValidateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ValidateTokenReq)
|
in := new(ValidateTokenReq)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -378,6 +428,24 @@ func _Usercenter_CheckPermission_Handler(srv interface{}, ctx context.Context, d
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Usercenter_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LogoutReq)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UsercenterServer).Logout(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Usercenter_Logout_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UsercenterServer).Logout(ctx, req.(*LogoutReq))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// Usercenter_ServiceDesc is the grpc.ServiceDesc for Usercenter service.
|
// Usercenter_ServiceDesc is the grpc.ServiceDesc for Usercenter service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -413,6 +481,10 @@ var Usercenter_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Login",
|
MethodName: "Login",
|
||||||
Handler: _Usercenter_Login_Handler,
|
Handler: _Usercenter_Login_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Register",
|
||||||
|
Handler: _Usercenter_Register_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "ValidateToken",
|
MethodName: "ValidateToken",
|
||||||
Handler: _Usercenter_ValidateToken_Handler,
|
Handler: _Usercenter_ValidateToken_Handler,
|
||||||
@@ -421,6 +493,10 @@ var Usercenter_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "CheckPermission",
|
MethodName: "CheckPermission",
|
||||||
Handler: _Usercenter_CheckPermission_Handler,
|
Handler: _Usercenter_CheckPermission_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Logout",
|
||||||
|
Handler: _Usercenter_Logout_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "users.proto",
|
Metadata: "users.proto",
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ type (
|
|||||||
GetUsersByIdResp = pb.GetUsersByIdResp
|
GetUsersByIdResp = pb.GetUsersByIdResp
|
||||||
LoginReq = pb.LoginReq
|
LoginReq = pb.LoginReq
|
||||||
LoginResp = pb.LoginResp
|
LoginResp = pb.LoginResp
|
||||||
|
LogoutReq = pb.LogoutReq
|
||||||
|
LogoutResp = pb.LogoutResp
|
||||||
|
RegisterReq = pb.RegisterReq
|
||||||
|
RegisterResp = pb.RegisterResp
|
||||||
SearchUsersReq = pb.SearchUsersReq
|
SearchUsersReq = pb.SearchUsersReq
|
||||||
SearchUsersResp = pb.SearchUsersResp
|
SearchUsersResp = pb.SearchUsersResp
|
||||||
UpdateUsersReq = pb.UpdateUsersReq
|
UpdateUsersReq = pb.UpdateUsersReq
|
||||||
@@ -43,8 +47,10 @@ type (
|
|||||||
GetUserByUsername(ctx context.Context, in *GetUserByUsernameReq, opts ...grpc.CallOption) (*GetUserByUsernameResp, error)
|
GetUserByUsername(ctx context.Context, in *GetUserByUsernameReq, opts ...grpc.CallOption) (*GetUserByUsernameResp, error)
|
||||||
SearchUsers(ctx context.Context, in *SearchUsersReq, opts ...grpc.CallOption) (*SearchUsersResp, error)
|
SearchUsers(ctx context.Context, in *SearchUsersReq, opts ...grpc.CallOption) (*SearchUsersResp, error)
|
||||||
Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error)
|
Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error)
|
||||||
|
Register(ctx context.Context, in *RegisterReq, opts ...grpc.CallOption) (*RegisterResp, error)
|
||||||
ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error)
|
ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error)
|
||||||
CheckPermission(ctx context.Context, in *CheckPermissionReq, opts ...grpc.CallOption) (*CheckPermissionResp, error)
|
CheckPermission(ctx context.Context, in *CheckPermissionReq, opts ...grpc.CallOption) (*CheckPermissionResp, error)
|
||||||
|
Logout(ctx context.Context, in *LogoutReq, opts ...grpc.CallOption) (*LogoutResp, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultUsercenter struct {
|
defaultUsercenter struct {
|
||||||
@@ -94,6 +100,11 @@ func (m *defaultUsercenter) Login(ctx context.Context, in *LoginReq, opts ...grp
|
|||||||
return client.Login(ctx, in, opts...)
|
return client.Login(ctx, in, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *defaultUsercenter) Register(ctx context.Context, in *RegisterReq, opts ...grpc.CallOption) (*RegisterResp, error) {
|
||||||
|
client := pb.NewUsercenterClient(m.cli.Conn())
|
||||||
|
return client.Register(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *defaultUsercenter) ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error) {
|
func (m *defaultUsercenter) ValidateToken(ctx context.Context, in *ValidateTokenReq, opts ...grpc.CallOption) (*ValidateTokenResp, error) {
|
||||||
client := pb.NewUsercenterClient(m.cli.Conn())
|
client := pb.NewUsercenterClient(m.cli.Conn())
|
||||||
return client.ValidateToken(ctx, in, opts...)
|
return client.ValidateToken(ctx, in, opts...)
|
||||||
@@ -103,3 +114,8 @@ func (m *defaultUsercenter) CheckPermission(ctx context.Context, in *CheckPermis
|
|||||||
client := pb.NewUsercenterClient(m.cli.Conn())
|
client := pb.NewUsercenterClient(m.cli.Conn())
|
||||||
return client.CheckPermission(ctx, in, opts...)
|
return client.CheckPermission(ctx, in, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *defaultUsercenter) Logout(ctx context.Context, in *LogoutReq, opts ...grpc.CallOption) (*LogoutResp, error) {
|
||||||
|
client := pb.NewUsercenterClient(m.cli.Conn())
|
||||||
|
return client.Logout(ctx, in, opts...)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewErrorResp(code int, msg error) *ErrorResponse {
|
||||||
|
return &ErrorResponse{
|
||||||
|
Code: code,
|
||||||
|
Msg: msg.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,320 +0,0 @@
|
|||||||
# Envoy Gateway 配置指南
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
Envoy Gateway 作为 API 统一入口,提供以下功能:
|
|
||||||
- **JWT 身份验证**:所有 API 请求(除登录/注册)都需要有效的 JWT token
|
|
||||||
- **CSRF 防护**:防止跨站点请求伪造攻击
|
|
||||||
- **速率限制**:防止 DDoS 攻击
|
|
||||||
- **TLS 加密**:所有通信都加密
|
|
||||||
- **负载均衡**:分担后端服务的流量
|
|
||||||
|
|
||||||
## 架构
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────┐
|
|
||||||
│ Client │
|
|
||||||
└──────┬──────┘
|
|
||||||
│ HTTP/HTTPS (Port 80/443)
|
|
||||||
│
|
|
||||||
┌──────▼────────────────┐
|
|
||||||
│ Envoy Gateway │
|
|
||||||
│ ┌────────────────┐ │
|
|
||||||
│ │ JWT Validator │ │ ◄─── JWT Verification (offline)
|
|
||||||
│ │ CSRF Filter │ │
|
|
||||||
│ │ Rate Limiter │ │
|
|
||||||
│ │ Router │ │
|
|
||||||
│ └────────────────┘ │
|
|
||||||
└────────┬─────────────┘
|
|
||||||
│ gRPC/HTTP
|
|
||||||
┌────┴────┬──────────┐
|
|
||||||
│ │ │
|
|
||||||
┌───▼──┐ ┌──▼────┐ ┌──▼─────┐
|
|
||||||
│User │ │Order │ │User │
|
|
||||||
│API │ │API │ │RPC │
|
|
||||||
└──────┘ └───────┘ └────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 部署步骤
|
|
||||||
|
|
||||||
### 1. 生成 TLS 证书
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 为 Envoy 生成自签名证书(生产环境应使用正式证书)
|
|
||||||
kubectl create secret tls envoy-tls \
|
|
||||||
--cert=path/to/tls.crt \
|
|
||||||
--key=path/to/tls.key \
|
|
||||||
-n juwan
|
|
||||||
|
|
||||||
# 或生成自签名证书(仅用于测试)
|
|
||||||
openssl req -x509 -newkey rsa:4096 -keyout tls.key -out tls.crt \
|
|
||||||
-days 365 -nodes -subj "/CN=api.juwan.local"
|
|
||||||
|
|
||||||
kubectl create secret tls envoy-tls \
|
|
||||||
--cert=tls.crt \
|
|
||||||
--key=tls.key \
|
|
||||||
-n juwan
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 部署 Envoy Gateway
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 应用 Envoy 配置
|
|
||||||
kubectl apply -f deploy/k8s/envoy-gateway.yaml
|
|
||||||
|
|
||||||
# 查看部署状态
|
|
||||||
kubectl get pods -n juwan -l app=envoy-gateway
|
|
||||||
kubectl get svc -n juwan envoy-gateway
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 配置 JWKS 端点
|
|
||||||
|
|
||||||
在 user-rpc 中暴露 JWKS 端点,供 Envoy 验证 JWT:
|
|
||||||
|
|
||||||
#### 在 `app/users/rpc` 中添加 HTTP 路由(go-zero)
|
|
||||||
|
|
||||||
编辑 `app/users/rpc/internal/handler/` 或在 `main.go` 中:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 在 rpc server 启动时,添加 HTTP 端点用于暴露 JWKS
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"juwan-backend/app/users/rpc/internal/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 在 main 函数中
|
|
||||||
http.HandleFunc("/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
secretKey := os.Getenv("JWT_SECRET_KEY")
|
|
||||||
if secretKey == "" {
|
|
||||||
secretKey = "your-default-secret-key"
|
|
||||||
}
|
|
||||||
|
|
||||||
jwksJSON, err := utils.GenerateJWKSEndpoint(secretKey, "default-key-id")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to generate JWKS", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Write([]byte(jwksJSON))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 在单独的 goroutine 中启动 HTTP 服务器
|
|
||||||
go func() {
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
}()
|
|
||||||
```
|
|
||||||
|
|
||||||
**或使用 Echo 框架(更推荐)**:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 在 main.go 中
|
|
||||||
import "github.com/labstack/echo/v4"
|
|
||||||
|
|
||||||
e := echo.New()
|
|
||||||
e.GET("/.well-known/jwks.json", func(c echo.Context) error {
|
|
||||||
secretKey := os.Getenv("JWT_SECRET_KEY")
|
|
||||||
jwksJSON, _ := utils.GenerateJWKSEndpoint(secretKey, "default-key-id")
|
|
||||||
return c.JSONBlob(http.StatusOK, []byte(jwksJSON))
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
e.Start(":8080")
|
|
||||||
}()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 更新环境变量
|
|
||||||
|
|
||||||
在 K8s Secret 中配置 JWT_SECRET_KEY:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: jwt-secret
|
|
||||||
namespace: juwan
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
JWT_SECRET_KEY: "$(echo -n 'your-secret-key-change-this' | base64)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 验证 JWKS 端点
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 端口转发
|
|
||||||
kubectl port-forward -n juwan svc/user-rpc-svc 9001:9001
|
|
||||||
|
|
||||||
# 验证 JWKS 端点可访问
|
|
||||||
curl http://localhost:9001/.well-known/jwks.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## JWT 验证流程
|
|
||||||
|
|
||||||
### 1. 登录获取 Token
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST http://api.juwan.local/api/v1/users/login \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"username": "testuser",
|
|
||||||
"password": "password123"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# 响应:
|
|
||||||
# {
|
|
||||||
# "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
||||||
# "expires": 1708780800
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 使用 Token 访问受保护资源
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -H "Authorization: Bearer YOUR_TOKEN" \
|
|
||||||
https://api.juwan.local/api/v1/users/123
|
|
||||||
|
|
||||||
# Envoy 验证步骤:
|
|
||||||
# 1. 从 Authorization header 提取 token
|
|
||||||
# 2. 从 JWKS 端点获取公钥(缓存 5 分钟)
|
|
||||||
# 3. 验证 token 签名
|
|
||||||
# 4. 检查 token 过期时间
|
|
||||||
# 5. 将验证后的用户信息添加到请求头(X-USER-ID)
|
|
||||||
# 6. 转发请求到 user-api
|
|
||||||
```
|
|
||||||
|
|
||||||
## CSRF 防护
|
|
||||||
|
|
||||||
### 配置说明
|
|
||||||
|
|
||||||
Envoy 的 CSRF 过滤器检查:
|
|
||||||
- 只对 POST/PUT/DELETE/PATCH 请求进行检查
|
|
||||||
- 检查 `Origin` 和 `Referer` header
|
|
||||||
- 验证请求来自已知域名
|
|
||||||
|
|
||||||
### 跨域请求配置
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Envoy 配置中允许的来源
|
|
||||||
additional_origins:
|
|
||||||
- exact: "https://admin.juwan.local"
|
|
||||||
- exact: "https://web.juwan.local"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Token 黑名单检查(可选)
|
|
||||||
|
|
||||||
如果需要验证 token 未被撤销,可启用额外的 RPC 验证:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# envoy-gateway.yaml 中取消注释
|
|
||||||
- name: envoy.filters.http.ext_authz
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
|
|
||||||
grpc_service:
|
|
||||||
envoy_grpc:
|
|
||||||
cluster_name: user_rpc_cluster
|
|
||||||
failure_mode_allow: false
|
|
||||||
```
|
|
||||||
|
|
||||||
然后在 user-rpc 中实现 ValidateToken RPC:
|
|
||||||
|
|
||||||
```protobuf
|
|
||||||
rpc ValidateToken(ValidateTokenReq) returns(ValidateTokenResp);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 故障排查
|
|
||||||
|
|
||||||
### 1. JWT 验证失败
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看 Envoy 日志
|
|
||||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
|
||||||
|
|
||||||
# 验证 JWKS 端点是否可访问
|
|
||||||
kubectl exec -it -n juwan <envoy-pod> -- \
|
|
||||||
curl http://user-rpc-svc:9001/.well-known/jwks.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 无法连接到后端服务
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 验证服务发现
|
|
||||||
kubectl get endpoints -n juwan
|
|
||||||
|
|
||||||
# 验证网络策略
|
|
||||||
kubectl get networkpolicy -n juwan
|
|
||||||
|
|
||||||
# 测试连接
|
|
||||||
kubectl exec -it -n juwan <envoy-pod> -- \
|
|
||||||
curl http://user-api-svc:8888/health
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. CSRF 错误
|
|
||||||
|
|
||||||
- 确保设置了 `Origin` 和 `Referer` header
|
|
||||||
- 检查 `additional_origins` 配置是否包含你的域名
|
|
||||||
|
|
||||||
## 性能优化
|
|
||||||
|
|
||||||
### 1. JWKS 缓存
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
cache_ttl:
|
|
||||||
seconds: 300 # 缓存 5 分钟,减少 RPC 调用
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 连接池
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
http2_protocol_options: {} # 启用 HTTP/2 多路复用
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 速率限制调整
|
|
||||||
|
|
||||||
根据实际流量调整令牌桶参数:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
token_bucket:
|
|
||||||
max_tokens: 10000 # 最大令牌数
|
|
||||||
tokens_per_fill: 10000 # 每次填充的令牌数
|
|
||||||
fill_interval: 1s # 填充间隔
|
|
||||||
```
|
|
||||||
|
|
||||||
## 监控和日志
|
|
||||||
|
|
||||||
### 访问日志
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看访问日志
|
|
||||||
kubectl logs -n juwan -l app=envoy-gateway --follow
|
|
||||||
|
|
||||||
# 格式包含:
|
|
||||||
# - 请求时间、方法、路径
|
|
||||||
# - 响应状态码、字节数
|
|
||||||
# - 上游服务信息
|
|
||||||
```
|
|
||||||
|
|
||||||
### Prometheus 指标
|
|
||||||
|
|
||||||
Envoy 在 `:9901/stats` 暴露 Prometheus 指标:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl port-forward -n juwan svc/envoy-gateway 9901:9901
|
|
||||||
curl localhost:9901/stats | grep jwt
|
|
||||||
```
|
|
||||||
|
|
||||||
## 生产环境检查清单
|
|
||||||
|
|
||||||
- [ ] 使用正式 TLS 证书(不是自签名)
|
|
||||||
- [ ] 配置正确的 JWT_SECRET_KEY(强密码)
|
|
||||||
- [ ] 启用 HTTPS(关闭 HTTP)
|
|
||||||
- [ ] 配置网络策略限制访问
|
|
||||||
- [ ] 启用访问日志和监控
|
|
||||||
- [ ] 设置合理的速率限制
|
|
||||||
- [ ] 测试 token 过期和刷新流程
|
|
||||||
- [ ] 配置告警规则
|
|
||||||
|
|
||||||
## 参考文档
|
|
||||||
|
|
||||||
- [Envoy JWT Authentication](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/jwt_authn/v3/config.proto)
|
|
||||||
- [Envoy CSRF Protection](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/csrf/v3/csrf.proto)
|
|
||||||
- [JWT RFC 7519](https://tools.ietf.org/html/rfc7519)
|
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
# Envoy 配置完整清单
|
|
||||||
|
|
||||||
## 📋 配置文件清单
|
|
||||||
|
|
||||||
### 1. Proto 更新
|
|
||||||
- **文件**: [desc/rpc/users.proto](../../desc/rpc/users.proto)
|
|
||||||
- **更改**: 添加了两个 RPC 方法
|
|
||||||
- `ValidateToken()`: 验证 token 是否有效(检查黑名单)
|
|
||||||
- `CheckPermission()`: 检查用户权限
|
|
||||||
|
|
||||||
### 2. Envoy 部署
|
|
||||||
- **配置文件**: [envoy.yaml](./envoy.yaml)
|
|
||||||
- HTTP 监听器(端口 8080)
|
|
||||||
- HTTPS 监听器(端口 8443)
|
|
||||||
- JWT 验证过滤器
|
|
||||||
- CSRF 防护过滤器
|
|
||||||
- 速率限制(DDoS 防护)
|
|
||||||
- 路由配置
|
|
||||||
|
|
||||||
- **K8s 部署**: [../k8s/envoy-gateway.yaml](../k8s/envoy-gateway.yaml)
|
|
||||||
- 2 个副本
|
|
||||||
- 负载均衡器服务
|
|
||||||
- Service Account 和 RBAC
|
|
||||||
- Network Policy
|
|
||||||
- ConfigMap 用于配置管理
|
|
||||||
|
|
||||||
### 3. 工具代码
|
|
||||||
- **JWKS 生成**: [app/users/rpc/internal/utils/jwks.go](../../app/users/rpc/internal/utils/jwks.go)
|
|
||||||
- `GenerateJWKSFromSecret()`: 从 JWT 密钥生成 JWKS
|
|
||||||
- `GenerateJWKSEndpoint()`: 生成 JSON 输出供 Envoy 使用
|
|
||||||
- `ExtractTokenMetadata()`: 提取 token 元数据
|
|
||||||
|
|
||||||
### 4. Dockerfile
|
|
||||||
- **文件**: [Dockerfile](./Dockerfile)
|
|
||||||
- **用途**: 构建 Envoy 容器镜像
|
|
||||||
|
|
||||||
### 5. 脚本
|
|
||||||
- **文件**: [generate-jwks.sh](./generate-jwks.sh)
|
|
||||||
- **用途**: 快速生成 JWKS JSON 文件
|
|
||||||
|
|
||||||
### 6. 文档
|
|
||||||
- **文件**: [ENVOY_CONFIG_GUIDE.md](./ENVOY_CONFIG_GUIDE.md)
|
|
||||||
- **内容**: 详细的配置和部署指南
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 快速部署步骤
|
|
||||||
|
|
||||||
### 步骤 1: 生成 TLS 证书
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 测试环境:生成自签名证书
|
|
||||||
openssl req -x509 -newkey rsa:4096 -keyout tls.key -out tls.crt \
|
|
||||||
-days 365 -nodes -subj "/CN=api.juwan.local"
|
|
||||||
|
|
||||||
# 创建 K8s Secret
|
|
||||||
kubectl create secret tls envoy-tls \
|
|
||||||
--cert=tls.crt \
|
|
||||||
--key=tls.key \
|
|
||||||
-n juwan
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤 2: 部署 Envoy Gateway
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 应用部署文件
|
|
||||||
kubectl apply -f deploy/k8s/envoy-gateway.yaml
|
|
||||||
|
|
||||||
# 验证部署
|
|
||||||
kubectl get pods -n juwan -l app=envoy-gateway
|
|
||||||
kubectl get svc -n juwan envoy-gateway
|
|
||||||
|
|
||||||
# 等待 LoadBalancer 获取外部 IP
|
|
||||||
kubectl get svc -n juwan envoy-gateway -w
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤 3: 在 User RPC 中暴露 JWKS 端点
|
|
||||||
|
|
||||||
编辑 `app/users/rpc/rpcserver.go` 或 `main.go`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"http"
|
|
||||||
"os"
|
|
||||||
"juwan-backend/app/users/rpc/internal/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 在启动 RPC server 前,添加 HTTP 端点
|
|
||||||
func setupJWKSEndpoint() {
|
|
||||||
secretKey := os.Getenv("JWT_SECRET_KEY")
|
|
||||||
if secretKey == "" {
|
|
||||||
secretKey = "your-default-secret-key"
|
|
||||||
}
|
|
||||||
|
|
||||||
http.HandleFunc("/.well-known/jwks.json", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
jwksJSON, err := utils.GenerateJWKSEndpoint(secretKey, "default-key-id")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Failed to generate JWKS", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.Header().Set("Cache-Control", "public, max-age=300") // 缓存 5 分钟
|
|
||||||
w.Write([]byte(jwksJSON))
|
|
||||||
})
|
|
||||||
|
|
||||||
// 在独立的 goroutine 中启动 HTTP 服务器
|
|
||||||
go func() {
|
|
||||||
http.ListenAndServe(":8080", nil)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
setupJWKSEndpoint()
|
|
||||||
|
|
||||||
// ... 其他 RPC 启动代码 ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤 4: 更新 User RPC 配置
|
|
||||||
|
|
||||||
编辑 `app/users/rpc/etc/pb.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
Name: pb.rpc
|
|
||||||
ListenOn: 0.0.0.0:9001
|
|
||||||
|
|
||||||
Prometheus:
|
|
||||||
Host: 0.0.0.0
|
|
||||||
Port: 4001
|
|
||||||
Path: /metrics
|
|
||||||
|
|
||||||
# ... 其他配置 ...
|
|
||||||
|
|
||||||
Jwt:
|
|
||||||
SecretKey: "${JWT_SECRET_KEY:your-secret-jwt-key-change-this-in-production}"
|
|
||||||
Issuer: "juwan-user-rpc"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤 5: 构建并推送容器镜像
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 构建 User API 镜像
|
|
||||||
docker build -t your-registry/user-api:latest ./app/users/api/
|
|
||||||
docker push your-registry/user-api:latest
|
|
||||||
|
|
||||||
# 构建 User RPC 镜像
|
|
||||||
docker build -t your-registry/user-rpc:latest ./app/users/rpc/
|
|
||||||
docker push your-registry/user-rpc:latest
|
|
||||||
|
|
||||||
# 构建 Envoy 镜像
|
|
||||||
docker build -f deploy/envoy/Dockerfile -t your-registry/envoy-gateway:latest .
|
|
||||||
docker push your-registry/envoy-gateway:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
### 步骤 6: 更新 K8s 部署
|
|
||||||
|
|
||||||
更新 `deploy/k8s/service/user/user-api.yaml` 和 `user-rpc.yaml`,确保使用正确的镜像和环境变量。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 测试流程
|
|
||||||
|
|
||||||
### 1. 登录获取 Token
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 获取 Envoy 外部 IP
|
|
||||||
ENVOY_IP=$(kubectl get svc -n juwan envoy-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
|
|
||||||
|
|
||||||
# 登录
|
|
||||||
curl -k -X POST "https://$ENVOY_IP:443/api/v1/users/login" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"username": "testuser",
|
|
||||||
"password": "password123"
|
|
||||||
}' | jq .
|
|
||||||
|
|
||||||
# 示例响应:
|
|
||||||
# {
|
|
||||||
# "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
||||||
# "expires": 1708780800
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 使用 Token 访问受保护资源
|
|
||||||
|
|
||||||
```bash
|
|
||||||
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
||||||
|
|
||||||
curl -k -X GET "https://$ENVOY_IP:443/api/v1/users/123" \
|
|
||||||
-H "Authorization: Bearer $TOKEN" | jq .
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 验证 CSRF 防护
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# POST 请求必须有正确的 Origin/Referer
|
|
||||||
curl -k -X POST "https://$ENVOY_IP:443/api/v1/users/logout" \
|
|
||||||
-H "Authorization: Bearer $TOKEN" \
|
|
||||||
-H "Origin: https://api.juwan.local" \
|
|
||||||
-H "Referer: https://api.juwan.local/" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"userId": 123}'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 验证检查清单
|
|
||||||
|
|
||||||
- [ ] Envoy 容器运行正常
|
|
||||||
```bash
|
|
||||||
kubectl logs -n juwan -l app=envoy-gateway
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] JWKS 端点可访问
|
|
||||||
```bash
|
|
||||||
kubectl exec -it -n juwan <envoy-pod> -- \
|
|
||||||
curl http://user-rpc-svc:9001/.well-known/jwks.json
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] 后端服务健康
|
|
||||||
```bash
|
|
||||||
kubectl exec -it -n juwan <envoy-pod> -- \
|
|
||||||
curl http://user-api-svc:8888/health
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] JWT 验证工作
|
|
||||||
```bash
|
|
||||||
# 不带 token 访问受保护资源应返回 401
|
|
||||||
curl -k https://api.juwan.local/api/v1/users/123
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] CSRF 防护生效
|
|
||||||
```bash
|
|
||||||
# 缺少 Origin header 的 POST 应被拒绝
|
|
||||||
curl -k -X POST https://api.juwan.local/api/v1/users/logout \
|
|
||||||
-H "Authorization: Bearer $TOKEN"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 配置调整
|
|
||||||
|
|
||||||
### 修改 JWT 密钥
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 更新 K8s Secret
|
|
||||||
kubectl patch secret jwt-secret -n juwan \
|
|
||||||
-p '{"data":{"JWT_SECRET_KEY":"'$(echo -n 'new-secret-key' | base64)'"}}'
|
|
||||||
|
|
||||||
# 2. 重启 User RPC 和 Envoy
|
|
||||||
kubectl rollout restart deployment/user-rpc-svc -n juwan
|
|
||||||
kubectl rollout restart deployment/envoy-gateway -n juwan
|
|
||||||
```
|
|
||||||
|
|
||||||
### 调整 Envoy 速率限制
|
|
||||||
|
|
||||||
编辑 ConfigMap:
|
|
||||||
```bash
|
|
||||||
kubectl edit cm envoy-config -n juwan
|
|
||||||
```
|
|
||||||
|
|
||||||
修改 `token_bucket` 参数:
|
|
||||||
```yaml
|
|
||||||
token_bucket:
|
|
||||||
max_tokens: 5000 # 降低限制
|
|
||||||
tokens_per_fill: 5000
|
|
||||||
fill_interval: 1s
|
|
||||||
```
|
|
||||||
|
|
||||||
### 添加信任的 CSRF 来源
|
|
||||||
|
|
||||||
编辑 ConfigMap:
|
|
||||||
```yaml
|
|
||||||
additional_origins:
|
|
||||||
- exact: "https://admin.juwan.local"
|
|
||||||
- exact: "https://web.juwan.local"
|
|
||||||
- prefix: "https://app.juwan.local" # 支持前缀匹配
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📈 监控和日志
|
|
||||||
|
|
||||||
### 查看 Envoy 统计
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl port-forward -n juwan svc/envoy-gateway 9901:9901
|
|
||||||
curl localhost:9901/stats | grep -E "(jwt_authn|csrf|http_ratelimit)"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实时日志
|
|
||||||
|
|
||||||
```bash
|
|
||||||
kubectl logs -n juwan -l app=envoy-gateway -f
|
|
||||||
|
|
||||||
# 查看特定日志行
|
|
||||||
kubectl logs -n juwan -l app=envoy-gateway | grep "401\|403"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 监控指标(集成 Prometheus)
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# prometheus-scrape-config.yaml
|
|
||||||
- job_name: 'envoy-gateway'
|
|
||||||
static_configs:
|
|
||||||
- targets: ['envoy-gateway.juwan.svc.cluster.local:9901']
|
|
||||||
metrics_path: '/stats/prometheus'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 相关文件位置
|
|
||||||
|
|
||||||
```
|
|
||||||
deploy/
|
|
||||||
├── envoy/
|
|
||||||
│ ├── envoy.yaml ← Envoy 核心配置
|
|
||||||
│ ├── ENVOY_CONFIG_GUIDE.md ← 详细指南
|
|
||||||
│ ├── generate-jwks.sh ← JWKS 生成脚本
|
|
||||||
│ ├── Dockerfile ← Envoy 镜像
|
|
||||||
│ └── QUICK_REFERENCE.md ← 本文件
|
|
||||||
├── k8s/
|
|
||||||
│ ├── envoy-gateway.yaml ← K8s 部署清单
|
|
||||||
│ └── secrets/jwt-secret.yaml ← JWT 密钥配置
|
|
||||||
└── script/
|
|
||||||
└── init-secrets.sh ← 初始化脚本
|
|
||||||
|
|
||||||
app/users/
|
|
||||||
├── rpc/
|
|
||||||
│ ├── internal/utils/
|
|
||||||
│ │ ├── jwks.go ← JWKS 生成工具
|
|
||||||
│ │ └── jwt.go ← JWT 管理器
|
|
||||||
│ └── etc/pb.yaml ← RPC 配置
|
|
||||||
└── api/
|
|
||||||
└── etc/user-api.yaml ← API 配置
|
|
||||||
|
|
||||||
desc/
|
|
||||||
└── rpc/users.proto ← Proto 定义(已更新)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤔 常见问题
|
|
||||||
|
|
||||||
1. **Envoy 无法连接到后端服务**
|
|
||||||
- 检查 K8s Service DNS: `user-api-svc.juwan.svc.cluster.local`
|
|
||||||
- 验证 NetworkPolicy 允许流量
|
|
||||||
|
|
||||||
2. **JWT 验证失败**
|
|
||||||
- 确保 JWT_SECRET_KEY 一致
|
|
||||||
- 检查 JWKS 端点是否可访问
|
|
||||||
- 查看 Envoy 日志: `grep "jwt_authn" envoy.log`
|
|
||||||
|
|
||||||
3. **CSRF 防护过于严格**
|
|
||||||
- 在 `additional_origins` 中添加允许的来源
|
|
||||||
- 对于单页应用,确保发送 `Origin` header
|
|
||||||
|
|
||||||
4. **速率限制阻止正常流量**
|
|
||||||
- 增加 `max_tokens` 和 `tokens_per_fill`
|
|
||||||
- 针对特定客户端配置不同的限制
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 获取帮助
|
|
||||||
|
|
||||||
- 查看 [Envoy 官方文档](https://www.envoyproxy.io/docs)
|
|
||||||
- 查看 [JWT 规范](https://tools.ietf.org/html/rfc7519)
|
|
||||||
- 检查 [CSRF 防护最佳实践](https://owasp.org/www-community/attacks/csrf)
|
|
||||||
@@ -1,331 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Envoy 快速部署脚本
|
|
||||||
# 用途:自动化部署 Envoy Gateway 到 Kubernetes
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 配置
|
|
||||||
NAMESPACE="${NAMESPACE:-juwan}"
|
|
||||||
RELEASE_NAME="${RELEASE_NAME:-envoy-gateway}"
|
|
||||||
TIMEOUT="${TIMEOUT:-300s}"
|
|
||||||
CONTEXT="${CONTEXT:-}"
|
|
||||||
|
|
||||||
# 颜色输出
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# 日志函数
|
|
||||||
log_info() {
|
|
||||||
echo -e "${BLUE}ℹ${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success() {
|
|
||||||
echo -e "${GREEN}✓${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warn() {
|
|
||||||
echo -e "${YELLOW}⚠${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}✗${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查依赖
|
|
||||||
check_dependencies() {
|
|
||||||
log_info "检查依赖..."
|
|
||||||
|
|
||||||
local missing_deps=()
|
|
||||||
|
|
||||||
if ! command -v kubectl &> /dev/null; then
|
|
||||||
missing_deps+=("kubectl")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v openssl &> /dev/null; then
|
|
||||||
missing_deps+=("openssl")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ${#missing_deps[@]} -gt 0 ]; then
|
|
||||||
log_error "缺少以下依赖: ${missing_deps[*]}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "所有依赖已安装"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 生成 TLS 证书
|
|
||||||
generate_tls_cert() {
|
|
||||||
log_info "生成 TLS 证书..."
|
|
||||||
|
|
||||||
local cert_dir="certs"
|
|
||||||
local key_file="$cert_dir/tls.key"
|
|
||||||
local cert_file="$cert_dir/tls.crt"
|
|
||||||
|
|
||||||
# 创建 certs 目录
|
|
||||||
mkdir -p "$cert_dir"
|
|
||||||
|
|
||||||
# 检查是否已存在证书
|
|
||||||
if [ -f "$cert_file" ] && [ -f "$key_file" ]; then
|
|
||||||
log_warn "证书已存在: $cert_file, $key_file"
|
|
||||||
read -p "是否要重新生成? (y/n) " -t 10 -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
log_success "使用现有证书"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 生成自签名证书(仅用于测试)
|
|
||||||
openssl req -x509 -newkey rsa:4096 \
|
|
||||||
-keyout "$key_file" \
|
|
||||||
-out "$cert_file" \
|
|
||||||
-days 365 -nodes \
|
|
||||||
-subj "/CN=api.juwan.local" \
|
|
||||||
-addext "subjectAltName=DNS:api.juwan.local,DNS:*.juwan.local" \
|
|
||||||
> /dev/null 2>&1
|
|
||||||
|
|
||||||
log_success "TLS 证书已生成"
|
|
||||||
log_warn "警告: 这是自签名证书,仅用于测试环境"
|
|
||||||
log_warn "生产环境应使用正式的 CA 签发证书"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 创建命名空间
|
|
||||||
create_namespace() {
|
|
||||||
log_info "创建 Kubernetes 命名空间..."
|
|
||||||
|
|
||||||
if kubectl get namespace "$NAMESPACE" &> /dev/null; then
|
|
||||||
log_warn "命名空间已存在: $NAMESPACE"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
kubectl create namespace "$NAMESPACE"
|
|
||||||
log_success "命名空间已创建: $NAMESPACE"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 创建 TLS Secret
|
|
||||||
create_tls_secret() {
|
|
||||||
log_info "创建 TLS Secret..."
|
|
||||||
|
|
||||||
local cert_dir="certs"
|
|
||||||
local key_file="$cert_dir/tls.key"
|
|
||||||
local cert_file="$cert_dir/tls.crt"
|
|
||||||
|
|
||||||
# 检查证书文件
|
|
||||||
if [ ! -f "$cert_file" ] || [ ! -f "$key_file" ]; then
|
|
||||||
log_error "证书文件不存在"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查 Secret 是否已存在
|
|
||||||
if kubectl get secret envoy-tls -n "$NAMESPACE" &> /dev/null; then
|
|
||||||
log_warn "Secret 已存在,删除后重建"
|
|
||||||
kubectl delete secret envoy-tls -n "$NAMESPACE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 创建 Secret
|
|
||||||
kubectl create secret tls envoy-tls \
|
|
||||||
-n "$NAMESPACE" \
|
|
||||||
--cert="$cert_file" \
|
|
||||||
--key="$key_file"
|
|
||||||
|
|
||||||
log_success "TLS Secret 已创建: envoy-tls"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 部署 Envoy Gateway
|
|
||||||
deploy_envoy() {
|
|
||||||
log_info "部署 Envoy Gateway..."
|
|
||||||
|
|
||||||
local manifest_file="deploy/k8s/envoy-gateway.yaml"
|
|
||||||
|
|
||||||
if [ ! -f "$manifest_file" ]; then
|
|
||||||
log_error "找不到部署清单: $manifest_file"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 应用部署
|
|
||||||
if [ -n "$CONTEXT" ]; then
|
|
||||||
kubectl apply -f "$manifest_file" --context="$CONTEXT"
|
|
||||||
else
|
|
||||||
kubectl apply -f "$manifest_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "Envoy Gateway 部署清单已应用"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 等待部署完成
|
|
||||||
wait_deployment() {
|
|
||||||
log_info "等待部署完成(超时: $TIMEOUT)..."
|
|
||||||
|
|
||||||
kubectl rollout status deployment/envoy-gateway \
|
|
||||||
-n "$NAMESPACE" \
|
|
||||||
--timeout="$TIMEOUT" || {
|
|
||||||
log_error "部署超时"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
log_success "部署已完成"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 验证部署
|
|
||||||
verify_deployment() {
|
|
||||||
log_info "验证部署..."
|
|
||||||
|
|
||||||
# 检查 Pod
|
|
||||||
local pod_count=$(kubectl get pods -n "$NAMESPACE" \
|
|
||||||
-l app=envoy-gateway \
|
|
||||||
-o jsonpath='{.items | length}')
|
|
||||||
|
|
||||||
if [ "$pod_count" -eq 0 ]; then
|
|
||||||
log_error "未找到 Envoy 容器"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "找到 $pod_count 个 Envoy 容器"
|
|
||||||
|
|
||||||
# 检查 Service
|
|
||||||
local svc_status=$(kubectl get svc -n "$NAMESPACE" |
|
|
||||||
grep envoy-gateway || echo "")
|
|
||||||
|
|
||||||
if [ -z "$svc_status" ]; then
|
|
||||||
log_error "未找到 Service"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_success "Service 已创建"
|
|
||||||
|
|
||||||
# 显示 LoadBalancer IP
|
|
||||||
log_info "等待 LoadBalancer IP..."
|
|
||||||
local lb_ip=""
|
|
||||||
for i in {1..30}; do
|
|
||||||
lb_ip=$(kubectl get svc envoy-gateway -n "$NAMESPACE" \
|
|
||||||
-o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
if [ -n "$lb_ip" ] && [ "$lb_ip" != "null" ]; then
|
|
||||||
log_success "LoadBalancer IP: $lb_ip"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $i -eq 30 ]; then
|
|
||||||
log_warn "未获得 LoadBalancer IP(可能在内网环境或使用 NodePort)"
|
|
||||||
kubectl get svc -n "$NAMESPACE" envoy-gateway
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# 显示部署信息
|
|
||||||
show_summary() {
|
|
||||||
log_info "部署摘要"
|
|
||||||
echo ""
|
|
||||||
echo " Namespace: $NAMESPACE"
|
|
||||||
echo " Release: $RELEASE_NAME"
|
|
||||||
echo ""
|
|
||||||
echo " Pods:"
|
|
||||||
kubectl get pods -n "$NAMESPACE" -l app=envoy-gateway \
|
|
||||||
-o custom-columns=NAME:.metadata.name,STATUS:.status.phase,IP:.status.podIP \
|
|
||||||
| sed 's/^/ /'
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " Service:"
|
|
||||||
kubectl get svc -n "$NAMESPACE" envoy-gateway \
|
|
||||||
-o custom-columns=NAME:.metadata.name,TYPE:.spec.type,IP:.spec.clusterIP,EXTERNAL_IP:.status.loadBalancer.ingress[0].ip \
|
|
||||||
| sed 's/^/ /'
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo " 后续步骤:"
|
|
||||||
echo " 1. 在 User RPC 中暴露 JWKS 端点 (/.well-known/jwks.json)"
|
|
||||||
echo " 2. 配置 JWT_SECRET_KEY 环境变量"
|
|
||||||
echo " 3. 测试 JWT 验证: curl -k https://<ENVOY_IP>/api/v1/users/login"
|
|
||||||
echo ""
|
|
||||||
echo " 文档:"
|
|
||||||
echo " - 配置指南: deploy/envoy/ENVOY_CONFIG_GUIDE.md"
|
|
||||||
echo " - 快速参考: deploy/envoy/QUICK_REFERENCE.md"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# 清理部署
|
|
||||||
cleanup() {
|
|
||||||
log_warn "清理 Envoy Gateway..."
|
|
||||||
|
|
||||||
kubectl delete -f deploy/k8s/envoy-gateway.yaml -n "$NAMESPACE" || true
|
|
||||||
kubectl delete secret envoy-tls -n "$NAMESPACE" || true
|
|
||||||
|
|
||||||
log_success "清理完成"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主函数
|
|
||||||
main() {
|
|
||||||
echo ""
|
|
||||||
echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
|
|
||||||
echo -e "${BLUE}║ Envoy Gateway 快速部署脚本 ║${NC}"
|
|
||||||
echo -e "${BLUE}╚════════════════════════════════════════╝${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 解析命令行参数
|
|
||||||
local cmd="${1:-deploy}"
|
|
||||||
|
|
||||||
case "$cmd" in
|
|
||||||
deploy)
|
|
||||||
check_dependencies || exit 1
|
|
||||||
generate_tls_cert || exit 1
|
|
||||||
create_namespace || exit 1
|
|
||||||
create_tls_secret || exit 1
|
|
||||||
deploy_envoy || exit 1
|
|
||||||
wait_deployment || exit 1
|
|
||||||
verify_deployment || exit 1
|
|
||||||
show_summary
|
|
||||||
log_success "Envoy Gateway 已成功部署!"
|
|
||||||
;;
|
|
||||||
cleanup)
|
|
||||||
cleanup
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
log_info "部署状态"
|
|
||||||
kubectl get all -n "$NAMESPACE" -l app=envoy-gateway
|
|
||||||
;;
|
|
||||||
logs)
|
|
||||||
log_info "Envoy 日志"
|
|
||||||
kubectl logs -n "$NAMESPACE" -l app=envoy-gateway -f
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "用法: $0 <命令>"
|
|
||||||
echo ""
|
|
||||||
echo "命令:"
|
|
||||||
echo " deploy 部署 Envoy Gateway(默认)"
|
|
||||||
echo " cleanup 移除部署"
|
|
||||||
echo " status 查看部署状态"
|
|
||||||
echo " logs 查看 Envoy 日志"
|
|
||||||
echo ""
|
|
||||||
echo "环境变量:"
|
|
||||||
echo " NAMESPACE K8s 命名空间(默认: juwan)"
|
|
||||||
echo " RELEASE_NAME 发布名称(默认: envoy-gateway)"
|
|
||||||
echo " TIMEOUT 部署超时(默认: 300s)"
|
|
||||||
echo " CONTEXT K8s 上下文(可选)"
|
|
||||||
echo ""
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
static_resources:
|
|
||||||
listeners:
|
|
||||||
# HTTP 监听器(重定向到 HTTPS)
|
|
||||||
- name: listener_http
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_number: 8080
|
|
||||||
filter_chains:
|
|
||||||
- filters:
|
|
||||||
- name: envoy.filters.network.http_connection_manager
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
|
||||||
stat_prefix: ingress_http
|
|
||||||
http_filters:
|
|
||||||
# CSRF 防护过滤器
|
|
||||||
- name: envoy.filters.http.local_ratelimit
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
|
|
||||||
stat_prefix: http_local_rate_limiter
|
|
||||||
token_bucket:
|
|
||||||
max_tokens: 1000
|
|
||||||
tokens_per_fill: 1000
|
|
||||||
fill_interval: 1s
|
|
||||||
filter_enabled:
|
|
||||||
runtime_key: local_rate_limit_enabled
|
|
||||||
default_value:
|
|
||||||
numerator: 100
|
|
||||||
denominator: HUNDRED
|
|
||||||
filter_enforced:
|
|
||||||
runtime_key: local_rate_limit_enforced
|
|
||||||
default_value:
|
|
||||||
numerator: 100
|
|
||||||
denominator: HUNDRED
|
|
||||||
|
|
||||||
# 路由过滤器
|
|
||||||
- name: envoy.filters.http.router
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
|
||||||
|
|
||||||
route_config:
|
|
||||||
name: local_route
|
|
||||||
virtual_hosts:
|
|
||||||
- name: backend
|
|
||||||
domains: ["*"]
|
|
||||||
routes:
|
|
||||||
# 登录端点 - 不需要 JWT
|
|
||||||
- match:
|
|
||||||
path: /api/v1/users/login
|
|
||||||
headers:
|
|
||||||
- name: ":method"
|
|
||||||
string_match:
|
|
||||||
exact: "POST"
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
|
|
||||||
# 注册端点 - 不需要 JWT
|
|
||||||
- match:
|
|
||||||
path: /api/v1/users/register
|
|
||||||
headers:
|
|
||||||
- name: ":method"
|
|
||||||
string_match:
|
|
||||||
exact: "POST"
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
|
|
||||||
# 其他所有用户 API 端点 - 需要 JWT
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/users
|
|
||||||
headers:
|
|
||||||
- name: ":method"
|
|
||||||
string_match:
|
|
||||||
exact: "GET"
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
request_headers_to_add:
|
|
||||||
- header:
|
|
||||||
key: "x-verified-user"
|
|
||||||
value: "%REQ(X-USER-ID)%"
|
|
||||||
|
|
||||||
# 订单 API - 需要 JWT
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/orders
|
|
||||||
route:
|
|
||||||
cluster: order_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
request_headers_to_add:
|
|
||||||
- header:
|
|
||||||
key: "x-verified-user"
|
|
||||||
value: "%REQ(X-USER-ID)%"
|
|
||||||
|
|
||||||
# 健康检查端点
|
|
||||||
- match:
|
|
||||||
path: /health
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 10s
|
|
||||||
|
|
||||||
# 默认路由
|
|
||||||
- match:
|
|
||||||
prefix: /
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
direct_response:
|
|
||||||
status: 404
|
|
||||||
body:
|
|
||||||
inline_string: "Not Found"
|
|
||||||
|
|
||||||
# HTTPS 监听器(需要配置 TLS 证书)
|
|
||||||
- name: listener_https
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_number: 8443
|
|
||||||
filter_chains:
|
|
||||||
- transport_socket:
|
|
||||||
name: envoy.transport_sockets.tls
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
|
|
||||||
common_tls_context:
|
|
||||||
tls_certificates:
|
|
||||||
- certificate_chain:
|
|
||||||
filename: /etc/envoy/certs/tls.crt
|
|
||||||
private_key:
|
|
||||||
filename: /etc/envoy/certs/tls.key
|
|
||||||
filters:
|
|
||||||
- name: envoy.filters.network.http_connection_manager
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
|
||||||
stat_prefix: ingress_https
|
|
||||||
access_log:
|
|
||||||
- name: envoy.access_loggers.file
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
|
|
||||||
path: /var/log/envoy/access.log
|
|
||||||
format: |
|
|
||||||
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"
|
|
||||||
%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT%
|
|
||||||
"%DURATION%" "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"
|
|
||||||
"%REQ(USER-AGENT)%" "%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"
|
|
||||||
|
|
||||||
http_filters:
|
|
||||||
# JWT 验证过滤器
|
|
||||||
- name: envoy.filters.http.jwt_authn
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
|
|
||||||
providers:
|
|
||||||
jwt_provider:
|
|
||||||
issuer: "juwan-user-rpc"
|
|
||||||
audiences: "api.juwan.local"
|
|
||||||
# 本地验证(离线模式)- 需要在 ConfigMap 中配置公钥
|
|
||||||
local_jwks:
|
|
||||||
inline_string: |
|
|
||||||
{
|
|
||||||
"keys": [
|
|
||||||
{
|
|
||||||
"kty": "oct",
|
|
||||||
"k": "YOUR-BASE64-ENCODED-SECRET-KEY"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
# 也可以使用远程 JWKS(更推荐)
|
|
||||||
# remote_jwks:
|
|
||||||
# http_uri:
|
|
||||||
# uri: "http://user-rpc-svc:9001/.well-known/jwks.json"
|
|
||||||
# cluster: user_rpc_cluster
|
|
||||||
# timeout: 5s
|
|
||||||
# cache_ttl:
|
|
||||||
# seconds: 300
|
|
||||||
# payload_in_metadata: "JWT_PAYLOAD"
|
|
||||||
rules:
|
|
||||||
# 不需要验证的路由
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/users/login
|
|
||||||
allow_missing_or_failed: true
|
|
||||||
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/users/register
|
|
||||||
allow_missing_or_failed: true
|
|
||||||
|
|
||||||
- match:
|
|
||||||
path: /health
|
|
||||||
allow_missing_or_failed: true
|
|
||||||
|
|
||||||
# 所有其他路由都需要有效的 JWT
|
|
||||||
- match:
|
|
||||||
prefix: /
|
|
||||||
requires:
|
|
||||||
provider_name: jwt_provider
|
|
||||||
|
|
||||||
# CSRF 防护过滤器
|
|
||||||
- name: envoy.filters.http.csrf
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy
|
|
||||||
filter_enabled:
|
|
||||||
default_value:
|
|
||||||
numerator: 100
|
|
||||||
denominator: HUNDRED
|
|
||||||
runtime_key: csrf_filter_enabled
|
|
||||||
shadow_enabled:
|
|
||||||
default_value:
|
|
||||||
numerator: 0
|
|
||||||
denominator: HUNDRED
|
|
||||||
runtime_key: csrf_filter_shadow_enabled
|
|
||||||
additional_origins:
|
|
||||||
- exact: "https://admin.juwan.local"
|
|
||||||
ignore_method_matches:
|
|
||||||
- google_re2:
|
|
||||||
regex: "^(GET|HEAD|OPTIONS|TRACE)$"
|
|
||||||
|
|
||||||
# 代理验证过滤器(可选 - 调用 RPC 验证 token 黑名单)
|
|
||||||
# - name: envoy.filters.http.ext_authz
|
|
||||||
# typed_config:
|
|
||||||
# "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
|
|
||||||
# grpc_service:
|
|
||||||
# envoy_grpc:
|
|
||||||
# cluster_name: user_rpc_cluster
|
|
||||||
# failure_mode_allow: false
|
|
||||||
# with_request_body:
|
|
||||||
# max_request_bytes: 8192
|
|
||||||
# allow_partial_message: false
|
|
||||||
|
|
||||||
# 本地速率限制(DDOS 防护)
|
|
||||||
- name: envoy.filters.http.local_ratelimit
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
|
|
||||||
stat_prefix: https_local_rate_limiter
|
|
||||||
token_bucket:
|
|
||||||
max_tokens: 10000
|
|
||||||
tokens_per_fill: 10000
|
|
||||||
fill_interval: 1s
|
|
||||||
filter_enabled:
|
|
||||||
runtime_key: local_rate_limit_enabled
|
|
||||||
default_value:
|
|
||||||
numerator: 100
|
|
||||||
denominator: HUNDRED
|
|
||||||
|
|
||||||
# 路由过滤器
|
|
||||||
- name: envoy.filters.http.router
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
|
||||||
|
|
||||||
route_config:
|
|
||||||
name: https_route
|
|
||||||
virtual_hosts:
|
|
||||||
- name: backend
|
|
||||||
domains: ["*"]
|
|
||||||
routes:
|
|
||||||
# 登录和注册不需要 JWT
|
|
||||||
- match:
|
|
||||||
path: /api/v1/users/login
|
|
||||||
headers:
|
|
||||||
- name: ":method"
|
|
||||||
string_match:
|
|
||||||
exact: "POST"
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
|
|
||||||
- match:
|
|
||||||
path: /api/v1/users/register
|
|
||||||
headers:
|
|
||||||
- name: ":method"
|
|
||||||
string_match:
|
|
||||||
exact: "POST"
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
|
|
||||||
# 用户 API(带 JWT 验证)
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/users
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
request_headers_to_add:
|
|
||||||
- header:
|
|
||||||
key: "x-verified-user"
|
|
||||||
value: "%REQ(X-USER-ID)%"
|
|
||||||
|
|
||||||
# 订单 API(带 JWT 验证)
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/orders
|
|
||||||
route:
|
|
||||||
cluster: order_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
request_headers_to_add:
|
|
||||||
- header:
|
|
||||||
key: "x-verified-user"
|
|
||||||
value: "%REQ(X-USER-ID)%"
|
|
||||||
|
|
||||||
# 健康检查
|
|
||||||
- match:
|
|
||||||
path: /health
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 10s
|
|
||||||
|
|
||||||
# 默认路由
|
|
||||||
- match:
|
|
||||||
prefix: /
|
|
||||||
direct_response:
|
|
||||||
status: 404
|
|
||||||
body:
|
|
||||||
inline_string: "Not Found"
|
|
||||||
|
|
||||||
clusters:
|
|
||||||
# User API 集群
|
|
||||||
- name: user_api_cluster
|
|
||||||
connect_timeout: 10s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: user_api_cluster
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: user-api-svc
|
|
||||||
port_number: 8888
|
|
||||||
health_checks:
|
|
||||||
- timeout: 5s
|
|
||||||
interval: 10s
|
|
||||||
unhealthy_threshold: 2
|
|
||||||
healthy_threshold: 2
|
|
||||||
http_health_check:
|
|
||||||
path: /health
|
|
||||||
expected_statuses:
|
|
||||||
- start: 200
|
|
||||||
end: 299
|
|
||||||
|
|
||||||
# Order API 集群
|
|
||||||
- name: order_api_cluster
|
|
||||||
connect_timeout: 10s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: order_api_cluster
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: order-api-svc
|
|
||||||
port_number: 8889
|
|
||||||
health_checks:
|
|
||||||
- timeout: 5s
|
|
||||||
interval: 10s
|
|
||||||
unhealthy_threshold: 2
|
|
||||||
healthy_threshold: 2
|
|
||||||
http_health_check:
|
|
||||||
path: /health
|
|
||||||
expected_statuses:
|
|
||||||
- start: 200
|
|
||||||
end: 299
|
|
||||||
|
|
||||||
# User RPC 集群(用于 ext_authz 调用)
|
|
||||||
- name: user_rpc_cluster
|
|
||||||
connect_timeout: 10s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: user_rpc_cluster
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: user-rpc-svc
|
|
||||||
port_number: 9001
|
|
||||||
http2_protocol_options: {}
|
|
||||||
|
|
||||||
admin:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_number: 9901
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 生成 JWKS JSON 文件的脚本
|
|
||||||
# 用于 Envoy JWT 验证
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 参数
|
|
||||||
JWT_SECRET_KEY="${1:-your-secret-key-change-this-in-production}"
|
|
||||||
OUTPUT_FILE="${2:-jwks.json}"
|
|
||||||
KEY_ID="${3:-default-key-id}"
|
|
||||||
|
|
||||||
echo "生成 JWKS JSON..."
|
|
||||||
echo "- Secret Key: ${JWT_SECRET_KEY:0:10}..."
|
|
||||||
echo "- Key ID: $KEY_ID"
|
|
||||||
echo "- Output: $OUTPUT_FILE"
|
|
||||||
|
|
||||||
# 对密钥进行 base64 编码(URL-safe 无填充)
|
|
||||||
ENCODED_KEY=$(echo -n "$JWT_SECRET_KEY" | base64 | tr '+/' '-_' | sed 's/=//g')
|
|
||||||
|
|
||||||
# 生成 JWKS JSON
|
|
||||||
cat > "$OUTPUT_FILE" <<EOF
|
|
||||||
{
|
|
||||||
"keys": [
|
|
||||||
{
|
|
||||||
"kty": "oct",
|
|
||||||
"use": "sig",
|
|
||||||
"kid": "$KEY_ID",
|
|
||||||
"k": "$ENCODED_KEY",
|
|
||||||
"alg": "HS256"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "✓ JWKS 文件已生成: $OUTPUT_FILE"
|
|
||||||
echo ""
|
|
||||||
echo "内容预览:"
|
|
||||||
cat "$OUTPUT_FILE"
|
|
||||||
echo ""
|
|
||||||
echo ""
|
|
||||||
echo "配置说明:"
|
|
||||||
echo "1. 在 user-rpc 的 .well-known/jwks.json 端点暴露此文件"
|
|
||||||
echo "2. 在 Envoy 中配置远程 JWKS URI:"
|
|
||||||
echo " remote_jwks:"
|
|
||||||
echo " http_uri:"
|
|
||||||
echo " uri: http://user-rpc-svc:9001/.well-known/jwks.json"
|
|
||||||
echo ""
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# JWT 和认证配置完整设置脚本
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "🔐 Juwan JWT 认证配置脚本"
|
|
||||||
echo "===================================="
|
|
||||||
|
|
||||||
NAMESPACE="juwan"
|
|
||||||
JWT_SECRET=$(openssl rand -hex 32)
|
|
||||||
JWKS_KEY_ID="juwan-key-2026"
|
|
||||||
|
|
||||||
echo "✅ 生成 JWT 密钥..."
|
|
||||||
echo " Secret: $JWT_SECRET"
|
|
||||||
|
|
||||||
# Step 1: 创建 JWT Secret
|
|
||||||
echo ""
|
|
||||||
echo "📝 创建 K8s Secret..."
|
|
||||||
kubectl create secret generic jwt-secret \
|
|
||||||
--from-literal=key=$JWT_SECRET \
|
|
||||||
-n $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
|
|
||||||
# Step 2: 生成 JWKS JSON(包含公钥)
|
|
||||||
# 注意:对于 HMAC 算法,JWKS 包含密钥本身
|
|
||||||
JWKS_JSON=$(cat <<EOF
|
|
||||||
{
|
|
||||||
"keys": [
|
|
||||||
{
|
|
||||||
"kty": "oct",
|
|
||||||
"kid": "$JWKS_KEY_ID",
|
|
||||||
"k": "$(echo -n $JWT_SECRET | base64 -w 0)",
|
|
||||||
"alg": "HS256",
|
|
||||||
"use": "sig"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
echo "📝 创建 JWKS ConfigMap..."
|
|
||||||
kubectl create configmap jwks-config \
|
|
||||||
--from-literal=jwks.json="$JWKS_JSON" \
|
|
||||||
-n $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✅ JWT 认证配置完成!"
|
|
||||||
echo ""
|
|
||||||
echo "后续步骤:"
|
|
||||||
echo "1. 更新 Envoy ConfigMap,挂载 JWKS 文件"
|
|
||||||
echo "2. 在各 API 服务中配置 JWT_SECRET 环境变量"
|
|
||||||
echo "3. 登录端点使用此密钥签名 Token"
|
|
||||||
echo ""
|
|
||||||
echo "JWT 密钥已保存到 K8s Secret: jwt-secret"
|
|
||||||
echo "JWKS 已保存到 K8s ConfigMap: jwks-config"
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: snowflake-sve
|
|
||||||
namespace: juwan
|
|
||||||
spec:
|
|
||||||
ClusterIP: None
|
|
||||||
selector:
|
|
||||||
app: snowflake
|
|
||||||
ports:
|
|
||||||
- port: 9000
|
|
||||||
targetPort: 9000
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: StatefulSet
|
|
||||||
metadata:
|
|
||||||
name: snowflake
|
|
||||||
namespace: juwan
|
|
||||||
spec:
|
|
||||||
serviceName: snowflake-svc
|
|
||||||
replicas: 3
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: snowflake
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: snowflake
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: snowflake
|
|
||||||
image:
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: juwan
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: envoy-config
|
|
||||||
namespace: juwan
|
|
||||||
data:
|
|
||||||
envoy.yaml: |
|
|
||||||
static_resources:
|
|
||||||
listeners:
|
|
||||||
- name: listener_http
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_value: 8080
|
|
||||||
filter_chains:
|
|
||||||
- filters:
|
|
||||||
- name: envoy.filters.network.http_connection_manager
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
|
||||||
stat_prefix: ingress_http
|
|
||||||
access_log:
|
|
||||||
- name: envoy.access_loggers.stdout
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
|
|
||||||
http_filters:
|
|
||||||
- name: envoy.filters.http.router
|
|
||||||
typed_config:
|
|
||||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
|
||||||
route_config:
|
|
||||||
name: local_route
|
|
||||||
virtual_hosts:
|
|
||||||
- name: backend
|
|
||||||
domains: ["*"]
|
|
||||||
routes:
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/users
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
- match:
|
|
||||||
prefix: /api/v1/orders
|
|
||||||
route:
|
|
||||||
cluster: order_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
- match:
|
|
||||||
prefix: /health
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 10s
|
|
||||||
- match:
|
|
||||||
prefix: /
|
|
||||||
route:
|
|
||||||
cluster: user_api_cluster
|
|
||||||
timeout: 30s
|
|
||||||
|
|
||||||
clusters:
|
|
||||||
- name: user_api_cluster
|
|
||||||
connect_timeout: 5s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: user_api_cluster
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: user-api-svc.juwan.svc.cluster.local
|
|
||||||
port_value: 8888
|
|
||||||
health_checks:
|
|
||||||
- timeout: 3s
|
|
||||||
interval: 10s
|
|
||||||
unhealthy_threshold: 2
|
|
||||||
healthy_threshold: 2
|
|
||||||
http_health_check:
|
|
||||||
path: /health
|
|
||||||
|
|
||||||
- name: order_api_cluster
|
|
||||||
connect_timeout: 5s
|
|
||||||
type: STRICT_DNS
|
|
||||||
dns_lookup_family: V4_ONLY
|
|
||||||
lb_policy: ROUND_ROBIN
|
|
||||||
load_assignment:
|
|
||||||
cluster_name: order_api_cluster
|
|
||||||
endpoints:
|
|
||||||
- lb_endpoints:
|
|
||||||
- endpoint:
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: order-api-svc.juwan.svc.cluster.local
|
|
||||||
port_value: 8889
|
|
||||||
health_checks:
|
|
||||||
- timeout: 3s
|
|
||||||
interval: 10s
|
|
||||||
unhealthy_threshold: 2
|
|
||||||
healthy_threshold: 2
|
|
||||||
http_health_check:
|
|
||||||
path: /health
|
|
||||||
|
|
||||||
admin:
|
|
||||||
access_log_path: /tmp/admin_access.log
|
|
||||||
address:
|
|
||||||
socket_address:
|
|
||||||
address: 0.0.0.0
|
|
||||||
port_value: 9901
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: envoy-gateway
|
|
||||||
namespace: juwan
|
|
||||||
spec:
|
|
||||||
type: LoadBalancer
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
protocol: TCP
|
|
||||||
- name: admin
|
|
||||||
port: 9901
|
|
||||||
targetPort: 9901
|
|
||||||
protocol: TCP
|
|
||||||
selector:
|
|
||||||
app: envoy-gateway
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: envoy-gateway
|
|
||||||
namespace: juwan
|
|
||||||
labels:
|
|
||||||
app: envoy-gateway
|
|
||||||
spec:
|
|
||||||
replicas: 2
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: envoy-gateway
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: envoy-gateway
|
|
||||||
spec:
|
|
||||||
serviceAccountName: envoy-gateway
|
|
||||||
containers:
|
|
||||||
- name: envoy
|
|
||||||
image: envoyproxy/envoy:v1.27-latest
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 8080
|
|
||||||
- name: admin
|
|
||||||
containerPort: 9901
|
|
||||||
volumeMounts:
|
|
||||||
- name: envoy-config
|
|
||||||
mountPath: /etc/envoy
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /stats
|
|
||||||
port: 9901
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 5
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /stats
|
|
||||||
port: 9901
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 10
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 128Mi
|
|
||||||
limits:
|
|
||||||
cpu: 500m
|
|
||||||
memory: 512Mi
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
- name: envoy-config
|
|
||||||
configMap:
|
|
||||||
name: envoy-config
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
name: envoy-gateway
|
|
||||||
namespace: juwan
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRole
|
|
||||||
metadata:
|
|
||||||
name: envoy-gateway
|
|
||||||
rules:
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["endpoints"]
|
|
||||||
verbs: ["get", "list", "watch"]
|
|
||||||
- apiGroups: ["discovery.k8s.io"]
|
|
||||||
resources: ["endpointslices"]
|
|
||||||
verbs: ["get", "list", "watch"]
|
|
||||||
- apiGroups: [""]
|
|
||||||
resources: ["services"]
|
|
||||||
verbs: ["get", "list", "watch"]
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
|
||||||
kind: ClusterRoleBinding
|
|
||||||
metadata:
|
|
||||||
name: envoy-gateway
|
|
||||||
roleRef:
|
|
||||||
apiGroup: rbac.authorization.k8s.io
|
|
||||||
kind: ClusterRole
|
|
||||||
name: envoy-gateway
|
|
||||||
subjects:
|
|
||||||
- kind: ServiceAccount
|
|
||||||
name: envoy-gateway
|
|
||||||
namespace: juwan
|
|
||||||
|
|
||||||
---
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: NetworkPolicy
|
|
||||||
metadata:
|
|
||||||
name: envoy-gateway-network-policy
|
|
||||||
namespace: juwan
|
|
||||||
spec:
|
|
||||||
podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app: envoy-gateway
|
|
||||||
policyTypes:
|
|
||||||
- Ingress
|
|
||||||
- Egress
|
|
||||||
ingress:
|
|
||||||
- from:
|
|
||||||
- namespaceSelector: {}
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 8080
|
|
||||||
egress:
|
|
||||||
- to:
|
|
||||||
- namespaceSelector: {}
|
|
||||||
ports:
|
|
||||||
- protocol: UDP
|
|
||||||
port: 53
|
|
||||||
- to:
|
|
||||||
- podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app: user-api
|
|
||||||
- podSelector:
|
|
||||||
matchLabels:
|
|
||||||
app: order-api
|
|
||||||
ports:
|
|
||||||
- protocol: TCP
|
|
||||||
port: 8888
|
|
||||||
- protocol: TCP
|
|
||||||
port: 8889
|
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: envoy-config
|
||||||
|
namespace: juwan
|
||||||
|
data:
|
||||||
|
envoy.yaml: |
|
||||||
|
static_resources:
|
||||||
|
listeners:
|
||||||
|
- name: ingress_http
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: 0.0.0.0
|
||||||
|
port_value: 8080
|
||||||
|
filter_chains:
|
||||||
|
- filters:
|
||||||
|
- name: envoy.filters.network.http_connection_manager
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||||
|
stat_prefix: ingress_http
|
||||||
|
codec_type: AUTO
|
||||||
|
generate_request_id: true
|
||||||
|
use_remote_address: true
|
||||||
|
route_config:
|
||||||
|
name: local_route
|
||||||
|
virtual_hosts:
|
||||||
|
- name: juwan_services
|
||||||
|
domains: ["*"]
|
||||||
|
routes:
|
||||||
|
- match:
|
||||||
|
path: /healthz
|
||||||
|
direct_response:
|
||||||
|
status: 200
|
||||||
|
body:
|
||||||
|
inline_string: ok
|
||||||
|
- match:
|
||||||
|
prefix: /api/email
|
||||||
|
route:
|
||||||
|
cluster: email_api_cluster
|
||||||
|
timeout: 30s
|
||||||
|
- match:
|
||||||
|
prefix: /api/users
|
||||||
|
route:
|
||||||
|
cluster: user_api_cluster
|
||||||
|
timeout: 30s
|
||||||
|
- match:
|
||||||
|
prefix: /
|
||||||
|
direct_response:
|
||||||
|
status: 404
|
||||||
|
body:
|
||||||
|
inline_string: "gateway route not found"
|
||||||
|
http_filters:
|
||||||
|
- name: envoy.filters.http.lua
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
|
||||||
|
inline_code: |
|
||||||
|
local TOKEN_COOKIE = "csrf_token"
|
||||||
|
local GUARD_COOKIE = "csrf_guard"
|
||||||
|
local TOKEN_HEADER = "x-csrf-token"
|
||||||
|
local GUARD_HEADER = "x-csrf-guard"
|
||||||
|
|
||||||
|
local seeded = false
|
||||||
|
|
||||||
|
local function seed_random()
|
||||||
|
if seeded then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
seeded = true
|
||||||
|
math.randomseed(os.time())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function split_cookie(header)
|
||||||
|
local out = {}
|
||||||
|
if not header then
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
for pair in string.gmatch(header, "([^;]+)") do
|
||||||
|
local key, value = string.match(pair, "^%s*([^=]+)=?(.*)$")
|
||||||
|
if key ~= nil and value ~= nil then
|
||||||
|
out[string.lower(key)] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
local function is_safe_method(method)
|
||||||
|
return method == "GET" or method == "HEAD" or method == "OPTIONS"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function build_token(request_id)
|
||||||
|
seed_random()
|
||||||
|
local rnd = tostring(math.random(100000, 999999))
|
||||||
|
local rid = request_id or "rid"
|
||||||
|
return tostring(os.time()) .. "-" .. rid .. "-" .. rnd
|
||||||
|
end
|
||||||
|
|
||||||
|
function envoy_on_request(request_handle)
|
||||||
|
local headers = request_handle:headers()
|
||||||
|
local method = headers:get(":method")
|
||||||
|
|
||||||
|
local cookie_header = headers:get("cookie")
|
||||||
|
local cookies = split_cookie(cookie_header)
|
||||||
|
local csrf_token_cookie = cookies[TOKEN_COOKIE]
|
||||||
|
local csrf_guard_cookie = cookies[GUARD_COOKIE]
|
||||||
|
|
||||||
|
request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_token_cookie", csrf_token_cookie == nil or csrf_token_cookie == "")
|
||||||
|
request_handle:streamInfo():dynamicMetadata():set("csrf", "need_set_guard_cookie", csrf_guard_cookie == nil or csrf_guard_cookie == "")
|
||||||
|
|
||||||
|
if csrf_token_cookie == nil or csrf_token_cookie == "" then
|
||||||
|
csrf_token_cookie = build_token(headers:get("x-request-id"))
|
||||||
|
request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", csrf_token_cookie)
|
||||||
|
else
|
||||||
|
request_handle:streamInfo():dynamicMetadata():set("csrf", "token_value", csrf_token_cookie)
|
||||||
|
end
|
||||||
|
|
||||||
|
if csrf_guard_cookie == nil or csrf_guard_cookie == "" then
|
||||||
|
csrf_guard_cookie = build_token(headers:get("x-request-id"))
|
||||||
|
request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", csrf_guard_cookie)
|
||||||
|
else
|
||||||
|
request_handle:streamInfo():dynamicMetadata():set("csrf", "guard_value", csrf_guard_cookie)
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_safe_method(method) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local csrf_token_header = headers:get(TOKEN_HEADER)
|
||||||
|
local csrf_guard_header = headers:get(GUARD_HEADER)
|
||||||
|
|
||||||
|
if csrf_token_header == nil or csrf_guard_header == nil then
|
||||||
|
request_handle:respond(
|
||||||
|
{[":status"] = "403", ["content-type"] = "application/json"},
|
||||||
|
'{"code":403,"message":"missing csrf headers"}'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if csrf_token_cookie == nil or csrf_guard_cookie == nil then
|
||||||
|
request_handle:respond(
|
||||||
|
{[":status"] = "403", ["content-type"] = "application/json"},
|
||||||
|
'{"code":403,"message":"missing csrf cookies"}'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if csrf_token_header ~= csrf_token_cookie or csrf_guard_header ~= csrf_guard_cookie then
|
||||||
|
request_handle:respond(
|
||||||
|
{[":status"] = "403", ["content-type"] = "application/json"},
|
||||||
|
'{"code":403,"message":"csrf token mismatch"}'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function envoy_on_response(response_handle)
|
||||||
|
local metadata = response_handle:streamInfo():dynamicMetadata():get("csrf")
|
||||||
|
if metadata == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local token_value = metadata["token_value"]
|
||||||
|
local guard_value = metadata["guard_value"]
|
||||||
|
|
||||||
|
if metadata["need_set_token_cookie"] == true and token_value ~= nil and token_value ~= "" then
|
||||||
|
response_handle:headers():add(
|
||||||
|
"set-cookie",
|
||||||
|
TOKEN_COOKIE .. "=" .. token_value .. "; Path=/; SameSite=Strict"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
if metadata["need_set_guard_cookie"] == true and guard_value ~= nil and guard_value ~= "" then
|
||||||
|
response_handle:headers():add(
|
||||||
|
"set-cookie",
|
||||||
|
GUARD_COOKIE .. "=" .. guard_value .. "; Path=/; SameSite=Strict"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
- name: envoy.filters.http.router
|
||||||
|
typed_config:
|
||||||
|
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||||
|
|
||||||
|
clusters:
|
||||||
|
- name: user_api_cluster
|
||||||
|
connect_timeout: 2s
|
||||||
|
type: STRICT_DNS
|
||||||
|
lb_policy: ROUND_ROBIN
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: user_api_cluster
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: user-api-svc.juwan.svc.cluster.local
|
||||||
|
port_value: 8888
|
||||||
|
- name: email_api_cluster
|
||||||
|
connect_timeout: 2s
|
||||||
|
type: STRICT_DNS
|
||||||
|
lb_policy: ROUND_ROBIN
|
||||||
|
load_assignment:
|
||||||
|
cluster_name: email_api_cluster
|
||||||
|
endpoints:
|
||||||
|
- lb_endpoints:
|
||||||
|
- endpoint:
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: email-api-svc.juwan.svc.cluster.local
|
||||||
|
port_value: 8888
|
||||||
|
|
||||||
|
admin:
|
||||||
|
access_log_path: /tmp/admin_access.log
|
||||||
|
address:
|
||||||
|
socket_address:
|
||||||
|
address: 0.0.0.0
|
||||||
|
port_value: 9901
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: envoy-gateway
|
||||||
|
namespace: juwan
|
||||||
|
labels:
|
||||||
|
app: envoy-gateway
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
revisionHistoryLimit: 5
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: envoy-gateway
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: envoy-gateway
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: envoy
|
||||||
|
image: envoyproxy/envoy:v1.31-latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/usr/local/bin/envoy"]
|
||||||
|
args:
|
||||||
|
- "-c"
|
||||||
|
- "/etc/envoy/envoy.yaml"
|
||||||
|
- "--log-level"
|
||||||
|
- "info"
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
- containerPort: 9901
|
||||||
|
name: admin
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 15
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: envoy-config
|
||||||
|
mountPath: /etc/envoy
|
||||||
|
volumes:
|
||||||
|
- name: envoy-config
|
||||||
|
configMap:
|
||||||
|
name: envoy-config
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: envoy-gateway
|
||||||
|
namespace: juwan
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: envoy-gateway
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
- name: admin
|
||||||
|
port: 9901
|
||||||
|
targetPort: 9901
|
||||||
|
type: ClusterIP
|
||||||
@@ -19,12 +19,13 @@ spec:
|
|||||||
serviceAccountName: find-endpoints
|
serviceAccountName: find-endpoints
|
||||||
containers:
|
containers:
|
||||||
- name: email-api
|
- name: email-api
|
||||||
image: email
|
image: 103.236.53.208:4418/library/email-api@sha256:fe5c66f5bcb1a39652620df42351de3e48227920a34be3110a45eb13db327020
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8888
|
- containerPort: 8888
|
||||||
|
- containerPort: 4001
|
||||||
env:
|
env:
|
||||||
- name: KAFKA_BROKER
|
- name: KAFKA_BROKER
|
||||||
value: "my-cluster-kafka-bootstrap.kafka.svc.cluster.local:9092"
|
value: "my-cluster-kafka-bootstrap.kafka:9092"
|
||||||
- name: REDIS_M_HOST
|
- name: REDIS_M_HOST
|
||||||
value: "user-redis-master.juwan:6379"
|
value: "user-redis-master.juwan:6379"
|
||||||
- name: REDIS_S_HOST
|
- name: REDIS_S_HOST
|
||||||
@@ -65,10 +66,18 @@ kind: Service
|
|||||||
metadata:
|
metadata:
|
||||||
name: email-api-svc
|
name: email-api-svc
|
||||||
namespace: juwan
|
namespace: juwan
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "4001"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
spec:
|
spec:
|
||||||
ports:
|
ports:
|
||||||
- port: 8888
|
- name: http
|
||||||
|
port: 8888
|
||||||
targetPort: 8888
|
targetPort: 8888
|
||||||
|
- name: metrics
|
||||||
|
port: 4001
|
||||||
|
targetPort: 4001
|
||||||
selector:
|
selector:
|
||||||
app: email-api
|
app: email-api
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ spec:
|
|||||||
serviceAccountName: find-endpoints
|
serviceAccountName: find-endpoints
|
||||||
containers:
|
containers:
|
||||||
- name: email-consumer
|
- name: email-consumer
|
||||||
image: 103.236.53.208:4418/library/email-consumer@sha256:6fe8a3a57310a5e79feecc4bf38ac2c5b8c58a7f200f104f7bf4707b9db5fc13
|
image: 103.236.53.208:4418/library/email-mq@sha256:a9f76e8f4a17d1c00cefc429962037550e17feebb5cf38b28d360c91c8ba3e68
|
||||||
|
ports:
|
||||||
|
- containerPort: 4001
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
@@ -46,10 +48,17 @@ kind: Service
|
|||||||
metadata:
|
metadata:
|
||||||
name: email-consumer-svc
|
name: email-consumer-svc
|
||||||
namespace: juwan
|
namespace: juwan
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
prometheus.io/port: "4001"
|
||||||
|
prometheus.io/path: "/metrics"
|
||||||
spec:
|
spec:
|
||||||
ports:
|
ports:
|
||||||
- port: 8080
|
# - port: 8080
|
||||||
targetPort: 8080
|
# targetPort: 8080
|
||||||
|
- name: metrics
|
||||||
|
port: 4001
|
||||||
|
targetPort: 4001
|
||||||
selector:
|
selector:
|
||||||
app: email-consumer
|
app: email-consumer
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: snowflake
|
name: snowflake
|
||||||
namespace: juwan
|
namespace: juwan
|
||||||
@@ -71,7 +71,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
scaleTargetRef:
|
scaleTargetRef:
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: StatefulSet
|
||||||
name: snowflake
|
name: snowflake
|
||||||
minReplicas: 3
|
minReplicas: 3
|
||||||
maxReplicas: 10
|
maxReplicas: 10
|
||||||
@@ -94,7 +94,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
scaleTargetRef:
|
scaleTargetRef:
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: StatefulSet
|
||||||
name: snowflake
|
name: snowflake
|
||||||
minReplicas: 3
|
minReplicas: 3
|
||||||
maxReplicas: 10
|
maxReplicas: 10
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ metadata:
|
|||||||
labels:
|
labels:
|
||||||
app: user-api
|
app: user-api
|
||||||
spec:
|
spec:
|
||||||
replicas: 3
|
replicas: 1
|
||||||
revisionHistoryLimit: 5
|
revisionHistoryLimit: 5
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
@@ -19,7 +19,7 @@ spec:
|
|||||||
serviceAccountName: find-endpoints
|
serviceAccountName: find-endpoints
|
||||||
containers:
|
containers:
|
||||||
- name: user-api
|
- name: user-api
|
||||||
image: user-api:v1
|
image: 103.236.53.208:4418/library/user-api@sha256:a152f5fd13fc865ae3d9aeaa54eacad6bcaa0cb4f0ccb770fbb746be95360991
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8888
|
- containerPort: 8888
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
@@ -61,50 +61,50 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
app: user-api
|
app: user-api
|
||||||
|
|
||||||
---
|
#---
|
||||||
|
#
|
||||||
apiVersion: autoscaling/v2
|
#apiVersion: autoscaling/v2
|
||||||
kind: HorizontalPodAutoscaler
|
#kind: HorizontalPodAutoscaler
|
||||||
metadata:
|
#metadata:
|
||||||
name: user-api-hpa-c
|
# name: user-api-hpa-c
|
||||||
namespace: juwan
|
# namespace: juwan
|
||||||
labels:
|
# labels:
|
||||||
app: user-api-hpa-c
|
# app: user-api-hpa-c
|
||||||
spec:
|
#spec:
|
||||||
scaleTargetRef:
|
# scaleTargetRef:
|
||||||
apiVersion: apps/v1
|
# apiVersion: apps/v1
|
||||||
kind: Deployment
|
# kind: Deployment
|
||||||
name: user-api
|
# name: user-api
|
||||||
minReplicas: 3
|
# minReplicas: 3
|
||||||
maxReplicas: 10
|
# maxReplicas: 10
|
||||||
metrics:
|
# metrics:
|
||||||
- type: Resource
|
# - type: Resource
|
||||||
resource:
|
# resource:
|
||||||
name: cpu
|
# name: cpu
|
||||||
target:
|
# target:
|
||||||
type: Utilization
|
# type: Utilization
|
||||||
averageUtilization: 80
|
# averageUtilization: 80
|
||||||
|
#
|
||||||
---
|
#---
|
||||||
|
#
|
||||||
apiVersion: autoscaling/v2
|
#apiVersion: autoscaling/v2
|
||||||
kind: HorizontalPodAutoscaler
|
#kind: HorizontalPodAutoscaler
|
||||||
metadata:
|
#metadata:
|
||||||
name: user-api-hpa-m
|
# name: user-api-hpa-m
|
||||||
namespace: juwan
|
# namespace: juwan
|
||||||
labels:
|
# labels:
|
||||||
app: user-api-hpa-m
|
# app: user-api-hpa-m
|
||||||
spec:
|
#spec:
|
||||||
scaleTargetRef:
|
# scaleTargetRef:
|
||||||
apiVersion: apps/v1
|
# apiVersion: apps/v1
|
||||||
kind: Deployment
|
# kind: Deployment
|
||||||
name: user-api
|
# name: user-api
|
||||||
minReplicas: 3
|
# minReplicas: 3
|
||||||
maxReplicas: 10
|
# maxReplicas: 10
|
||||||
metrics:
|
# metrics:
|
||||||
- type: Resource
|
# - type: Resource
|
||||||
resource:
|
# resource:
|
||||||
name: memory
|
# name: memory
|
||||||
target:
|
# target:
|
||||||
type: Utilization
|
# type: Utilization
|
||||||
averageUtilization: 80
|
# averageUtilization: 80
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ metadata:
|
|||||||
labels:
|
labels:
|
||||||
app: user-rpc
|
app: user-rpc
|
||||||
spec:
|
spec:
|
||||||
replicas: 3
|
replicas: 1
|
||||||
revisionHistoryLimit: 5
|
revisionHistoryLimit: 5
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
@@ -29,7 +29,7 @@ spec:
|
|||||||
]
|
]
|
||||||
containers:
|
containers:
|
||||||
- name: user-rpc
|
- name: user-rpc
|
||||||
image: 103.236.53.208:4418/library/user-rpc@sha256:57746256905acb5757153aef536ebfd19338b7f935f01ba1f538fbfd0a12f6f5
|
image: 103.236.53.208:4418/library/user-rpc@sha256:3d1d3cc02188a9b1a29a308a4867638b25b6e480e5a6bdaeb938f262f53969b7
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 9001
|
- containerPort: 9001
|
||||||
- containerPort: 4001
|
- containerPort: 4001
|
||||||
@@ -114,143 +114,151 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
app: user-rpc
|
app: user-rpc
|
||||||
|
|
||||||
---
|
#---
|
||||||
apiVersion: autoscaling/v2
|
#apiVersion: autoscaling/v2
|
||||||
kind: HorizontalPodAutoscaler
|
#kind: HorizontalPodAutoscaler
|
||||||
metadata:
|
#metadata:
|
||||||
name: user-rpc-hpa-c
|
# name: user-rpc-hpa-c
|
||||||
namespace: juwan
|
# namespace: juwan
|
||||||
labels:
|
# labels:
|
||||||
app: user-rpc-hpa-c
|
# app: user-rpc-hpa-c
|
||||||
spec:
|
#spec:
|
||||||
scaleTargetRef:
|
# scaleTargetRef:
|
||||||
apiVersion: apps/v1
|
# apiVersion: apps/v1
|
||||||
kind: Deployment
|
# kind: Deployment
|
||||||
name: user-rpc
|
# name: user-rpc
|
||||||
minReplicas: 3
|
# minReplicas: 3
|
||||||
maxReplicas: 10
|
# maxReplicas: 10
|
||||||
metrics:
|
# metrics:
|
||||||
- type: Resource
|
# - type: Resource
|
||||||
resource:
|
# resource:
|
||||||
name: cpu
|
# name: cpu
|
||||||
target:
|
# target:
|
||||||
type: Utilization
|
# type: Utilization
|
||||||
averageUtilization: 80
|
# averageUtilization: 80
|
||||||
|
#
|
||||||
---
|
#---
|
||||||
apiVersion: autoscaling/v2
|
#apiVersion: autoscaling/v2
|
||||||
kind: HorizontalPodAutoscaler
|
#kind: HorizontalPodAutoscaler
|
||||||
metadata:
|
#metadata:
|
||||||
name: user-rpc-hpa-m
|
# name: user-rpc-hpa-m
|
||||||
namespace: juwan
|
# namespace: juwan
|
||||||
labels:
|
# labels:
|
||||||
app: user-rpc-hpa-m
|
# app: user-rpc-hpa-m
|
||||||
spec:
|
#spec:
|
||||||
scaleTargetRef:
|
# scaleTargetRef:
|
||||||
apiVersion: apps/v1
|
# apiVersion: apps/v1
|
||||||
kind: Deployment
|
# kind: Deployment
|
||||||
name: user-rpc
|
# name: user-rpc
|
||||||
minReplicas: 3
|
# minReplicas: 3
|
||||||
maxReplicas: 10
|
# maxReplicas: 10
|
||||||
metrics:
|
# metrics:
|
||||||
- type: Resource
|
# - type: Resource
|
||||||
resource:
|
# resource:
|
||||||
name: memory
|
# name: memory
|
||||||
target:
|
# target:
|
||||||
type: Utilization
|
# type: Utilization
|
||||||
averageUtilization: 80
|
# averageUtilization: 80
|
||||||
---
|
#---
|
||||||
# Redis 主从复制
|
## Redis 主从复制
|
||||||
apiVersion: redis.redis.opstreelabs.in/v1beta2
|
#apiVersion: redis.redis.opstreelabs.in/v1beta2
|
||||||
kind: RedisReplication
|
#kind: RedisReplication
|
||||||
metadata:
|
#metadata:
|
||||||
name: user-redis
|
# name: user-redis
|
||||||
namespace: juwan
|
# namespace: juwan
|
||||||
spec:
|
#spec:
|
||||||
clusterSize: 3
|
# clusterSize: 3
|
||||||
kubernetesConfig:
|
# kubernetesConfig:
|
||||||
image: quay.io/opstree/redis:v7.0.12
|
# image: quay.io/opstree/redis:v7.0.12
|
||||||
imagePullPolicy: IfNotPresent
|
# imagePullPolicy: IfNotPresent
|
||||||
resources:
|
# resources:
|
||||||
requests:
|
# requests:
|
||||||
cpu: 100m
|
# cpu: 100m
|
||||||
memory: 128Mi
|
# memory: 128Mi
|
||||||
limits:
|
# limits:
|
||||||
cpu: 500m
|
# cpu: 500m
|
||||||
memory: 512Mi
|
# memory: 512Mi
|
||||||
redisSecret:
|
# redisSecret:
|
||||||
name: user-redis
|
# name: user-redis
|
||||||
key: password
|
# key: password
|
||||||
|
#
|
||||||
redisExporter:
|
# redisExporter:
|
||||||
enabled: true
|
# enabled: true
|
||||||
image: quay.io/opstree/redis-exporter:latest
|
# image: quay.io/opstree/redis-exporter:latest
|
||||||
imagePullPolicy: Always
|
# imagePullPolicy: Always
|
||||||
podSecurityContext:
|
# podSecurityContext:
|
||||||
runAsUser: 1000
|
# runAsUser: 1000
|
||||||
fsGroup: 1000
|
# fsGroup: 1000
|
||||||
storage:
|
# storage:
|
||||||
volumeClaimTemplate:
|
# volumeClaimTemplate:
|
||||||
spec:
|
# spec:
|
||||||
accessModes: ["ReadWriteOnce"]
|
# accessModes: ["ReadWriteOnce"]
|
||||||
resources:
|
# resources:
|
||||||
requests:
|
# requests:
|
||||||
storage: 1Gi
|
# storage: 1Gi
|
||||||
|
#
|
||||||
---
|
#---
|
||||||
# Sentinel 监控
|
## Sentinel 监控
|
||||||
apiVersion: redis.redis.opstreelabs.in/v1beta2
|
#apiVersion: redis.redis.opstreelabs.in/v1beta2
|
||||||
kind: RedisSentinel
|
#kind: RedisSentinel
|
||||||
metadata:
|
#metadata:
|
||||||
name: user-redis-sentinel
|
# name: user-redis-sentinel
|
||||||
namespace: juwan
|
# namespace: juwan
|
||||||
spec:
|
#spec:
|
||||||
clusterSize: 3
|
# clusterSize: 3
|
||||||
kubernetesConfig:
|
# kubernetesConfig:
|
||||||
image: quay.io/opstree/redis-sentinel:v7.0.12
|
# image: quay.io/opstree/redis-sentinel:v7.0.12
|
||||||
imagePullPolicy: IfNotPresent
|
# imagePullPolicy: IfNotPresent
|
||||||
resources:
|
# resources:
|
||||||
requests:
|
# requests:
|
||||||
cpu: 100m
|
# cpu: 100m
|
||||||
memory: 128Mi
|
# memory: 128Mi
|
||||||
limits:
|
# limits:
|
||||||
cpu: 500m
|
# cpu: 500m
|
||||||
memory: 512Mi
|
# memory: 512Mi
|
||||||
podSecurityContext:
|
# podSecurityContext:
|
||||||
runAsUser: 1000
|
# runAsUser: 1000
|
||||||
fsGroup: 1000
|
# fsGroup: 1000
|
||||||
redisSentinelConfig:
|
# redisSentinelConfig:
|
||||||
redisReplicationName: user-redis
|
# redisReplicationName: user-redis
|
||||||
masterGroupName: mymaster
|
# masterGroupName: mymaster
|
||||||
redisPort: "6379"
|
# redisPort: "6379"
|
||||||
quorum: "2"
|
# quorum: "2"
|
||||||
downAfterMilliseconds: "5000"
|
# downAfterMilliseconds: "5000"
|
||||||
failoverTimeout: "10000"
|
# failoverTimeout: "10000"
|
||||||
parallelSyncs: "1"
|
# parallelSyncs: "1"
|
||||||
|
#
|
||||||
---
|
#---
|
||||||
# PostgreSQL 集群
|
## PostgreSQL 集群
|
||||||
apiVersion: postgresql.cnpg.io/v1
|
#apiVersion: postgresql.cnpg.io/v1
|
||||||
kind: Cluster
|
#kind: Cluster
|
||||||
metadata:
|
#metadata:
|
||||||
namespace: juwan
|
# namespace: juwan
|
||||||
name: user-db
|
# name: user-db
|
||||||
spec:
|
#spec:
|
||||||
instances: 3
|
# instances: 3
|
||||||
backup:
|
# primaryUpdateStrategy: unsupervised
|
||||||
barmanObjectStore:
|
# bootstrap:
|
||||||
destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/
|
# initdb:
|
||||||
endpointURL: https://cn-nb1.rains3.com
|
# database: app
|
||||||
s3Credentials:
|
# owner: app
|
||||||
accessKeyId:
|
# # 只在 PVC 为空时初始化
|
||||||
name: rc-creds
|
# postInitSQL:
|
||||||
key: SOucqRaJr4OyfcIu
|
# - CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
||||||
secretAccessKey:
|
# backup:
|
||||||
name: rc-creds
|
# barmanObjectStore:
|
||||||
key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz
|
# destinationPath: s3://juwan-dev-pg-backups-zj/pg-data/
|
||||||
wal:
|
# endpointURL: https://cn-nb1.rains3.com
|
||||||
compression: gzip
|
# s3Credentials:
|
||||||
storage:
|
# accessKeyId:
|
||||||
size: 1Gi
|
# name: rc-creds
|
||||||
monitoring:
|
# key: SOucqRaJr4OyfcIu
|
||||||
enablePodMonitor: true
|
# secretAccessKey:
|
||||||
|
# name: rc-creds
|
||||||
|
# key: tn2Agj9EowMwuPA9y7TdSL0AXKsMEz
|
||||||
|
# wal:
|
||||||
|
# compression: gzip
|
||||||
|
# storage:
|
||||||
|
# size: 1Gi
|
||||||
|
# monitoring:
|
||||||
|
# enablePodMonitor: true
|
||||||
|
|||||||
+4
-2
@@ -12,6 +12,7 @@ type (
|
|||||||
Password string `json:"password" binding:"required,min=6,max=128"`
|
Password string `json:"password" binding:"required,min=6,max=128"`
|
||||||
Email string `json:"email,omitempty" binding:"omitempty,email"`
|
Email string `json:"email,omitempty" binding:"omitempty,email"`
|
||||||
Phone string `json:"phone,omitempty" binding:"omitempty,len=11"`
|
Phone string `json:"phone,omitempty" binding:"omitempty,len=11"`
|
||||||
|
Vcode int32 `json:"vcode"`
|
||||||
}
|
}
|
||||||
RegisterResp {
|
RegisterResp {
|
||||||
UserId int64 `json:"userId"`
|
UserId int64 `json:"userId"`
|
||||||
@@ -62,7 +63,8 @@ type (
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
LogoutReq {
|
LogoutReq {
|
||||||
UserId int64 `path:"userId" binding:"required,gt=0"`
|
UserId int64 `path:"userId" binding:"required,gt=0"`
|
||||||
|
Token string `header:"Authorization" binding:"required"`
|
||||||
}
|
}
|
||||||
LogoutResp {
|
LogoutResp {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
@@ -116,7 +118,7 @@ service user-api {
|
|||||||
|
|
||||||
@doc (
|
@doc (
|
||||||
summary: "用户登出"
|
summary: "用户登出"
|
||||||
description: "清除用户的登录会话,使用户令牌失效"
|
description: "需要携带 Authorization 令牌,清除用户会话并使令牌失效"
|
||||||
)
|
)
|
||||||
@handler Logout
|
@handler Logout
|
||||||
post /:userId/logout (LogoutReq) returns (LogoutResp)
|
post /:userId/logout (LogoutReq) returns (LogoutResp)
|
||||||
|
|||||||
+44
-18
@@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
option go_package ="./pb";
|
option go_package = "./pb";
|
||||||
|
|
||||||
package pb;
|
package pb;
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ package pb;
|
|||||||
|
|
||||||
//--------------------------------users--------------------------------
|
//--------------------------------users--------------------------------
|
||||||
message Users {
|
message Users {
|
||||||
string userId = 1; //userId
|
int64 userId = 1; //userId
|
||||||
string username = 2; //username
|
string username = 2; //username
|
||||||
string passwd = 3; //passwd
|
string passwd = 3; //passwd
|
||||||
string nickname = 4; //nickname
|
string nickname = 4; //nickname
|
||||||
@@ -24,7 +24,7 @@ message Users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message AddUsersReq {
|
message AddUsersReq {
|
||||||
string userId = 1; //userId
|
int64 userId = 1; //userId
|
||||||
string username = 2; //username
|
string username = 2; //username
|
||||||
string passwd = 3; //passwd
|
string passwd = 3; //passwd
|
||||||
string nickname = 4; //nickname
|
string nickname = 4; //nickname
|
||||||
@@ -41,7 +41,7 @@ message AddUsersResp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message UpdateUsersReq {
|
message UpdateUsersReq {
|
||||||
string userId = 1; //userId
|
int64 userId = 1; //userId
|
||||||
string username = 2; //username
|
string username = 2; //username
|
||||||
string passwd = 3; //passwd
|
string passwd = 3; //passwd
|
||||||
string nickname = 4; //nickname
|
string nickname = 4; //nickname
|
||||||
@@ -75,7 +75,7 @@ message GetUsersByIdResp {
|
|||||||
message SearchUsersReq {
|
message SearchUsersReq {
|
||||||
int64 page = 1; //page
|
int64 page = 1; //page
|
||||||
int64 limit = 2; //limit
|
int64 limit = 2; //limit
|
||||||
string userId = 3; //userId
|
int64 userId = 3; //userId
|
||||||
string username = 4; //username
|
string username = 4; //username
|
||||||
string passwd = 5; //passwd
|
string passwd = 5; //passwd
|
||||||
string nickname = 6; //nickname
|
string nickname = 6; //nickname
|
||||||
@@ -107,22 +107,25 @@ message LoginReq {
|
|||||||
|
|
||||||
message LoginResp {
|
message LoginResp {
|
||||||
string token = 1;
|
string token = 1;
|
||||||
|
string username = 2;
|
||||||
|
string email = 3;
|
||||||
|
int64 id = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ValidateTokenReq {
|
message ValidateTokenReq {
|
||||||
string token = 1; // JWT token
|
string token = 1; // JWT token
|
||||||
string userId = 2; // 用户ID
|
int64 userId = 2; // 用户ID
|
||||||
}
|
}
|
||||||
|
|
||||||
message ValidateTokenResp {
|
message ValidateTokenResp {
|
||||||
bool valid = 1; // token 是否有效(不在黑名单中)
|
bool valid = 1; // token 是否有效(不在黑名单中)
|
||||||
string message = 2; // 验证失败原因
|
string message = 2; // 验证失败原因
|
||||||
string userId = 3; // 用户ID
|
int64 userId = 3; // 用户ID
|
||||||
int64 roleType = 4; // 用户角色
|
int64 roleType = 4; // 用户角色
|
||||||
}
|
}
|
||||||
|
|
||||||
message CheckPermissionReq {
|
message CheckPermissionReq {
|
||||||
string userId = 1; // 用户ID
|
int64 userId = 1; // 用户ID
|
||||||
string resource = 2; // 资源 ID
|
string resource = 2; // 资源 ID
|
||||||
string action = 3; // 操作类型: read/write/delete
|
string action = 3; // 操作类型: read/write/delete
|
||||||
}
|
}
|
||||||
@@ -132,20 +135,43 @@ message CheckPermissionResp {
|
|||||||
string message = 2; // 拒绝原因
|
string message = 2; // 拒绝原因
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RegisterReq {
|
||||||
|
string username = 1;
|
||||||
|
string passwd = 2;
|
||||||
|
string phone = 3;
|
||||||
|
int32 vcode = 4;
|
||||||
|
string email = 5;
|
||||||
|
string requestId = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterResp {
|
||||||
|
string res = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LogoutReq {
|
||||||
|
int64 userId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LogoutResp {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
// Rpc Func
|
// Rpc Func
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
|
|
||||||
service usercenter {
|
service usercenter {
|
||||||
|
|
||||||
//-----------------------users-----------------------
|
//-----------------------users-----------------------
|
||||||
rpc AddUsers(AddUsersReq) returns (AddUsersResp);
|
rpc AddUsers(AddUsersReq) returns (AddUsersResp);
|
||||||
rpc UpdateUsers(UpdateUsersReq) returns (UpdateUsersResp);
|
rpc UpdateUsers(UpdateUsersReq) returns (UpdateUsersResp);
|
||||||
rpc DelUsers(DelUsersReq) returns (DelUsersResp);
|
rpc DelUsers(DelUsersReq) returns (DelUsersResp);
|
||||||
rpc GetUsersById(GetUsersByIdReq) returns (GetUsersByIdResp);
|
rpc GetUsersById(GetUsersByIdReq) returns (GetUsersByIdResp);
|
||||||
rpc GetUserByUsername(GetUserByUsernameReq) returns (GetUserByUsernameResp);
|
rpc GetUserByUsername(GetUserByUsernameReq) returns (GetUserByUsernameResp);
|
||||||
rpc SearchUsers(SearchUsersReq) returns (SearchUsersResp);
|
rpc SearchUsers(SearchUsersReq) returns (SearchUsersResp);
|
||||||
rpc Login(LoginReq) returns (LoginResp);
|
rpc Login(LoginReq) returns (LoginResp);
|
||||||
rpc ValidateToken(ValidateTokenReq) returns (ValidateTokenResp);
|
rpc Register(RegisterReq) returns (RegisterResp);
|
||||||
rpc CheckPermission(CheckPermissionReq) returns (CheckPermissionResp);
|
rpc ValidateToken(ValidateTokenReq) returns (ValidateTokenResp);
|
||||||
|
rpc CheckPermission(CheckPermissionReq) returns (CheckPermissionResp);
|
||||||
|
rpc Logout(LogoutReq) returns (LogoutResp);
|
||||||
}
|
}
|
||||||
+9
-7
@@ -1,15 +1,17 @@
|
|||||||
create extension if not exists "uuid-ossp";
|
-- create
|
||||||
create extension if not exists "pg_trgm";
|
-- extension if not exists "uuid-ossp";
|
||||||
|
create
|
||||||
|
extension if not exists "pg_trgm";
|
||||||
|
|
||||||
create table users
|
create table users
|
||||||
(
|
(
|
||||||
user_id uuid primary key default uuid_generate_v4(),
|
user_id BIGINT primary key not null,
|
||||||
username VARCHAR(50) UNIQUE NOT NULL,
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
passwd VARCHAR(255) NOT NULL,
|
passwd VARCHAR(255) NOT NULL,
|
||||||
nickname VARCHAR(50) NOT NULL,
|
nickname VARCHAR(50) NOT NULL DEFAULT ('user_' || substr(md5(random()::text), 1, 10)),
|
||||||
phone VARCHAR(20) UNIQUE NOT NULL,
|
phone VARCHAR(20) UNIQUE NOT NULL default '',
|
||||||
role_type SMALLINT NOT NULL, -- 1:玩家, 2:打手, 3:店长
|
email varchar(50) unique not null default '',
|
||||||
|
role_type SMALLINT NOT NULL default 1, -- 1:玩家, 2:打手, 3:店长
|
||||||
is_verified BOOLEAN DEFAULT false,
|
is_verified BOOLEAN DEFAULT false,
|
||||||
state BOOLEAN NOT NULL DEFAULT true,
|
state BOOLEAN NOT NULL DEFAULT true,
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|||||||
+38
-25
@@ -1,16 +1,18 @@
|
|||||||
# Envoy Gateway Configuration
|
# Envoy Gateway Configuration
|
||||||
|
|
||||||
This document explains how the Envoy gateway is configured and how to modify it.
|
This document explains how the Envoy unified ingress gateway is configured and how to modify it.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
- envoy.yaml: ConfigMap + Deployment + Service for Envoy
|
- deploy/k8s/envoy/envoy.yaml: ConfigMap + Deployment + Service for Envoy
|
||||||
|
|
||||||
## Current Behavior
|
## Current Behavior
|
||||||
|
|
||||||
- Envoy listens on port 8080 in the Pod and exposes port 80 via a ClusterIP Service.
|
- Envoy listens on port 8080 in the Pod and exposes port 80 via a ClusterIP Service.
|
||||||
- All HTTP traffic is routed to user-api only.
|
- Route `/api/users` to `user-api-svc:8888`.
|
||||||
- gRPC is not exposed by this gateway.
|
- Route `/api/email` to `email-api-svc:8888`.
|
||||||
|
- Route `/healthz` returns `200 ok` directly from gateway.
|
||||||
|
- Unknown routes return `404` from gateway.
|
||||||
|
|
||||||
## Routing
|
## Routing
|
||||||
|
|
||||||
@@ -20,27 +22,30 @@ static_resources -> listeners -> http_connection_manager -> route_config -> virt
|
|||||||
|
|
||||||
The current routing rules are:
|
The current routing rules are:
|
||||||
|
|
||||||
- All requests (prefix: "/") -> cluster: user-api
|
- `prefix: /api/users` -> `cluster: user_api_cluster`
|
||||||
|
- `prefix: /api/email` -> `cluster: email_api_cluster`
|
||||||
|
- `path: /healthz` -> direct response `200`
|
||||||
|
- `prefix: /` -> direct response `404`
|
||||||
|
|
||||||
To add a new HTTP service, add a new route above the default route and define a new cluster.
|
To add a new HTTP service, add a new route above the default route and define a new cluster.
|
||||||
|
|
||||||
Example: route /order to order-api-svc:8899
|
Example: route `/api/order` to `order-api-svc:8899`
|
||||||
|
|
||||||
1) Add a route match:
|
1) Add a route match:
|
||||||
|
|
||||||
- match:
|
- match:
|
||||||
prefix: "/order"
|
prefix: "/api/order"
|
||||||
route:
|
route:
|
||||||
cluster: order-api
|
cluster: order_api_cluster
|
||||||
|
|
||||||
2) Add a cluster:
|
1) Add a cluster:
|
||||||
|
|
||||||
- name: order-api
|
- name: order_api_cluster
|
||||||
connect_timeout: 2s
|
connect_timeout: 2s
|
||||||
type: STRICT_DNS
|
type: STRICT_DNS
|
||||||
lb_policy: ROUND_ROBIN
|
lb_policy: ROUND_ROBIN
|
||||||
load_assignment:
|
load_assignment:
|
||||||
cluster_name: order-api
|
cluster_name: order_api_cluster
|
||||||
endpoints:
|
endpoints:
|
||||||
- lb_endpoints:
|
- lb_endpoints:
|
||||||
- endpoint:
|
- endpoint:
|
||||||
@@ -49,22 +54,29 @@ Example: route /order to order-api-svc:8899
|
|||||||
address: order-api-svc.juwan.svc.cluster.local
|
address: order-api-svc.juwan.svc.cluster.local
|
||||||
port_value: 8899
|
port_value: 8899
|
||||||
|
|
||||||
## CSRF Protection
|
## CSRF Protection (Double Cookie)
|
||||||
|
|
||||||
Envoy uses a Lua filter for CSRF validation:
|
Envoy uses a Lua filter for double-cookie CSRF validation:
|
||||||
|
|
||||||
- Safe methods (GET/HEAD/OPTIONS):
|
- Safe methods (GET/HEAD/OPTIONS):
|
||||||
- If csrf_token cookie is missing, Envoy generates one and sets it in the response.
|
- If missing, Envoy auto-issues two cookies:
|
||||||
|
- `csrf_token`
|
||||||
|
- `csrf_guard`
|
||||||
- Unsafe methods (POST/PUT/PATCH/DELETE, etc):
|
- Unsafe methods (POST/PUT/PATCH/DELETE, etc):
|
||||||
- Requires BOTH:
|
- Requires BOTH headers:
|
||||||
- header: X-CSRF-Token
|
- `X-CSRF-Token`
|
||||||
- cookie: csrf_token
|
- `X-CSRF-Guard`
|
||||||
- Values must match, otherwise Envoy returns 403.
|
- Requires BOTH cookies:
|
||||||
|
- `csrf_token`
|
||||||
|
- `csrf_guard`
|
||||||
|
- Header values must exactly match cookie values, otherwise Envoy returns `403`.
|
||||||
|
|
||||||
If you want a different cookie name or header name, update these in the Lua code:
|
If you want different cookie or header names, update these constants in Lua:
|
||||||
|
|
||||||
- Header: x-csrf-token
|
- `TOKEN_COOKIE`
|
||||||
- Cookie: csrf_token
|
- `GUARD_COOKIE`
|
||||||
|
- `TOKEN_HEADER`
|
||||||
|
- `GUARD_HEADER`
|
||||||
|
|
||||||
To relax or tighten rules, edit the functions:
|
To relax or tighten rules, edit the functions:
|
||||||
|
|
||||||
@@ -75,9 +87,8 @@ To relax or tighten rules, edit the functions:
|
|||||||
|
|
||||||
Current Set-Cookie:
|
Current Set-Cookie:
|
||||||
|
|
||||||
csrf_token=<value>; Path=/; SameSite=Strict
|
- `csrf_token=<value>; Path=/; SameSite=Strict`
|
||||||
|
- `csrf_guard=<value>; Path=/; SameSite=Strict`
|
||||||
To add Secure or HttpOnly, update the string in envoy_on_response.
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
@@ -90,6 +101,8 @@ kubectl apply -f deploy/k8s/envoy/envoy.yaml
|
|||||||
- Change listening port:
|
- Change listening port:
|
||||||
- Update listener port_value and Service targetPort/port.
|
- Update listener port_value and Service targetPort/port.
|
||||||
- Change service namespace:
|
- Change service namespace:
|
||||||
- Update cluster DNS addresses (e.g. service.ns.svc.cluster.local).
|
- Update cluster DNS addresses (e.g. `service.ns.svc.cluster.local`).
|
||||||
- Add more services:
|
- Add more services:
|
||||||
- Add route + add cluster, as shown above.
|
- Add route + add cluster, as shown above.
|
||||||
|
- Update CSRF policy:
|
||||||
|
- Edit Lua validation logic in `envoy.filters.http.lua`.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ go 1.25.1
|
|||||||
require (
|
require (
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/lib/pq v1.11.2
|
||||||
github.com/redis/go-redis/v9 v9.17.3
|
github.com/redis/go-redis/v9 v9.17.3
|
||||||
github.com/zeromicro/go-zero v1.10.0
|
github.com/zeromicro/go-zero v1.10.0
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.46.0
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||||
|
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
|||||||
Reference in New Issue
Block a user