🏗️ microservice fabric splitting

This commit is contained in:
2024-12-24 00:38:41 +08:00
parent 462e811742
commit 89d64336f5
311 changed files with 18384 additions and 2428 deletions

View File

@@ -0,0 +1,20 @@
package config
import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
Auth struct {
AccessSecret string
}
Mysql struct {
DataSource string
MaxOpenConn int
MaxIdleConn int
}
Redis struct {
Host string
Pass string
DB int
}
}

View File

@@ -0,0 +1,44 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.7.3
package handler
import (
"net/http"
"time"
upscale "schisandra-album-cloud-microservices/app/file/api/internal/handler/upscale"
websocket "schisandra-album-cloud-microservices/app/file/api/internal/handler/websocket"
"schisandra-album-cloud-microservices/app/file/api/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.SecurityHeadersMiddleware, serverCtx.NonceMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/upload",
Handler: upscale.UploadImageHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/auth/upscale"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithMaxBytes(10485760),
)
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/file",
Handler: websocket.FileWebsocketHandler(serverCtx),
},
},
rest.WithPrefix("/api/ws"),
)
}

View File

@@ -0,0 +1,25 @@
package upscale
import (
"github.com/zeromicro/go-zero/rest/httpx"
"net/http"
"schisandra-album-cloud-microservices/app/file/api/internal/logic/upscale"
"schisandra-album-cloud-microservices/app/file/api/internal/svc"
"schisandra-album-cloud-microservices/app/file/api/internal/types"
"schisandra-album-cloud-microservices/common/xhttp"
)
func UploadImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadRequest
if err := httpx.Parse(r, &req); err != nil {
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
return
}
l := upscale.NewUploadImageLogic(r.Context(), svcCtx)
err := l.UploadImage(r, &req)
xhttp.JsonBaseResponseCtx(r.Context(), w, err)
}
}

View File

@@ -0,0 +1,14 @@
package websocket
import (
"net/http"
"schisandra-album-cloud-microservices/app/file/api/internal/logic/websocket"
"schisandra-album-cloud-microservices/app/file/api/internal/svc"
)
func FileWebsocketHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := websocket.NewFileWebsocketLogic(r.Context(), svcCtx)
l.FileWebsocket(w, r)
}
}

View File

@@ -0,0 +1,59 @@
package upscale
import (
"context"
"encoding/json"
"net/http"
"schisandra-album-cloud-microservices/app/file/api/internal/logic/websocket"
"schisandra-album-cloud-microservices/app/file/api/internal/svc"
"schisandra-album-cloud-microservices/app/file/api/internal/types"
"schisandra-album-cloud-microservices/common/xhttp"
"schisandra-album-cloud-microservices/common/errors"
"schisandra-album-cloud-microservices/common/jwt"
"github.com/zeromicro/go-zero/core/logx"
)
type UploadImageLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic {
return &UploadImageLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UploadImageLogic) UploadImage(r *http.Request, req *types.UploadRequest) (err error) {
token, ok := jwt.ParseAccessToken(l.svcCtx.Config.Auth.AccessSecret, req.AccessToken)
if !ok {
return errors.New(http.StatusForbidden, "invalid access token")
}
if token.UserID != req.UserId {
return errors.New(http.StatusForbidden, "invalid user id")
}
correct, err := l.svcCtx.CasbinEnforcer.Enforce(req.UserId, r.URL.Path, r.Method)
if err != nil || !correct {
return errors.New(http.StatusForbidden, "permission denied")
}
data, err := json.Marshal(xhttp.BaseResponse[string]{
Data: req.Image,
Msg: "success",
Code: http.StatusOK,
})
if err != nil {
return errors.New(http.StatusForbidden, err.Error())
}
err = websocket.FileWebSocketHandler.SendMessageToClient(req.UserId, data)
if err != nil {
return errors.New(http.StatusForbidden, err.Error())
}
return nil
}

View File

