feat: Phase 4 多平台接入 — Platform Bridge + 6平台适配器 + 身份权限系统 (22文件, 2129行)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 16:19:57 +08:00
parent 717ad65b05
commit 965cce7192
22 changed files with 2129 additions and 2 deletions
@@ -0,0 +1,149 @@
package handler
import (
"encoding/json"
"net/http"
"github.com/yourname/cyrene-ai/platform-bridge/internal/bridge"
)
// BridgeHandler exposes the Platform Bridge REST API.
type BridgeHandler struct {
router *bridge.PlatformRouter
}
func NewBridgeHandler(router *bridge.PlatformRouter) *BridgeHandler {
return &BridgeHandler{router: router}
}
func (h *BridgeHandler) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/health", h.health)
mux.HandleFunc("/api/v1/platforms", h.listPlatforms)
mux.HandleFunc("/api/v1/platforms/", h.platformInfo)
mux.HandleFunc("/api/v1/identities", h.listIdentities)
mux.HandleFunc("/api/v1/webhook/telegram", h.telegramWebhook)
mux.HandleFunc("/api/v1/webhook/", h.genericWebhook)
}
func (h *BridgeHandler) health(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": "ok", "service": "platform-bridge",
"platforms": h.router.ListAdapters(),
})
}
func (h *BridgeHandler) listPlatforms(w http.ResponseWriter, r *http.Request) {
names := h.router.ListAdapters()
type platformSummary struct {
Name string `json:"name"`
Connected bool `json:"connected"`
Caps bridge.PlatformCapabilities `json:"capabilities"`
}
var platforms []platformSummary
for _, name := range names {
a, err := h.router.GetAdapter(name)
if err != nil {
continue
}
platforms = append(platforms, platformSummary{
Name: name,
Connected: a.IsConnected(),
Caps: a.Capabilities(),
})
}
writeJSON(w, http.StatusOK, map[string]interface{}{"platforms": platforms, "total": len(platforms)})
}
func (h *BridgeHandler) platformInfo(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path[len("/api/v1/platforms/"):]
a, err := h.router.GetAdapter(name)
if err != nil {
writeJSON(w, http.StatusNotFound, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"name": name,
"connected": a.IsConnected(),
"capabilities": a.Capabilities(),
})
}
func (h *BridgeHandler) listIdentities(w http.ResponseWriter, r *http.Request) {
all := h.router.ListAllIdentities()
writeJSON(w, http.StatusOK, all)
}
// telegramWebhook receives updates from Telegram Bot API.
func (h *BridgeHandler) telegramWebhook(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
// Parse into a generic map first to check for message presence.
var raw map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
writeJSON(w, http.StatusBadRequest, errResp("invalid telegram update"))
return
}
if _, hasMsg := raw["message"]; !hasMsg {
writeJSON(w, http.StatusOK, map[string]string{"status": "ignored"})
return
}
_, err := h.router.RouteMessage("telegram", raw)
if err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}
// genericWebhook receives standard webhook payloads.
func (h *BridgeHandler) genericWebhook(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
return
}
// Extract platform name from path: /api/v1/webhook/{platform}
platform := r.URL.Path[len("/api/v1/webhook/"):]
if platform == "" || platform == "telegram" {
writeJSON(w, http.StatusBadRequest, errResp("specify platform name in path"))
return
}
var payload map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
writeJSON(w, http.StatusBadRequest, errResp("invalid JSON payload"))
return
}
response, err := h.router.RouteMessage(platform, &payload)
if err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
// Convert to platform-specific format.
msgs, err := h.router.SendResponse(response)
if err != nil {
writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"messages": msgs,
"reply_to": response.ReplyTo,
})
}
func errResp(msg string) map[string]string {
return map[string]string{"error": msg}
}
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}