Files
responsex/response.go
T
yun 8f50c2ce9b 适配 errorx v1.2:错误值不持有 ctx,改用 errors.As + GetCode/Msg(ctx)
- response.go: errorx.As/val.Code() 换成 errors.As(errorx.ErrorInterface) 取
  GetCode(),文案改用 Msg(ctx) 按请求语言渲染(errorx v1.2 起错误值只保存语言,
  渲染时再传 ctx)。
- FormatMessage: langx v0.1 起未知 key 的默认码从 200 变成 -1,命中默认码时
  回落到 SetDefaultSuccessCode,保持升级前 code:200 的行为。
- go.mod: github.com/yuninks/errorx v1.2.0 / github.com/yuninks/langx v0.1.0。
2026-10-02 22:53:10 +08:00

201 lines
6.1 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"
"errors"
"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)
// langx v0.1 起「未知 key」的默认码是 -1(v0.0.x 是 200)。responsex 的成功码
// 由 SetDefaultSuccessCode 决定,所以把默认码视为「没有业务码」,回落到它,
// 保持与升级前一致的 code:200。
if code == langx.GetDefaultCode() {
code = op.defaultSuccessCode
}
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 {
// errorx v1.2 起错误值不再持有 context:错误常量只负责定义与构造,
// 文案要在出口用 Msg(ctx) 按请求语言渲染,业务码用 GetCode() 取。
var ei errorx.ErrorInterface
if errors.As(err, &ei) {
if bizCode := ei.GetCode(); langx.GetDefaultCode() != bizCode {
code = bizCode
}
msg = ei.Msg(ctx)
} else {
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...)
}