// Package server 提供错误码查询与校验 HTTP 服务。默认监听 :8091。 package server import ( "encoding/json" "net/http" "strings" "git-ssh.u1680832.nyat.app/weichen/Error-Code-System.git/errcode" "git-ssh.u1680832.nyat.app/weichen/Error-Code-System.git/httpx" ) // DefaultAddr 是 errcode-serve 的默认监听地址。 const DefaultAddr = ":8091" // Server 对外提供已登记错误码的只读 API。 type Server struct { Reg *errcode.Registry } // New 使用给定注册表。nil 表示 errcode.Default(需已导入 domains)。 func New(reg *errcode.Registry) *Server { return &Server{Reg: reg} } // Handler 返回带追踪中间件的路由。 func (s *Server) Handler() http.Handler { mux := http.NewServeMux() mux.HandleFunc("GET /v1/health", s.handleHealth) mux.HandleFunc("GET /v1/codes", s.handleList) mux.HandleFunc("GET /v1/codes/{code}", s.handleGet) mux.HandleFunc("POST /v1/validate", s.handleValidate) mux.HandleFunc("GET /v1/export", s.handleExport) return httpx.Trace(mux) } // ListenAndServe 在 addr 上服务。addr 为空时用 DefaultAddr。 func (s *Server) ListenAndServe(addr string) error { if strings.TrimSpace(addr) == "" { addr = DefaultAddr } return http.ListenAndServe(addr, s.Handler()) } func (s *Server) reg() *errcode.Registry { if s != nil && s.Reg != nil { return s.Reg } return errcode.Default } func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"ok": true}) } func (s *Server) handleList(w http.ResponseWriter, r *http.Request) { defs := s.reg().Search(r.URL.Query().Get("q")) if domain := r.URL.Query().Get("domain"); domain != "" { filtered := make([]errcode.Definition, 0, len(defs)) for _, def := range defs { if def.Code.Domain() == domain { filtered = append(filtered, def) } } defs = filtered } writeJSON(w, http.StatusOK, defs) } func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) { code := r.PathValue("code") def, ok := s.reg().Lookup(code) if !ok { httpx.WriteError(s.reg(), w, r, errcode.New(errcode.SysNotFound, code)) return } writeJSON(w, http.StatusOK, def) } type validateRequest struct { Code string `json:"code"` Codes []string `json:"codes"` } type validateResult struct { Input string `json:"input"` Known bool `json:"known"` Resolved string `json:"resolved"` Definition errcode.Definition `json:"definition"` } func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) { var req validateRequest dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) if err := dec.Decode(&req); err != nil { httpx.WriteError(s.reg(), w, r, errcode.Wrap(err, errcode.SysInvalidArgument, "请求体不是合法 JSON")) return } inputs := req.Codes if req.Code != "" { inputs = append([]string{req.Code}, inputs...) } if len(inputs) == 0 { httpx.WriteError(s.reg(), w, r, errcode.New(errcode.SysInvalidArgument, "code 或 codes 必填")) return } results := make([]validateResult, 0, len(inputs)) for _, input := range inputs { def, ok := s.reg().Lookup(input) resolved := def if !ok { resolved = s.reg().Resolve(input) } results = append(results, validateResult{ Input: input, Known: ok, Resolved: string(resolved.Code), Definition: resolved, }) } writeJSON(w, http.StatusOK, map[string]any{"results": results}) } func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) { data, err := s.reg().ExportJSON() if err != nil { httpx.WriteError(s.reg(), w, r, errcode.Wrap(err, errcode.SysInternal, "导出失败")) return } if domain := r.URL.Query().Get("domain"); domain != "" { raw, err := json.Marshal(s.reg().List(domain)) if err != nil { httpx.WriteError(s.reg(), w, r, errcode.Wrap(err, errcode.SysInternal, "导出失败")) return } data = append(raw, '\n') } w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) _, _ = w.Write(data) } func writeJSON(w http.ResponseWriter, code int, v any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(code) enc := json.NewEncoder(w) enc.SetEscapeHTML(false) _ = enc.Encode(v) }