@@ -0,0 +1,138 @@
package websocket
import (
"context"
"fmt"
"github.com/lxzan/gws"
"net/http"
"schisandra-album-cloud-microservices/app/file/api/internal/svc"
"schisandra-album-cloud-microservices/common/jwt"
"time"
"github.com/zeromicro/go-zero/core/logx"
)
type FileWebsocketLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewFileWebsocketLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FileWebsocketLogic {
return &FileWebsocketLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
const (
PingInterval = 5 * time.Second // 客户端心跳间隔
HeartbeatWaitTimeout = 10 * time.Second // 心跳等待超时时间
)
type FileWebSocket struct {
ctx context.Context
gws.BuiltinEventHandler
sessions *gws.ConcurrentMap[string, *gws.Conn]
}
var FileWebSocketHandler = NewFileWebSocket()
func (l *FileWebsocketLogic) FileWebsocket(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Sec-Websocket-Protocol")
accessToken, res := jwt.ParseAccessToken(l.svcCtx.Config.Auth.AccessSecret, token)
if !res {
return
}
upGrader := gws.NewUpgrader(FileWebSocketHandler, &gws.ServerOption{
HandshakeTimeout: 5 * time.Second, // 握手超时时间
ReadBufferSize: 1024, // 读缓冲区大小
ParallelEnabled: true, // 开启并行消息处理
Recovery: gws.Recovery, // 开启异常恢复
CheckUtf8Enabled: false, // 关闭UTF8校验
PermessageDeflate: gws.PermessageDeflate{
Enabled: true, // 开启压缩
},
Authorize: func(r *http.Request, session gws.SessionStorage) bool {
var clientId = r.URL.Query().Get("user_id")
if clientId == "" {
return false
}
if accessToken.UserID != clientId {
return false
}
session.Store("user_id", clientId)
return true
},
SubProtocols: []string{token},
})
socket, err := upGrader.Upgrade(w, r)
if err != nil {
panic(err)
}
go func() {
socket.ReadLoop()
}()
}
func NewFileWebSocket() *FileWebSocket {
return &FileWebSocket{
ctx: context.Background(),
sessions: gws.NewConcurrentMap[string, *gws.Conn](64, 128),
}
}
// OnOpen 连接建立
func (c *FileWebSocket) OnOpen(socket *gws.Conn) {
clientId := MustLoad[string](socket.Session(), "user_id")
c.sessions.Store(clientId, socket)
// 订阅该用户的频道
fmt.Printf("websocket client %s connected\n", clientId)
}
// OnClose 关闭连接
func (c *FileWebSocket) OnClose(socket *gws.Conn, err error) {
name := MustLoad[string](socket.Session(), "user_id")
sharding := c.sessions.GetSharding(name)
c.sessions.Delete(name)
sharding.Lock()
defer sharding.Unlock()
fmt.Printf("websocket closed, name=%s, msg=%s\n", name, err.Error())
}
// OnPing 处理客户端的Ping消息
func (c *FileWebSocket) OnPing(socket *gws.Conn, payload []byte) {
_ = socket.SetDeadline(time.Now().Add(PingInterval + HeartbeatWaitTimeout))
_ = socket.WritePong(payload)
}
// OnPong 处理客户端的Pong消息
func (c *FileWebSocket) OnPong(_ *gws.Conn, _ []byte) {}
// OnMessage 接受消息
func (c *FileWebSocket) OnMessage(socket *gws.Conn, message *gws.Message) {
defer message.Close()
clientId := MustLoad[string](socket.Session(), "user_id")
if conn, ok := c.sessions.Load(clientId); ok {
_ = conn.WriteMessage(gws.OpcodeText, message.Bytes())
}
// fmt.Printf("received message from client %s\n", message.Data)
}
// SendMessageToClient 向指定客户端发送消息
func (c *FileWebSocket) SendMessageToClient(clientId string, message []byte) error {
conn, ok := c.sessions.Load(clientId)
if ok {
return conn.WriteMessage(gws.OpcodeText, message)
}
return fmt.Errorf("client %s not found", clientId)
}
// MustLoad 从session中加载数据
func MustLoad[T any](session gws.SessionStorage, key string) (v T) {
if value, exist := session.Load(key); exist {
v = value.(T)
}
return
}

View File

@@ -0,0 +1,24 @@
package middleware
import (
"github.com/redis/go-redis/v9"
"net/http"
"schisandra-album-cloud-microservices/common/middleware"
)
type NonceMiddleware struct {
RedisClient *redis.Client
}
func NewNonceMiddleware(redisClient *redis.Client) *NonceMiddleware {
return &NonceMiddleware{
RedisClient: redisClient,
}
}
func (m *NonceMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
middleware.NonceMiddleware(w, r, m.RedisClient)
next(w, r)
}
}

View File

@@ -0,0 +1,20 @@
package middleware
import (
"net/http"
"schisandra-album-cloud-microservices/common/middleware"
)
type SecurityHeadersMiddleware struct {
}
func NewSecurityHeadersMiddleware() *SecurityHeadersMiddleware {
return &SecurityHeadersMiddleware{}
}
func (m *SecurityHeadersMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
middleware.SecurityHeadersMiddleware(r)
next(w, r)
}
}

View File

@@ -0,0 +1,36 @@
package svc
import (
"github.com/casbin/casbin/v2"
"github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/rest"
"schisandra-album-cloud-microservices/app/file/api/internal/config"
"schisandra-album-cloud-microservices/app/file/api/internal/middleware"
"schisandra-album-cloud-microservices/app/file/api/model/mysql"
"schisandra-album-cloud-microservices/app/file/api/model/mysql/query"
"schisandra-album-cloud-microservices/common/casbinx"
"schisandra-album-cloud-microservices/common/redisx"
)
type ServiceContext struct {
Config config.Config
NonceMiddleware rest.Middleware
SecurityHeadersMiddleware rest.Middleware
DB *query.Query
CasbinEnforcer *casbin.SyncedCachedEnforcer
RedisClient *redis.Client
}
func NewServiceContext(c config.Config) *ServiceContext {
redisClient := redisx.NewRedis(c.Redis.Host, c.Redis.Pass, c.Redis.DB)
db, queryDB := mysql.NewMySQL(c.Mysql.DataSource, c.Mysql.MaxOpenConn, c.Mysql.MaxIdleConn, redisClient)
casbinEnforcer := casbinx.NewCasbin(db)
return &ServiceContext{
Config: c,
NonceMiddleware: middleware.NewNonceMiddleware(redisClient).Handle,
SecurityHeadersMiddleware: middleware.NewSecurityHeadersMiddleware().Handle,
DB: queryDB,
CasbinEnforcer: casbinEnforcer,
RedisClient: redisClient,
}
}

View File

@@ -0,0 +1,10 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.7.3
package types
type UploadRequest struct {
Image string `json:"image"`
AccessToken string `json:"access_token"`
UserId string `json:"user_id"`
}