feat 包命名规范

This commit is contained in:
2026-02-11 14:49:44 +08:00
parent fc9b32b115
commit 14cb2f3270
22 changed files with 137 additions and 135 deletions

View File

@@ -0,0 +1,81 @@
package httpgateway
import (
"fmt"
"git.hlsq.asia/mmorpg/service-common/db/redis"
"git.hlsq.asia/mmorpg/service-common/net/http/httpresp"
"git.hlsq.asia/mmorpg/service-common/utils"
"git.hlsq.asia/mmorpg/service-gateway/internal/global"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"strings"
"time"
)
func corsConfig() cors.Config {
return cors.Config{
AllowMethods: []string{"GET", "POST", "OPTIONS"},
AllowHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: false,
AllowAllOrigins: true,
MaxAge: 12 * time.Hour,
}
}
func ginLogger(logger *zap.SugaredLogger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next()
cost := time.Since(start)
usn, _ := c.Get("usn")
logger.Infof(fmt.Sprintf(
"[usn:%v] Method:%v Code:%v Time:%v IP:%v Path:%v",
usn,
c.Request.Method,
c.Writer.Status(),
cost,
c.ClientIP(),
path),
)
}
}
func authJwt() gin.HandlerFunc {
return func(c *gin.Context) {
pList := strings.SplitN(c.Request.URL.Path, "/", 4)
if len(pList) < 4 || pList[2] == "" {
httpresp.JsonNotFound(c)
c.Abort()
return
}
// 如果是Public接口,有Token就读,没有就算了
public := pList[2] == "open"
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
if token == "" {
if public {
c.Next()
return
}
httpresp.JsonUnauthorized(c)
c.Abort()
return
}
usn, _ := redis.GetClient().HGet(c, global.KeyGatewayAccessToken+token, (&utils.UserSession{}).GetUsnKey()).Int64()
if usn == 0 {
httpresp.JsonUnauthorized(c)
c.Abort()
return
}
// 这里将Header写到请求中,grpc-gateway框架会读取然后传给grpc服务
c.Request.Header.Set("X-Usn", utils.Int64ToString(usn))
// 这里写到上下文中,打日志
c.Set("usn", usn)
c.Next()
}
}

View File

@@ -0,0 +1,127 @@
package httpgateway
import (
"context"
"git.hlsq.asia/mmorpg/service-common/discover/common"
"git.hlsq.asia/mmorpg/service-common/log"
"git.hlsq.asia/mmorpg/service-common/net/grpc/grpcclient"
"git.hlsq.asia/mmorpg/service-common/net/http/httpresp"
"git.hlsq.asia/mmorpg/service-common/proto/rs/rspb"
"git.hlsq.asia/mmorpg/service-gateway/internal/handler/httphandler"
"git.hlsq.asia/mmorpg/service-gateway/internal/net/httpgateway/wrapper"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
"google.golang.org/protobuf/encoding/protojson"
"strings"
)
func InitServeMux() *runtime.ServeMux {
baseMarshaler := &runtime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: false,
EmitUnpopulated: true,
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
}
unifiedMarshaler := wrapper.NewWrappedMarshaler(baseMarshaler)
mux := runtime.NewServeMux(
runtime.WithMarshalerOption(runtime.MIMEWildcard, unifiedMarshaler),
runtime.WithErrorHandler(wrapper.ErrorHandler),
runtime.WithIncomingHeaderMatcher(func(header string) (string, bool) {
if header == "X-Usn" {
return "X-Usn", true
}
return runtime.DefaultHeaderMatcher(header)
}),
)
return mux
}
func InitRouter() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(
gin.Recovery(),
ginLogger(log.GetLogger().Named("GIN")),
cors.New(corsConfig()),
otelgin.Middleware(
common.KeyDiscoverServiceNameGateway,
otelgin.WithSpanNameFormatter(func(c *gin.Context) string {
method := strings.ToUpper(c.Request.Method)
return method + " " + c.Request.URL.Path
}),
),
)
r.HandleMethodNotAllowed = true
r.NoMethod(func(c *gin.Context) {
httpresp.JsonMethodNotAllowed(c)
c.Abort()
})
r.NoRoute(func(c *gin.Context) {
httpresp.JsonNotFound(c)
c.Abort()
})
auth := r.Group("/")
auth.Use(authJwt())
// 网关
initGatewayPath(auth)
// 用户中心
initUserPath(auth)
// 奇怪的知识-服务端
initQgdzsPath(auth)
return r
}
func initGatewayPath(r *gin.RouterGroup) {
g := r.Group("/" + common.KeyDiscoverServiceNameGateway)
g.POST("/open/login", httphandler.Login)
g.POST("/open/refresh_token", httphandler.RefreshToken)
g.POST("/open/logout", httphandler.Logout)
}
func initUserPath(r *gin.RouterGroup) {
g := r.Group("/" + common.KeyDiscoverServiceNameUser)
client, err := grpcclient.UserNewClientLB()
if err != nil {
log.Errorf("get user conn failed: %v", err)
return
}
gwMux := InitServeMux()
if err = rspb.RegisterUserHandlerClient(context.Background(), gwMux, client); err != nil {
log.Errorf("RegisterUserHandlerClient err: %v", err)
return
}
g.Any("/*path", gin.WrapH(gwMux))
}
func initQgdzsPath(r *gin.RouterGroup) {
g := r.Group("/" + common.KeyDiscoverServiceNameQgdzs)
client, err := grpcclient.QgdzsNewClientLB()
if err != nil {
log.Errorf("get qgdzs conn failed: %v", err)
return
}
gwMux := InitServeMux()
if err = rspb.RegisterQgdzsHandlerClient(context.Background(), gwMux, client); err != nil {
log.Errorf("RegisterQgdzsHandlerClient err: %v", err)
return
}
g.Any("/*path", gin.WrapH(gwMux))
}

