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