Browse Source

init:添加微信小程序接口

master
刘浩东 3 weeks ago
commit
db6910b712
  1. 13
      .gitignore
  2. 203
      api/client/mini_program_client.go
  3. 5
      api/constant/constant.go
  4. 1
      api/entity/third_party.go
  5. 40
      api/handler/handler.go
  6. 65
      api/main.go
  7. 226
      api/publish-prod.sh
  8. 25
      api/route/route.go
  9. 3
      api/version.go
  10. 1
      config/third-party-gateway-dev.consul.yaml
  11. 9
      config/third-party-gateway-prod.conf
  12. 1
      config/third-party-gateway-prod.consul.yaml
  13. 75
      go.mod
  14. 79
      utils/error.go
  15. 47
      utils/gin.go
  16. 30
      utils/logger.go

13
.gitignore

@ -0,0 +1,13 @@
*.pb.go
*.log
go.sum
*.bin
*.pem
*.srv
*.signature
*.zip
*.pdf
.idea
.vscode

203
api/client/mini_program_client.go

@ -0,0 +1,203 @@
package client
import (
"encoding/json"
"errors"
"fmt"
"jhttp"
"jredis"
"log"
"strconv"
"sync"
"third-party-gateway/api/constant"
"time"
)
type MiniProgramAuthReq struct {
Code string `json:"code" binding:"required"`
SceneId string `json:"sceneId"`
}
type MiniProgramLoginResponse struct {
OpenID string `json:"openid"`
SessionKey string `json:"session_key"`
UnionID string `json:"unionid,omitempty"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
type MiniProgramTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
var (
_app_id = ""
_app_secret = ""
)
// InitMiniProgramClient 初始化小程序客户端配置
func InitMiniProgramClient(appid, appsecret string) {
_app_id = appid
_app_secret = appsecret
}
// GetMiniProgramQrCode 获取小程序码
func GetMiniProgramQrCode(envVersion string) ([]byte, int64, error) {
if envVersion == "" {
envVersion = "release"
}
// 生成唯一的场景值
sceneId := generateUniqueID()
// 从缓存获取access_token
key := fmt.Sprintf(constant.MiniProgramAccessToken, _app_id)
accessToken, err := jredis.GetString(key)
if err != nil || accessToken == "" {
// 重新获取access_token
accessToken, err = refreshAccessToken()
if err != nil {
return nil, 0, fmt.Errorf("获取access_token失败: %w", err)
}
}
// 调用微信接口获取小程序码
url := fmt.Sprintf("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", accessToken)
params := map[string]any{
"scene": sceneId,
"width": 280,
"env_version": envVersion,
}
qrcode, err := jhttp.Post(url, nil, params)
if err != nil {
return nil, 0, err
}
// 尝试解析错误响应
var errResp MiniProgramTokenResponse
if err := json.Unmarshal(qrcode, &errResp); err == nil && errResp.ErrCode != 0 {
return nil, 0, fmt.Errorf("获取小程序码失败:%s", errResp.ErrMsg)
}
// 将sceneId存入Redis,设置过期时间
//err = common.SetValueWithExpired(strconv.FormatInt(sceneId, 10), "waiting", 300) // 5分钟过期
//if err != nil {
// log.ERROR("存储sceneId状态失败", err)
// return nil, 0, err
//}
return qrcode, sceneId, nil
}
var (
mu sync.Mutex
seq int64
lastTs int64
)
// GenerateUniqueID 生成小于14位的唯一ID(最多13位)
func generateUniqueID() int64 {
mu.Lock()
defer mu.Unlock()
// 当前时间戳:秒级(10位)或毫秒级(13位)可调
ts := time.Now().Unix() // 秒级时间戳(10位)
// 如果在同一秒内生成,则递增序列
if ts == lastTs {
seq++
} else {
lastTs = ts
seq = 0
}
// 拼接时间戳和序列(限制在13位内)
idStr := fmt.Sprintf("%d%02d", ts%1e10, seq%100) // 10位时间戳 + 2位序列(最多12位)
id, _ := strconv.ParseInt(idStr, 10, 64)
return id
}
// GetMiniProgramQrCode 获取小程序码
func GetPrintMiniProgramQrCode(scene_Id, envVersion string) ([]byte, error) {
if envVersion == "" {
envVersion = "release"
}
// 生成唯一的场景值
sceneId := fmt.Sprintf("scene_Id:%v", scene_Id)
// 从缓存获取access_token
key := fmt.Sprintf(constant.MiniProgramAccessToken, _app_id)
accessToken, err := jredis.GetString(key)
if err != nil || accessToken == "" {
// 重新获取access_token
accessToken, err = refreshAccessToken()
if err != nil {
return nil, fmt.Errorf("获取access_token失败: %w", err)
}
}
// 调用微信接口获取小程序码
url := fmt.Sprintf("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", accessToken)
params := map[string]any{
"scene": sceneId,
"page": "pages/index/index",
"width": 280,
"env_version": envVersion,
}
qrcode, err := jhttp.Post(url, nil, params)
if err != nil {
return nil, err
}
// 尝试解析错误响应
var errResp MiniProgramTokenResponse
if err := json.Unmarshal(qrcode, &errResp); err == nil && errResp.ErrCode != 0 {
log.Printf("获取小程序码失败:%s", errResp.ErrMsg)
return nil, fmt.Errorf("获取小程序码失败:%s", errResp.ErrMsg)
}
// 将sceneId存入Redis,设置过期时间
//err = common.SetValueWithExpired(strconv.FormatInt(sceneId, 10), "waiting", 300) // 5分钟过期
//if err != nil {
// log.ERROR("存储sceneId状态失败", err)
// return nil, 0, err
//}
return qrcode, nil
}
// GetMiniProgramOpenId 获取小程序用户openId
func GetMiniProgramOpenId(code string) (string, error) {
if code == "" {
return "", errors.New("code不能为空")
}
// 构建请求URL
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
_app_id, _app_secret, code)
// 发送请求并解析响应
if result, err := jhttp.GetJson[MiniProgramLoginResponse](url, nil); err != nil {
log.Println("获取openId失败", err)
return "", err
} else if result.ErrCode != 0 {
log.Println(fmt.Errorf("获取openId失败:%s", result.ErrMsg))
return "", fmt.Errorf("获取openId失败:%s", result.ErrMsg)
} else {
return result.OpenID, nil
}
}
// refreshAccessToken 刷新access_token
func refreshAccessToken() (string, error) {
// 获取新的access_token
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
_app_id, _app_secret)
// 发送请求并解析响应
if result, err := jhttp.GetJson[MiniProgramTokenResponse](url, nil); err != nil {
return "", err
} else if result.ErrCode != 0 {
return "", fmt.Errorf("获取access_token失败:%s", result.ErrMsg)
} else {
// 将access_token存入Redis,设置过期时间为7000秒(微信默认7200秒)
key := fmt.Sprintf(constant.MiniProgramAccessToken, _app_id)
err := jredis.SetValueWithExpired(key, result.AccessToken, 7000)
if err != nil {
return "", err
}
return result.AccessToken, nil
}
}

5
api/constant/constant.go

@ -0,0 +1,5 @@
package constant
const (
MiniProgramAccessToken = "third-party-gateway:mini_program:access_token:%s" //二维码扫描信息,存储唯一时间戳
)

1
api/entity/third_party.go

@ -0,0 +1 @@
package entity

40
api/handler/handler.go

@ -0,0 +1,40 @@
package handler
import (
"github.com/gin-gonic/gin"
"jrest"
"net/http"
"third-party-gateway/api/client"
"third-party-gateway/utils"
)
func GetPrintMiniProgramQrCode(c *gin.Context) {
var sceneId string
var envVersion string
err := jrest.QueryScanOptional(c, "sceneId:envVersion",
&sceneId, &envVersion)
if err != nil {
utils.ParamError(c)
return
}
resp, err := client.GetPrintMiniProgramQrCode(sceneId, envVersion)
if err != nil {
utils.Error(c, http.StatusInternalServerError, "获取微信小程序二维码失败")
return
}
c.Data(http.StatusOK, "application/octet-stream", resp)
}
func GetMiniProgramOpenId(c *gin.Context) {
var code string
if err := jrest.QueryScan(c, "code", &code); err != nil {
utils.ParamError(c)
return
}
openId, err := client.GetMiniProgramOpenId(code)
if err != nil {
utils.Error(c, http.StatusInternalServerError, "权限检验失败")
return
}
utils.OKWithData(c, openId)
}

65
api/main.go

@ -0,0 +1,65 @@
package main
import (
"flag"
"fmt"
"jconfig"
"jconsul"
"jlog"
"jredis"
"jrest"
"jversion"
"log"
"third-party-gateway/api/client"
"third-party-gateway/api/route"
"third-party-gateway/utils"
)
const ServiceName = "third-party-gateway"
type ConsulConf struct {
Url string `yaml:"url"`
}
type MPConfig struct {
AppId string `yaml:"appid"`
AppSecret string `yaml:"appsecret"`
}
type Config struct {
HttpPort int32 `yaml:"api-port"`
Redis string `yaml:"redis"`
MP MPConfig `yaml:"mini-program"`
Debug bool `yaml:"debug"`
}
func main() {
jversion.Init(VERSION)
jversion.LogVersion()
env := flag.String("env", "dev", "environment")
flag.Parse()
serviceNameEnv := ServiceName + "-" + *env
consulConf := ConsulConf{}
err := jconfig.ParseConfig("config/"+serviceNameEnv+".consul.yaml", &consulConf)
if err != nil {
return
}
jconsul.InitConsul(consulConf.Url)
config := &Config{}
if err := jconsul.LoadConfig(serviceNameEnv, config); err != nil {
log.Println("load config error", err)
return
}
jlog.Init(config.Debug)
log.Println("config:", config)
jconsul.RegisterServices(fmt.Sprintf("%s:%d", serviceNameEnv, config.HttpPort))
log.Println("conf:", config)
jredis.Init(config.Redis)
defer jredis.Close()
client.InitMiniProgramClient(config.MP.AppId, config.MP.AppSecret)
jrest.ServerWithHandlers(config.Debug, utils.TracingHandler, utils.ErrorHandler)
route.InitRouter()
jrest.ServerRun(config.HttpPort)
}

226
api/publish-prod.sh

@ -0,0 +1,226 @@
#!/bin/bash
#部署的服务器上要安装supervisor进程管理工具
TARGET="third-party-gateway-prod.srv" # 编译后的文件名
REMOTE="root@jg-serv1" # 远程服务器
REMOTE_DIR="/usr/local/third-party-gateway-prod" # 部署目录
SERVICE_NAME="third-party-gateway-prod" # Supervisor 配置里的服务名
WAIT_TIME=2 # 等待服务启动时间(秒)
BACKUP_DIR="$REMOTE_DIR/backup" # 旧版本备份目录
# Supervisor 配置目录
#cat /etc/os-release 查看操作系统版本信息
#Debian 安装supervisor
#sudo apt update
#sudo apt install supervisor -y
#systemctl status supervisor
SUPERVISOR_CONF_DIR="/etc/supervisor/conf.d" #Ubuntu/Debian 默认路径
#SUPERVISOR_CONF_DIR="/etc/supervisord.d/" #CentOS 7(EPEL)默认路径
trap 'ssh -O exit $REMOTE 2>/dev/null || true' EXIT
function remote_cmd() {
ssh "$REMOTE" "$@" </dev/null
}
# 1️⃣ 本地编译
function build_local() {
echo "🛠️ 本地交叉编译 (GOOS=linux GOARCH=amd64)..."
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o "$TARGET"
if [ $? -ne 0 ]; then
echo "❌ 编译失败"
exit 1
fi
}
# 2️⃣ 确保远程目录存在
function ensure_remote_dir() {
echo "📂 确保远程部署目录存在..."
remote_cmd "mkdir -p '$REMOTE_DIR'"
remote_cmd "mkdir -p '$REMOTE_DIR/config'"
remote_cmd "mkdir -p '$REMOTE_DIR/log'"
remote_cmd "mkdir -p '$BACKUP_DIR'"
}
# 3️⃣ 备份旧版本
function backup_old() {
echo "📦 备份旧版本..."
remote_cmd "
if [ -f '$REMOTE_DIR/$TARGET' ]; then
TIMESTAMP=\$(date +'%Y%m%d%H%M%S'); \
BACKUP_FILE='$BACKUP_DIR/$TARGET.'\$TIMESTAMP; \
cp '$REMOTE_DIR/$TARGET' \"\$BACKUP_FILE\"; \
echo \"✅ 旧版本已备份到 \$BACKUP_FILE\"; \
else
echo 'ℹ️ 没有旧版本需要备份'
fi
"
}
# 4️⃣ 上传二进制
function upload_binary() {
echo "📤 上传新版本到服务器..."
# 1️⃣ 上传临时文件(避免直接覆盖正在运行的程序)
TMP_TARGET="$TARGET.new"
scp "$TARGET" "$REMOTE:$REMOTE_DIR/$TMP_TARGET"
# 2️⃣ 替换旧文件并设置可执行权限
remote_cmd "mv -f '$REMOTE_DIR/$TMP_TARGET' '$REMOTE_DIR/$TARGET' && chmod +x '$REMOTE_DIR/$TARGET'"
echo "✅ 上传并替换完成: $REMOTE_DIR/$TARGET"
}
function upload_consul_conf() {
LOCAL_CONF_FILE="../config/$SERVICE_NAME.consul.yaml"
REMOTE_CONF_FILE="$REMOTE_DIR/config/$SERVICE_NAME.consul.yaml"
if [ -f "$LOCAL_CONF_FILE" ]; then
# 检查远程文件是否存在并计算差异
remote_cmd "touch $REMOTE_CONF_FILE" # 确保远程文件存在
DIFF=$(diff "$LOCAL_CONF_FILE" <(ssh "$REMOTE" "cat $REMOTE_CONF_FILE") || true)
if [ -n "$DIFF" ]; then
echo "📄 Consul 配置有变动,上传并刷新..."
scp "$LOCAL_CONF_FILE" "$REMOTE:$REMOTE_CONF_FILE"
else
echo "ℹ️ Consul 配置未变化,跳过上传"
fi
else
echo "ℹ️ 本地未找到 $LOCAL_CONF_FILE,跳过 Consul 配置"
fi
}
function upload_locale_conf() {
CONF_FILE="locale.zh.yaml"
LOCAL_PATH="../$CONF_FILE"
if [ -f "$LOCAL_PATH" ]; then
echo "找到文件:$LOCAL_PATH"
else
echo "❌ 文件不存在:$LOCAL_PATH"
return
fi
REMOTE_CONF="$REMOTE_DIR/$CONF_FILE"
remote_cmd "touch $REMOTE_CONF" # 确保远程文件存在
DIFF=$(diff "$LOCAL_PATH" <(ssh "$REMOTE" "cat $REMOTE_CONF") || true)
if [ -n "$DIFF" ]; then
echo "📄 Locale 配置有变动,上传并刷新..."
scp "$LOCAL_PATH" "$REMOTE:$REMOTE_DIR/"
else
echo "ℹ️ Locale 配置未变化,跳过上传"
fi
}
function upload_supervisor_conf() {
CONF_FILE="$SERVICE_NAME.conf"
LOCAL_PATH="../config/$CONF_FILE"
# 检查本地文件是否存在
if [ -f "$LOCAL_PATH" ]; then
echo "✅ 找到文件:$LOCAL_PATH"
else
echo "❌ 文件不存在:$LOCAL_PATH"
return
fi
# 定义远程路径
REMOTE_CONF="$SUPERVISOR_CONF_DIR/$CONF_FILE"
# 确保远程文件存在(防止 cat 报错)
remote_cmd "touch $REMOTE_CONF"
# 比较本地和远程差异
DIFF=$(diff "$LOCAL_PATH" <(ssh "$REMOTE" "cat $REMOTE_CONF") || true)
if [ -n "$DIFF" ]; then
echo "📄 Supervisor 配置有变动,上传并刷新..."
scp "$LOCAL_PATH" "$REMOTE:$SUPERVISOR_CONF_DIR/"
# 重新加载 supervisor 配置
remote_cmd "supervisorctl reread && supervisorctl update"
echo "🔄 已刷新 Supervisor 配置"
else
echo "ℹ️ Supervisor 配置未变化,跳过上传"
fi
}
# 6️⃣ 重启服务并检查状态
function restart_service() {
echo "🔄 检查服务状态并重启 (Supervisor)..."
STATUS=$(remote_cmd "supervisorctl status '$SERVICE_NAME' | awk '{print \$2}'")
echo "ℹ️ 当前服务状态: $STATUS"
if [ "$STATUS" = "RUNNING" ]; then
echo "🔁 服务正在运行,执行 restart..."
remote_cmd "supervisorctl restart '$SERVICE_NAME'"
else
echo "⚡ 服务未运行 ($STATUS),执行 start..."
remote_cmd "supervisorctl start '$SERVICE_NAME'"
fi
echo "⏳ 等待服务启动 ($WAIT_TIME 秒)..."
sleep $WAIT_TIME
FINAL_STATUS=$(remote_cmd "supervisorctl status '$SERVICE_NAME'")
echo "$FINAL_STATUS"
if echo "$FINAL_STATUS" | grep -q "RUNNING"; then
echo "✅ 服务已启动"
echo "📋 最新日志:"
remote_cmd "tail -n 10 $REMOTE_DIR/log/info.log" || true
else
echo "❌ 服务启动失败,尝试回滚旧版本..."
rollback
exit 1
fi
}
# 7️⃣ 自动回滚
function rollback() {
LATEST_BACKUP=$(remote_cmd "ls -t $BACKUP_DIR/$TARGET.* 2>/dev/null | head -n1")
if [ -z "$LATEST_BACKUP" ]; then
echo "⚠️ 没有找到备份,无法回滚!"
return
fi
echo "♻️ 回滚到 $LATEST_BACKUP ..."
remote_cmd "cp '$LATEST_BACKUP' '$REMOTE_DIR/$TARGET' && chmod +x '$REMOTE_DIR/$TARGET'"
echo "🔄 重启服务..."
remote_cmd "supervisorctl restart '$SERVICE_NAME'"
STATUS=$(remote_cmd "supervisorctl status '$SERVICE_NAME'")
echo "$STATUS"
if echo "$STATUS" | grep -q "RUNNING"; then
echo "✅ 回滚成功,服务已恢复"
else
echo "❌ 回滚失败,请手动处理"
fi
}
# 8️⃣ 清理本地产物
function clear() {
echo "🧹 清理本地编译产物..."
rm -f "$TARGET"
}
# 🔹 主流程
build_local
ensure_remote_dir
backup_old
upload_binary
upload_consul_conf
#upload_locale_conf
upload_supervisor_conf
restart_service
clear
echo "✅ 部署完成!"

25
api/route/route.go

@ -0,0 +1,25 @@
package route
import (
"jrest"
"third-party-gateway/api/handler"
)
func InitRouter() {
root := jrest.RouteGroup("thirdParty")
//base := root.Group("base")
//{
// //上传文件
// base.POST("upload", handler.FileUpload)
//}
wechat := root.Group("wechat")
{
//获取授权二维码
wechat.GET("getPrintMiniProgramQrCode", handler.GetPrintMiniProgramQrCode)
//扫码注册登录
wechat.GET("getMiniProgramOpenId", handler.GetMiniProgramOpenId)
}
}

3
api/version.go

@ -0,0 +1,3 @@
package main
var VERSION = "0.0.1"

1
config/third-party-gateway-dev.consul.yaml

@ -0,0 +1 @@
url: http://192.168.10.111:8500

9
config/third-party-gateway-prod.conf

@ -0,0 +1,9 @@
[program:third-party-gateway-prod]
command=/usr/local/third-party-gateway-prod/third-party-gateway-prod.srv --env=prod
directory=/usr/local/third-party-gateway-prod/
autostart=true
autorestart=true
startsecs=3
stderr_logfile=/usr/local/third-party-gateway-prod/log/err.log
stdout_logfile=/usr/local/third-party-gateway-prod/log/info.log
user=root

1
config/third-party-gateway-prod.consul.yaml

@ -0,0 +1 @@
url: http://192.168.1.13:8500

75
go.mod

@ -0,0 +1,75 @@
module third-party-gateway
go 1.25.3
replace (
jconfig => ../golib/config
jconsul => ../golib/consul
jhttp => ../golib/http
jlog => ../golib/log
jobs => ../golib/obs
jredis => ../golib/redis
jrest => ../golib/restful
jversion => ../golib/version
)
require (
github.com/gin-gonic/gin v1.10.1
jconfig v0.0.0-00010101000000-000000000000
jconsul v0.0.0-00010101000000-000000000000
jhttp v0.0.0-00010101000000-000000000000
jlog v0.0.0-00010101000000-000000000000
jobs v0.0.0-00010101000000-000000000000
jredis v0.0.0-00010101000000-000000000000
jrest v0.0.0-00010101000000-000000000000
jversion v0.0.0-00010101000000-000000000000
)
require (
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/gomodule/redigo v1.8.9 // indirect
github.com/hashicorp/consul/api v1.31.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.21.12+incompatible // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

79
utils/error.go

@ -0,0 +1,79 @@
package utils
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
)
type BusinessError struct {
Code int
Msg string
}
type ErrorResult struct {
Msg string `json:"msg,omitempty"`
}
func (e *BusinessError) Error() string {
return e.Msg
}
func NewBusinessError(code int, msg string) *BusinessError {
return &BusinessError{code, msg}
}
func ErrorHandler(c *gin.Context) {
c.Next()
for _, e := range c.Errors {
switch err := e.Err.(type) {
case *BusinessError:
GinLogger(c).ERROR(err.Msg, "err", err.Error())
c.JSON(err.Code, &ErrorResult{err.Msg})
default:
GinLogger(c).ERROR("generic error", "err", err.Error())
c.JSON(http.StatusInternalServerError, "ERROR")
}
return
}
}
func ThrowAgError(c *gin.Context, err *BusinessError) {
c.AbortWithError(err.Code, err)
}
func Error(c *gin.Context, code int, msg string) {
c.AbortWithError(code, &BusinessError{code, msg})
}
func AuthError(c *gin.Context) {
Error(c, http.StatusUnauthorized, "INVALID_TOKEN")
}
func CodeError(c *gin.Context) {
Error(c, http.StatusBadRequest, "WRONG_CODE")
}
func ParamError(c *gin.Context) {
Error(c, http.StatusBadRequest, "INVALID_PARAM")
}
func PermissionError(c *gin.Context) {
Error(c, http.StatusForbidden, "NO_PERMISSION")
}
func DbError(c *gin.Context) {
Error(c, http.StatusInternalServerError, "DB_ERROR")
}
// 错误日志就近输出,后续不需要重复输出
// 无错误返回true
func ErrorCheckAndLog(err error) bool {
if err != nil {
log.Println("db:", err)
return false
}
return true
}

47
utils/gin.go

@ -0,0 +1,47 @@
package utils
import (
"net/http"
"github.com/gin-gonic/gin"
)
type R struct {
Code int `json:"code"`
Data any `json:"data"`
}
func OK(c *gin.Context) {
c.JSON(http.StatusOK, R{
Code: http.StatusOK,
})
}
func OKNoData(c *gin.Context) {
c.JSON(http.StatusNoContent, R{
Code: http.StatusOK,
})
}
func OKWithData(c *gin.Context, obj interface{}) {
c.JSON(http.StatusOK, R{
Code: http.StatusOK,
Data: obj,
})
}
func FailWithData(c *gin.Context, obj interface{}) {
c.JSON(http.StatusOK, R{
Code: http.StatusInternalServerError,
Data: obj,
})
}
func OKWithList(c *gin.Context, total int, obj any) {
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"data": gin.H{
"total": total,
"list": obj,
},
})
}

30
utils/logger.go

@ -0,0 +1,30 @@
package utils
import (
"context"
"jlog"
"github.com/gin-gonic/gin"
)
type ctxKey string
const _trace_key ctxKey = "trace_id"
func TracingHandler(c *gin.Context) {
traceId := jlog.NewTraceId()
ctx := context.WithValue(c.Request.Context(), _trace_key, traceId)
c.Request = c.Request.WithContext(ctx)
c.Next()
}
func TraceLogger(ctx context.Context) *jlog.Logger {
if traceID, ok := ctx.Value(_trace_key).(string); ok {
return jlog.NewLoggerWithTraceId(traceID)
}
return jlog.NewLoggerWithTraceId("mqttResp111222333")
}
func GinLogger(c *gin.Context) *jlog.Logger {
return TraceLogger(c.Request.Context())
}
Loading…
Cancel
Save