View File

@@ -0,0 +1,58 @@
package wrapper
import (
"context"
"encoding/json"
"git.hlsq.asia/mmorpg/service-common/net/http/httpresp"
"git.hlsq.asia/mmorpg/service-common/proto/rs/rscommon"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net/http"
)
// ErrorHandler 将 gRPC 错误转为统一 JSON 格式
func ErrorHandler(_ context.Context, _ *runtime.ServeMux, _ runtime.Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
st, ok := status.FromError(err)
if !ok {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
_ = json.NewEncoder(w).Encode(httpresp.Error(httpresp.Failed))
return
}
code, msg := 0, ""
for _, detail := range st.Details() {
if errorInfo, ok := detail.(*rscommon.ErrorInfo); ok {
code = int(errorInfo.Code)
msg = errorInfo.Msg
break
}
}
if code == 0 {
code = httpresp.Failed.Code()
msg = httpresp.Failed.Error()
}
if st.Code() == codes.Unknown || st.Code() == codes.Unimplemented || st.Code() == codes.NotFound {
msg = st.Message()
}
if st.Code() == codes.NotFound {
code = httpresp.NotFound.Code()
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(grpcCodeToHTTPCode(st.Code()))
_ = json.NewEncoder(w).Encode(httpresp.Error(httpresp.NewCode(code, msg)))
}
// 这里定义 Internal 属于业务错误,其他的属于 500 报错
func grpcCodeToHTTPCode(c codes.Code) int {
switch c {
case codes.OK, codes.Unknown:
return http.StatusOK
case codes.Unimplemented, codes.NotFound:
return http.StatusNotFound
default:
return http.StatusInternalServerError
}
}

View File

@@ -0,0 +1,42 @@
package wrapper
import (
"encoding/json"
"git.hlsq.asia/mmorpg/service-common/net/http/httpresp"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"io"
)
// WrappedMarshaler 自动包装响应为 { code, message, data }
type WrappedMarshaler struct {
inner runtime.Marshaler
}
func NewWrappedMarshaler(inner runtime.Marshaler) *WrappedMarshaler {
return &WrappedMarshaler{inner: inner}
}
// Marshal 将 gRPC 响应包装成统一格式
func (w *WrappedMarshaler) Marshal(v interface{}) ([]byte, error) {
dataBytes, err := w.inner.Marshal(v)
if err != nil {
return json.Marshal(httpresp.Error(httpresp.Failed))
}
return json.Marshal(httpresp.Success(json.RawMessage(dataBytes)))
}
func (w *WrappedMarshaler) Unmarshal(data []byte, v interface{}) error {
return w.inner.Unmarshal(data, v)
}
func (w *WrappedMarshaler) NewDecoder(r io.Reader) runtime.Decoder {
return w.inner.NewDecoder(r)
}
func (w *WrappedMarshaler) NewEncoder(wr io.Writer) runtime.Encoder {
return w.inner.NewEncoder(wr)
}
func (w *WrappedMarshaler) ContentType(v interface{}) string {
return "application/json"
}