commit
db6910b712
16 changed files with 823 additions and 0 deletions
@ -0,0 +1,13 @@ |
|||
*.pb.go |
|||
*.log |
|||
go.sum |
|||
*.bin |
|||
*.pem |
|||
*.srv |
|||
*.signature |
|||
*.zip |
|||
*.pdf |
|||
.idea |
|||
.vscode |
|||
|
|||
|
|||
@ -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 |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
package constant |
|||
|
|||
const ( |
|||
MiniProgramAccessToken = "third-party-gateway:mini_program:access_token:%s" //二维码扫描信息,存储唯一时间戳
|
|||
) |
|||
@ -0,0 +1 @@ |
|||
package entity |
|||
@ -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) |
|||
} |
|||
@ -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) |
|||
} |
|||
@ -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 "✅ 部署完成!" |
|||
@ -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) |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
package main |
|||
|
|||
var VERSION = "0.0.1" |
|||
@ -0,0 +1 @@ |
|||
url: http://192.168.10.111:8500 |
|||
@ -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 |
|||
@ -0,0 +1 @@ |
|||
url: http://192.168.1.13:8500 |
|||
@ -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 |
|||
) |
|||
@ -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 |
|||
} |
|||
@ -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, |
|||
}, |
|||
}) |
|||
} |
|||
@ -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…
Reference in new issue