Files
responsex/response.go
T
2026-09-19 01:31:34 +08:00

189 lines
5.5 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package responsex
// Author: Yun
// Version: 2023年6月12日19:25:54
import (
"context"
"encoding/json"
"net/http"
"github.com/yuninks/errorx"
"github.com/yuninks/langx"
)
type response struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data,omitempty"`
Pagination *Pagination `json:"pagination,omitempty"`
TraceId string `json:"trace_id,omitempty"`
}
// 成功文案的多语言 key(独立 key,避免与 errorx 预注册的 "success" 冲突)
const successMsgKey = "responsex_success"
// 内置默认成功文案,也用作 defaultOptions 的初值
const defaultSuccessMessage = "请求成功"
// SuccessMessageCtx 按 ctx 语言解析成功文案:
// 1. 显式配置过 SetDefaultSuccessMessage → 用配置值;
// 2. 注册过 key "responsex_success" → 用该语言的译文;
// 3. 都没有 → 内置默认文案。
//
// 多语言接入示例(由调用方决定何时注册):
//
// langx.AppendCode(map[string]int{"responsex_success": 200})
// langx.AppendTrans("zh", map[string]string{"responsex_success": "请求成功"})
// langx.AppendTrans("en", map[string]string{"responsex_success": "success"})
//
// Success 系列内部使用同一套解析逻辑,无需额外配置。
func SuccessMessageCtx(ctx context.Context) string {
return resolveSuccessMessage(ctx)
}
func resolveSuccessMessage(ctx context.Context) string {
if op != nil && op.successMsgSet {
return op.defaultSuccessMessage
}
if msg := langx.GetMsgCtx(ctx, successMsgKey); msg != successMsgKey {
return msg
}
if op != nil {
return op.defaultSuccessMessage
}
return defaultSuccessMessage
}
// Msg格式化响应
func FormatMessage(ctx context.Context, w http.ResponseWriter, msgKey string, format map[string]string, data interface{}, ops ...Option) {
code, msg := langx.GetTransFormatCtx(ctx, msgKey, format)
baseOps := []Option{SetCode(code), SetMessage(msg), SetData(data)}
baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...)
}
// 成功的响应
func Success(ctx context.Context, w http.ResponseWriter, data any, ops ...Option) {
baseOps := []Option{SetCode(op.defaultSuccessCode), SetMessage(resolveSuccessMessage(ctx)), SetData(data)}
baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...)
}
func SuccessWithPage(ctx context.Context, w http.ResponseWriter, data any, page *Pagination, ops ...Option) {
baseOps := []Option{SetCode(op.defaultSuccessCode), SetMessage(resolveSuccessMessage(ctx)), SetData(data), SetPage(page)}
baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...)
}
// 失败的响应
func Error(ctx context.Context, w http.ResponseWriter, err error, ops ...Option) {
ErrorWithData(ctx, w, err, "", ops...)
}
func ErrorWithData(ctx context.Context, w http.ResponseWriter, err error, data interface{}, ops ...Option) {
code := op.defaultErrorCode
msg := "请求失败"
if err != nil {
if val, ok := errorx.As(err); ok {
if langx.GetDefaultCode() != val.Code() {
code = val.Code()
}
}
msg = err.Error()
}
baseOps := []Option{SetCode(code), SetMessage(msg), SetData(data)}
baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...)
}
func ErrorStr(ctx context.Context, w http.ResponseWriter, msg string, ops ...Option) {
translated := langx.GetMsgCtx(ctx, msg)
// msg 命中语言表时按 key 取其业务码;未命中则保持默认错误码
code := op.defaultErrorCode
if translated != msg {
if c := langx.GetCode(msg); c != langx.GetDefaultCode() {
code = c
}
}
baseOps := []Option{SetCode(code), SetMessage(translated)}
baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...)
}
// 基础的响应
func ResponseCtx(ctx context.Context, w http.ResponseWriter, ops ...Option) {
def := defaultOptions() // 默认值
for _, o := range ops {
o(def)
}
resp := response{}
resp.Code = def.code
resp.Message = def.message
resp.Data = def.data
resp.Pagination = def.pagination
if def.traceFunc != nil {
resp.TraceId = def.traceFunc(ctx)
}
bs, err := json.Marshal(resp)
if err != nil {
// 序列化失败不能回空 body,否则客户端无法解析
logError(ctx, def, "response marshal failed err:%+v", err)
fallback := response{
Code: op.defaultErrorCode,
Message: "响应序列化失败",
}
if def.traceFunc != nil {
fallback.TraceId = def.traceFunc(ctx)
}
bs, _ = json.Marshal(fallback)
writeResponse(ctx, w, def, bs, http.StatusInternalServerError)
return
}
if !def.ignoreLog {
// 记录日志
logInfo(ctx, def, "response:", string(bs))
}
writeResponse(ctx, w, def, bs, def.httpStatusCode)
}
// writeResponse 统一写响应,httpStatus 为 0 时按 200 处理
func writeResponse(ctx context.Context, w http.ResponseWriter, def *options, bs []byte, httpStatus int) {
// 写入自定义响应头
for k, v := range def.headers {
w.Header().Set(k, v)
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if httpStatus == 0 {
httpStatus = 200
}
w.WriteHeader(httpStatus)
n, err := w.Write(bs)
if err != nil || n != len(bs) {
logError(ctx, def, "write response failed err:%+v", err)
}
}
// logInfo / logError 使用本次响应的 logger(SetLogger 生效),并做空值保护
func logInfo(ctx context.Context, def *options, args ...interface{}) {
if def == nil || def.logger == nil {
return
}
def.logger.Info(ctx, args...)
}
func logError(ctx context.Context, def *options, format string, args ...interface{}) {
if def == nil || def.logger == nil {
return
}
def.logger.Errorf(ctx, format, args...)
}