Clean repository development artifacts

This commit is contained in:
2026-06-02 20:56:05 -07:00
parent 3df59ef345
commit fc6f193479
19 changed files with 9 additions and 1404 deletions
@@ -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)
}
-23
View File
@@ -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)
}
-36
View File
@@ -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)
}
}
-28
View File
@@ -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)
}
-337
View File
@@ -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
-27
View File
@@ -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
-23
View File
@@ -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
-79
View File
@@ -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
+8
View File
@@ -26,6 +26,13 @@ Thumbs.db
*.crt
*.key
*.pem
*.pub
*id_rsa*
*id_ed25519*
*_bak
*.go_bak
deploy/**/keys/
deliverables/
# ==================== 日志 ====================
*.log
@@ -70,6 +77,7 @@ script/*.sh
# Codex local configuration
.codex/
.codex-tmp/
# Claude Flow runtime data
.claude-flow/data/
-66
View File
@@ -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
-20
View File
@@ -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 association ID: `eipassoc-09184aa9161b6a4d9`
- Status: `running`
- SSH recovery private key: `deploy/aws/ap-northeast-1/keys/ppanel-jp-recovery`
- SSH recovery public key: `deploy/aws/ap-northeast-1/keys/ppanel-jp-recovery.pub`
- SSH recovery key material: not stored in this repository; keep any recovery keys in an approved secret manager or other secure channel.
- RDS subnet group:
- Name: `ppanel-jp-rds-subnet-group`
- 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
-2
View File
@@ -1,5 +1,3 @@
version: '3'
services:
ppanel:
container_name: ppanel-server
-675
View File
@@ -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
}
-44
View File
@@ -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'