Files
huangguo_server/common/ginUtil.go
T
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

530 lines
13 KiB
Go

package common
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"os"
"reflect"
"strconv"
"time"
"unicode"
"91porn-server/common/constant"
"91porn-server/common/crypt"
"91porn-server/common/log"
"91porn-server/common/stderr"
"91porn-server/common/version"
"91porn-server/middleware/ua"
"github.com/gin-gonic/gin"
)
var (
env string
pid = os.Getgid()
cryptSecret360 string
ErrUserNotExist = errors.New("userId not exist")
FirstRealUserID uint64
)
type FileType string
const (
FileCSV FileType = "csv"
FileExcel FileType = "xlsx"
firstRealUserIDTest = 300001 // 首个真实的用户id(测试环境)
firstRealUserIDProd = 300001 // 首个真实的用户id(正式环境)
)
// ServeFile serve request with an file by attachment.
func ServeFile(c *gin.Context, fileName string, fileType FileType, fileBuffer *bytes.Buffer) {
c.DataFromReader(http.StatusOK, int64(fileBuffer.Len()), "application/octet-stream", fileBuffer, map[string]string{
"Content-Disposition": fmt.Sprintf("attachment;filename=%s-%s.%s",
fileName, time.Now().Local().Format("2006-01-02"), fileType),
})
}
// ServeJSON 返回数据并处理多语言
func ServeJSON(c *gin.Context, code stderr.Code, data interface{}) {
ServeJsonWithExtra(c, code, data, nil)
}
func ServeJsonWithExtra(c *gin.Context, code stderr.Code, data interface{}, extra map[string]interface{}) {
var hash bool
if code != stderr.Success && code != stderr.ErrVersionUpdate { //发生错误,记录日志
var version, sysType, devType string
id, _ := GetUID(c)
ua, _ := GetUA(c)
if ua.Ver != "" {
version = ua.Ver
}
if ua.SysType != "" {
sysType = ua.SysType
}
if devType != "" {
devType = ua.DevType
}
// 预防打印出现空指针异常 PANIC=runtime error: invalid memory address or nil pointer dereference
if data == nil {
data = "nil"
}
log.WarnX(c, "Error:",
log.Any("UID", strconv.FormatUint(id, 10)),
log.Any("IP", c.ClientIP()),
log.Any("Version", version),
log.Any("SysType", sysType),
log.Any("DevType", devType),
log.Any("Router", c.Request.RequestURI),
log.Any("PID", pid),
log.Any("Data", data),
log.Any("Code", code))
}
if !IsNilOrEmpty(data) && code == stderr.Success {
t := reflect.TypeOf(data)
if !(t.Kind() == reflect.Map || t.Kind() == reflect.Struct || t.Kind() == reflect.Slice) {
log.WarnX(c, "[===TypeError===] Return Data Type error is not struct or slice",
log.Any("path", c.Request.URL.Path))
}
}
// 在 data 加密前抽取一次 msg:敏感词命中等场景需要把 data 的明文详情同步到 msg
msg := resolveMsg(code, data)
if env == constant.ProdEnv {
//返给前端是否加密
hash = true
dataByte, _ := json.Marshal(data)
cipher, _ := crypt.CoreAesEncryptEx(dataByte, 12, cryptSecret360)
data = base64.StdEncoding.EncodeToString(cipher)
}
if IsNilOrEmpty(data) {
data = ""
}
h := gin.H{
"code": code,
"hash": hash,
"msg": msg,
"tip": code.Tip(),
"data": data,
"time": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
}
if len(extra) > 0 {
for k, v := range extra {
h[k] = v
}
}
c.JSON(http.StatusOK, h)
}
// resolveMsg 按 code 选择 msg:默认走 code.Msg();对于内容敏感词命中这类
// 详情完全包含在 data 里的错误码,把 data 的字符串明文同步覆盖到 msg,
// 便于前端直接用 msg 弹窗,无需再额外读取 data 字段。
func resolveMsg(code stderr.Code, data interface{}) string {
if code == stderr.ContentSensitiveHit {
if s, ok := data.(string); ok && s != "" {
return s
}
}
return code.Msg()
}
// ServeToJSON 返回数据并处理多语言
func ServeToJSON(c *gin.Context, code stderr.Code, data interface{}) {
var hash bool
if code != stderr.Success && code != stderr.ErrVersionUpdate { //发生错误,记录日志
var version, sysType, devType string
id, _ := GetUID(c)
ua, _ := GetUA(c)
if ua.Ver != "" {
version = ua.Ver
}
if ua.SysType != "" {
sysType = ua.SysType
}
if devType != "" {
devType = ua.DevType
}
log.WarnX(c, "Error:",
log.Any("UID", strconv.FormatUint(id, 10)),
log.Any("IP", c.ClientIP()),
log.Any("Version", version),
log.Any("SysType", sysType),
log.Any("DevType", devType),
log.Any("Router", c.Request.RequestURI),
log.Any("PID", pid),
log.Any("Error", data),
log.Any("Code", code))
}
if !IsNilOrEmpty(data) && code == stderr.Success {
t := reflect.TypeOf(data)
if !(t.Kind() == reflect.Map || t.Kind() == reflect.Struct || t.Kind() == reflect.Slice || t.Kind() == reflect.Ptr) {
log.WarnX(c, "[===TypeError===] Return Data Type error is not struct or slice",
log.Any("kind", t.Kind()),
log.Any("path", c.Request.URL.Path))
}
}
if IsNilOrEmpty(data) {
data = gin.H{}
}
c.JSON(http.StatusOK, gin.H{
"code": code,
"hash": hash,
"msg": code.Msg(),
"tip": code.Tip(),
"data": data,
"time": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
})
}
func ServeError(c *gin.Context, err error) {
cErr, ok := err.(*stderr.CustomErr)
if ok {
serveJsonLogic(c, cErr.Code, cErr.Msg, nil, cErr.Msg, cErr.Msg)
} else {
log.ErrorX(c, "ServeError", log.E(err))
var errMsg string
if err != nil {
errMsg = err.Error()
}
serveJsonLogic(c, stderr.Failure, errMsg, nil, stderr.Failure.Error(), stderr.Failure.Tip())
}
}
func serveJsonLogic(c *gin.Context, code stderr.Code, data interface{}, extra map[string]interface{}, msg, tip string) {
var hash bool
if code != stderr.Success && code != stderr.ErrVersionUpdate { //发生错误,记录日志
var version, sysType, devType string
id, _ := GetUID(c)
ua, _ := GetUA(c)
if ua.Ver != "" {
version = ua.Ver
}
if ua.SysType != "" {
sysType = ua.SysType
}
if devType != "" {
devType = ua.DevType
}
// 预防打印出现空指针异常 PANIC=runtime error: invalid memory address or nil pointer dereference
if data == nil {
data = "nil"
}
log.WarnX(c, "Error:",
log.Any("UID", strconv.FormatUint(id, 10)),
log.Any("IP", c.ClientIP()),
log.Any("Version", version),
log.Any("SysType", sysType),
log.Any("DevType", devType),
log.Any("Router", c.Request.RequestURI),
log.Any("PID", pid),
log.Any("Data", data),
log.Any("Code", code))
}
if !IsNilOrEmpty(data) && code == stderr.Success {
t := reflect.TypeOf(data)
if !(t.Kind() == reflect.Map || t.Kind() == reflect.Struct || t.Kind() == reflect.Slice) {
log.WarnX(c, "[===TypeError===] Return Data Type error is not struct or slice",
log.Any("path", c.Request.URL.Path))
}
}
if env == constant.ProdEnv {
//返给前端是否加密
hash = true
dataByte, _ := json.Marshal(data)
cipher, _ := crypt.CoreAesEncryptEx(dataByte, 12, cryptSecret360)
data = base64.StdEncoding.EncodeToString(cipher)
}
if IsNilOrEmpty(data) {
data = ""
}
h := gin.H{
"code": code,
"hash": hash,
"msg": msg,
"tip": tip,
"data": data,
"time": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
}
if len(extra) > 0 {
for k, v := range extra {
h[k] = v
}
}
c.JSON(http.StatusOK, h)
}
// GetUID 获取用户uid
// oauth.Auth执行后uid有效
func GetUID(ctx *gin.Context) (uid uint64, err error) {
val, exists := ctx.Get(constant.CtxUserID)
if !exists {
err = ErrUserNotExist
return
}
uid, ok := val.(uint64)
if !ok {
return 0, errors.New("userId type error")
}
return
}
// TryGetUID 尝试获取用户ID,用户未登录的情况下uid为0
func TryGetUID(ctx *gin.Context) (uid uint64) {
val, exists := ctx.Get(constant.CtxUserID)
if !exists {
return
}
uid, _ = val.(uint64)
return
}
// GetUA
func GetUA(ctx *gin.Context) (u ua.UA, err error) {
val, exists := ctx.Get(constant.CtxUA)
if !exists {
err = errors.New("user-agent not exists")
return
}
u, ok := val.(ua.UA)
if !ok {
err = errors.New("user-agent type error")
return
}
return u, nil
}
/**
* 判断当前App 是否是制定的版本号
* 历史判断:
1. 是否是flutter版本 ver=2.0.0 主要更新列表,使用hasNext 替换total
2. 是否2.0.1版本 ver=2.0.1 用于获取账单更改
3. 是否2.0.9版本 ver=2.0.9 用户用户手机登陆流程
4. 是否2.1.0版本 ver=2.1.0 用于接口防重放
*/
func IsGTESpecifyVer(ctx *gin.Context, specVer string) bool {
val, exists := ctx.Get(constant.CtxUA)
if !exists {
log.Warn("user-agent not exists")
return false
}
u, ok := val.(ua.UA)
if !ok {
log.Warn("user-agent type error")
return false
}
if u.Ver == "" {
return false
}
v1, err := version.New(u.Ver)
if v1 == nil || err != nil {
return false
}
v2, err := version.New(specVer)
if v2 == nil || err != nil {
return false
}
if v1.GTE(v2) {
return true
}
return false
}
func IsSpecifyVerBaseOnUa(u ua.UA, specVer string) bool {
if u.Ver == "" {
return false
}
v1, err := version.New(u.Ver)
if v1 == nil || err != nil {
return false
}
v2, err := version.New(specVer)
if v2 == nil || err != nil {
return false
}
if v1.GTE(v2) {
return true
}
return false
}
// GetAdminAct 获取管理员账号
// oauth.Auth执行后账号有效
func GetAdminAct(ctx *gin.Context) (string, error) {
t1 := ctx.Request.Header.Get("mod")
if t1 == "debug" {
return "debug", nil
}
val, exists := ctx.Get(constant.CtxAdminAct)
if !exists {
return "", errors.New("Admin not exist")
}
v, ok := val.(string)
if !ok {
return "", errors.New("Admin type error")
}
return v, nil
}
// GetAdminRole 获取管理员账号
// oauth.Auth执行后账号有效
func GetAdminRole(ctx *gin.Context) (string, error) {
val, exists := ctx.Get(constant.CtxAdminRole)
if !exists {
return "", errors.New("AdminRole not exist")
}
v, ok := val.(string)
if !ok {
return "", errors.New("AdminRole type error")
}
return v, nil
}
// GetJuShangId 获取管理员账号
// oauth.Auth执行后账号有效
func GetJuShangID(ctx *gin.Context) (string, error) {
val, exists := ctx.Get(constant.CtxJuShangCID)
if !exists {
return "", errors.New("JuShangID not exist")
}
v, ok := val.(string)
if !ok {
return "", errors.New("JuShangID type error")
}
return v, nil
}
// GetUIDAct 获取管理员账号
// oauth.Auth执行后uid有效
func GetDistrictAct(ctx *gin.Context) (string, error) {
val, exists := ctx.Get(constant.CtxDistrictName)
if !exists {
return "", errors.New("DistrictUserID not exist")
}
act, ok := val.(string)
if !ok {
return "", errors.New("DistrictAct type error")
}
return act, nil
}
// InitGinSecret InitResponseParam
func InitGinSecret(secret, secret360, env_ string) {
cryptSecret360 = secret360
env = env_
FirstRealUserID = firstRealUserIDTest
if env == constant.ProdEnv {
FirstRealUserID = firstRealUserIDProd
}
}
func IsNilOrEmpty(in interface{}) bool {
return in == nil || in == ""
}
// GetIP 获取真实IP
func GetIP(ctx *gin.Context) string {
relIP, exists := ctx.Get(constant.CtxIP)
v, _ := relIP.(string)
// CtxIP 未设置(或存的是空串)时回落到 ClientIP;
// 此前写成 if !exists { if relIP == "" ... } —— relIP 为 nil 接口,永不等于 "",回落是死代码,会返回空串
if !exists || v == "" {
v = ctx.ClientIP()
}
//log.Info(fmt.Sprintf("[IP-ROUTER] %s,PID %d", ctx.GetHeader("X-Forwarded-For"), os.Getpid()))
return v
}
// 判断是否是ip4
func IsIP4(ip string) (bool, string) {
ipAddr := net.ParseIP(ip).To4()
if ipAddr == nil {
return false, ""
}
return true, ipAddr.String()
}
// 判断是否是正常的设备id
func IsNormalDevId(devId string) bool {
for _, r := range devId {
//判断是否包含中文汉子
if unicode.Is(unicode.Scripts["Han"], r) {
return false
}
//判断是否包含空格
if unicode.IsSpace(r) {
return false
}
}
return true
}
func ServeJSONNoEncrypt(c *gin.Context, code stderr.Code, data interface{}) {
ServeJsonWithExtraNoEncrypt(c, code, data, nil)
}
func ServeJsonWithExtraNoEncrypt(c *gin.Context, code stderr.Code, data interface{}, extra map[string]interface{}) {
var hash bool
if code != stderr.Success && code != stderr.ErrVersionUpdate { //发生错误,记录日志
var version, sysType, devType string
id, _ := GetUID(c)
ua, _ := GetUA(c)
if ua.Ver != "" {
version = ua.Ver
}
if ua.SysType != "" {
sysType = ua.SysType
}
if devType != "" {
devType = ua.DevType
}
// 预防打印出现空指针异常 PANIC=runtime error: invalid memory address or nil pointer dereference
if data == nil {
data = "nil"
}
log.WarnX(c, "Error:",
log.Any("UID", strconv.FormatUint(id, 10)),
log.Any("IP", c.ClientIP()),
log.Any("Version", version),
log.Any("SysType", sysType),
log.Any("DevType", devType),
log.Any("Router", c.Request.RequestURI),
log.Any("PID", pid),
log.Any("Data", data),
log.Any("Code", code))
}
if !IsNilOrEmpty(data) && code == stderr.Success {
t := reflect.TypeOf(data)
if !(t.Kind() == reflect.Map || t.Kind() == reflect.Struct || t.Kind() == reflect.Slice) {
log.WarnX(c, "[===TypeError===] Return Data Type error is not struct or slice",
log.Any("path", c.Request.URL.Path))
}
}
msg := resolveMsg(code, data)
if IsNilOrEmpty(data) {
data = ""
}
h := gin.H{
"code": code,
"hash": hash,
"msg": msg,
"tip": code.Tip(),
"data": data,
"time": time.Now().UTC().Format("2006-01-02T15:04:05.000Z"),
}
if len(extra) > 0 {
for k, v := range extra {
h[k] = v
}
}
c.JSON(http.StatusOK, h)
}