package main import ( "encoding/json" "fmt" "net/http" "runtime/debug" "strings" "sync/atomic" "time" ) const jiaxingReaderType = "ZJ_SX_MT_TSW" type apiServer struct { cfg Config reader cardOperations logger *appLogger request atomic.Uint64 } type apiResponse struct { Code int `json:"code"` Message string `json:"message"` Data any `json:"data"` } func newHandler(cfg Config, reader cardOperations, logger *appLogger) http.Handler { s := &apiServer{cfg: cfg, reader: reader, logger: logger} mux := http.NewServeMux() mux.HandleFunc("GET /health", s.health) mux.HandleFunc("POST /api/card/read-nopin", s.readNoPIN) mux.HandleFunc("POST /api/card/read", s.readWithPIN) mux.HandleFunc("POST /api/hmac-sm3", s.hmacSM3) mux.HandleFunc("/", s.notFound) return s.middleware(mux) } func (s *apiServer) middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := s.cfg.CORSOrigin if origin == "*" { w.Header().Set("Access-Control-Allow-Origin", "*") } else if r.Header.Get("Origin") == origin { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Add("Vary", "Origin") } w.Header().Set("Access-Control-Allow-Headers", "Content-Type") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } requestID := fmt.Sprintf("%d-%06d", time.Now().Unix(), s.request.Add(1)) w.Header().Set("X-Request-ID", requestID) wrapped := &statusWriter{ResponseWriter: w, status: http.StatusOK} started := time.Now() defer func() { if recovered := recover(); recovered != nil { s.logger.Error("HTTP 处理发生 panic", "event", "http_panic", "request_id", requestID, "method", r.Method, "path", r.URL.Path, "panic", fmt.Sprint(recovered), "stack", string(debug.Stack())) if !wrapped.wroteHeader { writeJSON(wrapped, http.StatusInternalServerError, apiResponse{Code: 500, Message: "服务内部异常", Data: nil}) } } if wrapped.status >= 400 { s.logger.Warn("HTTP 请求失败", "event", "http_failure", "request_id", requestID, "method", r.Method, "path", r.URL.Path, "status", wrapped.status, "duration_ms", time.Since(started).Milliseconds(), "remote", r.RemoteAddr) } }() next.ServeHTTP(wrapped, r) }) } type statusWriter struct { http.ResponseWriter status int wroteHeader bool } func (w *statusWriter) WriteHeader(status int) { if w.wroteHeader { return } w.status = status w.wroteHeader = true w.ResponseWriter.WriteHeader(status) } func (w *statusWriter) Write(p []byte) (int, error) { if !w.wroteHeader { w.WriteHeader(http.StatusOK) } return w.ResponseWriter.Write(p) } func (s *apiServer) health(w http.ResponseWriter, _ *http.Request) { status := s.reader.Status() data := map[string]any{ "service": "card-read-service", "loaded": status.Loaded, "dllDir": status.DLLDir, "arch": status.Arch, "platform": status.Platform, "error": nil, "jiaxing_reader_type": jiaxingReaderType, "dll_init_ok": status.Loaded, } if status.Error != "" { data["error"] = status.Error } writeJSON(w, http.StatusOK, apiResponse{Code: 0, Message: "ok", Data: data}) } func (s *apiServer) readNoPIN(w http.ResponseWriter, r *http.Request) { var body struct { ReaderType string `json:"reader_type"` } if !decodeBody(w, r, &body) { return } readerType := strings.TrimSpace(body.ReaderType) if readerType == "" { writeJSON(w, http.StatusBadRequest, apiResponse{Code: 400, Message: "请传入 reader_type(地市读卡器标识,如 ZJ_HZ_310000)", Data: nil}) return } result, err := s.reader.ReadCard(readerType, true) if err != nil { s.logger.Error("免 PIN 读卡异常", "event", "dll_call_error", "method", "ZJ_ReadCardInfo_NoPin", "reader_type", readerType, "error", err) writeJSON(w, http.StatusInternalServerError, apiResponse{Code: 500, Message: err.Error(), Data: nil}) return } if !result.Success { message := resultMessage(result, "读卡失败") s.logger.Error("免 PIN 读卡失败", "event", "card_read_failure", "method", "ZJ_ReadCardInfo_NoPin", "reader_type", readerType, "dll_code", result.Code, "message", message) writeJSON(w, http.StatusInternalServerError, apiResponse{Code: 500, Message: message, Data: result}) return } writeJSON(w, http.StatusOK, apiResponse{Code: 0, Message: "ok", Data: successfulData(result)}) } func (s *apiServer) readWithPIN(w http.ResponseWriter, r *http.Request) { var body struct { ReaderType any `json:"reader_type"` } if !decodeBody(w, r, &body) { return } if body.ReaderType != nil && strings.TrimSpace(fmt.Sprint(body.ReaderType)) != jiaxingReaderType { writeJSON(w, http.StatusBadRequest, apiResponse{Code: 400, Message: "带 PIN 读卡仅支持嘉兴,reader_type 必须为 " + jiaxingReaderType, Data: nil}) return } result, err := s.reader.ReadCard(jiaxingReaderType, false) if err != nil { s.logger.Error("带 PIN 读卡异常", "event", "dll_call_error", "method", "ZJ_ReadCardInfo", "reader_type", jiaxingReaderType, "error", err) writeJSON(w, http.StatusInternalServerError, apiResponse{Code: 500, Message: err.Error(), Data: nil}) return } if !result.Success { message := resultMessage(result, "读卡失败") s.logger.Error("带 PIN 读卡失败", "event", "card_read_failure", "method", "ZJ_ReadCardInfo", "reader_type", jiaxingReaderType, "dll_code", result.Code, "message", message) writeJSON(w, http.StatusInternalServerError, apiResponse{Code: 500, Message: message, Data: result}) return } writeJSON(w, http.StatusOK, apiResponse{Code: 0, Message: "ok", Data: successfulData(result)}) } func (s *apiServer) hmacSM3(w http.ResponseWriter, r *http.Request) { var body map[string]any if !decodeBody(w, r, &body) { return } key, keyOK := body["key"] secret, secretOK := body["secret"] requestBody, bodyOK := body["request_body"] if !keyOK || key == nil || !secretOK || secret == nil || !bodyOK || requestBody == nil { writeJSON(w, http.StatusBadRequest, apiResponse{Code: 400, Message: "缺少 key / secret / request_body", Data: nil}) return } timestamp := "0" if value, ok := body["unix_timestamp"]; ok && value != nil { timestamp = fmt.Sprint(value) } result, err := s.reader.HMACSM3(fmt.Sprint(key), fmt.Sprint(secret), timestamp, fmt.Sprint(requestBody)) if err != nil { s.logger.Error("HMAC-SM3 调用异常", "event", "dll_call_error", "method", "ZJ_Hmac_SM3", "error", err) writeJSON(w, http.StatusInternalServerError, apiResponse{Code: 500, Message: err.Error(), Data: nil}) return } if !result.Success { message := resultMessage(result, "签名失败") s.logger.Error("HMAC-SM3 失败", "event", "hmac_failure", "method", "ZJ_Hmac_SM3", "dll_code", result.Code, "message", message) writeJSON(w, http.StatusInternalServerError, apiResponse{Code: 500, Message: message, Data: result}) return } writeJSON(w, http.StatusOK, apiResponse{Code: 0, Message: "ok", Data: successfulData(result)}) } func (s *apiServer) notFound(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusNotFound, apiResponse{Code: 404, Message: "Not Found", Data: nil}) } func decodeBody(w http.ResponseWriter, r *http.Request, target any) bool { r.Body = http.MaxBytesReader(w, r.Body, 1<<20) decoder := json.NewDecoder(r.Body) if err := decoder.Decode(target); err != nil { writeJSON(w, http.StatusBadRequest, apiResponse{Code: 400, Message: "请求 JSON 格式错误: " + err.Error(), Data: nil}) return false } return true } func resultMessage(result DLLResult, fallback string) string { if object, ok := result.Data.(map[string]any); ok { if message, ok := object["Message"].(string); ok && strings.TrimSpace(message) != "" { return message } } if strings.TrimSpace(result.Raw) != "" { return result.Raw } return fallback } func successfulData(result DLLResult) any { if result.Data != nil { return result.Data } return result } func writeJSON(w http.ResponseWriter, status int, response apiResponse) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(response) }