refactor(wish-tree): 用 WebGL 重写许愿树
Publish Mini Program Dev Version / publish (push) Successful in 4m10s
Publish Mini Program Dev Version / publish (push) Successful in 4m10s
- 移除 Canvas 2D 绘制,改用 WebGL - 手写着色器实现光照和发光效果 - 树干使用圆柱体变形,带扭曲效果 - 树冠使用多层变形球体,带噪声 - 相机自动旋转,可触摸控制 - 保留陀螺仪和麦克风交互 - 简化场景,只保留一棵祈福树 - 深色主题 UI,金色点缀
This commit is contained in:
@@ -0,0 +1,397 @@
|
|||||||
|
# 1Panel 自动部署配置指南
|
||||||
|
|
||||||
|
本文档说明如何配置 1Panel 实现后端服务的全自动部署。
|
||||||
|
|
||||||
|
## 架构说明
|
||||||
|
|
||||||
|
```
|
||||||
|
代码 Push → Gitea Actions 构建 → 保存部署包 → 自动部署到 1Panel 容器 → 重启服务 → 发送通知
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前提条件
|
||||||
|
|
||||||
|
1. ✅ 1Panel 已安装在 doc79 服务器
|
||||||
|
2. ✅ Gitea Actions runner 已配置(lunar-ci)
|
||||||
|
3. ✅ Docker 已安装并可被 runner 访问
|
||||||
|
|
||||||
|
## 配置步骤
|
||||||
|
|
||||||
|
### 1. 在 1Panel 中创建容器
|
||||||
|
|
||||||
|
#### 方法 1:通过 1Panel 网页界面
|
||||||
|
|
||||||
|
1. 登录 1Panel 管理界面
|
||||||
|
2. 进入 **容器** → **创建容器**
|
||||||
|
3. 配置如下:
|
||||||
|
- **名称**: `lunar-server`
|
||||||
|
- **镜像**: `golang:1.22-bookworm`
|
||||||
|
- **端口映射**: `8080:8080`(根据你的应用端口调整)
|
||||||
|
- **挂载卷**:
|
||||||
|
- 主机路径: `/opt/lunar/production/current`
|
||||||
|
- 容器路径: `/app`
|
||||||
|
- **重启策略**: `unless-stopped`
|
||||||
|
- **启动命令**: `/app/bin/server`
|
||||||
|
- **环境变量**: 根据需要添加(如数据库连接等)
|
||||||
|
|
||||||
|
4. 点击 **创建** 启动容器
|
||||||
|
|
||||||
|
#### 方法 2:通过命令行
|
||||||
|
|
||||||
|
SSH 到 doc79 服务器,执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建部署目录
|
||||||
|
sudo mkdir -p /opt/lunar/production/current
|
||||||
|
sudo mkdir -p /opt/lunar/backups
|
||||||
|
|
||||||
|
# 创建容器
|
||||||
|
docker run -d \
|
||||||
|
--name lunar-server \
|
||||||
|
-v /opt/lunar/production/current:/app \
|
||||||
|
-p 8080:8080 \
|
||||||
|
--restart unless-stopped \
|
||||||
|
golang:1.22-bookworm \
|
||||||
|
/app/bin/server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置 Gitea Actions Variables
|
||||||
|
|
||||||
|
由于 Gitea Actions API 不可用,需要直接写入 MySQL 数据库。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH 到 Gitea 服务器
|
||||||
|
ssh doc79
|
||||||
|
|
||||||
|
# 连接 MySQL
|
||||||
|
mysql -u gitea -p gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
执行以下 SQL:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 容器名称(可选,默认为 lunar-server)
|
||||||
|
INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix)
|
||||||
|
VALUES (0, 0, 'CONTAINER_NAME', 'lunar-server', UNIX_TIMESTAMP(), UNIX_TIMESTAMP())
|
||||||
|
ON DUPLICATE KEY UPDATE data = 'lunar-server', updated_unix = UNIX_TIMESTAMP();
|
||||||
|
|
||||||
|
-- 部署目录(可选,默认为 /opt/lunar/production)
|
||||||
|
INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix)
|
||||||
|
VALUES (0, 0, 'DEPLOY_DIR', '/opt/lunar/production', UNIX_TIMESTAMP(), UNIX_TIMESTAMP())
|
||||||
|
ON DUPLICATE KEY UPDATE data = '/opt/lunar/production', updated_unix = UNIX_TIMESTAMP();
|
||||||
|
|
||||||
|
-- 备份目录(可选,默认为 /opt/lunar/backups)
|
||||||
|
INSERT INTO action_variable (owner_id, repo_id, name, data, created_unix, updated_unix)
|
||||||
|
VALUES (0, 0, 'BACKUP_DIR', '/opt/lunar/backups', UNIX_TIMESTAMP(), UNIX_TIMESTAMP())
|
||||||
|
ON DUPLICATE KEY UPDATE data = '/opt/lunar/backups', updated_unix = UNIX_TIMESTAMP();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置 Runner 权限
|
||||||
|
|
||||||
|
确保 Gitea Actions runner 有权限执行 Docker 命令。
|
||||||
|
|
||||||
|
#### 检查 runner 配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH 到 doc79
|
||||||
|
ssh doc79
|
||||||
|
|
||||||
|
# 查看 runner 配置
|
||||||
|
sudo cat /etc/act_runner-lunar/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
确保配置中包含 Docker socket 挂载:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
container:
|
||||||
|
options: -v /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有,需要修改配置并重启 runner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 编辑配置
|
||||||
|
sudo nano /etc/act_runner-lunar/config.yaml
|
||||||
|
|
||||||
|
# 添加或修改 container 部分
|
||||||
|
container:
|
||||||
|
options: -v /var/run/docker.sock:/var/run/docker.sock -v /opt/lunar:/opt/lunar
|
||||||
|
|
||||||
|
# 重启 runner
|
||||||
|
sudo systemctl restart act_runner-lunar.service
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 测试自动部署
|
||||||
|
|
||||||
|
配置完成后,推送代码到 main 分支触发自动部署:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 修改后端代码
|
||||||
|
cd /Users/gouki/server/wwwroot/own/lunar
|
||||||
|
echo "// test auto deploy" >> server/cmd/main.go
|
||||||
|
|
||||||
|
# 提交并推送
|
||||||
|
git add server/
|
||||||
|
git commit -m "test: 测试自动部署"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 验证部署
|
||||||
|
|
||||||
|
#### 查看 Actions 日志
|
||||||
|
|
||||||
|
1. 访问 Gitea 网页界面
|
||||||
|
2. 进入仓库 → Actions
|
||||||
|
3. 查看最新的工作流运行日志
|
||||||
|
4. 确认 "Deploy to production" 步骤成功
|
||||||
|
|
||||||
|
#### 查看容器状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH 到 doc79
|
||||||
|
ssh doc79
|
||||||
|
|
||||||
|
# 查看容器状态
|
||||||
|
docker ps | grep lunar-server
|
||||||
|
|
||||||
|
# 查看容器日志
|
||||||
|
docker logs -f lunar-server
|
||||||
|
|
||||||
|
# 查看部署文件
|
||||||
|
ls -la /opt/lunar/production/current/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查看通知
|
||||||
|
|
||||||
|
检查 Telegram 和 Discord 是否收到部署成功通知。
|
||||||
|
|
||||||
|
## 部署流程详解
|
||||||
|
|
||||||
|
### 1. 构建阶段(在 runner 容器中)
|
||||||
|
|
||||||
|
- 拉取代码
|
||||||
|
- 安装依赖
|
||||||
|
- 运行测试
|
||||||
|
- 构建二进制文件
|
||||||
|
- 构建前端(如果存在)
|
||||||
|
- 创建部署包 `deploy.tar.gz`
|
||||||
|
|
||||||
|
### 2. 保存阶段(在 runner 主机上)
|
||||||
|
|
||||||
|
- 将部署包保存到 `/opt/lunar/ci-artifacts/`
|
||||||
|
- 保留历史版本和 latest 版本
|
||||||
|
|
||||||
|
### 3. 部署阶段(在 runner 主机上)
|
||||||
|
|
||||||
|
执行 `deploy.sh` 脚本:
|
||||||
|
|
||||||
|
1. **备份当前版本**
|
||||||
|
- 将 `/opt/lunar/production/current` 打包到 `/opt/lunar/backups/backup_TIMESTAMP.tar.gz`
|
||||||
|
|
||||||
|
2. **解压新版本**
|
||||||
|
- 解压 `deploy.tar.gz` 到临时目录
|
||||||
|
- 移动到 `/opt/lunar/production/current`
|
||||||
|
|
||||||
|
3. **设置权限**
|
||||||
|
- 确保 `bin/server` 可执行
|
||||||
|
|
||||||
|
4. **重启容器**
|
||||||
|
- 执行 `docker restart lunar-server`
|
||||||
|
- 等待 3 秒
|
||||||
|
- 检查容器状态
|
||||||
|
|
||||||
|
5. **清理旧备份**
|
||||||
|
- 保留最近 5 个备份
|
||||||
|
- 删除更早的备份
|
||||||
|
|
||||||
|
### 4. 通知阶段
|
||||||
|
|
||||||
|
- 部署成功:发送 ✅ 通知到 Telegram 和 Discord
|
||||||
|
- 部署失败:发送 ❌ 通知到 Telegram 和 Discord
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 部署失败:容器不存在
|
||||||
|
|
||||||
|
**错误信息**: `Docker 容器不存在: lunar-server`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
1. 检查容器是否已创建:`docker ps -a | grep lunar-server`
|
||||||
|
2. 如果没有,按照上述步骤创建容器
|
||||||
|
3. 如果容器名不同,修改 Gitea Actions Variable `CONTAINER_NAME`
|
||||||
|
|
||||||
|
### 部署失败:权限不足
|
||||||
|
|
||||||
|
**错误信息**: `permission denied while trying to connect to the Docker daemon socket`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
1. 检查 runner 配置是否挂载了 Docker socket
|
||||||
|
2. 重启 runner 服务:`sudo systemctl restart act_runner-lunar.service`
|
||||||
|
|
||||||
|
### 容器重启后立即退出
|
||||||
|
|
||||||
|
**可能原因**:
|
||||||
|
1. 二进制文件损坏或不完整
|
||||||
|
2. 端口被占用
|
||||||
|
3. 环境变量缺失
|
||||||
|
4. 数据库连接失败
|
||||||
|
|
||||||
|
**排查步骤**:
|
||||||
|
```bash
|
||||||
|
# 查看容器日志
|
||||||
|
docker logs lunar-server
|
||||||
|
|
||||||
|
# 检查二进制文件
|
||||||
|
ls -lh /opt/lunar/production/current/bin/server
|
||||||
|
file /opt/lunar/production/current/bin/server
|
||||||
|
|
||||||
|
# 手动运行测试
|
||||||
|
docker run --rm -it \
|
||||||
|
-v /opt/lunar/production/current:/app \
|
||||||
|
golang:1.22-bookworm \
|
||||||
|
/app/bin/server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 备份占用空间过大
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
```bash
|
||||||
|
# 手动清理旧备份
|
||||||
|
ls -lh /opt/lunar/backups/
|
||||||
|
rm /opt/lunar/backups/backup_20240101_*.tar.gz
|
||||||
|
|
||||||
|
# 或修改 deploy.sh 中的保留数量
|
||||||
|
# 将 "tail -n +6" 改为 "tail -n +3" 只保留 2 个备份
|
||||||
|
```
|
||||||
|
|
||||||
|
## 回滚操作
|
||||||
|
|
||||||
|
如果部署后发现问题,可以快速回滚到之前的版本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH 到 doc79
|
||||||
|
ssh doc79
|
||||||
|
|
||||||
|
# 查看备份列表
|
||||||
|
ls -lh /opt/lunar/backups/
|
||||||
|
|
||||||
|
# 选择要恢复的备份(例如 backup_20260807_120000.tar.gz)
|
||||||
|
BACKUP_FILE="/opt/lunar/backups/backup_20260807_120000.tar.gz"
|
||||||
|
|
||||||
|
# 停止容器
|
||||||
|
docker stop lunar-server
|
||||||
|
|
||||||
|
# 恢复备份
|
||||||
|
rm -rf /opt/lunar/production/current
|
||||||
|
tar -xzf "$BACKUP_FILE" -C /opt/lunar/production/
|
||||||
|
|
||||||
|
# 重启容器
|
||||||
|
docker start lunar-server
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker logs -f lunar-server
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级配置
|
||||||
|
|
||||||
|
### 自定义容器环境变量
|
||||||
|
|
||||||
|
如果你的应用需要环境变量(如数据库连接),可以在 1Panel 中配置:
|
||||||
|
|
||||||
|
1. 编辑容器
|
||||||
|
2. 添加环境变量:
|
||||||
|
- `DB_HOST=localhost`
|
||||||
|
- `DB_PORT=3306`
|
||||||
|
- `DB_USER=lunar`
|
||||||
|
- `DB_PASS=your_password`
|
||||||
|
- `DB_NAME=lunar`
|
||||||
|
|
||||||
|
或者在 `docker run` 命令中添加:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name lunar-server \
|
||||||
|
-v /opt/lunar/production/current:/app \
|
||||||
|
-p 8080:8080 \
|
||||||
|
-e DB_HOST=localhost \
|
||||||
|
-e DB_PORT=3306 \
|
||||||
|
-e DB_USER=lunar \
|
||||||
|
-e DB_PASS=your_password \
|
||||||
|
-e DB_NAME=lunar \
|
||||||
|
--restart unless-stopped \
|
||||||
|
golang:1.22-bookworm \
|
||||||
|
/app/bin/server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 Docker Compose
|
||||||
|
|
||||||
|
如果你更喜欢使用 Docker Compose,可以创建 `/opt/lunar/docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
lunar-server:
|
||||||
|
image: golang:1.22-bookworm
|
||||||
|
container_name: lunar-server
|
||||||
|
volumes:
|
||||||
|
- /opt/lunar/production/current:/app
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
- DB_HOST=localhost
|
||||||
|
- DB_PORT=3306
|
||||||
|
- DB_USER=lunar
|
||||||
|
- DB_PASS=your_password
|
||||||
|
- DB_NAME=lunar
|
||||||
|
restart: unless-stopped
|
||||||
|
command: /app/bin/server
|
||||||
|
```
|
||||||
|
|
||||||
|
然后修改 `deploy.sh` 中的重启命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 将
|
||||||
|
docker restart "$CONTAINER_NAME"
|
||||||
|
|
||||||
|
# 改为
|
||||||
|
cd /opt/lunar && docker-compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### 健康检查
|
||||||
|
|
||||||
|
可以在 `deploy.sh` 中添加健康检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在 "等待容器启动" 后添加
|
||||||
|
log_info "执行健康检查..."
|
||||||
|
for i in {1..10}; do
|
||||||
|
if curl -f http://localhost:8080/health; then
|
||||||
|
log_info "✅ 健康检查通过"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ $i -eq 10 ]; then
|
||||||
|
log_error "❌ 健康检查失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安全建议
|
||||||
|
|
||||||
|
1. **限制 Docker socket 访问**:只在必要的 runner 上挂载 Docker socket
|
||||||
|
2. **使用非 root 用户**:在容器中使用非 root 用户运行应用
|
||||||
|
3. **定期清理备份**:避免备份文件占用过多磁盘空间
|
||||||
|
4. **监控部署日志**:定期检查 Actions 日志和容器日志
|
||||||
|
5. **备份数据库**:部署前自动备份数据库(可选)
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- 部署脚本:`.gitea/scripts/deploy.sh`
|
||||||
|
- 通知脚本:`.gitea/scripts/notify.sh`
|
||||||
|
- 工作流配置:`.gitea/workflows/server-deploy.yml`
|
||||||
|
- Runner 配置:`/etc/act_runner-lunar/config.yaml`(在 doc79 上)
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
- 2026-08-07: 初始版本,支持 1Panel Docker 容器自动部署
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 自动部署脚本 - 部署到 1Panel Docker 容器
|
||||||
|
# 使用方法: ./deploy.sh <deploy_package_path>
|
||||||
|
# deploy_package_path: 部署包路径(如 /ci-artifacts/server-deploy-latest.tar.gz)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DEPLOY_PACKAGE="$1"
|
||||||
|
CONTAINER_NAME="${CONTAINER_NAME:-lunar-server}"
|
||||||
|
DEPLOY_DIR="${DEPLOY_DIR:-/opt/lunar/production}"
|
||||||
|
BACKUP_DIR="${BACKUP_DIR:-/opt/lunar/backups}"
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查部署包是否存在
|
||||||
|
if [ ! -f "$DEPLOY_PACKAGE" ]; then
|
||||||
|
log_error "部署包不存在: $DEPLOY_PACKAGE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "开始自动部署..."
|
||||||
|
log_info "部署包: $DEPLOY_PACKAGE"
|
||||||
|
log_info "目标容器: $CONTAINER_NAME"
|
||||||
|
log_info "部署目录: $DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 创建必要的目录
|
||||||
|
mkdir -p "$DEPLOY_DIR"
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
# 备份当前版本
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
BACKUP_FILE="$BACKUP_DIR/backup_${TIMESTAMP}.tar.gz"
|
||||||
|
|
||||||
|
if [ -d "$DEPLOY_DIR/current" ]; then
|
||||||
|
log_info "备份当前版本到: $BACKUP_FILE"
|
||||||
|
tar -czf "$BACKUP_FILE" -C "$DEPLOY_DIR" current/ || log_warn "备份失败,继续部署"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 解压新版本
|
||||||
|
log_info "解压部署包..."
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
tar -xzf "$DEPLOY_PACKAGE" -C "$TEMP_DIR"
|
||||||
|
|
||||||
|
# 检查解压后的目录结构
|
||||||
|
if [ ! -d "$TEMP_DIR/deploy" ]; then
|
||||||
|
log_error "部署包结构错误:缺少 deploy 目录"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 部署新版本
|
||||||
|
log_info "部署新版本..."
|
||||||
|
rm -rf "$DEPLOY_DIR/current"
|
||||||
|
mv "$TEMP_DIR/deploy" "$DEPLOY_DIR/current"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
|
||||||
|
# 设置权限
|
||||||
|
log_info "设置文件权限..."
|
||||||
|
chmod +x "$DEPLOY_DIR/current/bin/server" || log_warn "设置可执行权限失败"
|
||||||
|
|
||||||
|
# 检查 Docker 容器是否存在
|
||||||
|
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||||
|
log_error "Docker 容器不存在: $CONTAINER_NAME"
|
||||||
|
log_info "请在 1Panel 中创建容器,或使用以下命令:"
|
||||||
|
echo ""
|
||||||
|
echo "docker run -d \\"
|
||||||
|
echo " --name $CONTAINER_NAME \\"
|
||||||
|
echo " -v $DEPLOY_DIR/current:/app \\"
|
||||||
|
echo " -p 8080:8080 \\"
|
||||||
|
echo " --restart unless-stopped \\"
|
||||||
|
echo " golang:1.22-bookworm \\"
|
||||||
|
echo " /app/bin/server"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 重启 Docker 容器
|
||||||
|
log_info "重启 Docker 容器: $CONTAINER_NAME"
|
||||||
|
if docker restart "$CONTAINER_NAME"; then
|
||||||
|
log_info "✅ 容器重启成功"
|
||||||
|
else
|
||||||
|
log_error "❌ 容器重启失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 等待容器启动
|
||||||
|
log_info "等待容器启动..."
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# 检查容器状态
|
||||||
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
||||||
|
log_info "✅ 容器运行正常"
|
||||||
|
|
||||||
|
# 显示容器日志(最后 20 行)
|
||||||
|
log_info "容器日志(最后 20 行):"
|
||||||
|
docker logs --tail 20 "$CONTAINER_NAME"
|
||||||
|
else
|
||||||
|
log_error "❌ 容器未运行"
|
||||||
|
log_error "容器日志:"
|
||||||
|
docker logs --tail 50 "$CONTAINER_NAME"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理旧备份(保留最近 5 个)
|
||||||
|
log_info "清理旧备份..."
|
||||||
|
ls -t "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | tail -n +6 | xargs -r rm -f
|
||||||
|
|
||||||
|
log_info "✅ 部署完成!"
|
||||||
|
log_info "备份文件: $BACKUP_FILE"
|
||||||
|
log_info "部署目录: $DEPLOY_DIR/current"
|
||||||
|
|
||||||
|
# 输出部署信息(用于通知)
|
||||||
|
echo ""
|
||||||
|
echo "DEPLOY_SUCCESS=true"
|
||||||
|
echo "DEPLOY_TIME=$TIMESTAMP"
|
||||||
|
echo "CONTAINER_NAME=$CONTAINER_NAME"
|
||||||
+666
-897
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,19 @@
|
|||||||
<view class="container">
|
<view class="container">
|
||||||
<!-- 全屏画布 -->
|
<!-- WebGL Canvas -->
|
||||||
<canvas
|
<canvas
|
||||||
canvas-id="wishTree"
|
type="webgl"
|
||||||
class="wish-tree-canvas"
|
id="webgl-canvas"
|
||||||
style="width: {{canvasWidth}}px; height: {{canvasHeight}}px;"
|
class="webgl-canvas"
|
||||||
bindtouchstart="onCanvasTouch"
|
bindtouchstart="onCanvasTouch"
|
||||||
|
bindtouchmove="onCanvasMove"
|
||||||
|
bindtouchend="onCanvasEnd"
|
||||||
></canvas>
|
></canvas>
|
||||||
|
|
||||||
<!-- 顶部导航 -->
|
<!-- 顶部导航 -->
|
||||||
<view class="top-bar">
|
<view class="top-bar">
|
||||||
<view class="tree-switcher">
|
<view class="tree-info">
|
||||||
<view class="switch-btn" bindtap="switchTree" data-direction="prev">
|
<text class="tree-name">{{treeName}}</text>
|
||||||
<text class="switch-icon">‹</text>
|
<text class="tree-desc">{{treeDesc}}</text>
|
||||||
</view>
|
|
||||||
<view class="tree-name">{{trees.length > 0 ? trees[currentTreeIndex].name : '许愿树'}}</view>
|
|
||||||
<view class="switch-btn" bindtap="switchTree" data-direction="next">
|
|
||||||
<text class="switch-icon">›</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 传感器控制 -->
|
<!-- 传感器控制 -->
|
||||||
|
|||||||
+111
-103
@@ -3,11 +3,11 @@
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #0f3460;
|
background: linear-gradient(180deg, #0a0a1a 0%, #1a1a3a 50%, #0f2a4a 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 全屏画布 */
|
/* WebGL Canvas */
|
||||||
.wish-tree-canvas {
|
.webgl-canvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -22,50 +22,35 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 80rpx 30rpx 20rpx;
|
padding: 100rpx 40rpx 30rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar .tree-switcher,
|
.top-bar .tree-info,
|
||||||
.top-bar .sensor-controls {
|
.top-bar .sensor-controls {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-switcher {
|
.tree-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 20rpx;
|
gap: 8rpx;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 10rpx 20rpx;
|
|
||||||
border-radius: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-btn {
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-icon {
|
|
||||||
font-size: 40rpx;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-name {
|
.tree-name {
|
||||||
font-size: 32rpx;
|
font-size: 40rpx;
|
||||||
color: #fff;
|
color: #ffd700;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
min-width: 150rpx;
|
text-shadow: 0 2rpx 10rpx rgba(255, 215, 0, 0.5);
|
||||||
text-align: center;
|
}
|
||||||
|
|
||||||
|
.tree-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 传感器控制 */
|
/* 传感器控制 */
|
||||||
@@ -75,29 +60,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sensor-btn {
|
.sensor-btn {
|
||||||
width: 70rpx;
|
width: 80rpx;
|
||||||
height: 70rpx;
|
height: 80rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sensor-btn.active {
|
.sensor-btn.active {
|
||||||
background: rgba(255, 215, 0, 0.5);
|
background: rgba(255, 215, 0, 0.3);
|
||||||
box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.5);
|
border-color: rgba(255, 215, 0, 0.6);
|
||||||
|
box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sensor-icon {
|
.sensor-icon {
|
||||||
font-size: 36rpx;
|
font-size: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 许愿按钮 */
|
/* 许愿按钮 */
|
||||||
.action-bar {
|
.action-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 60rpx;
|
bottom: 80rpx;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@@ -107,18 +94,26 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 12rpx;
|
gap: 16rpx;
|
||||||
background: linear-gradient(135deg, #C41E3A, #E74C3C);
|
background: linear-gradient(135deg, #c41e3a 0%, #e74c3c 50%, #ff6b6b 100%);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 32rpx;
|
font-size: 36rpx;
|
||||||
padding: 24rpx 60rpx;
|
font-weight: bold;
|
||||||
border-radius: 50rpx;
|
padding: 28rpx 80rpx;
|
||||||
|
border-radius: 60rpx;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 8rpx 30rpx rgba(196, 30, 58, 0.4);
|
box-shadow: 0 10rpx 40rpx rgba(196, 30, 58, 0.5),
|
||||||
|
0 0 60rpx rgba(255, 107, 107, 0.3);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-wish:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 5rpx 20rpx rgba(196, 30, 58, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-wish .icon {
|
.btn-wish .icon {
|
||||||
font-size: 36rpx;
|
font-size: 44rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 弹窗 */
|
/* 弹窗 */
|
||||||
@@ -137,7 +132,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@@ -145,34 +140,36 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: #fff;
|
background: linear-gradient(180deg, #2a2a4a 0%, #1a1a2e 100%);
|
||||||
border-radius: 32rpx 32rpx 0 0;
|
border-radius: 40rpx 40rpx 0 0;
|
||||||
max-height: 80vh;
|
max-height: 85vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border-top: 2rpx solid rgba(255, 215, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 30rpx;
|
padding: 40rpx;
|
||||||
border-bottom: 1rpx solid #eee;
|
border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-title {
|
.modal-title {
|
||||||
font-size: 32rpx;
|
font-size: 36rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: #ffd700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-close {
|
.modal-close {
|
||||||
font-size: 48rpx;
|
font-size: 56rpx;
|
||||||
color: #999;
|
color: rgba(255, 255, 255, 0.5);
|
||||||
padding: 10rpx;
|
padding: 10rpx;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
padding: 30rpx;
|
padding: 40rpx;
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -180,129 +177,140 @@
|
|||||||
/* 类型选择 */
|
/* 类型选择 */
|
||||||
.type-selector {
|
.type-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20rpx;
|
gap: 24rpx;
|
||||||
margin-bottom: 30rpx;
|
margin-bottom: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-item {
|
.type-item {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 24rpx;
|
padding: 32rpx;
|
||||||
border: 2rpx solid #eee;
|
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-item.active {
|
.type-item.active {
|
||||||
border-color: #C41E3A;
|
border-color: #ffd700;
|
||||||
background: #FFF5F5;
|
background: rgba(255, 215, 0, 0.1);
|
||||||
|
box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-name {
|
.type-name {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: #fff;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.type-desc {
|
.type-desc {
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: rgba(255, 255, 255, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 输入框 */
|
/* 输入框 */
|
||||||
.wish-input {
|
.wish-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200rpx;
|
height: 240rpx;
|
||||||
padding: 20rpx;
|
padding: 28rpx;
|
||||||
border: 2rpx solid #eee;
|
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
font-size: 28rpx;
|
font-size: 30rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wish-input::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-count {
|
.input-count {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: rgba(255, 255, 255, 0.5);
|
||||||
margin-top: 10rpx;
|
margin-top: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 商品选择 */
|
/* 商品选择 */
|
||||||
.product-section {
|
.product-section {
|
||||||
margin-top: 30rpx;
|
margin-top: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: #ffd700;
|
||||||
margin-bottom: 20rpx;
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-list {
|
.product-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16rpx;
|
gap: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-item {
|
.product-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 24rpx;
|
padding: 32rpx;
|
||||||
border: 2rpx solid #eee;
|
border: 2rpx solid rgba(255, 255, 255, 0.15);
|
||||||
border-radius: 16rpx;
|
border-radius: 20rpx;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-item.active {
|
.product-item.active {
|
||||||
border-color: #C41E3A;
|
border-color: #ffd700;
|
||||||
background: #FFF5F5;
|
background: rgba(255, 215, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-name {
|
.product-name {
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #333;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-desc {
|
.product-desc {
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: rgba(255, 255, 255, 0.6);
|
||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-price {
|
.product-price {
|
||||||
font-size: 32rpx;
|
font-size: 40rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #C41E3A;
|
color: #ffd700;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部按钮 */
|
/* 底部按钮 */
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20rpx;
|
gap: 24rpx;
|
||||||
padding: 30rpx;
|
padding: 40rpx;
|
||||||
border-top: 1rpx solid #eee;
|
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer button {
|
.modal-footer button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 24rpx;
|
padding: 28rpx;
|
||||||
border-radius: 50rpx;
|
border-radius: 60rpx;
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline {
|
.btn-outline {
|
||||||
background: #fff;
|
background: transparent;
|
||||||
border: 2rpx solid #C41E3A;
|
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
||||||
color: #C41E3A;
|
color: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: linear-gradient(135deg, #C41E3A, #E74C3C);
|
background: linear-gradient(135deg, #c41e3a 0%, #e74c3c 100%);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
|
box-shadow: 0 8rpx 30rpx rgba(196, 30, 58, 0.4);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user