package http import ( "net/http" "time" roomv1 "hyapp/api/proto/room/v1" "hyapp/pkg/idgen" "hyapp/pkg/tencentim" "hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/client" ) // Handler 是 gateway 的 HTTP transport adapter。 // 它只依赖跨服务 client,不持有房间状态,也不直接实现房间命令语义。 type Handler struct { roomClient client.RoomClient roomGuardClient client.RoomGuardClient userClient client.UserAuthClient userIdentityClient client.UserIdentityClient tencentIM TencentIMConfig } // TencentIMConfig 是 gateway transport 层签发客户端腾讯云 IM 登录票据所需的配置。 type TencentIMConfig struct { SDKAppID int64 SecretKey string UserSigTTL time.Duration CallbackAuthToken string } // NewHandler 装配 gateway HTTP handler 的内部依赖。 func NewHandler(roomClient client.RoomClient, userClient ...client.UserAuthClient) *Handler { handler := &Handler{roomClient: roomClient} if len(userClient) > 0 { handler.userClient = userClient[0] } return handler } // NewHandlerWithClients 装配完整 gateway HTTP handler。 // 保留 NewHandler 是为了不破坏只测 room/auth transport 的旧测试构造。 func NewHandlerWithClients(roomClient client.RoomClient, userClient client.UserAuthClient, userIdentityClient client.UserIdentityClient) *Handler { return &Handler{ roomClient: roomClient, userClient: userClient, userIdentityClient: userIdentityClient, } } // NewHandlerWithConfig 装配完整 gateway HTTP handler 和外部云服务配置。 func NewHandlerWithConfig(roomClient client.RoomClient, roomGuardClient client.RoomGuardClient, userClient client.UserAuthClient, userIdentityClient client.UserIdentityClient, tencentIM TencentIMConfig) *Handler { return &Handler{ roomClient: roomClient, roomGuardClient: roomGuardClient, userClient: userClient, userIdentityClient: userIdentityClient, tencentIM: tencentIM, } } // meta 把外部 HTTP 请求上下文转换成 room-service 命令元信息。 // 当前 gateway 只生成命令入口所需的最小追踪字段,状态版本和幂等落点由 room-service 负责。 func meta(request *http.Request, roomID string) *roomv1.RequestMeta { return &roomv1.RequestMeta{ RequestId: requestIDFromContext(request.Context()), CommandId: idgen.New("cmd"), ActorUserId: auth.UserIDFromContext(request.Context()), RoomId: roomID, GatewayNodeId: "gateway-local", SessionId: idgen.New("sess"), SentAtMs: time.Now().UnixMilli(), } } // decode 统一处理 HTTP JSON 入参解析失败分支。 // gateway 在这里拒绝非法 JSON,不做房间业务校验,避免把 Room Cell 规则复制到入口层。 func decode(writer http.ResponseWriter, request *http.Request, out any) bool { if err := decodeJSON(request.Body, out); err != nil { writeError(writer, request, http.StatusBadRequest, codeInvalidJSON, "invalid request body") return false } return true } // write 统一把内部 gRPC 调用结果转换为 HTTP JSON 响应。 // 首版不在 gateway 细分 gRPC 错误码,避免入口层提前耦合 room-service 的业务错误模型。 func write(writer http.ResponseWriter, request *http.Request, body any, err error) { if err != nil { writeRPCError(writer, request, err) return } writeOK(writer, request, body) } func (h *Handler) issueTencentIMUserSig(writer http.ResponseWriter, request *http.Request) { userID := auth.UserIDFromContext(request.Context()) if userID <= 0 { // 正常路径已由 auth middleware 拦截;这里兜底避免签发匿名 IM 票据。 writeError(writer, request, http.StatusUnauthorized, codeUnauthorized, "unauthorized") return } result, err := tencentim.GenerateUserSig(tencentim.UserSigConfig{ SDKAppID: h.tencentIM.SDKAppID, SecretKey: h.tencentIM.SecretKey, TTL: h.tencentIM.UserSigTTL, }, tencentim.FormatUserID(userID), time.Now()) if err != nil { // 缺少 SDKAppID 或 SecretKey 属于服务端配置错误,不能返回假票据给客户端。 writeError(writer, request, http.StatusInternalServerError, codeInternalError, "internal error") return } writeOK(writer, request, result) }