diff --git a/go.mod b/go.mod index f586919c..6d45a2e4 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/bwmarrin/snowflake v0.3.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.3.0 + github.com/gorilla/websocket v1.5.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stripe/stripe-go/v74 v74.26.0 diff --git a/go.sum b/go.sum index 1fef35de..959acd49 100644 --- a/go.sum +++ b/go.sum @@ -165,6 +165,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A= diff --git a/server/websocket/etc/websocket.yaml b/server/websocket/etc/websocket.yaml new file mode 100644 index 00000000..d22f03a7 --- /dev/null +++ b/server/websocket/etc/websocket.yaml @@ -0,0 +1,8 @@ +Name: websocket +Host: 0.0.0.0 +Port: 8888 +SourceMysql: fusentest:XErSYmLELKMnf3Dh@tcp(110.41.19.98:3306)/fusentest +Auth: + AccessSecret: fusen2023 + AccessExpire: 2592000 + RefreshAfter: 1592000 \ No newline at end of file diff --git a/server/websocket/internal/config/config.go b/server/websocket/internal/config/config.go new file mode 100644 index 00000000..b24bb7d7 --- /dev/null +++ b/server/websocket/internal/config/config.go @@ -0,0 +1,9 @@ +package config + +import "github.com/zeromicro/go-zero/rest" + +type Config struct { + rest.RestConf + SourceMysql string + Auth types.Auth +} diff --git a/server/websocket/internal/handler/datatransferhandler.go b/server/websocket/internal/handler/datatransferhandler.go new file mode 100644 index 00000000..4534d041 --- /dev/null +++ b/server/websocket/internal/handler/datatransferhandler.go @@ -0,0 +1,58 @@ +package handler + +import ( + "fusenapi/server/websocket/internal/svc" + "github.com/gorilla/websocket" + "net/http" + "sync" +) + +var ( + //升级 + upgrade = websocket.Upgrader{ + //允许跨域 + CheckOrigin: func(r *http.Request) bool { + return true + }, + } + //连接map池 + mapConn = sync.Map{} +) + +type wsConnectItem struct { + conn *websocket.Conn //websocket的连接 + renImage sync.Map //需要渲染的图片 +} + +func DataTransferHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + /*// 解析JWT token,并对空用户进行判断 + claims, err := svcCtx.ParseJwtToken(r) + // 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息 + if err != nil { + logx.Info("unauthorized:", err.Error()) // 记录错误日志 + w.Write([]byte("connect failed:unauthorized")) + return + } + var userInfo *auth.UserInfo + if claims != nil { + // 从token中获取对应的用户信息 + userInfo, err = auth.GetUserInfoFormMapClaims(claims) + // 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息 + if err != nil { + return + } + } else { + // 如果claims为nil,则认为用户身份为白板用户 + w.Write([]byte("connect failed:unauthorized!!")) + return + } + //升级websocket + conn, err := upgrade.Upgrade(w, r, r.Header) + if err != nil { + logx.Error("http upgrade websocket err:", err) + w.Write([]byte("http upgrade websocket err")) + return + }*/ + } +} diff --git a/server/websocket/internal/handler/routes.go b/server/websocket/internal/handler/routes.go new file mode 100644 index 00000000..fd3200e4 --- /dev/null +++ b/server/websocket/internal/handler/routes.go @@ -0,0 +1,22 @@ +// Code generated by goctl. DO NOT EDIT. +package handler + +import ( + "net/http" + + "fusenapi/server/websocket/internal/svc" + + "github.com/zeromicro/go-zero/rest" +) + +func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { + server.AddRoutes( + []rest.Route{ + { + Method: http.MethodGet, + Path: "/api/websocket/data_transfer", + Handler: DataTransferHandler(serverCtx), + }, + }, + ) +} diff --git a/server/websocket/internal/logic/datatransferlogic.go b/server/websocket/internal/logic/datatransferlogic.go new file mode 100644 index 00000000..7b5db86b --- /dev/null +++ b/server/websocket/internal/logic/datatransferlogic.go @@ -0,0 +1,43 @@ +package logic + +import ( + "fusenapi/utils/auth" + "fusenapi/utils/basic" + + "context" + + "fusenapi/server/websocket/internal/svc" + "fusenapi/server/websocket/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DataTransferLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDataTransferLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DataTransferLogic { + return &DataTransferLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 处理进入前逻辑w,r +// func (l *DataTransferLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { +// } + +// 处理逻辑后 w,r 如:重定向, resp 必须重新处理 +// func (l *DataTransferLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { +// // httpx.OkJsonCtx(r.Context(), w, resp) +// } + +func (l *DataTransferLogic) DataTransfer(req *types.DataTransferReq, userinfo *auth.UserInfo) (resp *basic.Response) { + // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) + // userinfo 传入值时, 一定不为null + + return resp.SetStatus(basic.CodeOK) +} diff --git a/server/websocket/internal/svc/servicecontext.go b/server/websocket/internal/svc/servicecontext.go new file mode 100644 index 00000000..9a22471e --- /dev/null +++ b/server/websocket/internal/svc/servicecontext.go @@ -0,0 +1,61 @@ +package svc + +import ( + "errors" + "fmt" + "fusenapi/server/websocket/internal/config" + "net/http" + + "fusenapi/initalize" + "fusenapi/model/gmodel" + + "github.com/golang-jwt/jwt" + "gorm.io/gorm" +) + +type ServiceContext struct { + Config config.Config + + MysqlConn *gorm.DB + AllModels *gmodel.AllModelsGen +} + +func NewServiceContext(c config.Config) *ServiceContext { + + return &ServiceContext{ + Config: c, + MysqlConn: initalize.InitMysql(c.SourceMysql), + AllModels: gmodel.NewAllModels(initalize.InitMysql(c.SourceMysql)), + } +} + +func (svcCtx *ServiceContext) ParseJwtToken(r *http.Request) (jwt.MapClaims, error) { + AuthKey := r.Header.Get("Authorization") + if AuthKey == "" { + return nil, nil + } + AuthKey = AuthKey[7:] + + if len(AuthKey) <= 50 { + return nil, errors.New(fmt.Sprint("Error parsing token, len:", len(AuthKey))) + } + + token, err := jwt.Parse(AuthKey, func(token *jwt.Token) (interface{}, error) { + // 检查签名方法是否为 HS256 + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + // 返回用于验证签名的密钥 + return []byte(svcCtx.Config.Auth.AccessSecret), nil + }) + if err != nil { + return nil, errors.New(fmt.Sprint("Error parsing token:", err)) + } + + // 验证成功返回 + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return claims, nil + } + + return nil, errors.New(fmt.Sprint("Invalid token", err)) +} diff --git a/server/websocket/internal/types/types.go b/server/websocket/internal/types/types.go new file mode 100644 index 00000000..06201d39 --- /dev/null +++ b/server/websocket/internal/types/types.go @@ -0,0 +1,85 @@ +// Code generated by goctl. DO NOT EDIT. +package types + +import ( + "fusenapi/utils/basic" +) + +type DataTransferReq struct { + MsgType string `json:"msg_type"` //消息类型 + Message interface{} `json:"message"` //传递的消息 +} + +type DataTransferRsp struct { + MsgType string `json:"msg_type"` //消息类型 + Message interface{} `json:"message"` //传递的消息 +} + +type Request struct { +} + +type Response struct { + Code int `json:"code"` + Message string `json:"msg"` + Data interface{} `json:"data"` +} + +type Auth struct { + AccessSecret string `json:"accessSecret"` + AccessExpire int64 `json:"accessExpire"` + RefreshAfter int64 `json:"refreshAfter"` +} + +type File struct { + Filename string `fsfile:"filename"` + Header map[string][]string `fsfile:"header"` + Size int64 `fsfile:"size"` + Data []byte `fsfile:"data"` +} + +type Meta struct { + TotalCount int64 `json:"totalCount"` + PageCount int64 `json:"pageCount"` + CurrentPage int `json:"currentPage"` + PerPage int `json:"perPage"` +} + +// Set 设置Response的Code和Message值 +func (resp *Response) Set(Code int, Message string) *Response { + return &Response{ + Code: Code, + Message: Message, + } +} + +// Set 设置整个Response +func (resp *Response) SetWithData(Code int, Message string, Data interface{}) *Response { + return &Response{ + Code: Code, + Message: Message, + Data: Data, + } +} + +// SetStatus 设置默认StatusResponse(内部自定义) 默认msg, 可以带data, data只使用一个参数 +func (resp *Response) SetStatus(sr *basic.StatusResponse, data ...interface{}) *Response { + newResp := &Response{ + Code: sr.Code, + } + if len(data) == 1 { + newResp.Data = data[0] + } + return newResp +} + +// SetStatusWithMessage 设置默认StatusResponse(内部自定义) 非默认msg, 可以带data, data只使用一个参数 +func (resp *Response) SetStatusWithMessage(sr *basic.StatusResponse, msg string, data ...interface{}) *Response { + newResp := &Response{ + Code: sr.Code, + Message: msg, + } + if len(data) == 1 { + newResp.Data = data[0] + } + return newResp +} diff --git a/server/websocket/websocket.go b/server/websocket/websocket.go new file mode 100644 index 00000000..c5e7bdd0 --- /dev/null +++ b/server/websocket/websocket.go @@ -0,0 +1,36 @@ +package main + +import ( + "flag" + "fmt" + "net/http" + "time" + + "fusenapi/utils/auth" + + "fusenapi/server/websocket/internal/config" + "fusenapi/server/websocket/internal/handler" + "fusenapi/server/websocket/internal/svc" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/rest" +) + +var configFile = flag.String("f", "etc/websocket.yaml", "the config file") + +func main() { + flag.Parse() + + var c config.Config + conf.MustLoad(*configFile, &c) + c.Timeout = int64(time.Second * 15) + server := rest.MustNewServer(c.RestConf, rest.WithCustomCors(auth.FsCors, func(w http.ResponseWriter) { + })) + 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() +} diff --git a/server_api/websocket.api b/server_api/websocket.api new file mode 100644 index 00000000..786e67e7 --- /dev/null +++ b/server_api/websocket.api @@ -0,0 +1,25 @@ +syntax = "v1" + +info ( + title: "websocket"// TODO: add title + desc: // TODO: add description + author: "" + email: "" +) + +import "basic.api" +service websocket { + //websocket数据交互 + @handler DataTransferHandler + get /api/websocket/data_transfer(DataTransferReq) returns (response); +} + +//websocket数据交互 +type DataTransferReq { + MsgType string `json:"msg_type"` //消息类型 + Message interface{} `json:"message"` //传递的消息 +} +type DataTransferRsp { + MsgType string `json:"msg_type"` //消息类型 + Message interface{} `json:"message"` //传递的消息 +} \ No newline at end of file