Clean repository development artifacts
This commit is contained in:
@@ -1,25 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
"github.com/perfect-panel/server/pkg/conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var c config.Config
|
|
||||||
conf.MustLoad("/private/tmp/ppanel-local-upload.yaml", &c)
|
|
||||||
ctx := svc.NewServiceContext(c)
|
|
||||||
const key = "cache:auth:method:device"
|
|
||||||
before, _ := ctx.Redis.Get(ctx.DB.Statement.Context, key).Result()
|
|
||||||
fmt.Printf("cache before=%q\n", before)
|
|
||||||
m, err := ctx.AuthModel.FindOneByMethod(ctx.DB.Statement.Context, "device")
|
|
||||||
fmt.Printf("model err=%v enabled_nil=%v", err, m == nil || m.Enabled == nil)
|
|
||||||
if m != nil && m.Enabled != nil { fmt.Printf(" enabled=%v", *m.Enabled) }
|
|
||||||
if m != nil { fmt.Printf(" config=%s", m.Config) }
|
|
||||||
fmt.Println()
|
|
||||||
after, _ := ctx.Redis.Get(ctx.DB.Statement.Context, key).Result()
|
|
||||||
fmt.Printf("cache after=%q\n", after)
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
initpkg "github.com/perfect-panel/server/initialize"
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
"github.com/perfect-panel/server/pkg/conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var c config.Config
|
|
||||||
conf.MustLoad("/private/tmp/ppanel-local-upload.yaml", &c)
|
|
||||||
ctx := svc.NewServiceContext(c)
|
|
||||||
method, err := ctx.AuthModel.FindOneByMethod(ctx.DB.Statement.Context, "device")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("db auth_method.enabled=%v config=%s\n", *method.Enabled, method.Config)
|
|
||||||
initpkg.Device(ctx)
|
|
||||||
fmt.Printf("ctx.Config.Device.Enable=%v SecuritySecret=%q EnableSecurity=%v\n", ctx.Config.Device.Enable, ctx.Config.Device.SecuritySecret, ctx.Config.Device.EnableSecurity)
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
"github.com/perfect-panel/server/pkg/conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
type row struct {
|
|
||||||
ID int64
|
|
||||||
Method string
|
|
||||||
Enabled int
|
|
||||||
Config string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var c config.Config
|
|
||||||
conf.MustLoad("/private/tmp/ppanel-local-upload.yaml", &c)
|
|
||||||
ctx := svc.NewServiceContext(c)
|
|
||||||
|
|
||||||
var rows []row
|
|
||||||
if err := ctx.DB.Raw("SELECT id, method, enabled, config FROM auth_method WHERE method = ?", "device").Scan(&rows).Error; err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("raw rows: %+v\n", rows)
|
|
||||||
|
|
||||||
m, err := ctx.AuthModel.FindOneByMethod(ctx.DB.Statement.Context, "device")
|
|
||||||
fmt.Printf("model err=%v\n", err)
|
|
||||||
if err == nil && m != nil && m.Enabled != nil {
|
|
||||||
fmt.Printf("model row: id=%d method=%s enabled=%v config=%s\n", m.Id, m.Method, *m.Enabled, m.Config)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("model row nil or no enabled ptr: %#v\n", m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
authmodel "github.com/perfect-panel/server/internal/model/auth"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
"github.com/perfect-panel/server/pkg/conf"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var c config.Config
|
|
||||||
conf.MustLoad("/private/tmp/ppanel-local-upload.yaml", &c)
|
|
||||||
ctx := svc.NewServiceContext(c)
|
|
||||||
|
|
||||||
var a1 authmodel.Auth
|
|
||||||
err1 := ctx.DB.Model(&authmodel.Auth{}).Where("method = ?", "device").First(&a1).Error
|
|
||||||
fmt.Printf("gorm direct err=%v enabled_nil=%v", err1, a1.Enabled == nil)
|
|
||||||
if a1.Enabled != nil { fmt.Printf(" enabled=%v", *a1.Enabled) }
|
|
||||||
fmt.Printf(" config=%s\n", a1.Config)
|
|
||||||
|
|
||||||
var a2 authmodel.Auth
|
|
||||||
err2 := ctx.DB.Table("auth_method").Where("method = ?", "device").First(&a2).Error
|
|
||||||
fmt.Printf("gorm table err=%v enabled_nil=%v", err2, a2.Enabled == nil)
|
|
||||||
if a2.Enabled != nil { fmt.Printf(" enabled=%v", *a2.Enabled) }
|
|
||||||
fmt.Printf(" config=%s\n", a2.Config)
|
|
||||||
}
|
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
name: Build docker and publish
|
|
||||||
run-name: 简化的Docker构建和部署流程
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- internal
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- internal
|
|
||||||
|
|
||||||
env:
|
|
||||||
# Docker镜像仓库
|
|
||||||
REPO: ${{ vars.REPO || 'registry.kxsw.us/vpn-server' }}
|
|
||||||
# SSH连接信息 (根据分支自动选择服务器和用户)
|
|
||||||
SSH_HOST: ${{ github.ref_name == 'main' && vars.SSH_HOST || vars.DEV_SSH_HOST }}
|
|
||||||
SSH_PORT: ${{ vars.SSH_PORT }}
|
|
||||||
SSH_USER: ${{ github.ref_name == 'main' && 'ubuntu' || 'root' }}
|
|
||||||
# SSH私钥(Gitea Secret 名称:AWS)
|
|
||||||
SSH_KEY: ${{ secrets.AWS }}
|
|
||||||
# TG通知
|
|
||||||
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
|
||||||
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
|
||||||
# Go构建变量
|
|
||||||
SERVICE: vpn
|
|
||||||
SERVICE_STYLE: vpn
|
|
||||||
VERSION: ${{ github.sha }}
|
|
||||||
BUILDTIME: ${{ github.event.head_commit.timestamp }}
|
|
||||||
GOARCH: amd64
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ario-server
|
|
||||||
container:
|
|
||||||
image: node:20
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
# 只有node支持版本号别名
|
|
||||||
node: ['20.15.1']
|
|
||||||
steps:
|
|
||||||
# 步骤1: 下载代码
|
|
||||||
- name: 📥 下载代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
# 步骤2: 设置动态环境变量
|
|
||||||
- name: ⚙️ 设置动态环境变量
|
|
||||||
run: |
|
|
||||||
if [ "${{ github.ref_name }}" = "main" ]; then
|
|
||||||
echo "DOCKER_TAG_SUFFIX=latest" >> $GITHUB_ENV
|
|
||||||
echo "CONTAINER_NAME=ppanel-server" >> $GITHUB_ENV
|
|
||||||
echo "DEPLOY_PATH=/opt/ppanel" >> $GITHUB_ENV
|
|
||||||
echo "DEPLOY_ENV_LABEL=🚀 服务已成功部署到生产环境" >> $GITHUB_ENV
|
|
||||||
echo "为 main 分支设置生产环境变量"
|
|
||||||
elif [ "${{ github.ref_name }}" = "internal" ]; then
|
|
||||||
echo "DOCKER_TAG_SUFFIX=internal" >> $GITHUB_ENV
|
|
||||||
echo "CONTAINER_NAME=ppanel-server-internal" >> $GITHUB_ENV
|
|
||||||
echo "DEPLOY_PATH=/root/bindbox" >> $GITHUB_ENV
|
|
||||||
echo "DEPLOY_ENV_LABEL=🧪 服务已成功部署到测试环境" >> $GITHUB_ENV
|
|
||||||
echo "为 internal 分支设置开发环境变量"
|
|
||||||
else
|
|
||||||
echo "DOCKER_TAG_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV
|
|
||||||
echo "CONTAINER_NAME=ppanel-server-${{ github.ref_name }}" >> $GITHUB_ENV
|
|
||||||
echo "DEPLOY_PATH=/root/vpn_server_other" >> $GITHUB_ENV
|
|
||||||
echo "DEPLOY_ENV_LABEL=🔧 服务已成功部署到其他环境" >> $GITHUB_ENV
|
|
||||||
echo "为其他分支 (${{ github.ref_name }}) 设置环境变量"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 步骤3: 安装系统工具 (curl, jq) 并升级 Docker CLI 到 1.44+
|
|
||||||
- name: 🔧 安装系统工具并升级 Docker CLI
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
echo "等待 apt/dpkg 锁释放 (unattended-upgrades)..."
|
|
||||||
end=$((SECONDS+300))
|
|
||||||
while true; do
|
|
||||||
LOCKS_BUSY=0
|
|
||||||
if pgrep -x unattended-upgrades >/dev/null 2>&1; then LOCKS_BUSY=1; fi
|
|
||||||
if command -v fuser >/dev/null 2>&1; then
|
|
||||||
if fuser /var/lib/dpkg/lock >/dev/null 2>&1 \
|
|
||||||
|| fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \
|
|
||||||
|| fuser /var/lib/apt/lists/lock >/dev/null 2>&1; then
|
|
||||||
LOCKS_BUSY=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [ "$LOCKS_BUSY" -eq 0 ]; then break; fi
|
|
||||||
if [ $SECONDS -ge $end ]; then
|
|
||||||
echo "等待 apt/dpkg 锁超时,使用 Dpkg::Lock::Timeout 继续..."
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "仍在等待锁释放..."; sleep 5
|
|
||||||
done
|
|
||||||
|
|
||||||
# 基础工具
|
|
||||||
apt-get update -y -o Dpkg::Lock::Timeout=600
|
|
||||||
apt-get install -y -o Dpkg::Lock::Timeout=600 jq curl ca-certificates gnupg lsb-release
|
|
||||||
|
|
||||||
# 移除旧版 docker.io,避免客户端过旧 (API 1.41)
|
|
||||||
if dpkg -s docker.io >/dev/null 2>&1; then
|
|
||||||
apt-get remove -y docker.io || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 安装 Docker 官方仓库的 CLI (确保 API >= 1.44)
|
|
||||||
distro_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
|
|
||||||
install_repo="deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${distro_codename} stable"
|
|
||||||
mkdir -p /etc/apt/keyrings
|
|
||||||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
||||||
echo "$install_repo" > /etc/apt/sources.list.d/docker.list
|
|
||||||
apt-get update -y -o Dpkg::Lock::Timeout=600
|
|
||||||
apt-get install -y -o Dpkg::Lock::Timeout=600 docker-ce-cli docker-buildx-plugin
|
|
||||||
|
|
||||||
# 版本检查
|
|
||||||
docker --version || true
|
|
||||||
docker version || true
|
|
||||||
echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}')
|
|
||||||
|
|
||||||
# 步骤4: 构建镜像
|
|
||||||
- name: 🏗️ 构建镜像
|
|
||||||
run: |
|
|
||||||
echo "开始构建镜像..."
|
|
||||||
echo "仓库: ${{ env.REPO }}"
|
|
||||||
echo "版本标签: ${{ env.VERSION }}"
|
|
||||||
echo "分支标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
|
||||||
|
|
||||||
BUILD_TAG_ARGS="-t ${{ env.REPO }}:${{ env.VERSION }}"
|
|
||||||
if [ "${{ github.event_name }}" = "push" ]; then
|
|
||||||
BUILD_TAG_ARGS="$BUILD_TAG_ARGS -t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}"
|
|
||||||
else
|
|
||||||
echo "PR事件仅构建版本标签,不推送镜像、不部署"
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker build -f Dockerfile \
|
|
||||||
--platform linux/amd64 \
|
|
||||||
--build-arg TARGETARCH=amd64 \
|
|
||||||
--build-arg VERSION=${{ env.VERSION }} \
|
|
||||||
--build-arg BUILDTIME=${{ env.BUILDTIME }} \
|
|
||||||
$BUILD_TAG_ARGS \
|
|
||||||
.
|
|
||||||
|
|
||||||
echo "镜像构建完成"
|
|
||||||
|
|
||||||
# 步骤5: 发布到镜像仓库
|
|
||||||
- name: 📤 发布到镜像仓库
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
run: |
|
|
||||||
echo "开始推送镜像..."
|
|
||||||
|
|
||||||
echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}"
|
|
||||||
docker push ${{ env.REPO }}:${{ env.VERSION }}
|
|
||||||
|
|
||||||
echo "推送分支标签镜像: ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}"
|
|
||||||
docker push ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}
|
|
||||||
|
|
||||||
echo "镜像推送完成"
|
|
||||||
|
|
||||||
# 步骤6: 调试 - 打印部署目标(不输出敏感信息)
|
|
||||||
- name: 🔍 调试 - 打印部署目标
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
run: |
|
|
||||||
echo "========== 部署目标调试 =========="
|
|
||||||
echo "当前分支: ${{ github.ref_name }}"
|
|
||||||
echo "SSH_HOST: ${{ env.SSH_HOST }}"
|
|
||||||
echo "SSH_PORT: ${{ env.SSH_PORT }}"
|
|
||||||
echo "SSH_USER: ${{ env.SSH_USER }}"
|
|
||||||
echo "SSH认证方式: 私钥 (AWS)"
|
|
||||||
echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}"
|
|
||||||
echo "====================================="
|
|
||||||
|
|
||||||
# 步骤7: 传输配置文件
|
|
||||||
- name: 📂 传输配置文件
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: appleboy/scp-action@v0.1.7
|
|
||||||
with:
|
|
||||||
host: ${{ env.SSH_HOST }}
|
|
||||||
username: ${{ env.SSH_USER }}
|
|
||||||
key: ${{ env.SSH_KEY }}
|
|
||||||
port: ${{ env.SSH_PORT }}
|
|
||||||
source: "docker-compose.cloud.yml"
|
|
||||||
target: "/tmp/ppanel-deploy/"
|
|
||||||
|
|
||||||
# 步骤8: 连接服务器更新、健康检查并按需回滚
|
|
||||||
- name: 🚀 连接服务器更新并启动
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
uses: appleboy/ssh-action@v1.0.3
|
|
||||||
with:
|
|
||||||
host: ${{ env.SSH_HOST }}
|
|
||||||
username: ${{ env.SSH_USER }}
|
|
||||||
key: ${{ env.SSH_KEY }}
|
|
||||||
port: ${{ env.SSH_PORT }}
|
|
||||||
timeout: 300s
|
|
||||||
command_timeout: 600s
|
|
||||||
script: |
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "连接服务器成功,开始部署..."
|
|
||||||
echo "部署目录: ${{ env.DEPLOY_PATH }}"
|
|
||||||
echo "部署标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
|
||||||
echo "登录用户: ${{ env.SSH_USER }}"
|
|
||||||
|
|
||||||
HEALTHCHECK_URL="http://127.0.0.1:8080/v1/common/heartbeat"
|
|
||||||
NEW_TAG="${{ env.DOCKER_TAG_SUFFIX }}"
|
|
||||||
ROLLBACK_TAG="rollback-${{ env.VERSION }}"
|
|
||||||
SUDO=""
|
|
||||||
if [ "${{ github.ref_name }}" = "main" ]; then
|
|
||||||
SUDO="sudo"
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker_cmd() {
|
|
||||||
if [ -n "$SUDO" ]; then
|
|
||||||
sudo docker "$@"
|
|
||||||
else
|
|
||||||
docker "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
compose_with_tag() {
|
|
||||||
tag="$1"
|
|
||||||
shift
|
|
||||||
if [ -n "$SUDO" ]; then
|
|
||||||
sudo env PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
|
||||||
else
|
|
||||||
PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
write_previous_tag() {
|
|
||||||
if [ -n "$SUDO" ]; then
|
|
||||||
printf '%s\n' "${PREVIOUS_IMAGE_TAG:-}" | sudo tee .previous-ppanel-image-tag >/dev/null
|
|
||||||
else
|
|
||||||
printf '%s\n' "${PREVIOUS_IMAGE_TAG:-}" > .previous-ppanel-image-tag
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
health_check() {
|
|
||||||
attempt=1
|
|
||||||
while [ "$attempt" -le 3 ]; do
|
|
||||||
if curl -sf "$HEALTHCHECK_URL"; then
|
|
||||||
echo
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "健康检查第 ${attempt}/3 次失败,10s 后重试..."
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
sleep 10
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "${{ github.ref_name }}" = "main" ]; then
|
|
||||||
sudo mkdir -p ${{ env.DEPLOY_PATH }}
|
|
||||||
sudo cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml
|
|
||||||
else
|
|
||||||
mkdir -p ${{ env.DEPLOY_PATH }}
|
|
||||||
cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd ${{ env.DEPLOY_PATH }}
|
|
||||||
|
|
||||||
PREVIOUS_IMAGE_TAG="$(docker_cmd inspect --format '{{.Config.Image}}' ppanel-server 2>/dev/null || true)"
|
|
||||||
PREVIOUS_IMAGE_ID="$(docker_cmd inspect --format '{{.Image}}' ppanel-server 2>/dev/null || true)"
|
|
||||||
echo "上一版本镜像tag: ${PREVIOUS_IMAGE_TAG:-未发现}"
|
|
||||||
echo "上一版本镜像ID: ${PREVIOUS_IMAGE_ID:-未发现}"
|
|
||||||
write_previous_tag
|
|
||||||
|
|
||||||
echo "📥 拉取镜像: ${{ env.REPO }}:${NEW_TAG}"
|
|
||||||
compose_with_tag "$NEW_TAG" pull ppanel-server
|
|
||||||
echo "🚀 启动服务..."
|
|
||||||
compose_with_tag "$NEW_TAG" up -d ppanel-server
|
|
||||||
|
|
||||||
echo "🩺 部署后健康检查: ${HEALTHCHECK_URL}"
|
|
||||||
if health_check; then
|
|
||||||
docker_cmd image prune -f || true
|
|
||||||
echo "✅ 部署后健康检查通过"
|
|
||||||
echo "✅ 部署命令执行完成"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "❌ 部署后健康检查连续 3 次失败,开始回滚..."
|
|
||||||
if [ -n "$PREVIOUS_IMAGE_ID" ]; then
|
|
||||||
docker_cmd tag "$PREVIOUS_IMAGE_ID" "${{ env.REPO }}:${ROLLBACK_TAG}"
|
|
||||||
echo "回滚镜像tag: ${{ env.REPO }}:${ROLLBACK_TAG}"
|
|
||||||
compose_with_tag "$ROLLBACK_TAG" up -d ppanel-server
|
|
||||||
|
|
||||||
echo "🩺 回滚后健康检查: ${HEALTHCHECK_URL}"
|
|
||||||
if health_check; then
|
|
||||||
echo "✅ 回滚后健康检查通过"
|
|
||||||
else
|
|
||||||
echo "❌ 回滚后健康检查仍失败"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "未找到上一版本镜像ID,无法自动回滚"
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker_cmd image prune -f || true
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
# 步骤9: TG通知 (成功)
|
|
||||||
- name: 📱 发送成功通知到Telegram
|
|
||||||
if: success() && github.event_name == 'push'
|
|
||||||
uses: appleboy/telegram-action@master
|
|
||||||
with:
|
|
||||||
token: ${{ env.TG_BOT_TOKEN }}
|
|
||||||
to: ${{ env.TG_CHAT_ID }}
|
|
||||||
message: |
|
|
||||||
✅ 部署成功!
|
|
||||||
|
|
||||||
📦 项目: ${{ github.repository }}
|
|
||||||
🌿 分支: ${{ github.ref_name }}
|
|
||||||
📝 提交: ${{ github.sha }}
|
|
||||||
👤 提交者: ${{ github.actor }}
|
|
||||||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
|
||||||
|
|
||||||
${{ env.DEPLOY_ENV_LABEL }}
|
|
||||||
🩺 健康检查: 通过 (http://127.0.0.1:8080/v1/common/heartbeat)
|
|
||||||
parse_mode: Markdown
|
|
||||||
|
|
||||||
# 步骤10: TG通知 (失败)
|
|
||||||
- name: 📱 发送失败通知到Telegram
|
|
||||||
if: failure() && github.event_name == 'push'
|
|
||||||
uses: appleboy/telegram-action@master
|
|
||||||
with:
|
|
||||||
token: ${{ env.TG_BOT_TOKEN }}
|
|
||||||
to: ${{ env.TG_CHAT_ID }}
|
|
||||||
message: |
|
|
||||||
❌ 部署失败!
|
|
||||||
|
|
||||||
📦 项目: ${{ github.repository }}
|
|
||||||
🌿 分支: ${{ github.ref_name }}
|
|
||||||
📝 提交: ${{ github.sha }}
|
|
||||||
👤 提交者: ${{ github.actor }}
|
|
||||||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
|
||||||
|
|
||||||
🩺 健康检查: 失败或未完成;若新版本健康检查连续 3 次失败,已自动尝试回滚并重新检查
|
|
||||||
⚠️ 请检查构建日志获取详细信息
|
|
||||||
parse_mode: Markdown
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Production Environment Configuration for GitHub Actions
|
|
||||||
# This file defines production-specific deployment settings
|
|
||||||
|
|
||||||
environment:
|
|
||||||
name: production
|
|
||||||
url: https://api.ppanel.example.com
|
|
||||||
protection_rules:
|
|
||||||
- type: wait_timer
|
|
||||||
minutes: 5
|
|
||||||
- type: reviewers
|
|
||||||
reviewers:
|
|
||||||
- "@admin-team"
|
|
||||||
- "@devops-team"
|
|
||||||
variables:
|
|
||||||
ENVIRONMENT: production
|
|
||||||
LOG_LEVEL: info
|
|
||||||
DEPLOY_TIMEOUT: 300
|
|
||||||
|
|
||||||
# Environment-specific secrets required:
|
|
||||||
# PRODUCTION_HOST - Production server hostname/IP
|
|
||||||
# PRODUCTION_USER - SSH username for production server
|
|
||||||
# PRODUCTION_SSH_KEY - SSH private key for production server
|
|
||||||
# PRODUCTION_PORT - SSH port (default: 22)
|
|
||||||
# PRODUCTION_URL - Application URL for health checks
|
|
||||||
# DATABASE_PASSWORD - Production database password
|
|
||||||
# REDIS_PASSWORD - Production Redis password
|
|
||||||
# JWT_SECRET - JWT secret key for production
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Staging Environment Configuration for GitHub Actions
|
|
||||||
# This file defines staging-specific deployment settings
|
|
||||||
|
|
||||||
environment:
|
|
||||||
name: staging
|
|
||||||
url: https://staging-api.ppanel.example.com
|
|
||||||
protection_rules:
|
|
||||||
- type: wait_timer
|
|
||||||
minutes: 2
|
|
||||||
variables:
|
|
||||||
ENVIRONMENT: staging
|
|
||||||
LOG_LEVEL: debug
|
|
||||||
DEPLOY_TIMEOUT: 180
|
|
||||||
|
|
||||||
# Environment-specific secrets required:
|
|
||||||
# STAGING_HOST - Staging server hostname/IP
|
|
||||||
# STAGING_USER - SSH username for staging server
|
|
||||||
# STAGING_SSH_KEY - SSH private key for staging server
|
|
||||||
# STAGING_PORT - SSH port (default: 22)
|
|
||||||
# STAGING_URL - Application URL for health checks
|
|
||||||
# DATABASE_PASSWORD - Staging database password
|
|
||||||
# REDIS_PASSWORD - Staging Redis password
|
|
||||||
# JWT_SECRET - JWT secret key for staging
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
name: Build Linux Binary
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main, master ]
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: 'Version to build (leave empty for auto)'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build Linux Binary
|
|
||||||
runs-on: ario-server
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: '1.23.3'
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: amd64
|
|
||||||
run: |
|
|
||||||
VERSION=${{ github.event.inputs.version }}
|
|
||||||
if [ -z "$VERSION" ]; then
|
|
||||||
VERSION=$(git describe --tags --always --dirty)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building ppanel-server $VERSION"
|
|
||||||
BUILD_TIME=$(date +"%Y-%m-%d_%H:%M:%S")
|
|
||||||
go build -ldflags="-w -s -X github.com/perfect-panel/server/pkg/constant.Version=$VERSION -X github.com/perfect-panel/server/pkg/constant.BuildTime=$BUILD_TIME" -o ppanel-server ./ppanel.go
|
|
||||||
tar -czf ppanel-server-${VERSION}-linux-amd64.tar.gz ppanel-server
|
|
||||||
sha256sum ppanel-server ppanel-server-${VERSION}-linux-amd64.tar.gz > checksum.txt
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ppanel-server-linux-amd64
|
|
||||||
path: |
|
|
||||||
ppanel-server
|
|
||||||
ppanel-server-*-linux-amd64.tar.gz
|
|
||||||
checksum.txt
|
|
||||||
|
|
||||||
- name: Create Release
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
VERSION=${GITHUB_REF#refs/tags/}
|
|
||||||
|
|
||||||
# Check if release exists
|
|
||||||
if gh release view $VERSION >/dev/null 2>&1; then
|
|
||||||
echo "Release $VERSION already exists, deleting old assets..."
|
|
||||||
# Delete existing assets if they exist
|
|
||||||
gh release delete-asset $VERSION ppanel-server-${VERSION}-linux-amd64.tar.gz --yes 2>/dev/null || true
|
|
||||||
gh release delete-asset $VERSION checksum.txt --yes 2>/dev/null || true
|
|
||||||
else
|
|
||||||
echo "Creating new release $VERSION..."
|
|
||||||
gh release create $VERSION --title "PPanel Server $VERSION" --notes "Release $VERSION"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload assets (will overwrite if --clobber is supported, otherwise will fail gracefully)
|
|
||||||
echo "Uploading assets..."
|
|
||||||
gh release upload $VERSION ppanel-server-${VERSION}-linux-amd64.tar.gz checksum.txt --clobber
|
|
||||||
@@ -26,6 +26,13 @@ Thumbs.db
|
|||||||
*.crt
|
*.crt
|
||||||
*.key
|
*.key
|
||||||
*.pem
|
*.pem
|
||||||
|
*.pub
|
||||||
|
*id_rsa*
|
||||||
|
*id_ed25519*
|
||||||
|
*_bak
|
||||||
|
*.go_bak
|
||||||
|
deploy/**/keys/
|
||||||
|
deliverables/
|
||||||
|
|
||||||
# ==================== 日志 ====================
|
# ==================== 日志 ====================
|
||||||
*.log
|
*.log
|
||||||
@@ -70,6 +77,7 @@ script/*.sh
|
|||||||
|
|
||||||
# Codex local configuration
|
# Codex local configuration
|
||||||
.codex/
|
.codex/
|
||||||
|
.codex-tmp/
|
||||||
|
|
||||||
# Claude Flow runtime data
|
# Claude Flow runtime data
|
||||||
.claude-flow/data/
|
.claude-flow/data/
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
project_name: ppanel
|
|
||||||
version: 1
|
|
||||||
release:
|
|
||||||
prerelease: auto
|
|
||||||
builds:
|
|
||||||
- # If true, skip the build.
|
|
||||||
# Useful for library projects.
|
|
||||||
# Default is false
|
|
||||||
skip: true
|
|
||||||
|
|
||||||
changelog:
|
|
||||||
# Set it to true if you wish to skip the changelog generation.
|
|
||||||
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
|
||||||
disable: false
|
|
||||||
|
|
||||||
# Changelog generation implementation to use.
|
|
||||||
#
|
|
||||||
# Valid options are:
|
|
||||||
# - `git`: uses `git log`;
|
|
||||||
# - `github`: uses the compare GitHub API, appending the author login to the changelog.
|
|
||||||
# - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.
|
|
||||||
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
|
||||||
#
|
|
||||||
# Defaults to `git`.
|
|
||||||
use: github
|
|
||||||
|
|
||||||
# Sorts the changelog by the commit's messages.
|
|
||||||
# Could either be asc, desc or empty
|
|
||||||
# Default is empty
|
|
||||||
sort: asc
|
|
||||||
|
|
||||||
# Format to use for commit formatting.
|
|
||||||
# Only available when use is one of `github`, `gitea`, or `gitlab`.
|
|
||||||
#
|
|
||||||
# Default: '{{ .SHA }}: {{ .Message }} ({{ with .AuthorUsername }}@{{ . }}{{ else }}{{ .AuthorName }} <{{ .AuthorEmail }}>{{ end }})'.
|
|
||||||
# Extra template fields: `SHA`, `Message`, `AuthorName`, `AuthorEmail`, and
|
|
||||||
# `AuthorUsername`.
|
|
||||||
format: "{{ .Message }}"
|
|
||||||
|
|
||||||
# Group commits messages by given regex and title.
|
|
||||||
# Order value defines the order of the groups.
|
|
||||||
# Proving no regex means all commits will be grouped under the default group.
|
|
||||||
# Groups are disabled when using github-native, as it already groups things by itself.
|
|
||||||
#
|
|
||||||
# Default is no groups.
|
|
||||||
groups:
|
|
||||||
- title: "✨ Features"
|
|
||||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
|
||||||
order: 0
|
|
||||||
- title: "🐛 Bug Fixes"
|
|
||||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
|
||||||
order: 1
|
|
||||||
- title: "🎫 Chores"
|
|
||||||
regexp: "^.*chore[(\\w)]*:+.*$"
|
|
||||||
order: 2
|
|
||||||
- title: "🔨 Refactor"
|
|
||||||
regexp: "^.*refactor[(\\w)]*:+.*$"
|
|
||||||
order: 3
|
|
||||||
- title: "🔧 Build"
|
|
||||||
regexp: "^.*?(ci)(\\(.+\\))??!?:.+$"
|
|
||||||
order: 4
|
|
||||||
- title: "📝 Documentation"
|
|
||||||
regexp: "^.*?docs?(\\(.+\\))??!?:.+$"
|
|
||||||
order: 5
|
|
||||||
- title: "✨ Others"
|
|
||||||
order: 999
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
EC2 SSH 连接资料
|
|
||||||
|
|
||||||
服务器名称: hifast-hk-app-01
|
|
||||||
公网 IP: 43.198.248.161
|
|
||||||
登录用户: ubuntu
|
|
||||||
|
|
||||||
私钥文件:
|
|
||||||
- hifast-hk-app-01-reset
|
|
||||||
|
|
||||||
公钥文件:
|
|
||||||
- hifast-hk-app-01-reset.pub
|
|
||||||
|
|
||||||
连接命令:
|
|
||||||
ssh -i hifast-hk-app-01-reset ubuntu@43.198.248.161
|
|
||||||
|
|
||||||
如果在 Mac / Linux 上使用,先执行:
|
|
||||||
chmod 600 hifast-hk-app-01-reset
|
|
||||||
|
|
||||||
如果要给别人使用,只需要把私钥文件 hifast-hk-app-01-reset 发给对方即可。
|
|
||||||
出于安全考虑,建议通过安全渠道传输,并在后续需要时重新轮换密钥。
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
|
||||||
QyNTUxOQAAACDb6msDqmSHLv0mWgCP4vfjQ3A552qv95uQdsH94RnoCgAAAKjy5KDa8uSg
|
|
||||||
2gAAAAtzc2gtZWQyNTUxOQAAACDb6msDqmSHLv0mWgCP4vfjQ3A552qv95uQdsH94RnoCg
|
|
||||||
AAAEDwSb0b/0S6Tw8Od5hAtIKqt1JvomqQQS44Ty3xL+FjPtvqawOqZIcu/SZaAI/i9+ND
|
|
||||||
cDnnaq/3m5B2wf3hGegKAAAAIWhpZmFzdC1oay1hcHAtMDEtcmVzZXQtMjAyNi0wNS0xMA
|
|
||||||
ECAwQ=
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINvqawOqZIcu/SZaAI/i9+NDcDnnaq/3m5B2wf3hGegK hifast-hk-app-01-reset-2026-05-10
|
|
||||||
@@ -111,8 +111,7 @@
|
|||||||
- EIP allocation ID: `eipalloc-038b32d5c0119accf`
|
- EIP allocation ID: `eipalloc-038b32d5c0119accf`
|
||||||
- EIP association ID: `eipassoc-09184aa9161b6a4d9`
|
- EIP association ID: `eipassoc-09184aa9161b6a4d9`
|
||||||
- Status: `running`
|
- Status: `running`
|
||||||
- SSH recovery private key: `deploy/aws/ap-northeast-1/keys/ppanel-jp-recovery`
|
- SSH recovery key material: not stored in this repository; keep any recovery keys in an approved secret manager or other secure channel.
|
||||||
- SSH recovery public key: `deploy/aws/ap-northeast-1/keys/ppanel-jp-recovery.pub`
|
|
||||||
- RDS subnet group:
|
- RDS subnet group:
|
||||||
- Name: `ppanel-jp-rds-subnet-group`
|
- Name: `ppanel-jp-rds-subnet-group`
|
||||||
- VPC: `vpc-0846b23b4a7d64eac`
|
- VPC: `vpc-0846b23b4a7d64eac`
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
|
||||||
QyNTUxOQAAACBsJxZDjKvaIdVjer3QMHRYw43wkVnzHurA2TQqHlr1YwAAAKBaQXT3WkF0
|
|
||||||
9wAAAAtzc2gtZWQyNTUxOQAAACBsJxZDjKvaIdVjer3QMHRYw43wkVnzHurA2TQqHlr1Yw
|
|
||||||
AAAEArWQWWwPoJp+za5JhSnrE0Pw1/TtqWRrdY5e8hULqYDGwnFkOMq9oh1WN6vdAwdFjD
|
|
||||||
jfCRWfMe6sDZNCoeWvVjAAAAF0FwcGxlQE1hY0Jvb2stUHJvLmxvY2FsAQIDBAUG
|
|
||||||
-----END OPENSSH PRIVATE KEY-----
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGwnFkOMq9oh1WN6vdAwdFjDjfCRWfMe6sDZNCoeWvVj Apple@MacBook-Pro.local
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
ppanel:
|
ppanel:
|
||||||
container_name: ppanel-server
|
container_name: ppanel-server
|
||||||
|
|||||||
@@ -1,675 +0,0 @@
|
|||||||
package orderLogic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/constant"
|
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
|
||||||
|
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/hibiken/asynq"
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
|
||||||
"github.com/perfect-panel/server/internal/logic/telegram"
|
|
||||||
"github.com/perfect-panel/server/internal/model/order"
|
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
|
||||||
"github.com/perfect-panel/server/pkg/tool"
|
|
||||||
"github.com/perfect-panel/server/pkg/uuidx"
|
|
||||||
"github.com/perfect-panel/server/queue/types"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Subscribe = 1
|
|
||||||
Renewal = 2
|
|
||||||
ResetTraffic = 3
|
|
||||||
Recharge = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
type ActivateOrderLogic struct {
|
|
||||||
svc *svc.ServiceContext
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewActivateOrderLogic(svc *svc.ServiceContext) *ActivateOrderLogic {
|
|
||||||
return &ActivateOrderLogic{
|
|
||||||
svc: svc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ActivateOrderLogic) ProcessTask(ctx context.Context, task *asynq.Task) error {
|
|
||||||
payload := types.ForthwithActivateOrderPayload{}
|
|
||||||
if err := json.Unmarshal(task.Payload(), &payload); err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Unmarshal payload failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("payload", string(task.Payload())),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Find order by order no
|
|
||||||
orderInfo, err := l.svc.OrderModel.FindOneByOrderNo(ctx, payload.OrderNo)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find order failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("order_no", payload.OrderNo),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished
|
|
||||||
if orderInfo.Status != 2 {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Order status error",
|
|
||||||
logger.Field("order_no", orderInfo.OrderNo),
|
|
||||||
logger.Field("status", orderInfo.Status),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch orderInfo.Type {
|
|
||||||
case Subscribe:
|
|
||||||
err = l.NewPurchase(ctx, orderInfo)
|
|
||||||
case Renewal:
|
|
||||||
err = l.Renewal(ctx, orderInfo)
|
|
||||||
case ResetTraffic:
|
|
||||||
err = l.ResetTraffic(ctx, orderInfo)
|
|
||||||
case Recharge:
|
|
||||||
err = l.Recharge(ctx, orderInfo)
|
|
||||||
default:
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Order type is invalid", logger.Field("type", orderInfo.Type))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Process task failed", logger.Field("error", err.Error()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// if coupon is not empty
|
|
||||||
if orderInfo.Coupon != "" {
|
|
||||||
// update coupon status
|
|
||||||
err = l.svc.CouponModel.UpdateCount(ctx, orderInfo.Coupon)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update coupon status failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("coupon", orderInfo.Coupon),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// update order status
|
|
||||||
orderInfo.Status = 5
|
|
||||||
err = l.svc.OrderModel.Update(ctx, orderInfo)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update order status failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("order_no", orderInfo.OrderNo),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPurchase New purchase
|
|
||||||
func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.Order) error {
|
|
||||||
var userInfo *user.User
|
|
||||||
var err error
|
|
||||||
if orderInfo.UserId != 0 {
|
|
||||||
// find user by user id
|
|
||||||
userInfo, err = l.svc.UserModel.FindOne(ctx, orderInfo.UserId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("user_id", orderInfo.UserId),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If User ID is 0, it means that the order is a guest order, need to create a new user
|
|
||||||
// query info with redis
|
|
||||||
cacheKey := fmt.Sprintf(constant.TempOrderCacheKey, orderInfo.OrderNo)
|
|
||||||
data, err := l.svc.Redis.Get(ctx, cacheKey).Result()
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Get temp order cache failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("cache_key", cacheKey),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var tempOrder constant.TemporaryOrderInfo
|
|
||||||
if err = json.Unmarshal([]byte(data), &tempOrder); err != nil {
|
|
||||||
logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Unmarshal temp order failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// create user
|
|
||||||
|
|
||||||
userInfo = &user.User{
|
|
||||||
Password: tool.EncodePassWord(tempOrder.Password),
|
|
||||||
AuthMethods: []user.AuthMethods{
|
|
||||||
{
|
|
||||||
AuthType: tempOrder.AuthType,
|
|
||||||
AuthIdentifier: tempOrder.Identifier,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = l.svc.UserModel.Transaction(ctx, func(tx *gorm.DB) error {
|
|
||||||
// Save user information
|
|
||||||
if err := tx.Save(userInfo).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Generate ReferCode
|
|
||||||
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
|
|
||||||
// Update ReferCode
|
|
||||||
if err := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
orderInfo.UserId = userInfo.Id
|
|
||||||
return tx.Model(&order.Order{}).Where("order_no = ?", orderInfo.OrderNo).Update("user_id", userInfo.Id).Error
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Create user failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tempOrder.InviteCode != "" {
|
|
||||||
// find referer by refer code
|
|
||||||
referer, err := l.svc.UserModel.FindOneByReferCode(ctx, tempOrder.InviteCode)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("refer_code", tempOrder.InviteCode),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
userInfo.RefererId = referer.Id
|
|
||||||
err = l.svc.UserModel.Update(ctx, userInfo)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user referer failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("user_id", userInfo.Id),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.WithContext(ctx).Info("[ActivateOrderLogic] Create guest user success", logger.Field("user_id", userInfo.Id), logger.Field("Identifier", tempOrder.Identifier), logger.Field("AuthType", tempOrder.AuthType))
|
|
||||||
}
|
|
||||||
// find subscribe by id
|
|
||||||
sub, err := l.svc.SubscribeModel.FindOne(ctx, orderInfo.SubscribeId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Find subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("subscribe_id", orderInfo.SubscribeId),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// create user subscribe
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
userSub := user.Subscribe{
|
|
||||||
Id: 0,
|
|
||||||
UserId: orderInfo.UserId,
|
|
||||||
OrderId: orderInfo.Id,
|
|
||||||
SubscribeId: orderInfo.SubscribeId,
|
|
||||||
StartTime: now,
|
|
||||||
ExpireTime: tool.AddTime(sub.UnitTime, orderInfo.Quantity, now),
|
|
||||||
Traffic: sub.Traffic,
|
|
||||||
Download: 0,
|
|
||||||
Upload: 0,
|
|
||||||
Token: uuidx.SubscribeToken(orderInfo.OrderNo),
|
|
||||||
UUID: uuid.New().String(),
|
|
||||||
Status: 1,
|
|
||||||
}
|
|
||||||
err = l.svc.UserModel.InsertSubscribe(ctx, &userSub)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert user subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// handler commission
|
|
||||||
if userInfo.RefererId != 0 &&
|
|
||||||
l.svc.Config.Invite.ReferralPercentage != 0 &&
|
|
||||||
(!l.svc.Config.Invite.OnlyFirstPurchase || orderInfo.IsNew) {
|
|
||||||
referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("referer_id", userInfo.RefererId),
|
|
||||||
)
|
|
||||||
goto updateCache
|
|
||||||
}
|
|
||||||
// calculate commission
|
|
||||||
amount := float64(orderInfo.Price) * (float64(l.svc.Config.Invite.ReferralPercentage) / 100)
|
|
||||||
referer.Commission += int64(amount)
|
|
||||||
err = l.svc.UserModel.Update(ctx, referer)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update referer commission failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
goto updateCache
|
|
||||||
}
|
|
||||||
// create commission log
|
|
||||||
commissionLog := user.CommissionLog{
|
|
||||||
UserId: referer.Id,
|
|
||||||
OrderNo: orderInfo.OrderNo,
|
|
||||||
Amount: int64(amount),
|
|
||||||
}
|
|
||||||
err = l.svc.UserModel.InsertCommissionLog(ctx, &commissionLog)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert commission log failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
err = l.svc.UserModel.UpdateUserCache(ctx, referer)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Update referer cache", logger.Field("error", err.Error()), logger.Field("user_id", referer.Id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateCache:
|
|
||||||
for _, id := range tool.StringToInt64Slice(sub.Server) {
|
|
||||||
cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, id)
|
|
||||||
err = l.svc.Redis.Del(ctx, cacheKey).Err()
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Del server user list cache failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("cache_key", cacheKey),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data, err := l.svc.ServerModel.FindServerListByGroupIds(ctx, tool.StringToInt64Slice(sub.ServerGroup))
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find server list failed", logger.Field("error", err.Error()))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, item := range data {
|
|
||||||
cacheKey := fmt.Sprintf("%s%d", config.ServerUserListCacheKey, item.Id)
|
|
||||||
err = l.svc.Redis.Del(ctx, cacheKey).Err()
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Del server user list cache failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("cache_key", cacheKey),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userTelegramChatId, ok := findTelegram(userInfo)
|
|
||||||
|
|
||||||
// sendMessage To Telegram
|
|
||||||
if ok {
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.PurchaseNotify, map[string]string{
|
|
||||||
"OrderNo": orderInfo.OrderNo,
|
|
||||||
"SubscribeName": sub.Name,
|
|
||||||
"OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100),
|
|
||||||
"ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendUserNotifyWithTelegram(userTelegramChatId, text)
|
|
||||||
}
|
|
||||||
// send message to admin
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{
|
|
||||||
"OrderNo": orderInfo.OrderNo,
|
|
||||||
"TradeNo": orderInfo.TradeNo,
|
|
||||||
"SubscribeName": sub.Name,
|
|
||||||
//"UserEmail": userInfo.Email,
|
|
||||||
"OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100),
|
|
||||||
"OrderStatus": "已支付",
|
|
||||||
"OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
"PaymentMethod": orderInfo.Method,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendAdminNotifyWithTelegram(ctx, text)
|
|
||||||
logger.WithContext(ctx).Info("[ActivateOrderLogic] Insert user subscribe success")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renewal Renewal
|
|
||||||
func (l *ActivateOrderLogic) Renewal(ctx context.Context, orderInfo *order.Order) error {
|
|
||||||
// find user by user id
|
|
||||||
userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("user_id", orderInfo.UserId),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// find user subscribe by subscribe token
|
|
||||||
userSub, err := l.svc.UserModel.FindOneSubscribeByToken(ctx, orderInfo.SubscribeToken)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("order_id", orderInfo.Id),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// find subscribe by id
|
|
||||||
sub, err := l.svc.SubscribeModel.FindOne(ctx, orderInfo.SubscribeId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("subscribe_id", orderInfo.SubscribeId),
|
|
||||||
logger.Field("order_id", orderInfo.Id),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
if userSub.ExpireTime.Before(now) {
|
|
||||||
userSub.ExpireTime = now
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether traffic reset on renewal is enabled
|
|
||||||
if sub.RenewalReset != nil && *sub.RenewalReset {
|
|
||||||
userSub.Download = 0
|
|
||||||
userSub.Upload = 0
|
|
||||||
}
|
|
||||||
if userSub.FinishedAt != nil {
|
|
||||||
userSub.FinishedAt = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
userSub.ExpireTime = tool.AddTime(sub.UnitTime, orderInfo.Quantity, userSub.ExpireTime)
|
|
||||||
userSub.Status = 1
|
|
||||||
// update user subscribe
|
|
||||||
err = l.svc.UserModel.UpdateSubscribe(ctx, userSub)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// handler commission
|
|
||||||
if userInfo.RefererId != 0 &&
|
|
||||||
l.svc.Config.Invite.ReferralPercentage != 0 &&
|
|
||||||
!l.svc.Config.Invite.OnlyFirstPurchase {
|
|
||||||
referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find referer failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("referer_id", userInfo.RefererId),
|
|
||||||
)
|
|
||||||
goto sendMessage
|
|
||||||
}
|
|
||||||
// calculate commission
|
|
||||||
amount := float64(orderInfo.Price) * (float64(l.svc.Config.Invite.ReferralPercentage) / 100)
|
|
||||||
referer.Commission += int64(amount)
|
|
||||||
err = l.svc.UserModel.Update(ctx, referer)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update referer commission failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
goto sendMessage
|
|
||||||
}
|
|
||||||
// create commission log
|
|
||||||
commissionLog := user.CommissionLog{
|
|
||||||
UserId: referer.Id,
|
|
||||||
OrderNo: orderInfo.OrderNo,
|
|
||||||
Amount: int64(amount),
|
|
||||||
}
|
|
||||||
err = l.svc.UserModel.InsertCommissionLog(ctx, &commissionLog)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert commission log failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
err = l.svc.UserModel.UpdateUserCache(ctx, referer)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Errorw("[ActivateOrderLogic] Update referer cache", logger.Field("error", err.Error()), logger.Field("user_id", referer.Id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage:
|
|
||||||
userTelegramChatId, ok := findTelegram(userInfo)
|
|
||||||
// SendMessage To Telegram
|
|
||||||
if ok {
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.RenewalNotify, map[string]string{
|
|
||||||
"OrderNo": orderInfo.OrderNo,
|
|
||||||
"SubscribeName": sub.Name,
|
|
||||||
"OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100),
|
|
||||||
"ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendUserNotifyWithTelegram(userTelegramChatId, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// send message to admin
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{
|
|
||||||
"OrderNo": orderInfo.OrderNo,
|
|
||||||
"TradeNo": orderInfo.TradeNo,
|
|
||||||
"SubscribeName": sub.Name,
|
|
||||||
//"UserEmail": userInfo.Email,
|
|
||||||
"OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100),
|
|
||||||
"OrderStatus": "已支付",
|
|
||||||
"OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
"PaymentMethod": orderInfo.Method,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendAdminNotifyWithTelegram(ctx, text)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetTraffic Reset traffic
|
|
||||||
func (l *ActivateOrderLogic) ResetTraffic(ctx context.Context, orderInfo *order.Order) error {
|
|
||||||
// find user by user id
|
|
||||||
userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("user_id", orderInfo.UserId),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Generate a Subscribe Token through orderNo
|
|
||||||
// find user subscribe by subscribe token
|
|
||||||
userSub, err := l.svc.UserModel.FindOneSubscribeByToken(ctx, orderInfo.SubscribeToken)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("order_id", orderInfo.Id),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userSub.Download = 0
|
|
||||||
userSub.Upload = 0
|
|
||||||
userSub.Status = 1
|
|
||||||
// update user subscribe
|
|
||||||
err = l.svc.UserModel.UpdateSubscribe(ctx, userSub)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sub, err := l.svc.SubscribeModel.FindOne(ctx, userSub.SubscribeId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find subscribe failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("subscribe_id", userSub.SubscribeId),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userTelegramChatId, ok := findTelegram(userInfo)
|
|
||||||
// SendMessage To Telegram
|
|
||||||
if ok {
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.ResetTrafficNotify, map[string]string{
|
|
||||||
"OrderNo": orderInfo.OrderNo,
|
|
||||||
"SubscribeName": sub.Name,
|
|
||||||
"ResetTime": time.Now().Format("2006-01-02 15:04:05"),
|
|
||||||
"ExpireTime": userSub.ExpireTime.Format("2006-01-02 15:04:05"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendUserNotifyWithTelegram(userTelegramChatId, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// send message to admin
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{
|
|
||||||
"OrderNo": orderInfo.OrderNo,
|
|
||||||
"TradeNo": orderInfo.TradeNo,
|
|
||||||
"SubscribeName": "流量重置",
|
|
||||||
"OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100),
|
|
||||||
"OrderStatus": "已支付",
|
|
||||||
"OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
"PaymentMethod": orderInfo.Method,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendAdminNotifyWithTelegram(ctx, text)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recharge Recharge to user
|
|
||||||
func (l *ActivateOrderLogic) Recharge(ctx context.Context, orderInfo *order.Order) error {
|
|
||||||
// find user by user id
|
|
||||||
userInfo, err := l.svc.UserModel.FindOne(ctx, orderInfo.UserId)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Find user failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("user_id", orderInfo.UserId),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userInfo.Balance += orderInfo.Price
|
|
||||||
// update user
|
|
||||||
err = l.svc.DB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
err = l.svc.UserModel.Update(ctx, userInfo, tx)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Update user failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Create Balance Log
|
|
||||||
balanceLog := user.BalanceLog{
|
|
||||||
UserId: orderInfo.UserId,
|
|
||||||
Amount: orderInfo.Price,
|
|
||||||
Type: 1,
|
|
||||||
OrderId: orderInfo.Id,
|
|
||||||
Balance: userInfo.Balance,
|
|
||||||
}
|
|
||||||
err = l.svc.UserModel.InsertBalanceLog(ctx, &balanceLog, tx)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Insert balance log failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Database transaction failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
userTelegramChatId, ok := findTelegram(userInfo)
|
|
||||||
// SendMessage To Telegram
|
|
||||||
if ok {
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.RechargeNotify, map[string]string{
|
|
||||||
"OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100),
|
|
||||||
"PaymentMethod": orderInfo.Method,
|
|
||||||
"Time": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
"Balance": fmt.Sprintf("%.2f", float64(userInfo.Balance)/100),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendUserNotifyWithTelegram(userTelegramChatId, text)
|
|
||||||
}
|
|
||||||
// send message to admin
|
|
||||||
text, err := tool.RenderTemplateToString(telegram.AdminOrderNotify, map[string]string{
|
|
||||||
"OrderNo": orderInfo.OrderNo,
|
|
||||||
"TradeNo": orderInfo.TradeNo,
|
|
||||||
"OrderAmount": fmt.Sprintf("%.2f", float64(orderInfo.Price)/100),
|
|
||||||
"SubscribeName": "余额充值",
|
|
||||||
"OrderStatus": "已支付",
|
|
||||||
"OrderTime": orderInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
"PaymentMethod": orderInfo.Method,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Render AdminOrderNotify template failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
l.sendAdminNotifyWithTelegram(ctx, text)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendUserNotifyWithTelegram send message to user
|
|
||||||
func (l *ActivateOrderLogic) sendUserNotifyWithTelegram(chatId int64, text string) {
|
|
||||||
msg := tgbotapi.NewMessage(chatId, text)
|
|
||||||
msg.ParseMode = "markdown"
|
|
||||||
_, err := l.svc.TelegramBot.Send(msg)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("[ActivateOrderLogic] Send telegram user message failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendAdminNotifyWithTelegram send message to admin
|
|
||||||
func (l *ActivateOrderLogic) sendAdminNotifyWithTelegram(ctx context.Context, text string) {
|
|
||||||
admins, err := l.svc.UserModel.QueryAdminUsers(ctx)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Query admin users failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, admin := range admins {
|
|
||||||
telegramId, ok := findTelegram(admin)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
msg := tgbotapi.NewMessage(telegramId, text)
|
|
||||||
msg.ParseMode = "markdown"
|
|
||||||
_, err := l.svc.TelegramBot.Send(msg)
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[ActivateOrderLogic] Send telegram admin message failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// findTelegram find user telegram id
|
|
||||||
func findTelegram(u *user.User) (int64, bool) {
|
|
||||||
for _, item := range u.AuthMethods {
|
|
||||||
if item.AuthType == "telegram" {
|
|
||||||
// string to int64
|
|
||||||
parseInt, err := strconv.ParseInt(item.AuthIdentifier, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return parseInt, true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
导出数据库:
|
|
||||||
mysqldump --socket=/var/run/mysqld/mysqld.sock -uroot -p --single-transaction --routines --triggers --events --set-gtid-purged=OFF --source-data=2 --no-tablespaces hifast > /root/data/hifast-final-0515.sql
|
|
||||||
|
|
||||||
jpcV41ppanel
|
|
||||||
|
|
||||||
gzip -1 /root/hifast-final-0515.sql
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
导出redis:
|
|
||||||
redis-cli -a '0BVz9XOHf7KUfEuoFJRK-dURdKUGFiZ8QeaHpysHnKeKhLskZb55HPK121lFsKtr' BGSAVE
|
|
||||||
sleep 5
|
|
||||||
cp /var/lib/redis/dump.rdb /root/data/redis-backup-0515.rdb
|
|
||||||
|
|
||||||
|
|
||||||
导入数据库:
|
|
||||||
gunzip -c /root/hifast-final-0515.sql.gz | docker exec -i ppanel-mysql mysql -uroot -p'jpcV41ppanel' hifast
|
|
||||||
|
|
||||||
|
|
||||||
导入redis:
|
|
||||||
docker stop ppanel-redis
|
|
||||||
docker cp /root/data/redis-backup-0515.rdb ppanel-redis:/data/dump.rdb
|
|
||||||
docker start ppanel-redis
|
|
||||||
docker exec ppanel-redis redis-cli -a 'hifast67yj' DBSIZE
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
docker stop ppanel-redis || true
|
|
||||||
docker rm -f ppanel-redis
|
|
||||||
|
|
||||||
rm -f /root/data/dump.rdb
|
|
||||||
rm -rf /root/data/appendonlydir
|
|
||||||
rm -f /root/data/appendonly.aof*
|
|
||||||
mkdir -p /root/data
|
|
||||||
|
|
||||||
docker pull redis:8.6.3
|
|
||||||
docker run -d \
|
|
||||||
--name ppanel-redis \
|
|
||||||
--restart always \
|
|
||||||
-p 6379:6379 \
|
|
||||||
redis:8.6.3 \
|
|
||||||
redis-server --requirepass 'hifast67yj'
|
|
||||||
Reference in New Issue
Block a user