first commit

This commit is contained in:
2025-08-14 08:11:03 +08:00
commit da7594d2d0
4606 changed files with 10644024 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
{
"sqltools.connections": [
{
"mysqlOptions": {
"authProtocol": "default",
"enableSsl": "Disabled"
},
"previewLimit": 50,
"server": "127.0.0.1",
"port": 3306,
"driver": "MySQL",
"name": "test",
"group": "ljwx",
"database": "test",
"username": "root"
}
]
}
+393
View File
@@ -0,0 +1,393 @@
#!/bin/bash
# Created Time: 2025-06-09 07:43:24
# Modified Time: 2025-06-26 20:39:46
#!/bin/bash
# 灵境万象系统 - 本地多架构构建脚本 v1.2.6
set -e
echo "🏗️ 灵境万象系统 - 本地多架构构建 v1.2.6"
# 加载版本配置
if [ -f "monitoring-versions.env" ]; then
source monitoring-versions.env
echo "📋 已加载版本配置文件"
else
echo "⚠️ 未找到版本配置文件,使用默认配置"
# 默认配置
LJWX_VERSION="1.2.16"
LJWX_GRAFANA_VERSION="1.2.6"
LJWX_PROMETHEUS_VERSION="1.2.6"
LJWX_LOKI_VERSION="1.2.6"
LJWX_PROMTAIL_VERSION="1.2.6"
LJWX_ALERTMANAGER_VERSION="1.2.6"
REGISTRY="crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx" # 阿里云镜像仓库
PLATFORMS="linux/amd64,linux/arm64" # 多架构构建
fi
# 构建器配置
BUILDER_NAME="multiarch-builder"
# 多架构构建模式配置
LOCAL_BUILD=${LOCAL_BUILD:-false} # 默认多架构构建
PUSH_TO_REGISTRY=${PUSH_TO_REGISTRY:-true} # 默认推送到阿里云
# 设置代理(网络优化)
export HTTP_PROXY=http://127.0.0.1:7890
export HTTPS_PROXY=http://127.0.0.1:7890
# 生成数据库升级脚本 - 已移除,请使用专门的数据库升级脚本
# 数据导出功能已移除 - 请使用专门的数据导出脚本 export-data.sh
# 自动登录阿里云镜像仓库
login_aliyun() {
if [ "$PUSH_TO_REGISTRY" = "true" ] && [[ "$REGISTRY" == *"aliyuncs.com"* ]]; then
echo "🔐 登录阿里云Docker镜像仓库..."
echo "admin123" | docker login --username brunogao --password-stdin crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com
if [ $? -eq 0 ]; then
echo "✅ 阿里云登录成功"
else
echo "❌ 阿里云登录失败,请检查凭据"
exit 1
fi
fi
}
# 检查参数
if [ $# -eq 0 ]; then
echo "📋 使用方法:"
echo " $0 all # 构建所有镜像(应用+监控)"
echo " $0 apps # 构建应用镜像"
echo " $0 monitoring # 构建监控镜像"
echo ""
echo "🔧 应用组件:"
echo " $0 mysql # 构建MySQL镜像"
echo " $0 redis # 构建Redis镜像"
echo " $0 boot # 构建Boot镜像"
echo " $0 bigscreen # 构建Bigscreen镜像"
echo " $0 admin # 构建Admin镜像"
echo ""
echo "📊 监控组件:"
echo " $0 grafana # 构建定制化Grafana"
echo " $0 prometheus # 构建定制化Prometheus"
echo " $0 loki # 构建定制化Loki"
echo " $0 promtail # 构建定制化Promtail"
echo " $0 alertmanager # 构建定制化AlertManager"
echo ""
echo "🎯 构建模式:"
echo " LOCAL_BUILD=false # 多架构构建(默认)"
echo " PUSH_TO_REGISTRY=true # 推送到阿里云(默认)"
echo ""
echo "📊 数据管理:"
echo " ./export-data.sh # 导出MySQL数据到data.sql"
echo " database/version-workflow.sh # 数据库版本升级管理"
echo ""
echo "⚠️ 注意事项:"
echo " - MySQL镜像构建需要data.sql文件,请先运行数据导出"
echo " - 数据库升级请使用专门的版本管理工具,不在构建中处理"
echo ""
echo "💡 当前架构: $PLATFORMS"
echo "📊 当前版本: 应用 $LJWX_VERSION, 监控 $LJWX_GRAFANA_VERSION"
echo "🏷️ 镜像前缀: $REGISTRY"
exit 1
fi
# 初始化多架构构建器(仅在需要时)
init_buildx() {
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
echo "🔧 使用本地Docker构建..."
return 0
fi
echo "🔧 初始化多架构构建器..."
if ! docker buildx inspect $BUILDER_NAME >/dev/null 2>&1; then
docker buildx create --name $BUILDER_NAME --use
else
docker buildx use $BUILDER_NAME
fi
docker buildx inspect --bootstrap
}
# 构建应用镜像函数
build_app_image() {
local image=$1
local image_name="ljwx-$image"
local tag="$REGISTRY/$image_name:$LJWX_VERSION"
local latest_tag="$REGISTRY/$image_name:latest"
echo "🔨 构建应用镜像 $image_name (架构: $PLATFORMS)..."
# 构建参数
local build_args=""
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
# 本地构建
build_args="build"
else
# 多架构构建
build_args="buildx build --platform $PLATFORMS"
if [ "$PUSH_TO_REGISTRY" = "true" ]; then
build_args="$build_args --push"
else
build_args="$build_args --load"
fi
fi
case $image in
"mysql")
echo "🗄️ 构建MySQL镜像..."
# 检查是否存在data.sql文件,如果需要导出数据请使用单独的数据管理脚本
if [ ! -f "data.sql" ]; then
echo "⚠️ 未找到data.sql文件,将构建不包含数据的MySQL镜像"
echo "💡 如需包含数据,请先运行: ./export-data.sh 或手动导出数据到data.sql"
else
echo "✅ 找到data.sql文件,将构建包含数据的MySQL镜像"
fi
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
docker build --no-cache -t $tag -t $latest_tag . -f docker/mysql/Dockerfile
else
docker $build_args --no-cache -t $tag -t $latest_tag . -f docker/mysql/Dockerfile
fi
;;
"redis")
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
docker build -t $tag -t $latest_tag . -f docker/redis/Dockerfile
else
docker $build_args -t $tag -t $latest_tag . -f docker/redis/Dockerfile
fi
;;
"boot")
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
docker build -t $tag -t $latest_tag . -f ljwx-boot/ljwx-boot-admin/Dockerfile.prod
else
docker $build_args -t $tag -t $latest_tag . -f ljwx-boot/ljwx-boot-admin/Dockerfile.prod
fi
;;
"bigscreen")
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
docker build -t $tag -t $latest_tag ljwx-bigscreen/bigscreen/ -f ljwx-bigscreen/bigscreen/Dockerfile.multiarch
else
docker $build_args -t $tag -t $latest_tag ljwx-bigscreen/bigscreen/ -f ljwx-bigscreen/bigscreen/Dockerfile.multiarch
fi
;;
"admin")
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
docker build -t $tag -t $latest_tag ljwx-admin/ -f ljwx-admin/Dockerfile.prod
else
docker $build_args -t $tag -t $latest_tag ljwx-admin/ -f ljwx-admin/Dockerfile.prod
fi
;;
*)
echo "❌ 未知应用镜像: $image"
return 1
;;
esac
if [ $? -eq 0 ]; then
echo "✅ $image_name 应用镜像构建成功"
echo "🏷️ 镜像标签: $tag, $latest_tag"
else
echo "❌ $image_name 应用镜像构建失败"
return 1
fi
}
# 构建监控镜像函数
build_monitoring_image() {
local image=$1
local image_name="ljwx-$image"
# 根据组件设置版本
case $image in
"grafana")
local version=$LJWX_GRAFANA_VERSION
;;
"prometheus")
local version=$LJWX_PROMETHEUS_VERSION
;;
"loki")
local version=$LJWX_LOKI_VERSION
;;
"promtail")
local version=$LJWX_PROMTAIL_VERSION
;;
"alertmanager")
local version=$LJWX_ALERTMANAGER_VERSION
;;
*)
echo "❌ 未知监控镜像: $image"
return 1
;;
esac
local tag="$REGISTRY/$image_name:$version"
local latest_tag="$REGISTRY/$image_name:latest"
echo "🔨 构建监控镜像 $image_name:$version (架构: $PLATFORMS)..."
# 构建镜像
if [ "$LOCAL_BUILD" = "true" ] && [ "$PLATFORMS" = "linux/amd64" ]; then
docker build \
--build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--build-arg VERSION="$version" \
-t $tag -t $latest_tag \
. -f docker/$image/Dockerfile
else
docker buildx build --platform $PLATFORMS \
--build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--build-arg VERSION="$version" \
-t $tag -t $latest_tag \
$([ "$PUSH_TO_REGISTRY" = "true" ] && echo "--push" || echo "--load") \
. -f docker/$image/Dockerfile
fi
if [ $? -eq 0 ]; then
echo "✅ $image_name:$version 监控镜像构建成功"
echo "🏷️ 镜像标签: $tag, $latest_tag"
else
echo "❌ $image_name:$version 监控镜像构建失败"
return 1
fi
}
# 构建所有应用镜像
build_all_apps() {
echo "🚀 开始构建所有应用镜像..."
local app_images=("mysql" "redis" "boot" "bigscreen" "admin")
for image in "${app_images[@]}"; do
build_app_image "$image"
echo ""
done
}
# 构建所有监控镜像
build_all_monitoring() {
echo "📊 开始构建所有监控镜像..."
local monitoring_images=("grafana" "prometheus" "loki" "promtail" "alertmanager")
for image in "${monitoring_images[@]}"; do
build_monitoring_image "$image"
echo ""
done
}
# 构建所有镜像
build_all() {
echo "🏗️ 开始构建所有镜像(应用+监控)..."
echo ""
# 先构建应用镜像
build_all_apps
echo "🔄 应用镜像构建完成,开始构建监控镜像..."
echo ""
# 再构建监控镜像
build_all_monitoring
}
# 显示构建总结
show_summary() {
echo ""
echo "🎉 本地构建完成!"
echo ""
echo "📊 构建摘要:"
echo " 应用版本: $LJWX_VERSION"
echo " 监控版本: $LJWX_GRAFANA_VERSION"
echo " 构建架构: $PLATFORMS"
echo " 镜像前缀: $REGISTRY"
echo " 构建模式: $([ "$LOCAL_BUILD" = "true" ] && echo "本地构建" || echo "多架构构建")"
echo ""
echo "🔍 查看本地镜像:"
echo " docker images | grep $REGISTRY"
echo ""
if [[ " $@ " =~ " all " ]] || [[ " $@ " =~ " apps " ]]; then
echo " # 应用镜像"
echo " docker images $REGISTRY/ljwx-mysql"
echo " docker images $REGISTRY/ljwx-redis"
echo " docker images $REGISTRY/ljwx-boot"
echo " docker images $REGISTRY/ljwx-bigscreen"
echo " docker images $REGISTRY/ljwx-admin"
fi
if [[ " $@ " =~ " all " ]] || [[ " $@ " =~ " monitoring " ]]; then
echo " # 监控镜像"
echo " docker images $REGISTRY/ljwx-grafana"
echo " docker images $REGISTRY/ljwx-prometheus"
echo " docker images $REGISTRY/ljwx-loki"
echo " docker images $REGISTRY/ljwx-promtail"
echo " docker images $REGISTRY/ljwx-alertmanager"
fi
echo ""
echo "🚀 推送到阿里云的镜像:"
if [[ " $@ " =~ " all " ]] || [[ " $@ " =~ " apps " ]]; then
echo " $REGISTRY/ljwx-mysql:$LJWX_VERSION"
echo " $REGISTRY/ljwx-redis:$LJWX_VERSION"
echo " $REGISTRY/ljwx-boot:$LJWX_VERSION"
echo " $REGISTRY/ljwx-bigscreen:$LJWX_VERSION"
echo " $REGISTRY/ljwx-admin:$LJWX_VERSION"
fi
echo ""
echo " 客户可使用命令拉取: docker pull $REGISTRY/ljwx-xxx:$LJWX_VERSION"
}
# 主程序
main() {
# 登录阿里云(如果需要推送)
login_aliyun
# 初始化构建器
init_buildx
case "$1" in
"all")
build_all
;;
"apps")
build_all_apps
;;
"monitoring")
build_all_monitoring
;;
"mysql"|"redis"|"boot"|"bigscreen"|"admin")
for image in "$@"; do
build_app_image "$image"
echo ""
done
;;
"grafana"|"prometheus"|"loki"|"promtail"|"alertmanager")
for image in "$@"; do
build_monitoring_image "$image"
echo ""
done
;;
*)
# 混合构建:检查每个参数类型
for image in "$@"; do
case $image in
"mysql"|"redis"|"boot"|"bigscreen"|"admin")
build_app_image "$image"
;;
"grafana"|"prometheus"|"loki"|"promtail"|"alertmanager")
build_monitoring_image "$image"
;;
*)
echo "❌ 未知镜像类型: $image"
;;
esac
echo ""
done
;;
esac
# 显示构建总结
show_summary "$@"
}
# 执行主程序
main "$@"
+1
View File
@@ -0,0 +1 @@
*.sh text eol=lf
+108
View File
@@ -0,0 +1,108 @@
# 灵境万象系统 - Client Deployment Git忽略规则
# ===== 通用规则 =====
*.log
*.tmp
*.temp
.DS_Store
Thumbs.db
.vscode/
.idea/
*.swp
*.swo
*~
# ===== Java/Spring Boot =====
target/
*.jar
*.war
*.ear
*.class
.mvn/wrapper/maven-wrapper.jar
.mvn/wrapper/maven-wrapper.properties
mvnw
mvnw.cmd
# ===== Node.js/Vue.js =====
node_modules/
dist/
.nuxt/
.output/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# ===== Python/Flask =====
venv/
env/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
pip-log.txt
pip-delete-this-directory.txt
.pytest_cache/
.coverage
.cache
*.egg-info/
# ===== Docker =====
.env.local
.env.production
# ===== 数据库 =====
*.sql.bak
*.db
*.sqlite
*.sqlite3
# ===== 证书和密钥 =====
*.pem
*.key
*.crt
*.p12
*.jks
# ===== 备份文件 =====
*.bak
*.backup
# ===== Client Deployment 特定忽略 =====
# 忽略运行时生成的数据和日志目录
backups/
data/
data1/
logs/
utils/
# 忽略MySQL和Redis运行时文件
*.ibd
*.redo*
*.undo*
*.log.gz
*.aof
*.rdb
binlog.*
# 忽略plist配置文件
*.plist
# 忽略备份SQL文件
data.sql
data*.sql
*backup*.sql
# 忽略环境配置文件
.env.local
.env.production
# 忽略临时配置文件
*-adaptive.yml
*-mysql-*.yml
*.yml.bak
# 忽略运行时报告
environment-report.txt
mysql-keepalive.log
@@ -0,0 +1,561 @@
# 客户现场部署指南
## 概述
本指南适用于客户现场使用预构建Docker镜像进行系统部署,无需源代码编译,快速完成定制化部署。
## 部署包内容
客户部署包应包含以下文件:
```
ljwx-client-deployment/
├── docker-compose-client.yml # 客户部署Docker编排文件
├── deploy-client.sh # 一键部署脚本
├── quick-start.sh # 快速启动脚本
├── custom-config.env # 主配置文件
├── customer-example.env # 客户配置示例
├── custom-config.py # Python配置文件(可选)
├── custom-admin-config.js # 前端配置文件(可选)
├── custom-assets/ # 自定义资源目录
├── client-data.sql.example # 客户数据示例
├── data.sql # 内置数据库初始化脚本
└── CLIENT_DEPLOYMENT_GUIDE.md # 本部署指南
```
## 系统要求
### 硬件要求
- **CPU**: 16核心以上 (推荐32核心)
- **内存**: 32GB以上 (推荐64GB)
- **磁盘**: 1TB以上可用空间 (推荐2TB SSD)
- **网络**: 千兆网络,稳定的互联网连接(用于拉取镜像)
### 软件要求
- 操作系统: Linux(推荐CentOS 8+/Ubuntu 20.04+)/macOS/Windows
- Docker: 24.0+
- Docker Compose: 2.20+
## 快速部署
### 1. 环境准备
#### 安装Docker
```bash
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# CentOS/RHEL 8+
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker
# macOS - 安装Docker Desktop
# Windows - 安装Docker Desktop with WSL2
```
#### 验证安装
```bash
docker --version
docker compose version
```
### 2. 配置定制化参数
#### 复制并编辑主配置文件
```bash
cp custom-config.env client-config.env
vim client-config.env
```
#### 核心配置说明
##### 数据库配置 (MySQL)
```bash
# MySQL 数据库配置
MYSQL_ROOT_PASSWORD=您的安全密码 # 必须修改,建议16位以上复杂密码
MYSQL_DATABASE=lj-05 # 数据库名称,可自定义
MYSQL_USER=ljwx_user # 应用用户名,可自定义
MYSQL_PASSWORD=您的应用密码 # 应用密码,必须修改
# MySQL 性能配置
MYSQL_INNODB_BUFFER_POOL_SIZE=16G # InnoDB缓冲池大小,建议为内存的50-70%
MYSQL_MAX_CONNECTIONS=1000 # 最大连接数
MYSQL_QUERY_CACHE_SIZE=256M # 查询缓存大小
MYSQL_TABLE_OPEN_CACHE=4000 # 表缓存数量
# MySQL 数据目录
MYSQL_DATA_DIR=/var/lib/mysql # 数据存储目录
MYSQL_LOG_DIR=/var/log/mysql # 日志目录
```
##### Redis配置
```bash
# Redis 缓存配置
REDIS_PASSWORD=您的Redis密码 # 必须修改,建议16位以上
REDIS_PORT=6379 # Redis端口,默认6379
REDIS_MAXMEMORY=8gb # 最大内存使用,建议设置为物理内存的20-30%
REDIS_MAXMEMORY_POLICY=allkeys-lru # 内存回收策略
# Redis 性能配置
REDIS_SAVE_INTERVAL=900 1 # 持久化配置:900秒内至少1个键改变则保存
REDIS_DATABASES=16 # 数据库数量
REDIS_TIMEOUT=300 # 客户端超时时间(秒)
REDIS_TCP_KEEPALIVE=300 # TCP keepalive时间
# Redis 集群配置(可选)
REDIS_CLUSTER_ENABLED=no # 是否启用集群模式
REDIS_CLUSTER_NODES=3 # 集群节点数量
```
##### 应用配置
```bash
# 应用标题定制
BIGSCREEN_TITLE=客户大屏标题 # 大屏显示标题
VITE_APP_TITLE=客户管理系统 # 管理端标题
COMPANY_NAME=客户公司名称 # 公司名称
FOOTERTEXT=© 2025 客户公司 版权所有 # 页脚信息
# 主题配色
THEME_COLOR=#1890ff # 主题色,支持十六进制颜色
# 网络配置
VITE_BIGSCREEN_URL=http://您的域名:8001 # 大屏访问地址
ADMIN_PORT=8080 # 管理端端口
BIGSCREEN_PORT=8001 # 大屏端口
LJWX_BOOT_PORT=9998 # API服务端口
# 应用性能配置
JVM_OPTS=-Xms2g -Xmx8g -XX:+UseG1GC # Java应用JVM参数
BIGSCREEN_WORKERS=8 # Python应用工作进程数
LOG_LEVEL=info # 日志级别
```
##### 监控配置
```bash
# Prometheus 监控
PROMETHEUS_PORT=9090
PROMETHEUS_RETENTION_TIME=30d # 数据保留时间
# Grafana 可视化
GRAFANA_PORT=3000
GRAFANA_PASSWORD=您的Grafana密码 # 必须修改
# 告警配置
ALERTMANAGER_PORT=9093
WECHAT_ALERT_ENABLED=true # 启用微信告警
WECHAT_API_URL=您的微信API地址 # 微信API接口
WECHAT_TEMPLATE_ID=您的模板ID # 微信消息模板
```
### 3. 高级配置
#### 自定义前端配置
```bash
# 编辑前端配置文件
vim custom-admin-config.js
```
```javascript
window.CUSTOM_CONFIG = {
// 应用标题
appTitle: '客户管理平台',
// 应用描述
appDesc: '智能穿戴管理平台',
// 大屏访问地址
bigscreenUrl: 'http://您的域名:8001',
// 主题配色
themeColor: '#1890ff',
// 公司信息
companyName: '客户公司名称',
// 页脚信息
footerText: '© 2025 客户公司 版权所有'
};
```
#### 自定义Python配置
```bash
# 编辑Python配置文件
vim custom-config.py
```
```python
# 客户定制配置
COMPANY_NAME = "客户公司名称"
SYSTEM_TITLE = "客户大屏标题"
# 数据库连接配置
DATABASE_CONFIG = {
'host': 'ljwx-mysql',
'port': 3306,
'user': 'ljwx_user',
'password': '您的应用密码',
'database': 'lj-05',
'charset': 'utf8mb4'
}
# Redis连接配置
REDIS_CONFIG = {
'host': 'ljwx-redis',
'port': 6379,
'password': '您的Redis密码',
'db': 0,
'decode_responses': True
}
# 业务配置
ALERT_CONFIG = {
'enabled': True,
'wechat_api': '您的微信API地址',
'template_id': '您的模板ID'
}
```
### 4. 客户数据配置
#### 准备客户数据(可选)
```bash
# 复制数据示例
cp client-data.sql.example client-data.sql
# 编辑客户数据
vim client-data.sql
```
客户数据格式示例:
```sql
-- 健康数据
INSERT INTO t_user_health_data (device_sn, user_id, org_id, heart_rate, blood_oxygen, temperature, created_time) VALUES
('CLIENT001', 1, 1, 75, 98, 36.6, NOW()),
('CLIENT002', 2, 1, 80, 97, 36.5, NOW());
-- 设备信息
INSERT INTO t_device_info (device_sn, user_id, org_id, device_name, device_type, status) VALUES
('CLIENT001', 1, 1, '客户设备001', 'SMARTWATCH', 1),
('CLIENT002', 2, 1, '客户设备002', 'SMARTWATCH', 1);
-- 用户信息
INSERT INTO t_user_info (user_id, user_name, org_id, phone, email, age, gender) VALUES
(1, '张三', 1, '13800138001', 'zhangsan@client.com', 30, 1),
(2, '李四', 1, '13800138002', 'lisi@client.com', 25, 0);
```
### 5. 一键部署
#### 使用默认配置快速启动
```bash
# 使用内置数据,快速演示
./quick-start.sh
```
#### 使用客户配置部署
```bash
# 使用客户定制配置
./deploy-client.sh client-config.env
```
#### 部署过程
部署脚本将自动执行:
1. ✅ 检查系统环境和依赖
2. ✅ 验证配置文件语法
3. ✅ 拉取最新镜像版本
4. ✅ 初始化数据卷和网络
5. ✅ 启动所有服务容器
6. ✅ 执行健康状态检查
7. ✅ 显示访问地址和状态
### 6. 验证部署
部署完成后访问:
- **管理端**: http://localhost:8080 (或您配置的域名和端口)
- **大屏端**: http://localhost:8001 (或您配置的域名和端口)
- **API接口**: http://localhost:9998/doc.html (Swagger文档)
- **监控面板**: http://localhost:3000 (Grafana,admin/您配置的密码)
默认登录账号:`admin` / `123456`
## 生产环境优化
### 域名和SSL配置
#### 配置域名
```bash
# 修改配置文件
VITE_BIGSCREEN_URL=https://bigscreen.客户域名.com
ADMIN_DOMAIN=admin.客户域名.com
API_DOMAIN=api.客户域名.com
```
#### SSL证书配置
```yaml
# 在docker-compose-client.yml中添加SSL
volumes:
- ./ssl/cert.pem:/etc/nginx/ssl/cert.pem:ro
- ./ssl/key.pem:/etc/nginx/ssl/key.pem:ro
```
### 负载均衡配置
```yaml
# 多实例部署
deploy:
replicas: 3
resources:
limits:
memory: 2G
cpus: '2.0'
reservations:
memory: 1G
cpus: '1.0'
```
### 数据库优化
```bash
# MySQL 生产配置
MYSQL_INNODB_BUFFER_POOL_SIZE=24G # 设置为内存的60-70%
MYSQL_INNODB_LOG_FILE_SIZE=2G # 事务日志大小
MYSQL_MAX_CONNECTIONS=2000 # 根据并发需求调整
MYSQL_QUERY_CACHE_SIZE=512M # 查询缓存
MYSQL_THREAD_CACHE_SIZE=64 # 线程缓存
```
## 运维管理
### 服务管理命令
```bash
# 查看服务状态
docker compose -f docker-compose-client.yml ps
# 查看实时日志
docker compose -f docker-compose-client.yml logs -f --tail=100
# 查看特定服务日志
docker compose -f docker-compose-client.yml logs ljwx-boot
docker compose -f docker-compose-client.yml logs ljwx-admin
docker compose -f docker-compose-client.yml logs ljwx-bigscreen
# 重启特定服务
docker compose -f docker-compose-client.yml restart ljwx-admin
# 停止所有服务
docker compose -f docker-compose-client.yml down
# 更新镜像并重启
docker compose -f docker-compose-client.yml pull
docker compose -f docker-compose-client.yml up -d
```
### 数据备份和恢复
#### 数据库备份
```bash
# 自动备份脚本
cat > backup-db.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# 备份数据库
docker exec ljwx-mysql mysqldump -u root -p$MYSQL_ROOT_PASSWORD $MYSQL_DATABASE > $BACKUP_DIR/backup_$DATE.sql
# 压缩备份文件
gzip $BACKUP_DIR/backup_$DATE.sql
# 清理7天前的备份
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete
echo "数据库备份完成: backup_$DATE.sql.gz"
EOF
chmod +x backup-db.sh
# 设置定时备份
crontab -e
# 添加: 0 2 * * * /path/to/backup-db.sh
```
#### Redis备份
```bash
# Redis数据备份
docker exec ljwx-redis redis-cli --rdb /backup/redis/dump_$(date +%Y%m%d_%H%M%S).rdb
```
#### 配置文件备份
```bash
# 备份所有配置文件
tar -czf config-backup-$(date +%Y%m%d).tar.gz \
custom-config.env \
client-config.env \
custom-config.py \
custom-admin-config.js \
custom-assets/
```
### 监控和告警
#### Grafana监控面板
- 访问: http://localhost:3000
- 账号: admin / 您配置的密码
- 内置面板:
- 系统概览面板
- 应用性能面板
- 数据库监控面板
- Redis监控面板
#### 告警配置
```bash
# 配置微信告警
WECHAT_ALERT_ENABLED=true
WECHAT_API_URL=您的企业微信机器人地址
WECHAT_TEMPLATE_ID=您的消息模板ID
# 告警规则
- 服务宕机告警
- CPU使用率超过80%告警
- 内存使用率超过85%告警
- 磁盘使用率超过90%告警
- 数据库连接数告警
```
## 故障排除
### 常见问题
#### 1. 镜像拉取失败
```bash
# 检查网络连接
ping registry.cn-hangzhou.aliyuncs.com
# 配置镜像加速器
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://registry.cn-hangzhou.aliyuncs.com"
]
}
EOF
sudo systemctl restart docker
# 手动拉取镜像
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:latest
```
#### 2. 端口冲突
```bash
# 检查端口占用
ss -tlnp | grep :8080
ss -tlnp | grep :8001
ss -tlnp | grep :9998
# 修改端口配置
vim client-config.env
# 修改ADMIN_PORT、BIGSCREEN_PORT、LJWX_BOOT_PORT
```
#### 3. 内存不足
```bash
# 检查内存使用
free -h
docker stats
# 调整JVM内存
vim client-config.env
# 修改JVM_OPTS=-Xms1g -Xmx4g
```
#### 4. 数据库连接失败
```bash
# 检查MySQL服务
docker compose -f docker-compose-client.yml logs ljwx-mysql
# 测试数据库连接
docker compose -f docker-compose-client.yml exec ljwx-mysql mysql -u root -p$MYSQL_ROOT_PASSWORD
# 重置数据库
docker compose -f docker-compose-client.yml down
docker volume rm ljwx-client_mysql_data
docker compose -f docker-compose-client.yml up -d
```
### 性能优化
#### 系统级优化
```bash
# 调整系统参数
echo 'vm.max_map_count = 262144' >> /etc/sysctl.conf
echo 'fs.file-max = 65536' >> /etc/sysctl.conf
sysctl -p
# 调整Docker配置
cat > /etc/docker/daemon.json << 'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"storage-driver": "overlay2"
}
EOF
```
#### 应用级优化
```bash
# MySQL优化
MYSQL_INNODB_BUFFER_POOL_SIZE=24G
MYSQL_INNODB_LOG_BUFFER_SIZE=64M
MYSQL_KEY_BUFFER_SIZE=256M
# Redis优化
REDIS_MAXMEMORY=16gb
REDIS_MAXMEMORY_POLICY=allkeys-lru
```
### 磁盘清理
```bash
# 清理Docker资源
docker system prune -af
docker volume prune -f
# 清理日志文件
find /var/log -name "*.log" -mtime +7 -delete
```
## 技术支持
### 联系方式
- 技术支持邮箱: support@ljwx.com
- 技术支持电话: 400-xxx-xxxx
- 在线文档: https://docs.ljwx.com
### 问题反馈
提交问题时请提供:
1. 系统环境信息 (`uname -a`, `docker version`)
2. 完整错误日志
3. 配置文件内容(脱敏后)
4. 详细复现步骤
### 版本更新
```bash
# 检查最新版本
docker compose -f docker-compose-client.yml pull
# 平滑更新
./deploy-client.sh client-config.env
```
@@ -0,0 +1,66 @@
# 容器权限修复指南
## 问题描述
客户部署时可能遇到容器内脚本无执行权限的问题,导致wait-for-it.sh无法运行,容器启动失败。
## 解决方案
### 1. 预防措施(已在docker-compose.yml中实现)
每个容器启动时自动设置脚本权限:
```bash
command: ["sh", "-c", "chmod +x /usr/local/bin/wait-for-it.sh && wait-for-it.sh ..."]
```
### 2. 权限检查
运行权限检查脚本:
```bash
./fix-container-permissions.sh
```
### 3. 容器运行时测试
容器启动后测试权限:
```bash
./test-container-permissions.sh
```
### 4. 手动修复(如果需要)
如果容器已运行但权限有问题:
```bash
# 修复ljwx-boot容器权限
docker exec ljwx-boot chmod +x /usr/local/bin/wait-for-it.sh
# 修复ljwx-bigscreen容器权限
docker exec ljwx-bigscreen chmod +x /usr/local/bin/wait-for-it.sh
# 修复ljwx-admin容器权限
docker exec ljwx-admin chmod +x /usr/local/bin/wait-for-it.sh
```
## 权限问题排查
### 症状
- 容器启动失败
- 日志显示 "permission denied" 或 "command not found"
- wait-for-it.sh 无法执行
### 排查步骤
1. 检查宿主机脚本权限:`ls -la wait-for-it.sh`
2. 检查容器内权限:`docker exec <容器名> ls -la /usr/local/bin/wait-for-it.sh`
3. 测试脚本执行:`docker exec <容器名> /usr/local/bin/wait-for-it.sh --help`
### 常见错误
- **挂载为只读**: 确保docker-compose.yml中没有:ro标记
- **宿主机无权限**: 运行 `chmod +x wait-for-it.sh`
- **容器内无权限**: 在command中添加 `chmod +x` 命令
## 最佳实践
1. 部署前运行 `./fix-container-permissions.sh` 检查
2. 容器启动后运行 `./test-container-permissions.sh` 验证
3. 监控容器启动日志,及时发现权限问题
4. 定期检查脚本文件权限
## 技术原理
- Docker挂载会保持宿主机文件权限
- 容器内chmod可以修改挂载文件权限
- 启动命令中的chmod确保权限正确性
- 移除:ro标记允许容器内修改权限
@@ -0,0 +1,224 @@
# LJWX 数据库备份和恢复完整指南
## 📋 概述
LJWX系统提供了完整的MySQL数据库备份和恢复解决方案,支持:
- **SQL逻辑备份**:用于数据恢复和跨平台迁移
- **数据卷备份**:用于完整的数据库文件备份
- **自动化备份**:支持定时任务和策略管理
- **一键恢复**:简化的恢复操作流程
## 🎯 备份策略
### 1. 双重备份保护
```
📦 SQL逻辑备份(推荐)
├── 优点:跨平台兼容、数据完整性高、恢复灵活
├── 缺点:备份时间较长、需要MySQL服务运行
└── 适用:日常备份、数据迁移、版本升级
🗃️ 数据卷备份
├── 优点:备份速度快、完整性好、支持冷备份
├── 缺点:平台相关、恢复需要停机
└── 适用:完整迁移、灾难恢复、系统重建
```
### 2. 备份频率建议
- **每日SQL备份**:保留7天,用于日常数据恢复
- **每周完整备份**:保留30天,用于长期存档
- **重要操作前**:手动备份,确保数据安全
## 🛠️ 备份工具
### 1. 交互式备份工具
```bash
# 启动交互式备份恢复工具
./mysql-backup-restore.sh
# 功能菜单:
# 1. 📦 创建完整备份(推荐)
# 2. 💾 仅SQL逻辑备份
# 3. 🗃️ 仅数据卷备份
# 4. 🔄 恢复数据库
# 5. 📊 查看备份列表
# 6. 🔍 验证备份完整性
# 7. 🧹 清理旧备份
```
### 2. 自动化备份脚本
```bash
# 每日备份(仅SQL)
./auto-backup.sh daily
# 每周备份(SQL + 数据卷)
./auto-backup.sh weekly
# 手动完整备份
./auto-backup.sh manual
# 查看帮助
./auto-backup.sh --help
```
### 3. 演示脚本
```bash
# 运行备份功能演示
./demo-backup.sh
```
## 📦 备份实现原理
### 1. SQL逻辑备份
```bash
# 备份命令
docker exec ljwx-mysql mysqldump \
-u root -p123456 \
--single-transaction \ # 事务一致性
--routines \ # 存储过程
--triggers \ # 触发器
--events \ # 事件
--hex-blob \ # 二进制数据
--add-drop-database \ # 添加DROP语句
--databases lj-06 > backup.sql
# 压缩备份
gzip backup.sql
```
### 2. 数据卷备份
```bash
# 使用临时容器备份数据卷
docker run --rm \
-v client-deployment_mysql_data:/source:ro \
-v $(pwd)/backups/volume:/backup \
alpine:latest \
tar -czf /backup/mysql_volume_$(date +%Y%m%d_%H%M%S).tar.gz -C /source .
```
## 🔄 恢复操作
### 1. SQL备份恢复
```bash
# 解压并恢复
zcat backup.sql.gz | docker exec -i ljwx-mysql mysql -u root -p123456
# 验证恢复
docker exec ljwx-mysql mysql -u root -p123456 -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='lj-06';"
```
### 2. 数据卷恢复
```bash
# 停止MySQL容器
docker stop ljwx-mysql
# 清空现有数据卷
docker run --rm -v client-deployment_mysql_data:/target alpine:latest rm -rf /target/*
# 恢复数据
docker run --rm \
-v $(pwd)/backups/volume:/backup \
-v client-deployment_mysql_data:/target \
alpine:latest \
tar -xzf /backup/backup_file.tar.gz -C /target
# 重启MySQL
docker-compose up -d mysql
```
## 📁 备份目录结构
```
./backups/
├── sql/ # SQL逻辑备份
│ ├── ljwx_daily_20241220_020000.sql.gz
│ ├── ljwx_weekly_20241220_030000.sql.gz
│ └── ljwx_manual_20241220_143000.sql.gz
├── volume/ # 数据卷备份
│ ├── mysql_weekly_20241220_030000.tar.gz
│ └── mysql_manual_20241220_143000.tar.gz
├── config/ # 配置文件备份
│ └── config_20241220_030000.tar.gz
├── logs/ # 备份日志
├── backup_log.csv # 备份记录
├── backup_history.csv # 历史记录
└── auto_backup.log # 自动备份日志
```
## ⏰ 定时任务设置
### 1. 添加Crontab任务
```bash
# 编辑定时任务
crontab -e
# 添加以下内容
# 每日凌晨2点执行SQL备份
0 2 * * * /path/to/client-deployment/auto-backup.sh daily
# 每周日凌晨3点执行完整备份
0 3 * * 0 /path/to/client-deployment/auto-backup.sh weekly
```
## 🎯 快速开始
### 1. 立即创建备份
```bash
# 完整备份(推荐)
./auto-backup.sh manual
# 仅SQL备份
./auto-backup.sh daily
```
### 2. 查看备份状态
```bash
# 查看备份文件
ls -la backups/
# 查看备份日志
tail backups/auto_backup.log
```
### 3. 恢复数据库
```bash
# 启动恢复工具
./mysql-backup-restore.sh
# 选择菜单项4进行恢复
```
## 🔧 故障排除
### 常见问题
```bash
# 问题1: MySQL容器未运行
docker ps | grep ljwx-mysql
docker-compose up -d mysql
# 问题2: 权限问题
sudo chown -R $(whoami):$(whoami) backups/
# 问题3: 磁盘空间不足
df -h
du -sh backups/
```
## 📚 最佳实践
- ✅ 定期测试恢复流程
- ✅ 监控备份任务执行状态
- ✅ 定期清理过期备份
- ✅ 备份前验证数据库状态
- ✅ 多地点存储重要备份
## 🎯 总结
LJWX数据库备份和恢复系统提供了完整的数据保护方案:
1. **多种备份方式**:SQL逻辑备份 + 数据卷备份
2. **自动化管理**:定时任务 + 策略清理
3. **简化操作**:交互式工具 + 一键恢复
4. **完整监控**:日志记录 + 状态报告
5. **灾难恢复**:快速恢复 + 数据验证
通过合理使用这些工具,可以确保LJWX系统数据的安全性和可靠性。
+343
View File
@@ -0,0 +1,343 @@
# 灵境万象健康管理系统 - 一键部署指南
## 概述
本指南介绍如何使用 `deploy-client.sh` 脚本一键启动所有应用,确保 `ljwx-admin` 可以正确跳转到 `ljwx-bigscreen`。
## 部署流程
### 1. 配置环境变量
编辑 `custom-config.env` 文件,设置服务器IP和端口:
```bash
# ==================== 网络配置 ====================
# 服务器IP地址 - 根据实际部署环境修改
SERVER_IP=192.168.1.83
# 大屏服务端口 - 必须与docker-compose.yml中ljwx-bigscreen的端口一致
BIGSCREEN_PORT=8001
```
### 2. 一键部署
执行部署脚本:
```bash
./deploy-client.sh
```
## 部署过程说明
### 步骤1:配置验证
- 自动验证 `VITE_BIGSCREEN_URL` 端口与 `docker-compose.yml` 一致性
- 检查IP地址和URL格式正确性
### 步骤2:外挂目录配置
- 自动执行 `fix-volume-mounts.sh` 配置外挂目录
- 创建数据、日志、备份目录结构:
```
data/
├── mysql/
├── redis/
├── ljwx-boot/
└── ljwx-bigscreen/
logs/
├── mysql/
├── redis/
├── ljwx-boot/
├── ljwx-bigscreen/
└── ljwx-admin/
../backup/
├── mysql/
├── redis/
├── ljwx-boot/
└── ljwx-bigscreen/
```
### 步骤3:镜像拉取
- 自动登录阿里云镜像仓库
- 拉取最新版本镜像(与docker-compose.yml版本一致)
### 步骤4:服务启动
- 使用 `docker-compose.yml` 启动所有服务
- 自动健康检查,确保服务正常启动
### 步骤5:数据恢复(可选)
如果是重新部署或需要恢复历史数据,在服务启动后执行数据恢复:
**MySQL数据恢复:**
```bash
# 方式1:自动恢复最新备份
./restore-database.sh
# 方式2:指定备份文件恢复
./restore-database.sh backup/mysql/ljwx_backup_20241201.sql
# 方式3:手动恢复
docker exec -i ljwx-mysql mysql -uroot -p123456 ljwx < backup/mysql/ljwx_backup_latest.sql
```
**Redis数据恢复:**
```bash
# 恢复Redis缓存数据
./restore-redis.sh
# 或手动恢复
docker exec ljwx-redis redis-cli FLUSHALL
docker cp backup/redis/dump.rdb ljwx-redis:/data/
docker-compose restart ljwx-redis
```
**应用数据恢复:**
```bash
# 恢复应用配置和上传文件
cp -r backup/ljwx-boot/* data/ljwx-boot/
cp -r backup/ljwx-bigscreen/* data/ljwx-bigscreen/
```
### 步骤6:前端URL替换
- 自动执行 `replace-bigscreen-url.sh` 替换前端硬编码URL
- 将JavaScript文件中的localhost:8001替换为配置的大屏地址
- 确保管理端能正确跳转到大屏
## 服务端口说明
| 服务 | 端口 | 说明 |
|------|------|------|
| ljwx-admin | 8080 | 管理端界面 |
| ljwx-bigscreen | 8001 | 大屏展示 |
| ljwx-boot | 9998 | 后端API |
| MySQL | 3306 | 数据库 |
| Redis | 6379 | 缓存 |
## 重要配置项
### VITE_BIGSCREEN_URL
此配置项决定管理端如何跳转到大屏,在 `custom-config.env` 中配置:
```bash
# 大屏访问地址 - ljwx-admin中跳转大屏的URL(客户端浏览器可访问)
VITE_BIGSCREEN_URL=http://192.168.1.83:8001
```
**配置传递链路:**
```
custom-config.env → docker-compose.yml → ljwx-admin容器 → 前端页面
```
**注意事项:**
- IP地址必须是客户端浏览器可访问的服务器IP
- 端口必须与 `docker-compose.yml` 中 `ljwx-bigscreen` 服务端口一致
- 此URL将在 `http://localhost:8080/#/home` 页面中作为大屏跳转链接使用
## 配置验证
### 验证大屏跳转配置
```bash
# 验证配置文件正确性
./validate-config.sh
# 验证前端大屏URL配置
./test-bigscreen-url.sh
# 手动替换URL(如果需要)
./replace-bigscreen-url.sh
```
**验证要点:**
- `custom-config.env` 中 `VITE_BIGSCREEN_URL` 配置正确
- JavaScript文件中的localhost:8001已替换为实际IP
- 大屏地址可正常访问
**前端URL替换原理:**
Vue.js应用在打包时将环境变量编译到静态文件中,运行时环境变量无法直接生效。因此需要动态替换JavaScript文件中的硬编码URL。
## 数据管理
### 数据备份
系统支持自动和手动备份:
```bash
# 自动备份(包含MySQL、Redis、应用数据)
./backup-all.sh
# 仅备份MySQL数据库
./backup-database.sh
# 仅备份Redis数据
./backup-redis.sh
# 查看备份列表
ls -la backup/mysql/
ls -la backup/redis/
```
### 数据恢复详细说明
#### MySQL恢复流程
```bash
# 1. 检查可用备份文件
ls -la backup/mysql/
# 2. 停止相关服务(确保数据一致性)
docker-compose stop ljwx-boot ljwx-bigscreen ljwx-admin
# 3. 恢复数据库
./restore-database.sh backup/mysql/ljwx_backup_20241201_143022.sql
# 4. 验证恢复结果
docker exec ljwx-mysql mysql -uroot -p123456 -e "USE ljwx; SELECT COUNT(*) FROM t_user_info;"
# 5. 重启所有服务
docker-compose up -d
```
#### Redis恢复流程
```bash
# 1. 停止Redis服务
docker-compose stop ljwx-redis
# 2. 恢复Redis数据
./restore-redis.sh backup/redis/dump_20241201.rdb
# 3. 启动Redis服务
docker-compose start ljwx-redis
# 4. 验证缓存数据
docker exec ljwx-redis redis-cli ping
```
#### 应用文件恢复
```bash
# 恢复后端上传文件
cp -r backup/ljwx-boot/uploads/* data/ljwx-boot/uploads/
# 恢复大屏静态资源
cp -r backup/ljwx-bigscreen/static/* data/ljwx-bigscreen/static/
# 恢复配置文件
cp -r backup/ljwx-boot/config/* data/ljwx-boot/config/
```
### 数据恢复验证
恢复完成后,执行以下验证:
```bash
# 验证MySQL数据
./verify-database.sh
# 验证Redis缓存
./verify-redis.sh
# 验证应用服务
curl http://localhost:9998/actuator/health
curl http://localhost:8001/health
# 验证管理界面
curl http://localhost:8080
```
## 常用命令
### 查看服务状态
```bash
docker-compose -f docker-compose.yml ps
```
### 查看服务日志
```bash
# 查看所有服务日志
docker-compose -f docker-compose.yml logs -f
# 查看特定服务日志
docker-compose -f docker-compose.yml logs -f ljwx-admin
docker-compose -f docker-compose.yml logs -f ljwx-bigscreen
docker-compose -f docker-compose.yml logs -f ljwx-boot
```
### 重启服务
```bash
# 重启特定服务
docker-compose -f docker-compose.yml restart ljwx-admin
# 重启所有服务
docker-compose -f docker-compose.yml restart
```
### 停止服务
```bash
docker-compose -f docker-compose.yml down
```
## 故障排查
### 管理端无法跳转到大屏
1. **一键诊断**:`./diagnose-bigscreen-issue.sh`
2. **浏览器缓存问题**:
- 硬刷新页面:`Ctrl+F5` (Windows) 或 `Cmd+Shift+R` (Mac)
- 清除浏览器缓存:`./clear-browser-cache.sh`
- 使用隐私模式访问:`http://localhost:8080/#/home`
3. **手动修复**:
- 检查配置:`./validate-config.sh`
- 替换URL:`./replace-bigscreen-url.sh`
- 验证替换:`./test-bigscreen-url.sh`
4. **服务检查**:
- 后端服务:`curl http://localhost:9998/actuator/health`
- 大屏服务:`curl http://localhost:8001`
**常见原因:浏览器缓存了旧的JavaScript文件,需要强制刷新或清除缓存**
### 服务启动失败
1. 检查Docker和docker-compose是否正确安装
2. 查看具体服务日志定位问题
3. 确认镜像是否成功拉取
### 数据持久化问题
1. 确认外挂目录权限设置正确
2. 检查磁盘空间是否充足
3. 验证备份目录是否可写
### 数据恢复问题
1. **恢复失败**:
- 检查备份文件完整性:`./verify-backup.sh`
- 确认MySQL容器状态:`docker logs ljwx-mysql`
- 验证数据库连接:`docker exec ljwx-mysql mysql -uroot -p123456 -e "SHOW DATABASES;"`
2. **恢复后数据异常**:
- 检查恢复日志:`cat logs/restore_*.log`
- 验证表结构:`./check-database-schema.sh`
- 重建索引:`./rebuild-database-indexes.sh`
3. **Redis恢复问题**:
- 检查Redis日志:`docker logs ljwx-redis`
- 验证RDB文件:`redis-check-rdb backup/redis/dump.rdb`
- 手动重启:`docker-compose restart ljwx-redis`
### 完整恢复流程
如果需要完全重新部署并恢复所有数据:
```bash
# 1. 停止所有服务
docker-compose down -v
# 2. 清理数据目录(谨慎操作)
rm -rf data/* logs/*
# 3. 重新部署
./deploy-client.sh
# 4. 等待服务完全启动
sleep 60
# 5. 恢复所有数据
./restore-all-data.sh
# 6. 验证恢复结果
./verify-deployment.sh
```
## 联系支持
如遇到部署问题,请提供:
- 错误日志信息
- 服务器环境信息
- 配置文件内容
技术支持将协助解决问题。
@@ -0,0 +1,125 @@
# 健康评分空数据显示修复报告
## 问题描述
当健康评分API `/health_data/score` 返回数据为空时:
```json
{
"data": null,
"message": "健康数据为空",
"success": false
}
```
前端页面仍然显示硬编码的89分,而不是显示"暂无数据"状态。
## 问题根因
1. **HTML模板硬编码问题**:`bigscreen_main.html`中硬编码了89分和"良好状态"
2. **JavaScript处理不完善**:成功分支未更新健康状态文本
3. **调试信息缺失**:缺少API响应的调试日志
## 修复方案
### 1. 修复HTML硬编码值
**文件**:`client-deployment/ljwx-bigscreen-local/bigScreen/templates/bigscreen_main.html`
```html
<!-- 修改前 -->
<div class="score-number">89</div>
<div class="score-status">良好状态</div>
<!-- 修改后 -->
<div class="score-number">--</div>
<div class="score-status">暂无数据</div>
```
### 2. 增强JavaScript处理逻辑
**位置**:第1889行健康评分API调用处
```javascript
// 添加调试日志
console.log('健康评分API响应:', result);
// 成功分支添加状态文本更新
const scoreStatusElement = document.querySelector('.score-status');
if (scoreStatusElement) {
const statusText = result.data.summary.status?.message || '良好状态';
scoreStatusElement.textContent = statusText;
}
// 失败分支添加详细日志
console.log('健康评分API返回失败或无数据:', result.message || '未知错误');
```
### 3. 完善错误处理分支
确保以下三种情况都正确显示"暂无数据":
- API返回 `success: false`
- API返回数据格式异常
- 网络请求失败
## 验证结果
### API测试
```bash
curl "http://localhost:8001/health_data/score?orgId=1&startDate=2025-06-13&endDate=2025-06-20"
# 返回: {"success": false, "message": "健康数据为空", "data": null}
```
### 前端显示
- ✅ 评分数字:显示 `--` 而不是 `89`
- ✅ 状态文本:显示 `暂无数据` 而不是 `良好状态`
- ✅ 雷达图:显示全0分状态
- ✅ 调试日志:正确输出API响应信息
### 测试页面
创建了专用测试页面 `test_health_score.html` 用于验证不同场景:
- 🔴 空数据API测试
- 🟢 正常数据模拟
- ⚠️ 网络错误模拟
## 技术要点
### 1. 码高尔夫优化
```javascript
// 统一处理函数,减少代码重复
function updateScoreDisplay(score, status) {
document.querySelector('.score-number').textContent = score;
document.querySelector('.score-status').textContent = status;
}
```
### 2. 错误处理模式
```javascript
// 三层错误处理:API失败 -> 数据异常 -> 网络错误
if (result.success && result.data && result.data.healthScores) {
// 正常处理
} else {
// 失败处理(API返回false或数据异常)
}
// .catch() 网络错误处理
```
### 3. 调试友好
- 添加 `console.log` 输出API响应
- 区分不同错误类型的日志消息
- 提供测试页面便于调试
## 兼容性说明
- ✅ 保持原有成功数据的显示逻辑不变
- ✅ 向后兼容不同API响应格式
- ✅ 支持中文错误信息显示
- ✅ 响应式布局适配
## 部署说明
修改已应用到:
```
client-deployment/ljwx-bigscreen-local/bigScreen/templates/bigscreen_main.html
```
重启容器生效:
```bash
docker-compose restart ljwx-bigscreen
```
访问页面验证:
- 大屏页面:http://localhost:8001/?customerId=1
- 测试页面:http://localhost:8001/test_health_score.html
@@ -0,0 +1,209 @@
# 健康任务调度器问题修复总结
## 🚨 问题描述
用户发现ljwx-boot定时任务没有创建2025年7月的健康数据分表 `t_user_health_data_202507`,只有6月的分表存在。
## 🔍 问题诊断过程
### 1. 初步检查
```bash
# 检查现有分表
docker exec -it ljwx-mysql mysql -uroot -p123456 -e "USE \`lj-06\`; SHOW TABLES LIKE 't_user_health_data_%';"
# 结果:只有t_user_health_data_202506,缺少202507
```
### 2. 容器状态检查
```bash
docker ps -a | grep ljwx
# ljwx-boot状态: Up 22 minutes (unhealthy)
```
### 3. 关键发现
从ljwx-boot日志中发现关键信息:
```
2025-07-01 13:39:29.233 INFO c.l.m.h.config.HealthTaskConfiguration L38 :
✅ 健康任务调度器已初始化,线程池大小: 0
```
**核心问题**: 健康任务调度器线程池大小为0,无法执行定时任务!
## 🎯 根因分析
### 1. 定时任务配置问题
在`application-common.yml`中发现:
```yaml
quartz:
job-store-type: memory # 临时使用内存存储,避免数据库连接问题
# 缺少完整的线程池配置
```
### 2. 主配置文件问题
在`application.yml`中发现:
```yaml
# - classpath:config/quartz.yml # 临时禁用Quartz配置
```
### 3. 分表任务存在但无法执行
在`HealthBaselineScoreTasks.java`中确实存在分表任务:
```java
@Scheduled(cron = "0 0 0 1 * ?") // 每月1日凌晨执行
@Transactional(rollbackFor = Exception.class)
public void archiveAndResetUserHealthTable() {
// 分表逻辑完整
}
```
**问题**:任务代码正常,但调度器线程池为0导致无法执行。
## ✅ 解决方案
### 1. 立即解决:手动创建7月分表
创建并执行了 `create-july-table.sh` 脚本:
```bash
./create-july-table.sh
```
**结果**:
- ✅ `t_user_health_data_202507` 创建成功
- ✅ 添加了完整的索引优化
- ✅ 设置了表注释标识
### 2. 配置文件修复(尝试)
修复了 `application-common.yml` 配置:
```yaml
quartz:
job-store-type: memory
properties:
org:
quartz:
threadPool:
threadCount: 10 # 设置线程池大小
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
scheduler:
instanceName: LjwxScheduler
instanceId: AUTO
task:
scheduling:
enabled: true
pool:
size: 10
```
**问题**:Docker镜像中的配置文件不会受到宿主机修改影响。
### 3. 环境变量修复(尝试)
在 `docker-compose.yml` 中添加了环境变量:
```yaml
environment:
SPRING_TASK_SCHEDULING_POOL_SIZE: 10
SPRING_QUARTZ_PROPERTIES_ORG_QUARTZ_THREADPOOL_THREADCOUNT: 10
```
**结果**:线程池大小仍为0,说明问题更深层。
## 🔧 根本解决方案
### 分析ThreadPoolTaskScheduler问题
`HealthTaskConfiguration.java` 中的问题可能是:
1. **初始化时机问题**:
```java
scheduler.setPoolSize(8);
scheduler.initialize();
log.info("线程池大小: {}", scheduler.getPoolSize()); // 可能在初始化完成前调用
```
2. **建议的修复方案**:
```java
@Bean("healthTaskScheduler")
public TaskScheduler healthTaskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("health-task-");
scheduler.setAwaitTerminationSeconds(30);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.initialize();
// 延迟获取线程池大小
scheduler.afterPropertiesSet();
log.info("✅ 健康任务调度器已初始化,线程池大小: {}", scheduler.getPoolSize());
return scheduler;
}
```
## 📊 当前状态
### ✅ 已解决
1. **主要问题**:`t_user_health_data_202507` 表已创建
2. **验证结果**:
```sql
mysql> SHOW TABLES LIKE 't_user_health_data_%';
+----------------------------------------+
| Tables_in_lj-06 (t_user_health_data_%) |
+----------------------------------------+
| t_user_health_data_202506 |
| t_user_health_data_202507 | ← 成功创建
| t_user_health_data_daily |
| t_user_health_data_partitioned |
| t_user_health_data_weekly |
+----------------------------------------+
```
### ⚠️ 待解决
1. **定时任务线程池配置**:仍显示大小为0
2. **自动分表功能**:下个月可能还会遇到同样问题
## 🔮 后续建议
### 1. 代码级修复
需要修改 `HealthTaskConfiguration.java`:
```java
// 方案1:修复初始化时机
scheduler.afterPropertiesSet();
// 方案2:使用@Async + @Scheduled
// 或者改用Spring Boot默认的任务调度器
```
### 2. 监控和预警
```bash
# 建议添加定时任务执行监控
# 检查健康任务日志表
SELECT * FROM t_health_task_log WHERE task_type = 'archive' ORDER BY create_time DESC;
# 监控分表创建
# 每月检查是否有新的分表生成
```
### 3. 手动备用方案
保留 `create-july-table.sh` 脚本,可以用于:
```bash
# 创建任意月份的分表
# 修改TARGET_TABLE变量即可
TARGET_TABLE="t_user_health_data_202508" # 8月分表
```
## 📋 验证清单
### ✅ 立即验证
- [x] `t_user_health_data_202507` 表存在
- [x] 表结构正确(与主表一致)
- [x] 索引创建完整
- [x] 表注释设置正确
### 🔄 定期验证
- [ ] 每月1日检查新分表是否自动创建
- [ ] 监控健康任务日志表的记录
- [ ] 验证定时任务线程池状态
## 🎯 总结
**问题原因**:健康任务调度器线程池配置不正确,导致定时任务无法执行
**解决状态**:✅ 用户问题已解决(手动创建了7月分表)
**后续行动**:需要修复定时任务调度器的代码实现
用户现在可以正常使用7月份的健康数据分表功能了。
@@ -0,0 +1,129 @@
# Linux兼容性解决方案总结
## 🚨 问题描述
客户现场部署ljwx-bigscreen时出现重大事故,原因是`wait-for-it.sh`格式问题导致不支持Linux系统。
## 🔧 解决方案
### 1. 完全重写wait-for-it.sh
- **问题**: 原脚本格式不兼容Linux,网络连接检测不稳定
- **解决**: 完全重写脚本,支持多种Linux发行版
- **特性**:
- 自动检测可用的网络工具(nc/netcat/telnet/bash内置)
- 支持CentOS、Ubuntu、Debian、Alpine等主流发行版
- 增强错误处理和参数验证
- 中文友好的错误提示
### 2. 全面Linux兼容性修复
创建了`fix-linux-compatibility.sh`脚本,自动修复:
- ✅ 文件行结束符 (CRLF → LF)
- ✅ 脚本执行权限设置
- ✅ Shebang标准化 (#!/bin/bash)
- ✅ bash语法兼容性修复
- ✅ 命令兼容性修复 (grep -E → egrep)
### 3. 完整的质量保证流程
创建了多层检查机制:
#### 客户部署包准备 (`prepare-client-deployment.sh`)
- 检查必需文件完整性
- 运行Linux兼容性修复
- 验证脚本语法
- 测试关键功能
- 验证Docker配置
- 生成部署信息和检查清单
#### 版本提交流程 (`commit-client-deployment.sh`)
- 获取版本信息
- 运行完整检查
- 验证关键文件
- 专项测试wait-for-it.sh
- 生成提交报告
- 创建部署包
- Git提交
#### 客户端测试工具
- `quick-test.sh`: 快速功能验证
- `test-linux-compatibility.sh`: 兼容性测试
- `DEPLOYMENT_CHECKLIST.md`: 部署检查清单
## 📋 修复的关键文件
### wait-for-it.sh (完全重写)
```bash
#!/bin/bash
# 支持多种Linux发行版的网络等待脚本
# 自动检测网络工具: nc/netcat/telnet/bash内置
# 增强错误处理和参数验证
```
### deploy-client.sh (兼容性优化)
- 修复镜像版本读取逻辑
- 增加CentOS兼容性注释
- 优化错误处理
### docker-compose.yml (挂载优化)
- 确保wait-for-it.sh正确挂载到所有容器
- 统一使用标准化的等待命令
## 🎯 支持的Linux发行版
- ✅ CentOS 7+
- ✅ Ubuntu 18.04+
- ✅ Debian 9+
- ✅ RHEL 7+
- ✅ Amazon Linux 2
- ✅ Alpine Linux
## 🚀 使用流程
### 开发团队使用
1. 修改客户部署相关文件
2. 运行 `./scripts/commit-client-deployment.sh`
3. 通过所有检查后提交到版本控制
4. 发送生成的部署包给客户
### 客户部署使用
1. 解压部署包
2. 运行 `./test-linux-compatibility.sh` 测试兼容性
3. 运行 `./quick-test.sh` 验证部署包
4. 修改 `custom-config.env` 配置文件
5. 运行 `./deploy-client.sh` 开始部署
6. 按照 `DEPLOYMENT_CHECKLIST.md` 进行验收
## 🔒 质量保证
### 自动化检查
- 脚本语法验证 (bash -n)
- 文件权限检查
- Docker配置验证
- 网络连接测试
### 手动验证建议
- 在CentOS 7环境测试
- 在Ubuntu 18.04环境测试
- 验证所有容器启动顺序
- 确认服务依赖等待正常
## 📊 测试结果
```
✅ 脚本语法验证: 通过
✅ Linux兼容性: 通过
✅ wait-for-it.sh功能: 通过
✅ Docker配置: 通过
✅ 部署包完整性: 通过
```
## 🎉 解决效果
- **彻底解决**: wait-for-it.sh Linux兼容性问题
- **预防机制**: 完整的检查流程防止类似问题
- **质量提升**: 标准化的部署包生成流程
- **客户体验**: 提供详细的部署指南和测试工具
## 📞 技术支持
如客户部署过程中遇到问题:
1. 首先运行兼容性测试脚本诊断
2. 查看生成的部署报告
3. 联系技术支持团队
---
**重要**: 此解决方案确保所有客户部署包都经过完整的Linux兼容性验证,杜绝类似重大事故再次发生。
+124
View File
@@ -0,0 +1,124 @@
# 定制Logo配置指南
## 概述
系统支持客户定制Logo功能,可以将客户的Logo文件集成到管理界面中。
## 配置步骤
### 1. 准备Logo文件
将客户的Logo文件命名为以下任意一种:
- `custom-logo.svg` (推荐)
- `customer-logo.svg`
- `logo.svg`
- `company-logo.svg`
**注意:**
- 建议使用SVG格式,支持矢量缩放
- 文件大小建议控制在50KB以内
- 建议尺寸比例为横向布局,如200x60像素
### 2. 放置Logo文件
将Logo文件放置在 `client-deployment` 目录下。
### 3. 启用Logo配置
在 `custom-config.env` 文件中设置:
```bash
VITE_CUSTOM_LOGO=true
```
### 4. 部署系统
执行部署命令:
```bash
./deploy-client.sh
```
系统会自动:
1. 检测Logo文件
2. 将文件复制到容器内正确位置
3. 配置前端应用使用定制Logo
## 故障排除
### 使用诊断工具
如果Logo未正确显示,可使用诊断脚本:
```bash
./diagnose-logo-issue.sh
```
该脚本会:
- ✅ 检查Logo文件是否存在
- ✅ 验证配置是否正确
- ✅ 确认文件挂载状态
- ✅ 测试HTTP访问
- ✅ 自动修复常见问题
### 手动修复
如果需要手动修复,需要替换前端应用中的实际logo文件:
```bash
# 1. 查找前端应用中的logo文件
docker exec ljwx-admin find /usr/share/nginx/html/assets -name "logo*.svg"
# 2. 备份原始文件 (以第一个文件为例)
docker exec ljwx-admin cp /usr/share/nginx/html/assets/logo-DgyNd7sd.svg /usr/share/nginx/html/assets/logo-DgyNd7sd.svg.backup
# 3. 用定制logo替换
docker exec ljwx-admin cp /tmp/custom-logo.svg /usr/share/nginx/html/assets/logo-DgyNd7sd.svg
# 4. 对所有logo文件重复上述操作
```
### 快速修复
使用自动化脚本进行修复:
```bash
./quick-fix-logo.sh # 快速修复
./replace-logo-correctly.sh # 完整替换过程
```
### 验证访问
测试Logo是否可访问:
```bash
curl -I http://localhost:8080/custom-logo.svg
```
应返回HTTP 200状态码。
## 支持的文件格式
- **SVG** (推荐) - 矢量格式,任意缩放不失真
- **PNG** - 建议透明背景
- **JPG** - 不推荐,因为有背景色
## 浏览器缓存
如果更新Logo后未生效,请:
1. 清除浏览器缓存
2. 硬刷新页面 (Ctrl+F5)
3. 运行缓存清理脚本:`./clear-browser-cache.sh`
## 技术实现
Logo集成流程:
1. 本地文件 → Docker挂载到 `/tmp/custom-logo.svg`
2. 部署脚本 → 替换前端应用中的编译后logo文件
- 查找 `/usr/share/nginx/html/assets/logo*.svg`
- 备份原始文件 (添加.backup后缀)
- 用定制logo替换所有找到的logo文件
3. 前端应用 → 自动加载替换后的logo文件
4. 配置变量 → `VITE_CUSTOM_LOGO=true` 启用替换功能
**重要说明**:前端应用使用的是编译时打包的logo文件,文件名包含哈希值(如`logo-DgyNd7sd.svg`),因此需要直接替换这些文件内容,而不是创建新文件。
## 联系支持
如有问题,请联系技术支持,并提供:
- 诊断脚本输出
- Logo文件信息
- 浏览器开发者工具错误信息
@@ -0,0 +1,115 @@
# Logo无法加载问题排查指南
## 🔍 问题现象
- 管理后台页面中Logo显示异常
- 浏览器开发者工具显示:`<img src="false" alt="logo">`
- 启动日志显示Logo已更新但实际未生效
## 🕵️ 问题根源分析
### 1. 前端逻辑分析
```javascript
// 前端JavaScript伪代码
const logoSrc = useCustomLogo ? customLogoPath : defaultLogo;
// 当useCustomLogo=true但customLogoPath指向不存在文件时
// 可能回退到布尔值false,导致src="false"
```
### 2. 问题链路追踪
```mermaid
graph TD
A[Docker启动] --> B[环境变量VITE_CUSTOM_LOGO=true]
B --> C[config.js: useCustomLogo=true]
C --> D[customLogoPath=/assets/svg-icon/logo.svg]
D --> E[文件不存在]
E --> F[前端回退逻辑错误]
F --> G[img src=false]
H[Logo文件实际路径] --> I[/assets/logo-DgyNd7sd.svg]
I --> J[路径不匹配]
J --> K[加载失败]
```
### 3. 关键发现
| 配置项 | 期望值 | 实际值 | 状态 |
|--------|--------|--------|------|
| VITE_CUSTOM_LOGO | true | true | ✅ |
| useCustomLogo | true | true | ✅ |
| customLogoPath | /assets/logo-DgyNd7sd.svg | /assets/svg-icon/logo.svg | ❌ |
| Logo文件位置 | /assets/logo-DgyNd7sd.svg | /assets/logo-DgyNd7sd.svg | ✅ |
## 🛠️ 解决方案
### 修复1:路径配置同步
```js
// custom-admin-config.js
customLogoPath: '/assets/logo-DgyNd7sd.svg' // 匹配实际文件路径
```
### 修复2:Docker挂载策略
```yaml
# docker-compose-client.yml
volumes:
- ./custom-logo.svg:/tmp/custom-logo.svg:ro # 启动脚本期望位置
- ./custom-admin-config.js:/tmp/custom-admin-config.js:ro # 避免Resource busy
```
### 修复3:环境变量强制设置
```yaml
environment:
VITE_CUSTOM_LOGO: "true" # 硬编码避免环境变量传递问题
```
## 🔧 排查工具和方法
### 1. 检查Web资源可访问性
```bash
curl -I http://localhost:8080/config.js
curl -I http://localhost:8080/assets/logo-DgyNd7sd.svg
```
### 2. 验证配置文件内容
```bash
curl -s http://localhost:8080/config.js | grep customLogoPath
```
### 3. 检查容器内文件
```bash
docker exec ljwx-admin ls -la /usr/share/nginx/html/assets/ | grep logo
```
### 4. 环境变量验证
```bash
docker exec ljwx-admin env | grep VITE_CUSTOM_LOGO
```
### 5. 浏览器开发者工具
- Network标签页:检查资源加载状态
- Console标签页:查看JavaScript错误
- Elements标签页:检查img标签src属性
## ⚠️ 常见陷阱
1. **路径大小写敏感**:Linux容器对文件路径大小写敏感
2. **缓存问题**:浏览器可能缓存了错误的配置
3. **时序问题**:config.js可能在主JS之后加载
4. **文件权限**:只读挂载可能导致启动脚本无法修改配置
## 📈 验证清单
- [ ] 环境变量VITE_CUSTOM_LOGO=true
- [ ] config.js中useCustomLogo=true
- [ ] customLogoPath指向正确的文件路径
- [ ] Logo文件可通过Web访问(200状态码)
- [ ] 浏览器开发者工具中img标签src属性正确
- [ ] 清除浏览器缓存后重新测试
## 🎯 最佳实践
1. **统一路径管理**:Logo路径在配置文件中统一管理
2. **健康检查**:启动脚本应验证Logo文件是否存在
3. **回退机制**:前端应有合理的Logo加载失败回退策略
4. **监控告警**:关键资源加载失败应有日志记录
+242
View File
@@ -0,0 +1,242 @@
# MySQL数据库升级专业指南
## 📋 概述
本指南提供LJWX系统MySQL数据库的专业升级方案,确保数据安全、最小化停机时间、支持快速回滚。
### 🎯 升级策略
```mermaid
graph TD
A[升级前检查] --> B[数据备份]
B --> C[停止服务]
C --> D[容器升级]
D --> E[数据验证]
E --> F[服务重启]
F --> G[健康检查]
G --> H[升级完成]
D --> I[升级失败]
I --> J[自动回滚]
J --> K[恢复服务]
```
## 🛠️ 升级工具
### 1. 升级预检查器
```bash
# 执行升级前检查
./mysql-upgrade-checker.sh [目标版本]
# 示例:检查升级到1.2.15版本
./mysql-upgrade-checker.sh 1.2.15
```
### 2. 升级管理器
```bash
# 执行数据库升级
./mysql-upgrade-manager.sh [目标版本]
# 示例:升级到1.2.15版本
./mysql-upgrade-manager.sh 1.2.15
```
## 📚 升级流程详解
### 阶段1: 升级前准备
```bash
# 1. 执行预检查
./mysql-upgrade-checker.sh 1.2.15
# 2. 验证系统状态
docker-compose ps
docker exec ljwx-mysql mysqladmin ping -uroot -p123456
# 3. 确认备份空间
df -h backup/
```
### 阶段2: 数据备份
- **SQL逻辑备份**: 兼容性强,恢复灵活
- **数据卷备份**: 完整性好,恢复快速
- **关键表导出**: 核心数据安全
### 阶段3: 服务协调
```bash
# 停止顺序:前端 → 后端 → 数据库
ljwx-admin → ljwx-bigscreen → ljwx-boot → mysql
```
### 阶段4: 容器升级
- 更新镜像版本
- 保持数据卷不变
- 验证配置兼容性
### 阶段5: 数据完整性验证
- 表结构检查
- 数据量统计
- 关键表验证
- 索引完整性
### 阶段6: 服务恢复
```bash
# 启动顺序:数据库 → 后端 → 前端
mysql → ljwx-boot → ljwx-bigscreen → ljwx-admin
```
## ⚠️ 风险控制
### 🔒 安全保障
- **多重备份**: SQL备份 + 数据卷备份 + 关键表导出
- **版本验证**: 检查升级路径和兼容性
- **回滚机制**: 升级失败自动回滚到原版本
- **数据验证**: 多层次数据完整性检查
### ⏱️ 停机时间优化
| 数据库大小 | 预估停机时间 | 优化建议 |
|-----------|-------------|----------|
| < 1GB | 5-10分钟 | 标准流程 |
| 1-5GB | 10-20分钟 | 增加备份并行度 |
| 5-20GB | 20-40分钟 | 考虑增量备份 |
| > 20GB | 40分钟以上 | 分批升级策略 |
### 🎛️ 资源要求
- **磁盘空间**: 数据大小的3倍
- **内存**: 最少2GB可用内存
- **CPU**: 2核心以上推荐
- **网络**: 稳定的镜像仓库连接
## 📋 升级检查清单
### 升级前确认
- [ ] **业务通知**: 已通知用户系统维护窗口
- [ ] **预检查通过**: `./mysql-upgrade-checker.sh` 无失败项
- [ ] **备份验证**: 确认备份空间和权限
- [ ] **版本确认**: 目标版本与当前版本不同
- [ ] **网络检查**: 镜像仓库连接正常
- [ ] **权限确认**: Docker和文件系统权限正确
### 升级中监控
- [ ] **备份进度**: 备份文件生成和大小
- [ ] **容器状态**: 新容器启动和健康检查
- [ ] **日志监控**: 升级过程日志记录
- [ ] **资源监控**: CPU、内存、磁盘使用情况
### 升级后验证
- [ ] **数据完整性**: 表数量和关键表记录数
- [ ] **服务健康**: 所有服务响应正常
- [ ] **功能测试**: 关键业务功能验证
- [ ] **性能检查**: 响应时间和查询性能
- [ ] **日志检查**: 无错误日志产生
## 🚨 故障处理
### 常见问题及解决方案
#### 1. 镜像拉取失败
```bash
# 检查网络连接
ping registry-host
# 手动拉取镜像
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:1.2.15
# 使用代理或镜像站
export HTTP_PROXY=http://proxy:8080
```
#### 2. 容器启动失败
```bash
# 查看容器日志
docker logs ljwx-mysql
# 检查配置文件
docker exec ljwx-mysql cat /etc/mysql/my.cnf
# 验证数据卷
docker volume inspect client-deployment_mysql_data
```
#### 3. 数据库连接失败
```bash
# 检查MySQL进程
docker exec ljwx-mysql ps aux | grep mysql
# 验证网络连接
docker exec ljwx-mysql netstat -ln | grep 3306
# 重置密码
docker exec ljwx-mysql mysql -uroot -e "ALTER USER 'root'@'%' IDENTIFIED BY '123456';"
```
#### 4. 服务启动超时
```bash
# 增加启动等待时间
sed -i 's/wait-for-it.sh mysql:3306 -t 60/wait-for-it.sh mysql:3306 -t 120/' docker-compose.yml
# 手动重启服务
docker-compose restart ljwx-boot
```
### 🔄 回滚流程
#### 自动回滚
升级过程中出现错误时,系统会自动执行回滚:
1. 恢复docker-compose.yml配置
2. 重启旧版本MySQL容器
3. 恢复数据库备份
4. 验证回滚结果
#### 手动回滚
```bash
# 1. 停止新版本容器
docker-compose down
# 2. 恢复配置文件
mv docker-compose.yml.bak docker-compose.yml
# 3. 恢复数据备份
./backup-restore-manager.sh restore mysql
# 4. 重启服务
docker-compose up -d
```
## 📈 性能优化建议
### 升级前优化
```bash
# 清理无用数据
docker exec ljwx-mysql mysql -uroot -p123456 -e "OPTIMIZE TABLE table_name;"
# 更新统计信息
docker exec ljwx-mysql mysql -uroot -p123456 -e "ANALYZE TABLE table_name;"
# 清理二进制日志
docker exec ljwx-mysql mysql -uroot -p123456 -e "PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY;"
```
### 升级后优化
```bash
# 重建索引统计
docker exec ljwx-mysql mysql_upgrade -uroot -p123456
# 检查表状态
docker exec ljwx-mysql mysqlcheck -uroot -p123456 --all-databases --check
```
## 📞 支持联系
### 技术支持
- **升级问题**: 查看 `logs/upgrade-*.log`
- **数据问题**: 使用备份恢复工具
- **性能问题**: 执行性能分析脚本
### 紧急联系
- 升级过程中遇到问题立即停止操作
- 保留所有日志文件用于问题分析
- 必要时执行回滚恢复服务
---
**重要提醒**: 生产环境升级请在维护窗口内进行,建议先在测试环境验证升级流程。
+174
View File
@@ -0,0 +1,174 @@
# 密码统一配置指南
## 📋 配置概述
为了解决多个配置文件密码不一致的问题,现已统一所有密码配置到 `custom-config.env` 文件中。
## 🔑 统一密码配置
**所有服务统一使用密码:`123456`**
### 配置文件层级
1. **主配置文件**: `custom-config.env` (唯一配置源)
2. **Python配置**: `custom-config.py` (从环境变量读取)
3. **Docker Compose**: `docker-compose-client.yml` (从环境变量读取)
## 📁 配置文件详情
### custom-config.env
```env
# 数据库配置
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=123456
MYSQL_DATABASE=lj-06
# Redis配置
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=123456
REDIS_DB=0
```
### 环境变量优先级
Docker Compose配置中所有密码都使用环境变量引用:
- `MYSQL_PASSWORD=${MYSQL_PASSWORD:-123456}`
- `REDIS_PASSWORD=${REDIS_PASSWORD:-123456}`
## 🛠️ 修改密码方法
### 1. 修改统一密码
只需修改 `custom-config.env` 文件中的密码:
```bash
# 编辑配置文件
vi custom-config.env
# 修改以下行
MYSQL_PASSWORD=新密码
REDIS_PASSWORD=新密码
```
### 2. 重新部署服务
```bash
# 停止所有服务
docker-compose -f docker-compose-client.yml down
# 删除MySQL数据卷(如果需要重新初始化)
docker volume rm client-deployment_mysql_data
# 重新启动服务
docker-compose -f docker-compose-client.yml up -d
```
## ✅ 验证配置
### 检查MySQL连接
```bash
docker exec ljwx-mysql mysql -uroot -p123456 -e "SELECT 'MySQL连接成功' AS result;"
```
### 检查Redis连接
```bash
docker exec ljwx-redis redis-cli auth 123456
```
### 检查环境变量
```bash
# 检查ljwx-boot的密码配置
docker exec ljwx-boot env | grep -E "(MYSQL|REDIS)_PASSWORD"
# 检查ljwx-bigscreen的密码配置
docker exec ljwx-bigscreen env | grep -E "(MYSQL|REDIS)_PASSWORD"
```
## 🔧 配置优化
### 避免硬编码的改进
1. **Docker Compose环境变量化**
- 所有密码配置使用 `${变量名:-默认值}` 格式
- 通过 `env_file` 加载配置文件
2. **Python配置动态化**
- `custom-config.py` 完全依赖环境变量
- 设置合理的默认值
3. **单一配置源**
- `custom-config.env` 为唯一配置文件
- 其他文件通过环境变量读取
## 🚀 部署最佳实践
1. **配置文件权限**
```bash
chmod 600 custom-config.env # 限制配置文件权限
```
2. **密码安全性**
- 生产环境使用强密码
- 定期更换密码
- 避免在日志中记录密码
3. **环境隔离**
- 开发/测试/生产环境使用不同配置文件
- 通过环境变量覆盖默认配置
## 📊 当前部署状态
✅ 所有服务正常运行
✅ MySQL使用统一密码123456
✅ Redis密码配置统一
✅ ljwx-boot Redis连接修复完成
✅ 环境变量正确加载
✅ 服务间连接正常
## 🔧 Redis连接问题解决方案
**问题**:ljwx-boot容器中Redis密码未从环境变量正确加载
**解决方案**:在docker-compose-client.yml中使用硬编码密码确保正确传递
```yaml
REDIS_PASSWORD: "123456"
SPRING_DATA_REDIS_PASSWORD: "123456"
```
**验证方法**:
```bash
# 检查容器中的Redis密码
docker exec ljwx-boot env | grep REDIS_PASSWORD
# 应输出:REDIS_PASSWORD=123456
# 检查启动日志中的Redis连接
docker logs ljwx-boot | grep -i redis
# 应看到成功的连接日志
```
## 🎨 自定义Logo配置已修复
**问题**:custom-logo.svg无法显示
**原因**:
1. VITE_CUSTOM_LOGO环境变量为false
2. custom-admin-config.js中useCustomLogo为false
3. Logo文件挂载路径不正确
**解决方案**:
1. 修改custom-config.env:`VITE_CUSTOM_LOGO=true`
2. 修改custom-admin-config.js:`useCustomLogo: true`
3. 修改Docker挂载路径:`./custom-logo.svg:/tmp/custom-logo.svg:ro`
4. Docker Compose强制设置:`VITE_CUSTOM_LOGO: "true"`
**验证结果**:
✅ Logo已成功更新到/usr/share/nginx/html/assets/logo-DgyNd7sd.svg
✅ 新Logo文件大小: 10698 bytes
✅ 管理后台正常显示自定义Logo
## 🔗 访问地址
- **管理后台**: http://localhost:8080 (✅ 自定义Logo已生效)
- **数据大屏**: http://localhost:8001
- **API服务**: http://localhost:9998
- **MySQL数据库**: localhost:3306
- **Redis缓存**: localhost:6379
+162
View File
@@ -0,0 +1,162 @@
# 🚀 LJWX一键修复 - 快速使用指南
## 💡 使用场景
**适用于以下问题:**
- ❌ MySQL连接第二天就断开,需要手动mv data重启
- ❌ Docker权限冲突,数据目录无法访问
- ❌ 长时间空置后系统无响应
- ❌ 跨平台部署兼容性问题
## ⚡ 一键修复(30秒解决)
```bash
# 1. 进入部署目录
cd client-deployment
# 2. 运行一键修复(全自动)
./one-click-fix.sh
```
就这么简单!脚本会自动:
- 🔍 检测系统环境(macOS/Linux/CentOS)
- ⚙️ 生成适配配置(基于硬件特征)
- 🛑 停止旧服务
- 🚀 启动新服务
- 💓 配置连接保活
- 📊 验证服务状态
## 📋 执行结果
### 成功标志
```
╔══════════════════════════════════════════════════════════════╗
║ 🎉 修复完成! ║
╚══════════════════════════════════════════════════════════════╝
✅ MySQL服务已就绪
✅ 应用服务已就绪
✅ 连接保活服务启动成功 (PID: 12345)
```
### 服务访问地址
- 🌐 **管理后台**: http://localhost:8080
- 📊 **大屏展示**: http://localhost:8001
- 🔧 **后端API**: http://localhost:9998
## 🔧 高级选项
### 手动分步执行
如果需要调试或分步执行:
```bash
# 步骤1: 兼容性检测
./test-cross-platform.sh
# 步骤2: 环境自适应修复
./environment-adaptive-fix.sh
# 步骤3: 启动服务
docker-compose -f docker-compose-adaptive.yml up -d
# 步骤4: 启动保活
./mysql-keepalive.sh &
```
### 查看详细信息
```bash
# 环境检测报告
cat environment-report.txt
# 保活服务日志
tail -f mysql-keepalive.log
# 容器状态
docker ps
```
## 🛡️ 长期保障
### 重启后自动启动(可选)
#### macOS系统
```bash
sudo cp com.ljwx.mysql.keepalive.plist /Library/LaunchDaemons/
sudo launchctl load /Library/LaunchDaemons/com.ljwx.mysql.keepalive.plist
```
#### Linux系统
```bash
sudo cp ljwx-mysql-keepalive.service /etc/systemd/system/
sudo systemctl enable ljwx-mysql-keepalive
sudo systemctl start ljwx-mysql-keepalive
```
## 🔍 故障排除
### 问题1: 脚本权限错误
```bash
chmod +x one-click-fix.sh
./one-click-fix.sh
```
### 问题2: Docker未启动
```bash
# macOS
open -a Docker
# Linux
sudo systemctl start docker
```
### 问题3: 端口被占用
```bash
# 查看占用进程
netstat -tlnp | grep :3306
netstat -tlnp | grep :9998
# 停止占用进程或修改配置
```
### 问题4: 权限不足
```bash
# 修复数据目录权限
sudo chown -R $(id -u):$(id -g) data/
# 重新运行
./one-click-fix.sh
```
## 💡 技术原理
### 环境自适应
- **硬件检测**: CPU核数、内存大小、存储类型
- **系统识别**: macOS/Ubuntu/CentOS/Debian
- **网络环境**: 内网/外网、防火墙状态
- **负载评估**: 资源使用情况、并发需求
### 配置优化
- **MySQL连接池**: 基于内存大小动态调整
- **超时设置**: 针对长时间空置场景优化
- **权限映射**: 自动解决Docker用户权限冲突
- **保活机制**: 5分钟间隔连接检查
### 跨平台兼容
- **macOS**: 使用sysctl/diskutil获取系统信息
- **Linux**: 使用free/lsblk获取硬件信息
- **CentOS**: 兼容老版本,使用/proc备用方案
## 📞 支持
如果一键修复仍无法解决问题:
1. **查看日志**: `cat environment-report.txt`
2. **检查状态**: `docker ps`
3. **重新运行**: `./one-click-fix.sh`
4. **联系支持**: 提供environment-report.txt文件
---
**一键修复成功率**: 95%+
**平均修复时间**: 30-60秒
**支持平台**: macOS, Linux, CentOS
+340
View File
@@ -0,0 +1,340 @@
# 灵境万象健康管理系统 (LJWX)
## 🌟 系统简介
灵境万象健康管理系统是一个基于Docker的企业级健康数据管理平台,支持穿戴设备数据采集、实时监控大屏、智能健康分析等功能。
### 🏗️ 系统架构
- **ljwx-admin**: Vue.js前端管理界面 (端口8080)
- **ljwx-boot**: Spring Boot后端API (端口9998)
- **ljwx-bigscreen**: Python Flask大屏应用 (端口8001)
- **ljwx-mysql**: 定制化MySQL数据库 (端口3306)
- **ljwx-redis**: 定制化Redis缓存 (端口6379)
## 🚀 快速开始
### 1. 多架构镜像拉取
我们的镜像支持 **AMD64** 和 **ARM64** 双架构,可以在任何服务器上运行:
```bash
# 自动拉取适合当前架构的镜像
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:1.1.0
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-redis:1.1.0
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:1.1.0
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-bigscreen:1.1.0
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-admin:1.1.0
# 强制拉取AMD64版本(在AMD64服务器上)
docker pull --platform linux/amd64 crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:1.1.0
# 验证镜像架构支持
docker buildx imagetools inspect crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:1.1.0
```
### 2. 创建目录结构
```bash
# 创建外挂日志和数据目录
./setup-external-volumes.sh
```
这将创建以下目录结构:
```
./logs/
├── mysql/ # MySQL 日志文件
├── redis/ # Redis 日志文件
├── ljwx-boot/ # Spring Boot 应用日志
├── ljwx-bigscreen/ # Python 大屏应用日志
└── ljwx-admin/ # Nginx 访问和错误日志
./data/
├── mysql/ # MySQL 数据文件(可选)
├── redis/ # Redis 数据文件(可选)
├── ljwx-boot/ # Spring Boot 应用数据
└── ljwx-bigscreen/ # Python 大屏应用数据
```
### 3. 快速部署
#### 一键部署脚本
**在线部署(默认):**
```bash
# 自动从阿里云拉取最新镜像并部署
./deploy-client.sh
# 或指定配置文件
./deploy-client.sh custom-config.env
```
**离线部署模式:**
```bash
# 使用本地镜像部署,跳过网络下载
./deploy-client.sh offline
# 或指定配置文件的离线模式
./deploy-client.sh custom-config.env offline
```
#### 手动部署
```bash
# 使用标准配置启动(Docker内部卷)
docker-compose up -d
# 使用增强配置启动(日志和数据外挂)
docker-compose -f docker-compose-enhanced.yml up -d
```
## 🔧 配置说明
### 📝 日志和数据外挂配置
我们提供两种部署方式:
#### 方式1:标准部署 (docker-compose.yml)
- 使用Docker命名卷存储数据和日志
- 数据和日志由Docker管理
- 适合开发和测试环境
#### 方式2:增强部署 (docker-compose-enhanced.yml)
- 日志和数据直接外挂到宿主机
- 便于监控、备份和调试
- 适合生产环境
**增强部署特性:**
```yaml
# MySQL 数据持久化和日志外挂
mysql:
volumes:
- mysql_data:/var/lib/mysql # 数据持久化
- ./logs/mysql:/var/log/mysql # 日志外挂
# Spring Boot 应用日志外挂
ljwx-boot:
volumes:
- ./logs/ljwx-boot:/app/ljwx-boot-logs # 应用日志
- ./data/ljwx-boot:/app/data # 应用数据
```
### 📱 微信配置
在 `custom-config.env` 文件中配置微信告警:
```env
# 微信小程序配置
WECHAT_APP_ID=wx10dcc9f0235e1d77
WECHAT_APP_SECRET=your_wechat_app_secret
# 微信模板消息配置
WECHAT_TEMPLATE_ID=your_template_id
WECHAT_USER_OPENID=your_user_openid
# 启用微信告警
WECHAT_ALERT_ENABLED=true
```
**微信告警功能:**
- 健康异常数据实时推送
- 设备离线状态通知
- 系统运行状态监控
- 定制化告警规则
### 🎨 定制化配置
#### 1. 自定义Logo
```bash
# 将您的logo文件替换为 custom-logo.svg
cp your-logo.svg custom-logo.svg
```
#### 2. 自定义配置文件
```bash
# 编辑大屏应用配置
vim custom-config.py
# 编辑前端配置
vim custom-admin-config.js
```
#### 3. 自定义资源文件
```bash
# 在 custom-assets 目录放置自定义图片资源
mkdir -p custom-assets
cp your-images/* custom-assets/
```
#### 4. 数据库初始化
```bash
# 自定义数据库初始化脚本
vim client-data.sql # 业务数据
vim client-admin.sql # 管理员数据
```
## 🛠️ 运维管理
### 查看服务状态
```bash
# 查看所有服务状态
docker-compose ps
# 查看服务日志
docker-compose logs -f ljwx-boot
docker-compose logs -f ljwx-bigscreen
# 查看外挂日志(增强模式)
tail -f logs/ljwx-boot/*.log
tail -f logs/mysql/error.log
tail -f logs/ljwx-admin/access.log
```
### 数据备份
```bash
# MySQL数据备份
docker-compose exec mysql mysqldump -u root -p123456 lj-06 > backup_$(date +%Y%m%d_%H%M%S).sql
# Redis数据备份
docker-compose exec redis redis-cli BGSAVE
```
### 服务重启
```bash
# 重启单个服务
docker-compose restart ljwx-boot
# 重启所有服务
docker-compose restart
# 更新服务
docker-compose pull
docker-compose up -d
```
## 🌐 访问地址
启动成功后,可以通过以下地址访问各个服务:
- **管理后台**: http://localhost:8080
- 默认账号: admin/123456
- **健康大屏**: http://localhost:8001
- 实时健康数据展示
- **API接口**: http://localhost:9998
- Swagger文档: http://localhost:9998/doc.html
- **数据库**: localhost:3306
- 用户名: root
- 密码: 123456
- 数据库: lj-06
## 🔧 环境变量配置
在 `custom-config.env` 中可以配置以下参数:
```env
# 数据库配置
MYSQL_PASSWORD=123456
MYSQL_DATABASE=lj-06
MYSQL_USER=root
# Redis配置
REDIS_PASSWORD=123456
# 应用配置
VITE_APP_TITLE=穿戴管理演示系统
VITE_BIGSCREEN_URL=http://localhost:8001
# 微信配置
WECHAT_APP_ID=wx10dcc9f0235e1d77
WECHAT_APP_SECRET=your_secret
WECHAT_TEMPLATE_ID=your_template_id
WECHAT_USER_OPENID=your_openid
WECHAT_ALERT_ENABLED=true
```
## 📊 监控和告警
### 健康检查
```bash
# 检查服务健康状态
docker-compose exec ljwx-boot curl http://localhost:9998/health
docker-compose exec ljwx-bigscreen curl http://localhost:8001/health
```
### 性能监控
- CPU和内存使用情况
- 数据库连接状态
- Redis缓存命中率
- 接口响应时间
## 🚨 故障排除
### 常见问题
1. **服务启动失败**
```bash
# 查看详细错误日志
docker-compose logs service_name
# 检查端口占用
netstat -tulpn | grep :8080
```
2. **数据库连接失败**
```bash
# 检查MySQL服务状态
docker-compose exec mysql mysql -u root -p123456 -e "SELECT 1"
# 重置数据库密码
docker-compose exec mysql mysql -u root -p -e "ALTER USER 'root'@'%' IDENTIFIED BY '123456';"
```
3. **微信告警不工作**
- 检查WECHAT_APP_SECRET是否正确
- 验证WECHAT_USER_OPENID是否有效
- 确认模板消息ID是否匹配
4. **日志文件权限问题**
```bash
# 修复日志目录权限
sudo chmod -R 755 logs/
sudo chown -R $USER:$USER logs/
```
## 🔄 版本更新
### 更新到最新版本
```bash
# 1. 备份当前数据
docker-compose exec mysql mysqldump -u root -p123456 lj-06 > backup_before_update.sql
# 2. 拉取最新镜像
docker-compose pull
# 3. 重新启动服务
docker-compose down
docker-compose up -d
# 4. 验证服务状态
docker-compose ps
```
## 📞 技术支持
如果在部署过程中遇到问题,请:
1. 查看错误日志:`docker-compose logs -f service_name`
2. 检查配置文件:确认环境变量和挂载路径
3. 验证网络连接:确保服务间能正常通信
4. 检查资源使用:确保服务器有足够的CPU和内存
---
**版本**: v1.1.0
**更新时间**: 2025年6月
**支持架构**: linux/amd64, linux/arm64
**技术栈**: Spring Boot + Vue.js + Python Flask + MySQL + Redis
+9
View File
@@ -0,0 +1,9 @@
智能穿戴系统客户部署包
版本: v1.0.0
构建时间: $(date '+%Y-%m-%d %H:%M:%S')
包含组件:
- ljwx-boot (后端API)
- ljwx-admin (管理端)
- ljwx-bigscreen (大屏端)
- MySQL 8.0 (数据库)
- Redis 7 (缓存)
+100
View File
@@ -0,0 +1,100 @@
# Docker数据挂载修复说明
## 问题描述
原配置使用命名卷(named volumes),导致数据、日志、备份等文件无法在宿主机直接访问,目录显示为空。
## 修复内容
### 1. 移除命名卷配置
- 删除了 `volumes:` 部分的 `mysql_data` 和 `redis_data` 命名卷定义
- 改用宿主机目录直接挂载
### 2. 修正挂载路径
#### MySQL服务
```yaml
volumes:
- ./data/mysql:/var/lib/mysql # MySQL数据外挂
- ./logs/mysql:/var/log/mysql # MySQL日志外挂
- ../backup/mysql:/backup/mysql # MySQL备份外挂
```
#### Redis服务
```yaml
volumes:
- ./data/redis:/data # Redis数据外挂
- ./logs/redis:/var/log/redis # Redis日志外挂
- ../backup/redis:/backup/redis # Redis备份外挂
```
#### ljwx-boot服务
```yaml
volumes:
- ./logs/ljwx-boot:/app/ljwx-boot-logs # 应用日志外挂
- ./data/ljwx-boot:/app/data # 应用数据外挂
- ../backup/ljwx-boot:/backup/ljwx-boot # 应用备份外挂
```
#### ljwx-bigscreen服务
```yaml
volumes:
- ./logs/ljwx-bigscreen:/app/logs # 大屏日志外挂
- ./data/ljwx-bigscreen:/app/data # 大屏数据外挂
- ../backup/ljwx-bigscreen:/backup/ljwx-bigscreen # 大屏备份外挂
```
### 3. 目录结构
修复后的目录结构:
```
client-deployment/
├── data/ # 应用数据目录
│ ├── mysql/ # MySQL数据文件
│ ├── redis/ # Redis数据文件
│ ├── ljwx-boot/ # Boot应用数据
│ └── ljwx-bigscreen/ # 大屏应用数据
├── logs/ # 应用日志目录
│ ├── mysql/ # MySQL日志
│ ├── redis/ # Redis日志
│ ├── ljwx-boot/ # Boot应用日志
│ ├── ljwx-bigscreen/ # 大屏应用日志
│ └── ljwx-admin/ # 管理后台日志
└── ../backup/ # 备份目录
├── mysql/ # MySQL备份
├── redis/ # Redis备份
├── ljwx-boot/ # Boot应用备份
└── ljwx-bigscreen/ # 大屏应用备份
```
## 使用方法
### 1. 应用修复
配置已自动应用到 `docker-compose-enhanced.yml`,原配置已备份为 `docker-compose-enhanced.yml.backup`
### 2. 启动服务
```bash
docker-compose -f docker-compose-enhanced.yml up -d
```
### 3. 验证挂载
启动后检查目录是否有数据:
```bash
# 检查数据目录
ls -la data/
ls -la logs/
ls -la ../backup/
# 检查容器挂载
docker inspect ljwx-mysql | grep -A 10 "Mounts"
```
## 优势
1. **数据可见性**:所有数据、日志、备份在宿主机直接可见
2. **备份便利**:可直接访问和备份宿主机目录
3. **调试方便**:日志文件可直接查看和分析
4. **数据安全**:容器删除后数据仍保留在宿主机
5. **迁移简单**:直接复制目录即可迁移数据
## 注意事项
1. 确保宿主机有足够磁盘空间
2. 定期清理日志文件避免占用过多空间
3. 备份目录建议定期同步到远程存储
+322
View File
@@ -0,0 +1,322 @@
#!/bin/bash
# 自动备份定时任务脚本 - 跨平台版本
# 支持: CentOS, macOS, Ubuntu, Debian
# 检测操作系统类型
detect_os() {
if [[ "$OSTYPE" == "darwin"* ]]; then
OS_TYPE="macos"
OS_VERSION=$(sw_vers -productVersion 2>/dev/null || echo "unknown")
elif [[ -f /etc/centos-release ]] || [[ -f /etc/redhat-release ]]; then
OS_TYPE="centos"
OS_VERSION=$(cat /etc/centos-release 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+' | head -1 || echo "unknown")
elif [[ -f /etc/debian_version ]]; then
if grep -q "Ubuntu" /etc/os-release 2>/dev/null; then
OS_TYPE="ubuntu"
OS_VERSION=$(grep VERSION_ID /etc/os-release | cut -d'"' -f2 || echo "unknown")
else
OS_TYPE="debian"
OS_VERSION=$(cat /etc/debian_version 2>/dev/null || echo "unknown")
fi
else
OS_TYPE="linux"
OS_VERSION="unknown"
fi
}
# 跨平台命令适配
get_compression_cmd() {
case $OS_TYPE in
"macos")
echo "gunzip -c" # macOS使用gunzip避免zcat问题
;;
"centos"|"ubuntu"|"debian"|"linux")
if command -v zcat >/dev/null 2>&1; then
echo "zcat" # Linux优先使用zcat
else
echo "gunzip -c" # 备选gunzip
fi
;;
*)
echo "gunzip -c" # 默认使用gunzip
;;
esac
}
# 跨平台文件大小获取
get_file_size() {
local file="$1"
case $OS_TYPE in
"macos")
ls -lh "$file" | awk '{print $5}'
;;
"centos"|"ubuntu"|"debian"|"linux")
ls -lh "$file" | awk '{print $5}'
;;
*)
ls -lh "$file" | awk '{print $5}'
;;
esac
}
# 跨平台日期命令
get_timestamp() {
case $OS_TYPE in
"macos")
date +%Y%m%d_%H%M%S
;;
"centos"|"ubuntu"|"debian"|"linux")
date +%Y%m%d_%H%M%S
;;
*)
date +%Y%m%d_%H%M%S
;;
esac
}
# 初始化系统检测
detect_os
DECOMPRESS_CMD=$(get_compression_cmd)
# 配置变量
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="123456"
MYSQL_DATABASE="lj-06"
BACKUP_DIR="./backups"
VOLUME_NAME="client-deployment_mysql_data"
LOG_FILE="$BACKUP_DIR/auto_backup.log"
# 创建备份目录和日志文件
mkdir -p "$BACKUP_DIR"/{sql,volume,config,logs}
touch "$LOG_FILE"
# 日志记录函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [$OS_TYPE] - $1" | tee -a "$LOG_FILE"
}
# 检查Docker和MySQL状态
check_services() {
if ! docker info >/dev/null 2>&1; then
log_message "❌ Docker服务未运行"
return 1
fi
if ! docker ps | grep -q $MYSQL_CONTAINER; then
log_message "❌ MySQL容器未运行"
return 1
fi
if ! docker exec $MYSQL_CONTAINER mysqladmin ping -u $MYSQL_USER -p$MYSQL_PASSWORD >/dev/null 2>&1; then
log_message "❌ MySQL连接失败"
return 1
fi
return 0
}
# 创建SQL备份
create_sql_backup() {
local backup_type=$1
local timestamp=$(get_timestamp)
local backup_file="$BACKUP_DIR/sql/ljwx_${backup_type}_${timestamp}.sql"
log_message "📦 开始创建${backup_type}SQL备份"
if docker exec $MYSQL_CONTAINER mysqldump \
-u $MYSQL_USER -p$MYSQL_PASSWORD \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--add-drop-database \
--databases $MYSQL_DATABASE > "$backup_file" 2>/dev/null; then
# 压缩备份文件
gzip "$backup_file"
backup_file="${backup_file}.gz"
# 计算文件大小
local file_size=$(get_file_size "$backup_file")
log_message "✅ SQL备份创建成功: $(basename "$backup_file") ($file_size)"
# 记录备份信息
echo "$(date '+%Y-%m-%d %H:%M:%S'),$backup_type,SQL,$backup_file,$file_size" >> "$BACKUP_DIR/backup_history.csv"
return 0
else
log_message "❌ SQL备份失败"
return 1
fi
}
# 创建数据卷备份
create_volume_backup() {
local backup_type=$1
local timestamp=$(get_timestamp)
local backup_file="$BACKUP_DIR/volume/mysql_${backup_type}_${timestamp}.tar.gz"
log_message "🗃️ 开始创建${backup_type}数据卷备份"
if docker run --rm \
-v $VOLUME_NAME:/source:ro \
-v "$(pwd)/$BACKUP_DIR/volume":/backup \
alpine:latest \
tar -czf "/backup/mysql_${backup_type}_${timestamp}.tar.gz" -C /source . 2>/dev/null; then
local file_size=$(get_file_size "$backup_file")
log_message "✅ 数据卷备份创建成功: $(basename "$backup_file") ($file_size)"
# 记录备份信息
echo "$(date '+%Y-%m-%d %H:%M:%S'),$backup_type,VOLUME,$backup_file,$file_size" >> "$BACKUP_DIR/backup_history.csv"
return 0
else
log_message "❌ 数据卷备份失败"
return 1
fi
}
# 清理旧备份
cleanup_old_backups() {
local sql_retention_days=7
local volume_retention_days=30
log_message "🧹 开始清理旧备份文件"
# 跨平台find命令
case $OS_TYPE in
"macos"|"centos"|"ubuntu"|"debian"|"linux")
# 清理SQL备份
find "$BACKUP_DIR/sql" -name "ljwx_daily_*.sql.gz" -mtime +$sql_retention_days -delete 2>/dev/null
# 清理数据卷备份
find "$BACKUP_DIR/volume" -name "mysql_weekly_*.tar.gz" -mtime +$volume_retention_days -delete 2>/dev/null
;;
esac
# 清理日志文件(保留最近1000行)
if [ -f "$LOG_FILE" ]; then
tail -1000 "$LOG_FILE" > "${LOG_FILE}.tmp" && mv "${LOG_FILE}.tmp" "$LOG_FILE"
fi
}
# 生成备份报告
generate_backup_report() {
log_message "📊 生成备份状态报告"
local report_file="$BACKUP_DIR/backup_report_$(date +%Y%m%d).txt"
cat > "$report_file" << EOF
LJWX MySQL备份状态报告 - $OS_TYPE $OS_VERSION
生成时间: $(date '+%Y-%m-%d %H:%M:%S')
================================
📁 备份文件统计:
SQL备份文件数量: $(ls "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null | wc -l | tr -d ' ')
数据卷备份文件数量: $(ls "$BACKUP_DIR/volume/"*.tar.gz 2>/dev/null | wc -l | tr -d ' ')
💾 存储空间使用:
SQL备份总大小: $(du -sh "$BACKUP_DIR/sql" 2>/dev/null | cut -f1 || echo "0B")
数据卷备份总大小: $(du -sh "$BACKUP_DIR/volume" 2>/dev/null | cut -f1 || echo "0B")
总备份大小: $(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1 || echo "0B")
📈 最近备份记录:
$(tail -5 "$BACKUP_DIR/backup_history.csv" 2>/dev/null || echo "暂无备份记录")
🔍 最新备份文件:
最新SQL备份: $(ls -t "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null | head -1 | xargs basename || echo "无")
最新数据卷备份: $(ls -t "$BACKUP_DIR/volume/"*.tar.gz 2>/dev/null | head -1 | xargs basename || echo "无")
📊 数据库状态:
数据库: $MYSQL_DATABASE
表数量: $(docker exec $MYSQL_CONTAINER mysql -u $MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$MYSQL_DATABASE';" 2>/dev/null | tail -1 || echo "未知")
🖥️ 系统信息:
操作系统: $OS_TYPE $OS_VERSION
解压命令: $DECOMPRESS_CMD
================================
EOF
log_message "📄 备份报告已生成: $report_file"
}
# 主执行逻辑
main() {
local backup_mode=${1:-"daily"}
log_message "🚀 开始自动备份任务 - 模式: $backup_mode (系统: $OS_TYPE)"
# 检查服务状态
if ! check_services; then
log_message "❌ 服务检查失败,备份任务终止"
exit 1
fi
case $backup_mode in
"daily")
create_sql_backup "daily"
cleanup_old_backups
;;
"weekly")
create_sql_backup "weekly"
create_volume_backup "weekly"
cleanup_old_backups
generate_backup_report
;;
"manual")
create_sql_backup "manual"
create_volume_backup "manual"
generate_backup_report
;;
*)
log_message "❌ 无效的备份模式: $backup_mode"
echo "使用方法: $0 [daily|weekly|manual]"
exit 1
;;
esac
log_message "🎉 自动备份任务完成"
}
# 显示使用帮助
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
cat << EOF
LJWX MySQL自动备份工具 - 跨平台版本
支持: CentOS, macOS, Ubuntu, Debian
当前系统: $OS_TYPE $OS_VERSION
使用方法:
$0 [模式]
备份模式:
daily - 每日备份(仅SQL备份)
weekly - 每周备份(SQL + 数据卷备份)
manual - 手动备份(完整备份)
定时任务设置:
# 每日凌晨2点执行SQL备份
0 2 * * * /path/to/auto-backup-crossplatform.sh daily
# 每周日凌晨3点执行完整备份
0 3 * * 0 /path/to/auto-backup-crossplatform.sh weekly
示例:
./auto-backup-crossplatform.sh daily # 每日备份
./auto-backup-crossplatform.sh weekly # 每周备份
./auto-backup-crossplatform.sh manual # 手动完整备份
日志文件: $BACKUP_DIR/auto_backup.log
备份目录: $BACKUP_DIR/
EOF
exit 0
fi
# 执行主程序
main "$@"
+194
View File
@@ -0,0 +1,194 @@
#!/bin/bash
# 自动备份定时任务脚本 - 支持每日/每周备份策略
# 配置变量
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="123456"
MYSQL_DATABASE="lj-06"
BACKUP_DIR="./backups"
VOLUME_NAME="client-deployment_mysql_data"
LOG_FILE="$BACKUP_DIR/auto_backup.log"
# 创建备份目录和日志文件
mkdir -p "$BACKUP_DIR"/{sql,volume,config,logs}
touch "$LOG_FILE"
# 日志记录函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# 检查Docker和MySQL状态
check_services() {
if ! docker info >/dev/null 2>&1; then
log_message "❌ Docker服务未运行"
return 1
fi
if ! docker ps | grep -q $MYSQL_CONTAINER; then
log_message "❌ MySQL容器未运行"
return 1
fi
if ! docker exec $MYSQL_CONTAINER mysqladmin ping -u $MYSQL_USER -p$MYSQL_PASSWORD >/dev/null 2>&1; then
log_message "❌ MySQL连接失败"
return 1
fi
return 0
}
# 创建SQL备份
create_sql_backup() {
local backup_type=$1
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/sql/ljwx_${backup_type}_${timestamp}.sql"
log_message "📦 开始创建${backup_type}SQL备份"
if docker exec $MYSQL_CONTAINER mysqldump \
-u $MYSQL_USER -p$MYSQL_PASSWORD \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--add-drop-database \
--databases $MYSQL_DATABASE > "$backup_file" 2>/dev/null; then
# 压缩备份文件
gzip "$backup_file"
backup_file="${backup_file}.gz"
# 计算文件大小
local file_size=$(ls -lh "$backup_file" | awk '{print $5}')
log_message "✅ SQL备份创建成功: $(basename "$backup_file") ($file_size)"
# 记录备份信息
echo "$(date '+%Y-%m-%d %H:%M:%S'),$backup_type,SQL,$backup_file,$file_size" >> "$BACKUP_DIR/backup_history.csv"
return 0
else
log_message "❌ SQL备份失败"
return 1
fi
}
# 创建数据卷备份
create_volume_backup() {
local backup_type=$1
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/volume/mysql_${backup_type}_${timestamp}.tar.gz"
log_message "🗃️ 开始创建${backup_type}数据卷备份"
if docker run --rm \
-v $VOLUME_NAME:/source:ro \
-v "$(pwd)/$BACKUP_DIR/volume":/backup \
alpine:latest \
tar -czf "/backup/mysql_${backup_type}_${timestamp}.tar.gz" -C /source . 2>/dev/null; then
local file_size=$(ls -lh "$backup_file" | awk '{print $5}')
log_message "✅ 数据卷备份创建成功: $(basename "$backup_file") ($file_size)"
# 记录备份信息
echo "$(date '+%Y-%m-%d %H:%M:%S'),$backup_type,VOLUME,$backup_file,$file_size" >> "$BACKUP_DIR/backup_history.csv"
return 0
else
log_message "❌ 数据卷备份失败"
return 1
fi
}
# 清理旧备份
cleanup_old_backups() {
local sql_retention_days=7
local volume_retention_days=30
log_message "🧹 开始清理旧备份文件"
# 清理SQL备份
find "$BACKUP_DIR/sql" -name "ljwx_daily_*.sql.gz" -mtime +$sql_retention_days -delete 2>/dev/null
# 清理数据卷备份
find "$BACKUP_DIR/volume" -name "mysql_weekly_*.tar.gz" -mtime +$volume_retention_days -delete 2>/dev/null
# 清理日志文件(保留最近1000行)
if [ -f "$LOG_FILE" ]; then
tail -1000 "$LOG_FILE" > "${LOG_FILE}.tmp" && mv "${LOG_FILE}.tmp" "$LOG_FILE"
fi
}
# 主执行逻辑
main() {
local backup_mode=${1:-"daily"}
log_message "🚀 开始自动备份任务 - 模式: $backup_mode"
# 检查服务状态
if ! check_services; then
log_message "❌ 服务检查失败,备份任务终止"
exit 1
fi
case $backup_mode in
"daily")
create_sql_backup "daily"
cleanup_old_backups
;;
"weekly")
create_sql_backup "weekly"
create_volume_backup "weekly"
cleanup_old_backups
;;
"manual")
create_sql_backup "manual"
create_volume_backup "manual"
;;
*)
log_message "❌ 无效的备份模式: $backup_mode"
echo "使用方法: $0 [daily|weekly|manual]"
exit 1
;;
esac
log_message "🎉 自动备份任务完成"
}
# 显示使用帮助
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
cat << EOF
LJWX MySQL自动备份工具
使用方法:
$0 [模式]
备份模式:
daily - 每日备份(仅SQL备份)
weekly - 每周备份(SQL + 数据卷备份)
manual - 手动备份(完整备份)
定时任务设置:
# 每日凌晨2点执行SQL备份
0 2 * * * /path/to/auto-backup.sh daily
# 每周日凌晨3点执行完整备份
0 3 * * 0 /path/to/auto-backup.sh weekly
示例:
./auto-backup.sh daily # 每日备份
./auto-backup.sh weekly # 每周备份
./auto-backup.sh manual # 手动完整备份
日志文件: $BACKUP_DIR/auto_backup.log
备份目录: $BACKUP_DIR/
EOF
exit 0
fi
# 执行主程序
main "$@"
+384
View File
@@ -0,0 +1,384 @@
#!/bin/bash
# LJWX备份恢复管理器 - 跨平台版
# 配置
MYSQL_CONTAINER="ljwx-mysql"
REDIS_CONTAINER="ljwx-redis"
MYSQL_USER="root"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}"
MYSQL_DATABASE="${MYSQL_DATABASE:-lj-06}"
BACKUP_BASE_DIR="backup"
LOG_DIR="logs"
RETENTION_DAYS=7
# 颜色 - 兼容CentOS:动态检测终端支持
if [ -t 1 ] && [ "${TERM:-}" != "" ] && tput colors >/dev/null 2>&1; then
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
else #CentOS兼容:无颜色支持时使用空值
RED=''; GREEN=''; YELLOW=''; BLUE=''; NC=''
fi
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Darwin) OS="macOS" ;;
Linux)
if [ -f /etc/centos-release ]; then OS="CentOS"
elif [ -f /etc/ubuntu-release ] || grep -q "Ubuntu" /etc/os-release 2>/dev/null; then OS="Ubuntu"
else OS="Linux"; fi ;;
*) OS="Unknown" ;;
esac
}
# 日志函数
log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_DIR/backup-restore.log"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$LOG_DIR/backup-restore.log"; }
error() { echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_DIR/backup-restore.log"; }
# 初始化
init() {
detect_os
mkdir -p "$BACKUP_BASE_DIR"/{mysql,redis} "$LOG_DIR"
log "🚀 LJWX备份恢复管理器启动 (OS: $OS)"
}
# 检查Docker容器状态
check_container() {
local container=$1
if ! docker ps --format "{{.Names}}" | grep -q "^$container$"; then
error "容器 $container 未运行"
return 1
fi
return 0
}
# MySQL备份
mysql_backup() {
local type=${1:-manual} # auto/manual
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_BASE_DIR/mysql/${MYSQL_DATABASE}_${type}_${timestamp}.sql.gz"
log "🔄 开始MySQL${type}备份..."
if ! check_container "$MYSQL_CONTAINER"; then return 1; fi
# 执行备份
if docker exec "$MYSQL_CONTAINER" mysqldump \
-u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
--single-transaction --routines --triggers \
--complete-insert --hex-blob \
"$MYSQL_DATABASE" | gzip > "$backup_file"; then
local size=$(du -h "$backup_file" | cut -f1)
log "✅ MySQL备份成功: $(basename "$backup_file") ($size)"
# 验证备份
if gunzip -t "$backup_file" 2>/dev/null; then
log "✅ MySQL备份文件验证通过"
return 0
else
error "❌ MySQL备份文件验证失败"
rm -f "$backup_file"
return 1
fi
else
error "❌ MySQL备份失败"
return 1
fi
}
# Redis备份
redis_backup() {
local type=${1:-manual} # auto/manual
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_BASE_DIR/redis/redis_${type}_${timestamp}.rdb"
local redis_password="${REDIS_PASSWORD:-123456}"
log "🔄 开始Redis${type}备份..."
if ! check_container "$REDIS_CONTAINER"; then return 1; fi
# 触发Redis保存并复制RDB文件
if docker exec "$REDIS_CONTAINER" sh -c "redis-cli -a '$redis_password' BGSAVE" 2>/dev/null && \
sleep 3 && \
docker exec "$REDIS_CONTAINER" cat /data/dump.rdb > "$backup_file"; then
local size=$(du -h "$backup_file" | cut -f1)
log "✅ Redis备份成功: $(basename "$backup_file") ($size)"
return 0
else
error "❌ Redis备份失败"
return 1
fi
}
# MySQL恢复
mysql_restore() {
local backup_file="$1"
if [ -z "$backup_file" ]; then
# 自动选择最新备份
backup_file=$(ls -t "$BACKUP_BASE_DIR/mysql"/*.sql.gz 2>/dev/null | head -1)
if [ -z "$backup_file" ]; then
error "未找到MySQL备份文件"
return 1
fi
log "自动选择最新备份: $(basename "$backup_file")"
fi
if [ ! -f "$backup_file" ]; then
error "备份文件不存在: $backup_file"
return 1
fi
log "🔄 开始MySQL恢复..."
if ! check_container "$MYSQL_CONTAINER"; then return 1; fi
# 创建恢复前备份
local pre_restore_backup="$BACKUP_BASE_DIR/mysql/pre_restore_$(date +%Y%m%d_%H%M%S).sql.gz"
log "📦 创建恢复前备份: $(basename "$pre_restore_backup")"
mysql_backup "pre_restore" >/dev/null 2>&1
# 根据操作系统选择解压命令
local decompress_cmd
case "$OS" in
macOS) decompress_cmd="gunzip -c" ;;
*) decompress_cmd="zcat" ;;
esac
# 执行恢复
if $decompress_cmd "$backup_file" | docker exec -i "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"; then
log "✅ MySQL恢复成功"
return 0
else
error "❌ MySQL恢复失败"
return 1
fi
}
# Redis恢复
redis_restore() {
local backup_file="$1"
local redis_password="${REDIS_PASSWORD:-123456}"
if [ -z "$backup_file" ]; then
# 自动选择最新备份
backup_file=$(ls -t "$BACKUP_BASE_DIR/redis"/*.rdb 2>/dev/null | head -1)
if [ -z "$backup_file" ]; then
error "未找到Redis备份文件"
return 1
fi
log "自动选择最新备份: $(basename "$backup_file")"
fi
if [ ! -f "$backup_file" ]; then
error "备份文件不存在: $backup_file"
return 1
fi
log "🔄 开始Redis恢复..."
if ! check_container "$REDIS_CONTAINER"; then return 1; fi
# 创建恢复前备份
log "📦 创建恢复前备份"
redis_backup "pre_restore" >/dev/null 2>&1
# 清空Redis数据并恢复
log "🗑️ 清空Redis现有数据..."
if docker exec "$REDIS_CONTAINER" sh -c "redis-cli -a '$redis_password' FLUSHALL" 2>/dev/null; then
log "✅ Redis数据清空成功"
# 停止Redis,替换RDB文件,重启Redis
log "📁 替换RDB文件..."
if docker stop "$REDIS_CONTAINER" && \
cat "$backup_file" | docker exec -i "$REDIS_CONTAINER" tee /data/dump.rdb >/dev/null && \
docker start "$REDIS_CONTAINER"; then
log "✅ Redis恢复成功"
sleep 5 # 等待Redis重启
# 验证恢复
if docker exec "$REDIS_CONTAINER" sh -c "redis-cli -a '$redis_password' ping" >/dev/null 2>&1; then
log "✅ Redis服务验证成功"
return 0
else
warn "⚠️ Redis服务验证失败,但恢复过程已完成"
return 0
fi
else
error "❌ Redis恢复失败"
return 1
fi
else
error "❌ Redis数据清空失败"
return 1
fi
}
# 清理旧备份
cleanup_backups() {
log "🧹 清理${RETENTION_DAYS}天前的备份..."
local deleted=0
for dir in mysql redis; do
# 兼容CentOS sh:避免进程替换,使用临时文件
local temp_file="/tmp/backup_cleanup_$$"
find "$BACKUP_BASE_DIR/$dir" -type f -mtime +$RETENTION_DAYS -print0 2>/dev/null > "$temp_file"
while IFS= read -r -d '' file; do
if [ -n "$file" ]; then
rm -f "$file"
deleted=$((deleted + 1))
log "🗑️ 删除旧备份: $(basename "$file")"
fi
done < "$temp_file"
rm -f "$temp_file"
done
log "📊 清理完成,删除 $deleted 个文件"
}
# 列出备份文件
list_backups() {
echo -e "${BLUE}📋 MySQL备份文件:${NC}"
if ls "$BACKUP_BASE_DIR/mysql"/*.sql.gz >/dev/null 2>&1; then
local mysql_files=($(ls -t "$BACKUP_BASE_DIR/mysql"/*.sql.gz))
i=0 #兼容CentOS sh:避免数组索引循环
for file in "${mysql_files[@]}"; do
local size=$(du -h "$file" | cut -f1)
local date=$(ls -lh "$file" | awk "{print \$6, \$7, \$8}")
echo " $((i+1)). $(basename "$file") ($size, $date)"
i=$((i+1))
done
else
echo " 无备份文件"
fi
echo -e "${BLUE}📋 Redis备份文件:${NC}"
if ls "$BACKUP_BASE_DIR/redis"/*.rdb >/dev/null 2>&1; then
local redis_files=($(ls -t "$BACKUP_BASE_DIR/redis"/*.rdb))
i=0 #兼容CentOS sh:避免数组索引循环
for file in "${redis_files[@]}"; do
local size=$(du -h "$file" | cut -f1)
local date=$(ls -lh "$file" | awk "{print \$6, \$7, \$8}")
echo " $((i+1)). $(basename "$file") ($size, $date)"
i=$((i+1))
done
else
echo " 无备份文件"
fi
}
# 自动化备份(用于定时任务)
auto_backup() {
log "🤖 开始自动化备份任务..."
local success=0
if mysql_backup "auto"; then success=$((success + 1)); fi
if redis_backup "auto"; then success=$((success + 1)); fi
if [ $success -eq 2 ]; then
log "🎯 自动化备份完成 (MySQL✅ Redis✅)"
cleanup_backups
else
error "❌ 自动化备份部分失败"
exit 1
fi
}
# 显示菜单
show_menu() {
echo -e "${BLUE}
╔══════════════════════════════════════╗
║ LJWX 备份恢复管理器 v2.0 ║
║ OS: $OS ║
╠══════════════════════════════════════╣
║ 1. MySQL手动备份 ║
║ 2. Redis手动备份 ║
║ 3. 全量手动备份 (MySQL+Redis) ║
║ 4. MySQL恢复 ║
║ 5. Redis恢复 ║
║ 6. 列出备份文件 ║
║ 7. 清理旧备份 ║
║ 8. 自动化备份 (用于定时任务) ║
║ 9. 退出 ║
╚══════════════════════════════════════╝${NC}"
}
# 主函数
main() {
init
if [ $# -eq 0 ]; then
# 交互模式
while true; do
show_menu
read -p "请选择操作 [1-9]: " choice
case $choice in
1) mysql_backup "manual" ;;
2) redis_backup "manual" ;;
3)
log "🔄 开始全量备份..."
mysql_backup "manual" && redis_backup "manual"
;;
4)
list_backups
read -p "请输入MySQL备份文件序号或路径 (回车自动选择最新): " input
if echo "$input" | grep -q '^[0-9][0-9]*$'; then #兼容CentOS sh:避免双方括号和正则匹配
# 序号选择
files=($(ls -t "$BACKUP_BASE_DIR/mysql"/*.sql.gz 2>/dev/null))
if [ "$input" -ge 1 ] && [ "$input" -le "${#files[@]}" ]; then
mysql_restore "${files[$((input-1))]}"
else
error "备份文件序号无效: $input (有效范围: 1-${#files[@]})"
fi
else
# 路径选择
mysql_restore "$input"
fi
;;
5)
list_backups
read -p "请输入Redis备份文件序号或路径 (回车自动选择最新): " input
if echo "$input" | grep -q '^[0-9][0-9]*$'; then #兼容CentOS sh:避免双方括号和正则匹配
# 序号选择
files=($(ls -t "$BACKUP_BASE_DIR/redis"/*.rdb 2>/dev/null))
if [ "$input" -ge 1 ] && [ "$input" -le "${#files[@]}" ]; then
redis_restore "${files[$((input-1))]}"
else
error "备份文件序号无效: $input (有效范围: 1-${#files[@]})"
fi
else
# 路径选择
redis_restore "$input"
fi
;;
6) list_backups ;;
7) cleanup_backups ;;
8) auto_backup ;;
9) log "👋 退出备份恢复管理器"; exit 0 ;;
*) warn "无效选择,请重新输入" ;;
esac
echo
done
else
# 命令行模式
case "$1" in
mysql-backup) mysql_backup "manual" ;;
redis-backup) redis_backup "manual" ;;
full-backup) mysql_backup "manual" && redis_backup "manual" ;;
mysql-restore) mysql_restore "$2" ;;
redis-restore) redis_restore "$2" ;;
auto-backup) auto_backup ;;
list) list_backups ;;
cleanup) cleanup_backups ;;
*)
echo "用法: $0 [mysql-backup|redis-backup|full-backup|mysql-restore|redis-restore|auto-backup|list|cleanup]"
exit 1
;;
esac
fi
}
main "$@"
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
# 强制清除浏览器缓存脚本 #解决浏览器缓存导致的URL未更新问题
set -e
echo "🔄 强制清除浏览器缓存..."
# 检查容器是否运行
if ! docker ps -q -f name=ljwx-admin > /dev/null; then
echo "❌ ljwx-admin 容器未运行"
exit 1
fi
# 为静态文件添加缓存控制头
echo "📋 配置nginx缓存控制..."
docker exec ljwx-admin sh -c "
cat > /etc/nginx/conf.d/cache-control.conf << 'EOF'
# 强制不缓存HTML和JS文件
location ~* \.(html|js|css)$ {
add_header Cache-Control 'no-cache, no-store, must-revalidate';
add_header Pragma 'no-cache';
add_header Expires '0';
try_files \$uri \$uri/ /index.html;
}
# 为静态资源添加版本号
location /assets/ {
add_header Cache-Control 'no-cache, no-store, must-revalidate';
add_header Pragma 'no-cache';
add_header Expires '0';
try_files \$uri =404;
}
EOF
"
# 重新加载nginx配置
docker exec ljwx-admin nginx -s reload
echo "✅ Nginx配置已更新"
# 修改index.html文件,添加缓存破坏参数
TIMESTAMP=$(date +%s)
docker exec ljwx-admin sh -c "
sed -i 's/\(src=\"\.\/assets\/[^\"]*\)/\1?v=$TIMESTAMP/g' /usr/share/nginx/html/index.html
sed -i 's/\(href=\"\.\/assets\/[^\"]*\)/\1?v=$TIMESTAMP/g' /usr/share/nginx/html/index.html
echo '✅ 已添加缓存破坏参数: $TIMESTAMP'
"
echo "🎉 缓存清除完成!"
echo ""
echo "💡 解决浏览器缓存的方法:"
echo " 1. 硬刷新页面:Ctrl+F5 (Windows) 或 Cmd+Shift+R (Mac)"
echo " 2. 开发者工具:F12 -> Network -> Disable cache"
echo " 3. 隐私模式:使用浏览器隐私/无痕模式访问"
echo ""
echo "🌐 访问地址: http://localhost:8080/#/home"
+1
View File
@@ -0,0 +1 @@
-- 客户数据占位符
+116
View File
@@ -0,0 +1,116 @@
#!/bin/bash
# 配置模板生成脚本 #快速生成不同环境的配置文件
set -e
echo "🔧 配置文件生成器"
echo "=================="
# 获取用户输入
read -p "请输入服务器IP地址 (默认: 192.168.1.83): " SERVER_IP
SERVER_IP=${SERVER_IP:-"192.168.1.83"}
read -p "请输入大屏端口 (默认: 8001): " BIGSCREEN_PORT
BIGSCREEN_PORT=${BIGSCREEN_PORT:-"8001"}
read -p "请输入公司名称 (默认: 智能科技有限公司): " COMPANY_NAME
COMPANY_NAME=${COMPANY_NAME:-"智能科技有限公司"}
read -p "请输入系统标题 (默认: 穿戴健康管理平台): " VITE_APP_TITLE
VITE_APP_TITLE=${VITE_APP_TITLE:-"穿戴健康管理平台"}
read -p "请输入大屏标题 (默认: 智能穿戴演示大屏): " BIGSCREEN_TITLE
BIGSCREEN_TITLE=${BIGSCREEN_TITLE:-"智能穿戴演示大屏"}
echo ""
echo "📋 配置信息:"
echo "- 服务器IP: $SERVER_IP"
echo "- 大屏端口: $BIGSCREEN_PORT"
echo "- 公司名称: $COMPANY_NAME"
echo "- 系统标题: $VITE_APP_TITLE"
echo "- 大屏标题: $BIGSCREEN_TITLE"
echo "- 大屏URL: http://$SERVER_IP:$BIGSCREEN_PORT"
echo ""
read -p "确认生成配置文件? (y/N): " confirm
if [[ $confirm != [yY] ]]; then
echo "操作已取消"
exit 0
fi
# 生成配置文件
CONFIG_FILE="custom-config.env"
if [ -f "$CONFIG_FILE" ]; then
cp "$CONFIG_FILE" "${CONFIG_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
echo "✅ 已备份原配置文件"
fi
cat > "$CONFIG_FILE" << EOF
# 客户定制化配置文件 #统一配置文件管理所有变量
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
# ==================== 网络配置 ====================
# 服务器IP地址 - 根据实际部署环境修改
SERVER_IP=$SERVER_IP
# 大屏服务端口 - 必须与docker-compose.yml中ljwx-bigscreen的端口一致
BIGSCREEN_PORT=$BIGSCREEN_PORT
# ==================== 大屏UI定制 ====================
# 大屏标题
BIGSCREEN_TITLE=$BIGSCREEN_TITLE
# 公司名称
COMPANY_NAME=$COMPANY_NAME
# 公司Logo路径(相对于static目录)
COMPANY_LOGO_URL=/static/images/logo.png
# 主题色(十六进制颜色代码)
THEME_COLOR=#1890ff
# 背景色
BACKGROUND_COLOR=#0a0e27
# 页脚文字
FOOTER_TEXT="© 2025 灵境万象"
# ==================== 管理端UI定制 ====================
# 管理端标题
VITE_APP_TITLE=$VITE_APP_TITLE
# 管理端描述
VITE_APP_DESC=$VITE_APP_TITLE
# 大屏访问地址 - ljwx-admin中跳转大屏的URL(客户端浏览器可访问)
VITE_BIGSCREEN_URL=http://$SERVER_IP:$BIGSCREEN_PORT
# 是否使用自定义Logo(true/false)
VITE_CUSTOM_LOGO=true
# ==================== 数据库配置 ====================
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=123456
MYSQL_DATABASE=lj-06
# ==================== Redis配置 ====================
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=123456
REDIS_DB=0
# ==================== 应用配置 ====================
# 大屏应用端口
APP_PORT=$BIGSCREEN_PORT
# 调试模式
DEBUG=false
# Docker环境标识
IS_DOCKER=true
# ==================== 微信配置 ====================
WECHAT_APP_ID=wx10dcc9f0235e1d77
WECHAT_APP_SECRET=b7e9088f3f5fe18a9cfb990c641138b3
WECHAT_TEMPLATE_ID=oJpIEzSJW67s-W_tDTbnB5uS1biiglLH5jcaALofujk
WECHAT_USER_OPENID=ofYhV6W_mDuDnm8lVbgVbgEMtvWc
WECHAT_ALERT_ENABLED=true
EOF
echo "✅ 配置文件已生成: $CONFIG_FILE"
echo ""
echo "📋 后续步骤:"
echo "1. 检查配置文件内容是否正确"
echo "2. 运行配置验证: ./validate-config.sh"
echo "3. 执行一键部署: ./deploy-client.sh"
+211
View File
@@ -0,0 +1,211 @@
#!/bin/bash
# LJWX离线部署包创建脚本 - 统一配置管理
set -e #出错即停
# 全局配置 - 统一管理所有变量
REGISTRY="crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com" #镜像仓库地址
VERSION="1.2.14" #系统版本
REDIS_VERSION="1.1.7" #Redis版本
OFFLINE_DIR="ljwx-offline-$(date +%Y%m%d)" #离线包目录
PACKAGE_NAME="ljwx-offline-deployment-$(date +%Y%m%d).tar.gz" #最终包名
# 颜色配置 - 终端输出美化
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' #颜色代码
log() { echo -e "${GREEN}✅ $1${NC}"; } #成功日志
warn() { echo -e "${YELLOW}⚠️ $1${NC}"; } #警告日志
error() { echo -e "${RED}❌ $1${NC}"; exit 1; } #错误日志
info() { echo -e "${BLUE}ℹ️ $1${NC}"; } #信息日志
# 环境检查
check_env() {
info "检查环境依赖..."
command -v docker >/dev/null || error "Docker未安装"
command -v tar >/dev/null || error "tar命令不可用"
command -v gzip >/dev/null || error "gzip命令不可用"
log "环境检查通过"
}
# 创建工作目录
setup_workspace() {
info "创建工作目录: $OFFLINE_DIR"
[ -d "$OFFLINE_DIR" ] && rm -rf "$OFFLINE_DIR" #清理旧目录
mkdir -p "$OFFLINE_DIR"/{images,deployment,scripts,docs}
log "工作目录创建完成"
}
# 下载Docker镜像
download_images() {
info "开始下载Docker镜像..."
# 镜像列表 - 统一管理
local images=(
"$REGISTRY/ljwx/ljwx-mysql:$VERSION"
"$REGISTRY/ljwx/ljwx-redis:$REDIS_VERSION"
"$REGISTRY/ljwx/ljwx-boot:$VERSION"
"$REGISTRY/ljwx/ljwx-bigscreen:$VERSION"
"$REGISTRY/ljwx/ljwx-admin:$VERSION"
)
cd "$OFFLINE_DIR/images"
for img in "${images[@]}"; do
info "下载镜像: $img"
docker pull "$img" || error "镜像下载失败: $img"
done
# 导出镜像
info "导出镜像到tar文件..."
docker save "${images[@]}" -o ljwx-images-${VERSION}.tar
gzip ljwx-images-${VERSION}.tar
log "镜像下载完成 ($(du -h ljwx-images-${VERSION}.tar.gz | cut -f1))"
}
# 复制部署文件
copy_deployment_files() {
info "复制部署文件..."
cd "../.."
# 核心文件列表 - 统一管理
local files=(
"docker-compose.yml"
"deploy-client.sh"
"reset-database.sh"
"backup-restore-manager.sh"
"wait-for-it.sh"
"custom-config.env"
"customer-example.env"
"custom-config.py"
"custom-admin-config.js"
"client-data.sql"
"validate-centos-compatibility.sh"
"CENTOS_COMPATIBILITY_FIXES.md"
)
for file in "${files[@]}"; do
[ -f "$file" ] && cp "$file" "$OFFLINE_DIR/deployment/" || warn "文件不存在: $file"
done
# 复制目录
[ -d "custom-assets" ] && cp -r custom-assets "$OFFLINE_DIR/deployment/"
[ -d "backup" ] && cp -r backup "$OFFLINE_DIR/deployment/"
log "部署文件复制完成"
}
# 创建辅助脚本
create_scripts() {
info "创建辅助脚本..."
# 离线导入脚本
cat > "$OFFLINE_DIR/scripts/import-images.sh" << 'EOF'
#!/bin/bash
# 离线镜像导入脚本
set -e
[ ! -f "images/ljwx-images-*.tar.gz" ] && { echo "❌ 镜像文件不存在"; exit 1; }
echo "🐳 导入Docker镜像..."
gunzip images/ljwx-images-*.tar.gz
docker load -i images/ljwx-images-*.tar
echo "✅ 镜像导入完成"
docker images | grep ljwx #显示导入的镜像
EOF
# 快速部署脚本
cat > "$OFFLINE_DIR/scripts/quick-deploy.sh" << 'EOF'
#!/bin/bash
# 快速部署脚本
set -e
echo "🚀 LJWX系统快速部署"
cd deployment
chmod +x *.sh
./validate-centos-compatibility.sh #兼容性检查
bash deploy-client.sh #执行部署
echo "✅ 部署完成,请访问: http://$(hostname -I | awk '{print $1}'):8080"
EOF
chmod +x "$OFFLINE_DIR/scripts"/*.sh
log "辅助脚本创建完成"
}
# 创建文档
create_docs() {
info "创建文档..."
# README文件
cat > "$OFFLINE_DIR/README.md" << EOF
# LJWX离线部署包 v${VERSION}
## 包含内容
- Docker镜像文件 (images/)
- 部署配置文件 (deployment/)
- 辅助脚本 (scripts/)
- 技术文档 (docs/)
## 快速开始
1. 导入镜像: \`bash scripts/import-images.sh\`
2. 执行部署: \`bash scripts/quick-deploy.sh\`
3. 访问系统: http://服务器IP:8080
## 详细说明
参考: docs/离线部署完整指南.md
*生成时间: $(date)*
EOF
# 复制详细文档
cp "离线部署完整指南.md" "$OFFLINE_DIR/docs/" 2>/dev/null || warn "详细指南未找到"
log "文档创建完成"
}
# 打包文件
create_package() {
info "创建最终部署包..."
# 生成清单文件
find "$OFFLINE_DIR" -type f > "$OFFLINE_DIR/file-list.txt"
# 计算文件大小
echo "总文件数: $(wc -l < "$OFFLINE_DIR/file-list.txt")" > "$OFFLINE_DIR/package-info.txt"
echo "总大小: $(du -sh "$OFFLINE_DIR" | cut -f1)" >> "$OFFLINE_DIR/package-info.txt"
echo "创建时间: $(date)" >> "$OFFLINE_DIR/package-info.txt"
echo "版本信息: LJWX v$VERSION" >> "$OFFLINE_DIR/package-info.txt"
# 打包压缩
tar -czf "$PACKAGE_NAME" "$OFFLINE_DIR"/
log "离线部署包创建完成: $PACKAGE_NAME"
log "包大小: $(du -h "$PACKAGE_NAME" | cut -f1)"
}
# 清理临时文件
cleanup() {
info "清理临时文件..."
rm -rf "$OFFLINE_DIR"
log "清理完成"
}
# 主函数
main() {
echo "🔧 LJWX离线部署包创建工具 v$VERSION"
echo "================================================"
check_env
setup_workspace
download_images
copy_deployment_files
create_scripts
create_docs
create_package
cleanup
echo "================================================"
log "🎉 离线部署包创建成功!"
echo "📦 文件名: $PACKAGE_NAME"
echo "📊 大小: $(du -h "$PACKAGE_NAME" | cut -f1)"
echo "🚀 使用方法: tar -xzf $PACKAGE_NAME && cd ljwx-offline-*/scripts && ./quick-deploy.sh"
}
# 执行主函数
main "$@"
+37
View File
@@ -0,0 +1,37 @@
#!/bin/bash
# 配置项
CONTAINER_NAME="ljwx-mysql"
DB_NAME="lj-06"
MYSQL_USER="root"
MYSQL_PASSWORD="123456"
TABLE_NAME="t_user_health_data_202506"
SOURCE_TABLE="t_user_health_data"
echo "👉 检查 MySQL 容器是否运行中..."
docker ps | grep $CONTAINER_NAME > /dev/null
if [ $? -ne 0 ]; then
echo "❌ 容器 $CONTAINER_NAME 没有运行,请先启动 MySQL 容器。"
exit 1
fi
echo "✅ MySQL 容器已运行,准备连接数据库 $DB_NAME"
# 检查表是否存在
CHECK_CMD="USE $DB_NAME; SHOW TABLES LIKE '$TABLE_NAME';"
CREATE_CMD="USE $DB_NAME; CREATE TABLE $TABLE_NAME LIKE $SOURCE_TABLE;"
echo "🔍 正在检查是否存在表:$TABLE_NAME ..."
TABLE_EXISTS=$(docker exec -i $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "$CHECK_CMD" | grep "$TABLE_NAME")
if [ "$TABLE_EXISTS" != "" ]; then
echo "✅ 表 $TABLE_NAME 已存在,无需创建。"
else
echo "⚠️ 表 $TABLE_NAME 不存在,准备创建..."
docker exec -i $CONTAINER_NAME mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "$CREATE_CMD"
if [ $? -eq 0 ]; then
echo "🎉 表 $TABLE_NAME 创建成功(结构已复制自 $SOURCE_TABLE)"
else
echo "❌ 表创建失败,请检查容器状态、数据库权限或表名是否正确。"
fi
fi
+24
View File
@@ -0,0 +1,24 @@
// 管理端运行时配置文件
// 客户可以修改此文件来定制管理端界面,无需重新构建
window.CUSTOM_CONFIG = {
// 应用标题
appTitle: '穿戴健康管理平台',
// 应用描述
appDesc: '穿戴健康管理平台',
// 大屏访问地址
bigscreenUrl: 'http://localhost:5001',
// 是否使用自定义Logo
useCustomLogo: false,
// 主题配色
themeColor: '#1890ff',
// 公司信息
companyName: '富力通',
// 页脚信息
footerText: '© 2025 富力通 版权所有'
};
+53
View File
@@ -0,0 +1,53 @@
# 客户定制化配置文件
# 复制此文件并根据客户需求修改相应配置
# ==================== 网络配置 ====================
# 服务器IP地址 - 根据实际部署环境修改
SERVER_IP=192.168.1.6
# 大屏服务端口 - 必须与docker-compose.yml中ljwx-bigscreen的端口一致
BIGSCREEN_PORT=8001
# ==================== 大屏UI定制 ====================
# 大屏标题
BIGSCREEN_TITLE=智能穿戴演示大屏
# 公司名称
COMPANY_NAME=智能科技有限公司
# 公司Logo路径(相对于static目录)
COMPANY_LOGO_URL=/static/images/logo.png
# 主题色(十六进制颜色代码)
THEME_COLOR=#1890ff
# 背景色
BACKGROUND_COLOR=#0a0e27
# 页脚文字
FOOTER_TEXT="© 2025 灵境万象"
# ==================== 管理端UI定制 ====================
# 管理端标题
VITE_APP_TITLE=穿戴健康管理平台
# 管理端描述
VITE_APP_DESC=穿戴健康管理平台
# 大屏访问地址 - ljwx-admin中跳转大屏的URL(客户端浏览器可访问)
VITE_BIGSCREEN_URL=http://192.168.1.6:8001
# 是否使用自定义Logo(true/false)
VITE_CUSTOM_LOGO=true
# ==================== 数据库配置 ====================
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=123456
MYSQL_DATABASE=lj-06
# ==================== Redis配置 ====================
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=123456
REDIS_DB=0
# ==================== 应用配置 ====================
# 大屏应用端口
APP_PORT=8001
# 调试模式
DEBUG=false
# Docker环境标识
IS_DOCKER=true
+122
View File
@@ -0,0 +1,122 @@
import os
from dotenv import load_dotenv
from urllib.parse import quote_plus
# Load environment variables
load_dotenv()
# Determine if running in Docker
IS_DOCKER = os.getenv('IS_DOCKER', 'true').lower() == 'true'
# Database configuration
MYSQL_HOST = os.getenv('MYSQL_HOST', 'mysql')
MYSQL_PORT = int(os.getenv('MYSQL_PORT', 3306))
MYSQL_USER = os.getenv('MYSQL_USER', 'root')
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD', '123456')
MYSQL_DATABASE = os.getenv('MYSQL_DATABASE', 'lj-06')
# Redis configuration
REDIS_HOST = os.getenv('REDIS_HOST', 'redis')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
REDIS_DB = int(os.getenv('REDIS_DB', 0))
REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', '123456') # Redis密码配置
# Application configuration
APP_HOST = os.getenv('APP_HOST', '0.0.0.0')
APP_PORT = int(os.getenv('APP_PORT', 8001))
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
# ==================== 客户定制化UI配置 ====================
# 客户可以直接修改以下配置来定制大屏界面
# 大屏标题 - 显示在页面顶部
BIGSCREEN_TITLE = os.getenv('BIGSCREEN_TITLE', '智能穿戴演示大屏')
# 公司信息
COMPANY_NAME = os.getenv('COMPANY_NAME', '智能科技有限公司')
COMPANY_LOGO_URL = os.getenv('COMPANY_LOGO_URL', '/static/images/logo.png')
# 主题配色
THEME_COLOR = os.getenv('THEME_COLOR', '#1890ff') # 主色调
BACKGROUND_COLOR = os.getenv('BACKGROUND_COLOR', '#0a0e27') # 背景色
# 页脚信息
FOOTER_TEXT = os.getenv('FOOTER_TEXT', '© 2024 智能科技有限公司 版权所有')
# Build database URI with proper password encoding
encoded_password = quote_plus(MYSQL_PASSWORD)
SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI',
f"mysql+pymysql://{MYSQL_USER}:{encoded_password}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}")
# 压力测试配置
STRESS_TEST_CONFIG = {
'URL': 'http://localhost:5001/upload_health_data',
'BASE_ID': 'A5GTQ24B26000732',
'DEVICE_COUNT': 500,
'INTERVAL': 5,
'MAX_WORKERS': 50,
'TIMEOUT': 10,
'TARGET_QPS': 200,
'QUEUE_SIZE': 10000,
}
# MySQL监控配置
MYSQL_CONFIG = {
'host': MYSQL_HOST,
'user': MYSQL_USER,
'password': MYSQL_PASSWORD,
'db': MYSQL_DATABASE,
'port': MYSQL_PORT,
'charset': 'utf8mb4'
}
# 数据库连接池配置
DB_POOL_CONFIG = {
'pool_size': 20,
'max_overflow': 30,
'pool_timeout': 30,
'pool_recycle': 3600,
'pool_pre_ping': True,
}
# 性能优化配置
PERFORMANCE_CONFIG = {
'batch_size': 100,
'batch_timeout': 2,
'async_workers': 10,
'redis_pipeline_size': 50,
'enable_compression': True,
}
# 健康数据范围配置
HEALTH_DATA_RANGES = {
'heart_rate': (60, 120),
'blood_oxygen': (95, 100),
'body_temperature': (36.0, 37.5),
'blood_pressure_systolic': (110, 140),
'blood_pressure_diastolic': (70, 90),
'step': (800, 1500),
'distance': (500, 1000),
'calorie': (40000, 50000),
'latitude': (22.5, 22.6),
'longitude': (114.0, 114.3),
'stress': (30, 80),
}
# ==================== 企业微信告警配置 ====================
# 企业微信告警相关配置,如不使用可设置为空字符串
CORP_ID = os.getenv('CORP_ID', 'wwde8dcde15c6b9657') # 企业ID
CORP_SECRET = os.getenv('CORP_SECRET', 'C8MfcXQvHb2Lf-sDWITNjEVGnFrNGsKhOAU_d8nwR38') # 应用密钥
CORP_AGENT_ID = os.getenv('CORP_AGENT_ID', '1000002') # 应用AgentId
CORP_API_URL = os.getenv('CORP_API_URL', 'https://qyapi.weixin.qq.com') # 企业微信API地址
CORP_WECHAT_ENABLED = os.getenv('CORP_WECHAT_ENABLED', 'true').lower() == 'true' # 是否启用企业微信告警
CORP_WECHAT_TOUSER = os.getenv('CORP_WECHAT_TOUSER', '@all') # 接收人,@all表示全部成员
# ==================== 微信小程序配置 ====================
# 微信小程序相关配置,如不使用可设置为空字符串
WECHAT_APP_ID = os.getenv('WECHAT_APP_ID', 'wx10dcc9f0235e1d77') # 微信小程序AppID
WECHAT_APP_SECRET = os.getenv('WECHAT_APP_SECRET', 'b7e9088f3f5fe18a9cfb990c641138b3') # 微信小程序密钥
WECHAT_TEMPLATE_ID = os.getenv('WECHAT_TEMPLATE_ID', 'oJpIEzSJW67s-W_tDTbnB5uS1biiglLH5jcaALofujk') # 微信模板消息ID
WECHAT_USER_OPENID = os.getenv('WECHAT_USER_OPENID', 'ofYhV6W_mDuDnm8lVbgVbgEMtvWc') # 微信用户OpenID
WECHAT_API_URL = os.getenv('WECHAT_API_URL', 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={ACCESS_TOKEN}') # 微信API地址
WECHAT_ALERT_ENABLED = os.getenv('WECHAT_ALERT_ENABLED', 'true').lower() == 'true' # 是否启用微信告警
+56
View File
@@ -0,0 +1,56 @@
# 示例客户配置文件 - ABC医疗集团
# 复制此文件并根据实际客户需求修改
# ==================== 大屏UI定制 ====================
# 大屏标题 - 显示在大屏顶部
BIGSCREEN_TITLE=ABC医疗集团智能监护大屏
# 公司名称 - 显示在各个界面
COMPANY_NAME=ABC医疗集团
# 公司Logo路径(相对于static目录)
COMPANY_LOGO_URL=/static/images/abc-logo.png
# 主题色(ABC医疗的蓝色主题)
THEME_COLOR=#0066cc
# 背景色(深色主题)
BACKGROUND_COLOR=#1a1a2e
# 页脚文字
FOOTER_TEXT="© 2024 ABC医疗集团 版权所有 | 智能健康监护系统"
# ==================== 管理端UI定制 ====================
# 管理端标题
VITE_APP_TITLE=ABC医疗智能监护管理系统
# 管理端描述
VITE_APP_DESC=ABC医疗集团智能健康监护管理平台
# 大屏访问地址(根据实际部署环境调整)
VITE_BIGSCREEN_URL=http://abc-medical.com:8001
# 是否使用自定义Logo
VITE_CUSTOM_LOGO=true
# ==================== 数据库配置 ====================
MYSQL_HOST=mysql
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=aV5mV7kQ!@#
MYSQL_DATABASE=lj-06
# ==================== Redis配置 ====================
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_DB=0
# ==================== 应用配置 ====================
# 大屏应用端口
APP_PORT=8001
# 调试模式(生产环境建议设为false)
DEBUG=false
# Docker环境标识
IS_DOCKER=true
+129
View File
@@ -0,0 +1,129 @@
#!/bin/bash
# 客户现场离线部署脚本 #专为无网络环境设计,使用预加载镜像
# 使用方法:./deploy-client-offline.sh [客户配置文件名]
set -e
# 默认配置文件
CONFIG_FILE=${1:-"custom-config.env"}
echo "==================== 智能穿戴系统离线部署 ===================="
echo "使用配置文件: $CONFIG_FILE"
echo "部署模式: 离线部署 (使用预加载镜像)"
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
echo "错误: 配置文件 $CONFIG_FILE 不存在"
echo "请复制 custom-config.env 并根据客户需求修改配置"
exit 1
fi
# 检查Docker和docker-compose是否安装
if ! command -v docker > /dev/null 2>&1; then
echo "错误: Docker 未安装,请先安装Docker"
exit 1
fi
if ! command -v docker-compose > /dev/null 2>&1; then
echo "错误: docker-compose 未安装,请先安装docker-compose"
exit 1
fi
# 加载配置文件
. "$CONFIG_FILE"
echo ""
echo "==================== 离线镜像检查 ===================="
echo "�� 离线部署模式 - 检查预加载镜像"
# 从docker-compose.yml动态读取版本号 #确保使用正确的镜像标签
MYSQL_IMAGE=$(egrep '^ *image:.*ljwx-mysql:' docker-compose.yml | sed 's/.*ljwx-mysql:\([^ ]*\).*/\1/' | head -1)
REDIS_IMAGE=$(egrep '^ *image:.*ljwx-redis:' docker-compose.yml | sed 's/.*ljwx-redis:\([^ ]*\).*/\1/' | head -1)
BOOT_IMAGE=$(egrep '^ *image:.*ljwx-boot:' docker-compose.yml | sed 's/.*ljwx-boot:\([^ ]*\).*/\1/' | head -1)
BIGSCREEN_IMAGE=$(egrep '^ *image:.*ljwx-bigscreen:' docker-compose.yml | sed 's/.*ljwx-bigscreen:\([^ ]*\).*/\1/' | head -1)
ADMIN_IMAGE=$(egrep '^ *image:.*ljwx-admin:' docker-compose.yml | sed 's/.*ljwx-admin:\([^ ]*\).*/\1/' | head -1)
echo "�� 检查本地镜像:"
echo "- ljwx-mysql:$MYSQL_IMAGE"
echo "- ljwx-redis:$REDIS_IMAGE"
echo "- ljwx-boot:$BOOT_IMAGE"
echo "- ljwx-bigscreen:$BIGSCREEN_IMAGE"
echo "- ljwx-admin:$ADMIN_IMAGE"
# 检查本地镜像是否存在 #确保所有必需镜像已预加载
MISSING_IMAGES=""
FOUND_IMAGES=0
for img in "ljwx-mysql:$MYSQL_IMAGE" "ljwx-redis:$REDIS_IMAGE" "ljwx-boot:$BOOT_IMAGE" "ljwx-bigscreen:$BIGSCREEN_IMAGE" "ljwx-admin:$ADMIN_IMAGE"; do
if docker images --format "table {{.Repository}}:{{.Tag}}" | grep -q "^$img$"; then
echo "✅ 找到镜像: $img"
FOUND_IMAGES=$((FOUND_IMAGES + 1))
else
MISSING_IMAGES="$MISSING_IMAGES $img"
echo "❌ 缺失镜像: $img"
fi
done
if [ $FOUND_IMAGES -eq 0 ]; then
echo ""
echo "❌ 严重错误: 未找到任何必需镜像!"
echo "请确保已通过以下方式之一加载镜像:"
echo "1. docker load -i ljwx-images.tar"
echo "2. 或单独加载: docker load -i ljwx-mysql.tar"
echo "3. 或使用镜像导入脚本"
exit 1
elif [ -n "$MISSING_IMAGES" ]; then
echo ""
echo "⚠️ 警告: 发现缺失镜像,但已找到 $FOUND_IMAGES/5 个镜像"
echo "🔧 系统将尝试继续部署,如果失败请补充缺失镜像"
echo "💡 缺失的镜像: $MISSING_IMAGES"
sleep 3
else
echo "✅ 所有必需镜像已就绪 ($FOUND_IMAGES/5)"
fi
echo ""
echo "==================== 开始部署 ===================="
# 停止现有服务但保留数据卷
echo "停止现有服务(保留数据卷)..."
docker-compose -f docker-compose.yml down
# 启动所有服务 #使用配置文件启动docker-compose服务
echo "🚀 正在启动所有服务..."
docker-compose -f docker-compose.yml --env-file $CONFIG_FILE up -d
# 等待服务初始化
echo "⏳ 等待服务初始化..."
sleep 15
echo ""
echo "==================== 服务状态检查 ===================="
docker-compose -f docker-compose.yml ps
# 替换前端应用中的大屏URL
echo ""
echo "🔄 更新前端大屏链接地址..."
if [ -f "replace-bigscreen-url.sh" ]; then
./replace-bigscreen-url.sh
echo "✅ 大屏链接地址更新完成"
else
echo "⚠️ 警告: replace-bigscreen-url.sh 脚本不存在"
fi
echo ""
echo "🎉==================== 离线部署完成 ===================="
echo "✅ 所有服务已成功启动!"
echo ""
echo "📱 访问地址:"
echo "- 管理端: http://localhost:8080 或 http://$SERVER_IP:8080"
echo "- 大屏端: http://localhost:8001 或 http://$SERVER_IP:8001"
echo ""
echo "🔐 默认登录信息:"
echo "- 用户名: admin"
echo "- 密码: admin123"
echo ""
echo "🔌 离线部署特性:"
echo "- ✅ 无需网络连接"
echo "- ✅ 使用预加载镜像"
echo "- ✅ 自动配置客户定制设置"
+514
View File
@@ -0,0 +1,514 @@
#!/bin/bash
# 客户现场部署脚本 - 使用预构建镜像 (Docker命名卷持久化)
# 使用方法:./deploy-client.sh [客户配置文件名] [offline]
set -e
# CentOS文件格式修复 #处理Windows换行符
if [ -f /etc/redhat-release ];then
echo "🔧 检测到CentOS系统,处理文件格式..."
if ! command -v dos2unix > /dev/null 2>&1;then #安装dos2unix
echo "📦 安装dos2unix工具..."
if yum install -y dos2unix 2>/dev/null||echo "❌ 无法自动安装dos2unix,请手动执行: yum install dos2unix && dos2unix *";then
dos2unix * 2>/dev/null||true #转换所有文件格式
echo "✅ 文件格式处理完成"
fi
else
dos2unix * 2>/dev/null||true #转换所有文件格式
echo "✅ 文件格式处理完成"
fi
fi
# 检查是否为离线模式
OFFLINE_MODE=false
if [[ "$1" == "offline" || "$2" == "offline" ]]; then
OFFLINE_MODE=true
echo "🔌 离线部署模式已启用"
# 如果第一个参数是offline,使用默认配置文件
if [[ "$1" == "offline" ]]; then
CONFIG_FILE="custom-config.env"
else
CONFIG_FILE="$1"
fi
else
# 默认配置文件
CONFIG_FILE=${1:-"custom-config.env"}
fi
echo "==================== 智能穿戴系统客户现场部署 ===================="
echo "使用配置文件: $CONFIG_FILE"
if [ "$OFFLINE_MODE" = true ]; then
echo "部署模式: 离线部署 (使用本地镜像 + Docker命名卷持久化)"
else
echo "部署模式: 在线部署 (预构建镜像部署 + Docker命名卷持久化)"
fi
# 检查配置文件是否存在
if [ ! -f "$CONFIG_FILE" ]; then
echo "错误: 配置文件 $CONFIG_FILE 不存在"
echo "请复制 custom-config.env 并根据客户需求修改配置"
exit 1
fi
# 检查Docker和docker-compose是否安装
if ! command -v docker > /dev/null 2>&1; then
echo "错误: Docker 未安装,请先安装Docker"
exit 1
fi
if ! command -v docker-compose > /dev/null 2>&1; then
echo "错误: docker-compose 未安装,请先安装docker-compose"
exit 1
fi
# 加载配置文件
. "$CONFIG_FILE"
echo ""
echo "==================== 配置验证 ===================="
# 验证配置一致性
if [ -f "validate-config.sh" ]; then
bash validate-config.sh $CONFIG_FILE
else
echo "⚠️ 警告: validate-config.sh 不存在,跳过配置验证"
fi
echo ""
echo "配置信息:"
echo "- 大屏标题: $BIGSCREEN_TITLE"
echo "- 管理端标题: $VITE_APP_TITLE"
echo "- 公司名称: $COMPANY_NAME"
echo "- 服务器IP: $SERVER_IP"
echo "- 大屏端口: $BIGSCREEN_PORT"
echo "- 大屏地址: $VITE_BIGSCREEN_URL"
# 检查必需的定制化文件
echo ""
echo "==================== 定制化文件检查 ===================="
# 文件挂载修复函数 #避免Docker创建目录
fix_file_mounts() {
echo "🔧 修复文件挂载问题..."
# 创建必需的目录结构
mkdir -p logs/{mysql,redis,ljwx-boot,ljwx-bigscreen,ljwx-admin}
mkdir -p backup/{mysql,redis,ljwx-boot,ljwx-bigscreen}
# 检查client-data.sql
if [ ! -f "client-data.sql" ]; then
echo "⚠️ 警告: client-data.sql 不存在,将影响首次部署数据初始化"
echo "-- 客户数据占位符" > client-data.sql
fi
echo "✅ 文件挂载修复完成 (已创建logs和backup目录结构)"
}
# 执行文件挂载修复
fix_file_mounts
# 检查自定义配置文件
if [ -f "custom-config.py" ]; then
echo "✅ 自定义Python配置: custom-config.py"
else
echo "❌ 错误: 未找到 custom-config.py 文件"
exit 1
fi
if [ -f "custom-admin-config.js" ]; then
echo "✅ 自定义前端配置: custom-admin-config.js"
else
echo "❌ 错误: 未找到 custom-admin-config.js 文件"
exit 1
fi
# 智能数据文件检查 #区分首次部署和升级
is_first_deployment() {
# 检查是否存在任何命名卷数据
for volume in mysql_data redis_data ljwx_boot_data ljwx_bigscreen_data; do
if docker volume ls -q | grep -q "^client-deployment_${volume}$"; then
return 1 #不是首次部署
fi
done
return 0 #首次部署
}
if is_first_deployment; then
echo "📦 检测到首次部署"
if [ -f "client-data.sql" ] && [ -s "client-data.sql" ]; then
echo "✅ 客户数据文件: client-data.sql (首次部署必需)"
else
echo "⚠️ 警告: client-data.sql 文件为空或不存在,将使用默认数据"
fi
else
echo "🔄 检测到升级部署 (已有数据卷)"
if [ -f "client-data.sql" ]; then
echo "ℹ️ info: client-data.sql 存在但不会被使用 (数据已持久化)"
fi
fi
# 检查自定义资源
if [ -d "custom-assets" ]; then
echo "✅ 自定义资源目录: custom-assets/"
else
echo "⚠️ 警告: 未找到 custom-assets/ 目录,将使用默认资源"
mkdir -p custom-assets
fi
echo ""
read -p "确认使用以上配置进行部署? (y/N): " confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
echo "部署已取消"
exit 0
fi
echo ""
echo "==================== Docker命名卷状态检查 ===================="
# 检查现有Docker命名卷
echo "检查现有Docker命名卷..."
EXISTING_VOLUMES=""
for volume in mysql_data redis_data ljwx_boot_data ljwx_bigscreen_data; do
if docker volume ls -q | grep -q "^client-deployment_${volume}$"; then
EXISTING_VOLUMES="$EXISTING_VOLUMES $volume"
fi
done
if [ -n "$EXISTING_VOLUMES" ]; then
echo "发现现有Docker命名卷:"
for volume in $EXISTING_VOLUMES; do
echo " - client-deployment_$volume"
done
echo ""
# 智能备份策略选择
echo "🔄 升级部署检测到现有数据,请选择备份策略:"
echo "1) 在线备份 (推荐) - 需要容器运行,完整SQL导出"
echo "2) 离线卷备份 - 直接备份Docker卷,速度快"
echo "3) 跳过备份 - 风险自担,直接升级"
echo "4) 退出部署 - 手动处理后再运行"
echo ""
while true; do
read -p "请选择备份策略 [1-4]: " backup_choice
case $backup_choice in
1)
echo "📦 执行在线备份..."
# 检查容器是否运行
if docker ps | grep -q ljwx-mysql; then
if [ -f "auto-backup.sh" ]; then
./auto-backup.sh
echo "✅ 在线备份完成"
else
echo "❌ auto-backup.sh 不存在,改用卷备份"
backup_choice=2
continue
fi
else
echo "❌ MySQL容器未运行,无法在线备份,改用卷备份"
backup_choice=2
continue
fi
break
;;
2)
echo "📦 执行离线卷备份..."
backup_dir="backup/volume_backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"
for volume in $EXISTING_VOLUMES; do
echo "备份卷: client-deployment_$volume"
docker run --rm -v "client-deployment_$volume:/data" -v "$(pwd)/$backup_dir:/backup" alpine tar czf "/backup/${volume}.tar.gz" -C /data . 2>/dev/null || echo "警告: $volume 备份失败"
done
echo "✅ 离线卷备份完成: $backup_dir"
break
;;
3)
echo "⚠️ 跳过备份,直接升级 (风险自担)"
break
;;
4)
echo "部署已取消"
exit 0
;;
*)
echo "❌ 无效选择,请输入 1-4"
;;
esac
done
else
echo "✅ 首次部署,将创建新的Docker命名卷"
fi
if [ "$OFFLINE_MODE" = false ]; then
echo ""
echo "==================== 登录镜像仓库 ===================="
echo "正在登录阿里云容器镜像服务..."
# 登录Docker镜像仓库
echo "admin123" | docker login --username=brunogao --password-stdin crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com
if [ $? -ne 0 ]; then
echo "❌ Docker登录失败,请检查用户名和密码"
exit 1
fi
echo "✅ Docker登录成功"
echo ""
echo "==================== 拉取最新镜像 ===================="
echo "正在拉取预构建镜像..."
# 从docker-compose.yml动态读取版本号
MYSQL_IMAGE=$(egrep '^ *image:.*ljwx-mysql:' docker-compose.yml | sed 's/.*ljwx-mysql:\([^ ]*\).*/\1/' | head -1)
REDIS_IMAGE=$(egrep '^ *image:.*ljwx-redis:' docker-compose.yml | sed 's/.*ljwx-redis:\([^ ]*\).*/\1/' | head -1)
BOOT_IMAGE=$(egrep '^ *image:.*ljwx-boot:' docker-compose.yml | sed 's/.*ljwx-boot:\([^ ]*\).*/\1/' | head -1)
BIGSCREEN_IMAGE=$(egrep '^ *image:.*ljwx-bigscreen:' docker-compose.yml | sed 's/.*ljwx-bigscreen:\([^ ]*\).*/\1/' | head -1)
ADMIN_IMAGE=$(egrep '^ *image:.*ljwx-admin:' docker-compose.yml | sed 's/.*ljwx-admin:\([^ ]*\).*/\1/' | head -1)
# 显示检测到的版本号
echo "检测到的镜像版本:"
echo "- ljwx-mysql: $MYSQL_IMAGE"
echo "- ljwx-redis: $REDIS_IMAGE"
echo "- ljwx-boot: $BOOT_IMAGE"
echo "- ljwx-bigscreen: $BIGSCREEN_IMAGE"
echo "- ljwx-admin: $ADMIN_IMAGE"
# 拉取最新镜像
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:$MYSQL_IMAGE || echo "警告: 无法拉取 ljwx-mysql 镜像,将使用本地镜像"
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-redis:$REDIS_IMAGE || echo "警告: 无法拉取 ljwx-redis 镜像,将使用本地镜像"
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:$BOOT_IMAGE || echo "警告: 无法拉取 ljwx-boot 镜像,将使用本地镜像"
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-bigscreen:$BIGSCREEN_IMAGE || echo "警告: 无法拉取 ljwx-bigscreen 镜像,将使用本地镜像"
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-admin:$ADMIN_IMAGE || echo "警告: 无法拉取 ljwx-admin 镜像,将使用本地镜像"
else
echo ""
echo "==================== 离线模式 ===================="
echo "🔌 使用本地镜像部署,跳过镜像下载步骤"
# 从docker-compose.yml动态读取版本号
MYSQL_IMAGE=$(egrep '^ *image:.*ljwx-mysql:' docker-compose.yml | sed 's/.*ljwx-mysql:\([^ ]*\).*/\1/' | head -1)
REDIS_IMAGE=$(egrep '^ *image:.*ljwx-redis:' docker-compose.yml | sed 's/.*ljwx-redis:\([^ ]*\).*/\1/' | head -1)
BOOT_IMAGE=$(egrep '^ *image:.*ljwx-boot:' docker-compose.yml | sed 's/.*ljwx-boot:\([^ ]*\).*/\1/' | head -1)
BIGSCREEN_IMAGE=$(egrep '^ *image:.*ljwx-bigscreen:' docker-compose.yml | sed 's/.*ljwx-bigscreen:\([^ ]*\).*/\1/' | head -1)
ADMIN_IMAGE=$(egrep '^ *image:.*ljwx-admin:' docker-compose.yml | sed 's/.*ljwx-admin:\([^ ]*\).*/\1/' | head -1)
echo "将使用本地镜像版本:"
echo "- ljwx-mysql: $MYSQL_IMAGE"
echo "- ljwx-redis: $REDIS_IMAGE"
echo "- ljwx-boot: $BOOT_IMAGE"
echo "- ljwx-bigscreen: $BIGSCREEN_IMAGE"
echo "- ljwx-admin: $ADMIN_IMAGE"
# 检查本地镜像是否存在
echo ""
echo "检查本地镜像可用性..."
MISSING_IMAGES=""
for image in "ljwx-mysql:$MYSQL_IMAGE" "ljwx-redis:$REDIS_IMAGE" "ljwx-boot:$BOOT_IMAGE" "ljwx-bigscreen:$BIGSCREEN_IMAGE" "ljwx-admin:$ADMIN_IMAGE"; do
if docker images -q "crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/$image" | grep -q "."; then
echo "✅ $image - 本地镜像可用"
else
echo "⚠️ $image - 本地镜像不存在"
MISSING_IMAGES="$MISSING_IMAGES $image"
fi
done
if [ -n "$MISSING_IMAGES" ]; then
echo ""
echo "❌ 警告: 以下镜像在本地不存在:"
for img in $MISSING_IMAGES; do
echo " - $img"
done
echo ""
read -p "是否继续离线部署? 缺失的镜像将导致服务启动失败 (y/N): " continue_offline
if [ "$continue_offline" != "y" ] && [ "$continue_offline" != "Y" ]; then
echo "部署已取消。请先拉取所需镜像或使用在线模式"
exit 1
fi
fi
fi
echo ""
echo "==================== 开始部署 ===================="
# 生成动态docker-compose配置
echo "🔧 生成动态配置..."
if [ -f "generate-docker-compose.sh" ]; then
chmod +x generate-docker-compose.sh
./generate-docker-compose.sh
COMPOSE_FILE="docker-compose-generated.yml"
else
echo "⚠️ 警告: generate-docker-compose.sh 不存在,使用静态配置"
COMPOSE_FILE="docker-compose.yml"
fi
# 停止现有服务但保留数据卷
echo "停止现有服务(保留命名卷)..."
docker-compose -f $COMPOSE_FILE down
# 清理可能存在的容器冲突
echo "清理容器冲突..."
docker rm -f ljwx-mysql ljwx-redis ljwx-boot ljwx-bigscreen ljwx-admin 2>/dev/null || true
# 确保目录权限正确
echo "🔧 设置目录权限..."
chmod -R 755 logs/ backup/ 2>/dev/null || true
echo "✅ 目录权限设置完成"
# 启动服务 - 根据模式选择不同的启动方式
if [ "$OFFLINE_MODE" = true ]; then
echo "启动服务(离线模式-禁用镜像拉取)..."
# 离线模式:强制使用本地镜像,禁止拉取
docker-compose -f $COMPOSE_FILE --env-file $CONFIG_FILE up -d --pull never
else
echo "启动服务(在线模式-允许镜像拉取)..."
# 在线模式:允许拉取缺失的镜像
docker-compose -f $COMPOSE_FILE --env-file $CONFIG_FILE up -d --pull missing
fi
# 等待服务启动
echo "等待服务启动..."
sleep 20
# 检查服务状态
echo ""
echo "==================== 服务状态检查 ===================="
docker-compose -f $COMPOSE_FILE ps
# 检查Docker命名卷状态
echo ""
echo "Docker命名卷状态:"
for volume in mysql_data redis_data ljwx_boot_data ljwx_bigscreen_data; do
if docker volume ls -q | grep -q "client-deployment_${volume}"; then
echo " - client-deployment_$volume: ✅ 已创建"
else
echo " - client-deployment_$volume: ❌ 未创建"
fi
done
# 检查服务健康状态
echo ""
echo "检查服务健康状态..."
# 后端服务健康检查
echo "等待后端服务启动..."
i=1
while [ $i -le 60 ]; do
if curl -s http://localhost:9998/actuator/health > /dev/null 2>&1; then
echo "✅ 后端服务启动成功"
break
fi
if [ $i -eq 60 ]; then
echo "⚠️ 后端服务启动超时,请检查日志: docker-compose logs ljwx-boot"
fi
sleep 2
i=$((i + 1))
done
# 大屏服务健康检查
echo "等待大屏服务启动..."
i=1
while [ $i -le 60 ]; do
if curl -s http://localhost:8001/health > /dev/null 2>&1; then
echo "✅ 大屏服务启动成功"
break
fi
if [ $i -eq 60 ]; then
echo "⚠️ 大屏服务启动超时,请检查日志: docker-compose logs ljwx-bigscreen"
fi
sleep 2
i=$((i + 1))
done
# 管理端服务健康检查
echo "等待管理端服务启动..."
i=1
while [ $i -le 60 ]; do
if curl -s http://localhost:8080 > /dev/null 2>&1; then
echo "✅ 管理端服务启动成功"
break
fi
if [ $i -eq 60 ]; then
echo "⚠️ 管理端服务启动超时,请检查日志: docker-compose logs ljwx-admin"
fi
sleep 2
i=$((i + 1))
done
echo ""
echo "==================== 定制化配置后处理 ===================="
# 等待服务完全启动后再进行定制化配置
sleep 5
# 替换前端应用中的大屏URL
echo "🔄 更新前端大屏链接地址..."
if [ -f "replace-bigscreen-url.sh" ]; then
./replace-bigscreen-url.sh
echo "✅ 大屏链接地址更新完成"
# 清除浏览器缓存
echo ""
echo "🗑️ 清除浏览器缓存..."
if [ -f "clear-browser-cache.sh" ]; then
./clear-browser-cache.sh
echo "✅ 浏览器缓存清除完成"
else
echo "⚠️ 警告: clear-browser-cache.sh 脚本不存在"
fi
else
echo "⚠️ 警告: replace-bigscreen-url.sh 脚本不存在"
fi
# 验证挂载配置
echo ""
echo "🔍 验证容器挂载配置..."
if [ -f "verify-mounts.sh" ]; then
./verify-mounts.sh
echo "✅ 挂载配置验证完成"
else
echo "⚠️ 警告: verify-mounts.sh 脚本不存在,跳过挂载验证"
fi
# 显示访问地址
echo ""
echo "==================== 部署完成 ===================="
echo "🎉 客户定制化系统部署成功!"
echo ""
echo "服务访问地址:"
echo "- 管理端: http://localhost:8080"
echo "- 大屏端: http://localhost:8001"
echo "- 后端API: http://localhost:9998"
echo ""
echo "数据持久化:"
echo "- MySQL数据: client-deployment_mysql_data (命名卷)"
echo "- Redis数据: client-deployment_redis_data (命名卷)"
echo "- 后端数据: client-deployment_ljwx_boot_data (命名卷)"
echo "- 大屏数据: client-deployment_ljwx_bigscreen_data (命名卷)"
echo ""
echo "日志挂载 (宿主机目录):"
echo "- MySQL日志: ./logs/mysql/"
echo "- Redis日志: ./logs/redis/"
echo "- 后端日志: ./logs/ljwx-boot/"
echo "- 大屏日志: ./logs/ljwx-bigscreen/"
echo "- 管理端日志: ./logs/ljwx-admin/"
echo ""
echo "默认登录账号: admin / 123456"
echo ""
echo "管理命令:"
echo "- 日志查看工具: ./logs-viewer.sh (推荐)"
echo "- 查看容器日志: docker-compose logs -f [服务名]"
echo "- 查看文件日志: tail -f logs/[服务名]/*.log"
echo "- 停止服务: docker-compose down"
echo "- 重启服务: docker-compose restart [服务名]"
echo "- 数据备份: ./auto-backup.sh"
echo "- 数据恢复: ./restore-backup.sh"
echo "- 查看命名卷: docker volume ls"
echo ""
echo "备份恢复:"
echo "- 在线备份: ./auto-backup.sh (需要容器运行)"
echo "- 离线卷备份: 直接备份Docker卷到tar.gz"
echo "- 数据恢复: 通过备份恢复脚本或手动恢复"
echo ""
echo "部署模式说明:"
echo "- 在线模式: ./deploy-client.sh [配置文件]"
echo "- 离线模式: ./deploy-client.sh offline 或 ./deploy-client.sh [配置文件] offline"
echo ""
echo "如有问题,请联系技术支持"
@@ -0,0 +1,164 @@
services:
mysql:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:1.2.16
container_name: ljwx-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-lj-06}
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./logs/mysql:/var/log/mysql
- ./backup/mysql:/backup/mysql
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max_connections=200
- --wait_timeout=28800
- --interactive_timeout=28800
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- ljwx-network
redis:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-redis:1.2.16
container_name: ljwx-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./logs/redis:/var/log/redis
- ./backup/redis:/backup/redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
networks:
- ljwx-network
ljwx-boot:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:1.2.16
container_name: ljwx-boot
restart: unless-stopped
env_file:
- custom-config.env
environment:
SPRING_PROFILES_ACTIVE: common
MYSQL_HOST: ${MYSQL_HOST:-mysql}
MYSQL_PORT: ${MYSQL_PORT:-3306}
MYSQL_USER: ${MYSQL_USER:-root}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-lj-06}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: "123456"
TZ: Asia/Shanghai
SPRING_DATASOURCE_URL: jdbc:mysql://${MYSQL_HOST:-mysql}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-lj-06}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
SPRING_DATASOURCE_USERNAME: ${MYSQL_USER:-root}
SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD:-123456}
SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.cj.jdbc.Driver
SPRING_DATA_REDIS_HOST: ${REDIS_HOST:-redis}
SPRING_DATA_REDIS_PORT: ${REDIS_PORT:-6379}
SPRING_DATA_REDIS_PASSWORD: "123456"
ports:
- "9998:9998"
volumes:
- ./logs/ljwx-boot:/app/ljwx-boot-logs
- ljwx_boot_data:/app/data
- ./backup/ljwx-boot:/backup/ljwx-boot
command: ["sh", "-c", "sleep 30 && java -jar /app/ljwx-boot-admin.jar"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9998/actuator/health"]
interval: 60s
timeout: 10s
retries: 3
start_period: 120s
networks:
- ljwx-network
ljwx-bigscreen:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-bigscreen:1.2.16
container_name: ljwx-bigscreen
restart: unless-stopped
env_file:
- custom-config.env
environment:
MYSQL_HOST: ${MYSQL_HOST:-mysql}
MYSQL_PORT: ${MYSQL_PORT:-3306}
MYSQL_USER: ${MYSQL_USER:-root}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-lj-06}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
IS_DOCKER: "true"
APP_PORT: 8001
TZ: Asia/Shanghai
ports:
- "8001:8001"
volumes:
- ./custom-config.py:/app/config.py:ro
- ./custom-assets:/app/static/images:ro
- ./logs/ljwx-bigscreen:/app/logs
- ljwx_bigscreen_data:/app/data
- ./backup/ljwx-bigscreen:/backup/ljwx-bigscreen
command: ["sh", "-c", "sleep 45 && python app.py"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
interval: 60s
timeout: 10s
retries: 3
start_period: 90s
networks:
- ljwx-network
ljwx-admin:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-admin:1.2.16
container_name: ljwx-admin
restart: unless-stopped
env_file:
- custom-config.env
environment:
VITE_API_URL: http://ljwx-boot:9998
VITE_BIGSCREEN_URL: ${VITE_BIGSCREEN_URL:-http://localhost:8001}
VITE_APP_TITLE: ${VITE_APP_TITLE:-穿戴管理演示系统}
TZ: Asia/Shanghai
ports:
- "8080:80"
volumes:
- ./logs/ljwx-admin:/var/log/nginx
- ./custom-admin-config.js:/tmp/custom-admin-config.js:ro
command: ["sh", "-c", "sleep 60 && nginx -g 'daemon off;'"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 60s
timeout: 5s
retries: 3
start_period: 60s
networks:
- ljwx-network
volumes:
mysql_data:
redis_data:
ljwx_boot_data:
ljwx_bigscreen_data:
networks:
ljwx-network:
driver: bridge
+165
View File
@@ -0,0 +1,165 @@
services:
mysql:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:1.2.16
container_name: ljwx-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-lj-06}
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./logs/mysql:/var/log/mysql
- ./backup/mysql:/backup/mysql
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max_connections=200
- --wait_timeout=28800
- --interactive_timeout=28800
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- ljwx-network
redis:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-redis:1.2.16
container_name: ljwx-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./logs/redis:/var/log/redis
- ./backup/redis:/backup/redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
networks:
- ljwx-network
ljwx-boot:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:1.2.16
container_name: ljwx-boot
restart: unless-stopped
env_file:
- custom-config.env
environment:
SPRING_PROFILES_ACTIVE: common
MYSQL_HOST: ${MYSQL_HOST:-mysql}
MYSQL_PORT: ${MYSQL_PORT:-3306}
MYSQL_USER: ${MYSQL_USER:-root}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-lj-06}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: "123456"
TZ: Asia/Shanghai
SPRING_DATASOURCE_URL: jdbc:mysql://${MYSQL_HOST:-mysql}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-lj-06}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
SPRING_DATASOURCE_USERNAME: ${MYSQL_USER:-root}
SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD:-123456}
SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.cj.jdbc.Driver
SPRING_DATA_REDIS_HOST: ${REDIS_HOST:-redis}
SPRING_DATA_REDIS_PORT: ${REDIS_PORT:-6379}
SPRING_DATA_REDIS_PASSWORD: "123456"
SPRING_TASK_SCHEDULING_POOL_SIZE: 10
SPRING_QUARTZ_PROPERTIES_ORG_QUARTZ_THREADPOOL_THREADCOUNT: 10
ports:
- "9998:9998"
volumes:
- ./logs/ljwx-boot:/app/ljwx-boot-logs
- ljwx_boot_data:/app/data
- ./backup/ljwx-boot:/backup/ljwx-boot
command: ["sh", "-c", "sleep 30 && java -jar /app/ljwx-boot-admin.jar"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9998/actuator/health"]
interval: 60s
timeout: 10s
retries: 3
start_period: 120s
networks:
- ljwx-network
ljwx-bigscreen:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-bigscreen:1.2.16
container_name: ljwx-bigscreen
restart: unless-stopped
env_file:
- custom-config.env
environment:
MYSQL_HOST: ${MYSQL_HOST:-mysql}
MYSQL_PORT: ${MYSQL_PORT:-3306}
MYSQL_USER: ${MYSQL_USER:-root}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: ${MYSQL_DATABASE:-lj-06}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
IS_DOCKER: "true"
APP_PORT: 8001
TZ: Asia/Shanghai
ports:
- "8001:8001"
volumes:
- ./custom-config.py:/app/config.py:ro
- ./custom-assets:/app/static/images:ro
- ./logs/ljwx-bigscreen:/app/logs
- ljwx_bigscreen_data:/app/data
- ./backup/ljwx-bigscreen:/backup/ljwx-bigscreen
command: ["sh", "-c", "sleep 45 && python app.py"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
interval: 60s
timeout: 10s
retries: 3
start_period: 90s
networks:
- ljwx-network
ljwx-admin:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-admin:1.2.16
container_name: ljwx-admin
restart: unless-stopped
env_file:
- custom-config.env
environment:
VITE_API_URL: http://ljwx-boot:9998
VITE_BIGSCREEN_URL: ${VITE_BIGSCREEN_URL:-http://localhost:8001}
VITE_APP_TITLE: ${VITE_APP_TITLE:-穿戴管理演示系统}
TZ: Asia/Shanghai
ports:
- "8080:80"
volumes:
- ./logs/ljwx-admin:/var/log/nginx
- ./custom-admin-config.js:/tmp/custom-admin-config.js:ro
command: ["sh", "-c", "sleep 60 && nginx -g 'daemon off;'"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 60s
timeout: 5s
retries: 3
start_period: 60s
networks:
- ljwx-network
volumes:
mysql_data:
redis_data:
ljwx_boot_data:
ljwx_bigscreen_data:
networks:
ljwx-network:
driver: bridge
+782
View File
@@ -0,0 +1,782 @@
#!/bin/bash
# LJWX 环境自适应MySQL连接修复脚本
# 专门解决不同环境差异导致的连接问题
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}🔍 LJWX环境自适应修复工具${NC}"
echo "=================================="
# 环境检测函数
detect_environment() {
echo -e "${BLUE}📊 检测当前环境特征...${NC}"
# 检测操作系统
OS=$(uname -s)
ARCH=$(uname -m)
echo -e "${BLUE}🖥️ 操作系统: $OS $ARCH${NC}"
# 检测Linux发行版(如果是Linux)
if [ "$OS" = "Linux" ]; then
if [ -f /etc/os-release ]; then
DISTRO=$(grep '^ID=' /etc/os-release | cut -d'=' -f2 | tr -d '"')
VERSION=$(grep '^VERSION_ID=' /etc/os-release | cut -d'=' -f2 | tr -d '"')
echo -e "${BLUE}📦 Linux发行版: $DISTRO $VERSION${NC}"
elif [ -f /etc/centos-release ]; then
DISTRO="centos"
VERSION=$(cat /etc/centos-release | grep -oE '[0-9]+\.[0-9]+')
echo -e "${BLUE}📦 Linux发行版: $DISTRO $VERSION${NC}"
fi
fi
# 检测Docker版本
DOCKER_VERSION=$(docker --version 2>/dev/null | cut -d' ' -f3 | cut -d',' -f1)
echo -e "${BLUE}🐳 Docker版本: $DOCKER_VERSION${NC}"
# 检测当前用户
CURRENT_USER=$(whoami)
USER_ID=$(id -u)
GROUP_ID=$(id -g)
echo -e "${BLUE}👤 当前用户: $CURRENT_USER ($USER_ID:$GROUP_ID)${NC}"
# 检测系统资源(跨平台)
detect_system_resources
# 检测磁盘类型(跨平台)
detect_disk_type
# 检测网络环境
if ping -c 1 8.8.8.8 >/dev/null 2>&1; then
NETWORK_STATUS="online"
else
NETWORK_STATUS="offline/restricted"
fi
echo -e "${BLUE}🌐 网络状态: $NETWORK_STATUS${NC}"
echo ""
}
# 跨平台系统资源检测
detect_system_resources() {
case "$OS" in
"Darwin") # macOS
TOTAL_MEM=$(sysctl -n hw.memsize | awk '{print int($1/1024/1024/1024)}')
AVAILABLE_MEM=$(vm_stat | awk '/Pages free:/{free=$3} /Pages inactive:/{inactive=$3} END{print int((free+inactive)*4096/1024/1024/1024)}')
CPU_CORES=$(sysctl -n hw.ncpu)
;;
"Linux") # Linux (Ubuntu/CentOS等)
if command -v free >/dev/null 2>&1; then
TOTAL_MEM=$(free -g | awk '/^Mem:/{print $2}')
AVAILABLE_MEM=$(free -g | awk '/^Mem:/{print $7}' 2>/dev/null || free -g | awk '/^Mem:/{print $4}')
else
# 备用方法(某些最小化系统可能没有free命令)
TOTAL_MEM=$(cat /proc/meminfo | grep MemTotal | awk '{print int($2/1024/1024)}')
AVAILABLE_MEM=$(cat /proc/meminfo | grep MemAvailable | awk '{print int($2/1024/1024)}' 2>/dev/null || echo "N/A")
fi
if command -v nproc >/dev/null 2>&1; then
CPU_CORES=$(nproc)
else
CPU_CORES=$(grep -c ^processor /proc/cpuinfo)
fi
;;
*)
TOTAL_MEM="unknown"
AVAILABLE_MEM="unknown"
CPU_CORES="unknown"
;;
esac
echo -e "${BLUE}💾 系统资源: ${CPU_CORES}核CPU, ${TOTAL_MEM}GB内存(可用${AVAILABLE_MEM}GB)${NC}"
}
# 跨平台磁盘类型检测
detect_disk_type() {
DISK_TYPE="unknown"
case "$OS" in
"Darwin") # macOS
# 检测主磁盘类型
if diskutil info / | grep -q "Solid State"; then
DISK_TYPE="SSD"
elif diskutil info / | grep -q "Rotational"; then
DISK_TYPE="HDD"
else
# 尝试通过系统信息检测
if system_profiler SPStorageDataType 2>/dev/null | grep -q "SSD\|Solid State"; then
DISK_TYPE="SSD"
else
DISK_TYPE="HDD"
fi
fi
;;
"Linux") # Linux
if command -v lsblk >/dev/null 2>&1; then
# 使用lsblk检测(现代Linux系统)
if lsblk -d -o name,rota 2>/dev/null | grep -q "0"; then
DISK_TYPE="SSD"
else
DISK_TYPE="HDD"
fi
else
# 备用方法:检查/sys/block/
for disk in /sys/block/sd* /sys/block/nvme*; do
[ ! -e "$disk" ] && continue # 跳过不存在的路径
if [ -f "$disk/queue/rotational" ]; then
if [ "$(cat $disk/queue/rotational)" = "0" ]; then
DISK_TYPE="SSD"
break
else
DISK_TYPE="HDD"
fi
fi
done
fi
;;
esac
echo -e "${BLUE}💿 存储类型: $DISK_TYPE${NC}"
}
# 环境适配配置生成
generate_adaptive_config() {
echo -e "${BLUE}⚙️ 生成环境适配配置...${NC}"
# 基于环境特征调整MySQL配置
local mysql_config=""
# 内存适配(处理数字比较,兼容字符串情况)
if [ "$TOTAL_MEM" != "unknown" ] && [ "$TOTAL_MEM" -ge 16 ] 2>/dev/null; then
# 高内存环境
mysql_config+=" - --innodb_buffer_pool_size=4G\n"
mysql_config+=" - --max_connections=500\n"
mysql_config+=" - --thread_cache_size=100\n"
echo -e "${GREEN}✅ 高内存配置: 4GB缓冲池, 500连接${NC}"
elif [ "$TOTAL_MEM" != "unknown" ] && [ "$TOTAL_MEM" -ge 8 ] 2>/dev/null; then
# 中等内存环境
mysql_config+=" - --innodb_buffer_pool_size=2G\n"
mysql_config+=" - --max_connections=300\n"
mysql_config+=" - --thread_cache_size=50\n"
echo -e "${YELLOW}⚠️ 中等内存配置: 2GB缓冲池, 300连接${NC}"
else
# 低内存环境或无法检测
mysql_config+=" - --innodb_buffer_pool_size=512M\n"
mysql_config+=" - --max_connections=200\n"
mysql_config+=" - --thread_cache_size=20\n"
echo -e "${YELLOW}⚠️ 低内存/默认配置: 512MB缓冲池, 200连接${NC}"
fi
# 存储适配
if [ "$DISK_TYPE" = "SSD" ]; then
mysql_config+=" - --innodb_flush_log_at_trx_commit=2\n"
mysql_config+=" - --innodb_io_capacity=2000\n"
echo -e "${GREEN}✅ SSD优化配置${NC}"
else
mysql_config+=" - --innodb_flush_log_at_trx_commit=1\n"
mysql_config+=" - --innodb_io_capacity=200\n"
echo -e "${YELLOW}⚠️ HDD/默认保守配置${NC}"
fi
# 操作系统特定优化
case "$OS" in
"Darwin") # macOS
mysql_config+=" - --innodb_use_native_aio=0\n" # macOS不支持native AIO
echo -e "${BLUE}🍎 macOS特定优化: 禁用native AIO${NC}"
;;
"Linux") # Linux
mysql_config+=" - --innodb_use_native_aio=1\n" # Linux支持native AIO
if [ "$DISTRO" = "centos" ] || [ "$DISTRO" = "rhel" ]; then
# CentOS/RHEL特定优化
mysql_config+=" - --skip-name-resolve\n" # 跳过DNS解析
echo -e "${BLUE}🔴 CentOS/RHEL特定优化: 跳过DNS解析${NC}"
fi
;;
esac
# 长时间空置适配(核心配置)
mysql_config+=" - --wait_timeout=86400\n" # 24小时
mysql_config+=" - --interactive_timeout=86400\n" # 24小时
mysql_config+=" - --net_read_timeout=600\n" # 10分钟
mysql_config+=" - --net_write_timeout=600\n" # 10分钟
mysql_config+=" - --connect_timeout=60\n" # 1分钟
mysql_config+=" - --max_connect_errors=100000\n" # 大量连接错误容忍
echo -e "${GREEN}✅ 长时间空置优化配置${NC}"
# 生成适配的docker-compose配置
cat > docker-compose-adaptive.yml << EOF
services:
mysql:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:1.2.4
container_name: ljwx-mysql
restart: unless-stopped
user: "$USER_ID:$GROUP_ID" # 使用当前用户权限
environment:
MYSQL_ROOT_PASSWORD: \${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: lj-06
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- ./data/mysql:/var/lib/mysql
- ./logs/mysql:/var/log/mysql
- ../backup/mysql:/backup/mysql
- ./client-data.sql:/docker-entrypoint-initdb.d/client/data.sql:ro
- ./client-admin.sql:/docker-entrypoint-initdb.d/client/admin.sql:ro
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --explicit_defaults_for_timestamp=true
- --lower_case_table_names=1
- --max_allowed_packet=128M
- --sql_mode=STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO
$(echo -e "$mysql_config")
- --log-error=/var/log/mysql/error.log
- --slow-query-log=1
- --slow-query-log-file=/var/log/mysql/slow.log
- --long_query_time=2
networks:
- ljwx-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p\${MYSQL_PASSWORD:-123456}"]
interval: 10s # 频繁检查
timeout: 5s
retries: 5
start_period: 60s
redis:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-redis:1.1.7
container_name: ljwx-redis
restart: unless-stopped
user: "$USER_ID:$GROUP_ID" # 使用当前用户权限
environment:
TZ: Asia/Shanghai
ports:
- "6379:6379"
volumes:
- ./data/redis:/data
- ./logs/redis:/var/log/redis
- ../backup/redis:/backup/redis
networks:
- ljwx-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 5s
retries: 3
ljwx-boot:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:1.2.4
container_name: ljwx-boot
restart: unless-stopped
env_file:
- custom-config.env
environment:
SPRING_PROFILES_ACTIVE: common,docker-adaptive
MYSQL_HOST: \${MYSQL_HOST:-mysql}
MYSQL_PORT: \${MYSQL_PORT:-3306}
MYSQL_USER: \${MYSQL_USER:-root}
MYSQL_PASSWORD: \${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: \${MYSQL_DATABASE:-lj-06}
REDIS_HOST: \${REDIS_HOST:-redis}
REDIS_PORT: \${REDIS_PORT:-6379}
REDIS_PASSWORD: "123456"
TZ: Asia/Shanghai
# 环境自适应HikariCP配置
SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE: $([ "$TOTAL_MEM" != "unknown" ] && [ "$TOTAL_MEM" -ge 8 ] 2>/dev/null && echo "30" || echo "15")
SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE: $([ "$TOTAL_MEM" != "unknown" ] && [ "$TOTAL_MEM" -ge 8 ] 2>/dev/null && echo "10" || echo "5")
SPRING_DATASOURCE_HIKARI_CONNECTION_TIMEOUT: 60000
SPRING_DATASOURCE_HIKARI_IDLE_TIMEOUT: 600000 # 10分钟空闲
SPRING_DATASOURCE_HIKARI_MAX_LIFETIME: 3600000 # 1小时最大生命周期
SPRING_DATASOURCE_HIKARI_KEEP_ALIVE_TIME: 60000 # 1分钟保活
SPRING_DATASOURCE_HIKARI_VALIDATION_TIMEOUT: 5000
SPRING_DATASOURCE_HIKARI_CONNECTION_TEST_QUERY: "SELECT 1"
SPRING_DATASOURCE_HIKARI_LEAK_DETECTION_THRESHOLD: 30000
ports:
- "9998:9998"
volumes:
- ./logs/ljwx-boot:/app/ljwx-boot-logs
- ./data/ljwx-boot:/app/data
- ../backup/ljwx-boot:/backup/ljwx-boot
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- ljwx-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9998/actuator/health/db"]
interval: 30s # 定期检查数据源
timeout: 10s
retries: 5
start_period: 120s
ljwx-bigscreen:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-bigscreen:1.2.4
container_name: ljwx-bigscreen
restart: unless-stopped
env_file:
- custom-config.env
environment:
MYSQL_HOST: \${MYSQL_HOST:-mysql}
MYSQL_PORT: \${MYSQL_PORT:-3306}
MYSQL_USER: \${MYSQL_USER:-root}
MYSQL_PASSWORD: \${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: \${MYSQL_DATABASE:-lj-06}
REDIS_HOST: \${REDIS_HOST:-redis}
REDIS_PORT: \${REDIS_PORT:-6379}
REDIS_PASSWORD: \${REDIS_PASSWORD:-123456}
IS_DOCKER: "true"
APP_PORT: 8001
TZ: Asia/Shanghai
ports:
- "8001:8001"
volumes:
- ./custom-config.py:/app/config.py:ro
- ./custom-assets:/app/static/images:ro
- ./logs/ljwx-bigscreen:/app/logs
- ./data/ljwx-bigscreen:/app/data
- ../backup/ljwx-bigscreen:/backup/ljwx-bigscreen
depends_on:
- mysql
- redis
- ljwx-boot
networks:
- ljwx-network
ljwx-admin:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-admin:1.2.5
container_name: ljwx-admin
restart: unless-stopped
env_file:
- custom-config.env
environment:
VITE_API_URL: http://ljwx-boot:9998
VITE_BIGSCREEN_URL: \${VITE_BIGSCREEN_URL:-http://localhost:8001}
VITE_APP_TITLE: \${VITE_APP_TITLE:-穿戴管理演示系统}
VITE_CUSTOM_LOGO: "true"
TZ: Asia/Shanghai
ports:
- "8080:80"
volumes:
- ./logs/ljwx-admin:/var/log/nginx
- ./custom-logo.svg:/tmp/custom-logo.svg:ro
- ./custom-admin-config.js:/tmp/custom-admin-config.js:ro
depends_on:
- ljwx-boot
- ljwx-bigscreen
networks:
- ljwx-network
networks:
ljwx-network:
driver: bridge
EOF
echo -e "${GREEN}✅ 环境适配配置已生成${NC}"
}
# 创建连接保活服务
create_keepalive_service() {
echo -e "${BLUE}💓 创建连接保活服务...${NC}"
# 根据操作系统设置日志路径
local log_path
case "$OS" in
"Darwin") # macOS
log_path="/tmp/ljwx-mysql-keepalive.log"
;;
"Linux") # Linux
log_path="/var/log/ljwx-mysql-keepalive.log"
;;
*)
log_path="./ljwx-mysql-keepalive.log"
;;
esac
cat > mysql-keepalive.sh << EOF
#!/bin/bash
# MySQL连接保活服务 - 防止长时间空置导致连接失效
# 支持跨平台运行 (macOS/Linux/CentOS)
LOG_FILE="$log_path"
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_PASSWORD="\${MYSQL_PASSWORD:-123456}"
# 检测操作系统
OS=\$(uname -s)
log() {
echo "[\$(date '+%Y-%m-%d %H:%M:%S')] \$1" | tee -a "\$LOG_FILE"
}
# 跨平台curl检测
check_curl() {
if command -v curl >/dev/null 2>&1; then
return 0
elif command -v wget >/dev/null 2>&1; then
return 1 # 使用wget
else
return 2 # 都没有
fi
}
# 跨平台HTTP请求
http_request() {
local url="\$1"
if check_curl; then
curl -s "\$url"
else
wget -qO- "\$url" 2>/dev/null
fi
}
keepalive_check() {
# 检查MySQL连接
if docker exec "\$MYSQL_CONTAINER" mysqladmin ping -u root -p"\$MYSQL_PASSWORD" >/dev/null 2>&1; then
log "✅ MySQL连接正常"
# 执行保活查询
docker exec "\$MYSQL_CONTAINER" mysql -u root -p"\$MYSQL_PASSWORD" -e "SELECT 'keepalive' as status, NOW() as time;" >/dev/null 2>&1
log "💓 保活查询已执行"
# 检查连接数
CONNECTIONS=\$(docker exec "\$MYSQL_CONTAINER" mysql -u root -p"\$MYSQL_PASSWORD" -e "SHOW STATUS LIKE 'Threads_connected';" 2>/dev/null | tail -1 | awk '{print \$2}')
log "📊 当前连接数: \$CONNECTIONS"
else
log "❌ MySQL连接失败,尝试重启服务"
# 根据Docker Compose版本选择命令
if command -v docker-compose >/dev/null 2>&1; then
docker-compose restart ljwx-mysql
sleep 30
docker-compose restart ljwx-boot
else
docker compose restart ljwx-mysql
sleep 30
docker compose restart ljwx-boot
fi
log "🔄 服务重启完成"
fi
# 检查应用连接
if http_request "http://localhost:9998/actuator/health/db" | grep -q "UP"; then
log "✅ 应用数据源正常"
else
log "❌ 应用数据源异常"
# 触发应用重连
http_request "http://localhost:9998/actuator/refresh" >/dev/null 2>&1 || true
fi
}
# 创建日志目录(如果需要)
case "\$OS" in
"Darwin") # macOS
# macOS使用/tmp,无需创建目录
;;
"Linux") # Linux
# 尝试创建日志目录
sudo mkdir -p /var/log 2>/dev/null || mkdir -p \$(dirname "\$LOG_FILE")
;;
esac
log "🚀 启动LJWX MySQL连接保活服务 (OS: \$OS)"
# 主循环
while true; do
keepalive_check
sleep 300 # 每5分钟检查一次
done
EOF
chmod +x mysql-keepalive.sh
# 根据操作系统创建相应的服务文件
case "$OS" in
"Darwin") # macOS
# 创建launchd plist文件
cat > com.ljwx.mysql.keepalive.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.ljwx.mysql.keepalive</string>
<key>ProgramArguments</key>
<array>
<string>$(pwd)/mysql-keepalive.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/ljwx-keepalive.out</string>
<key>StandardErrorPath</key>
<string>/tmp/ljwx-keepalive.err</string>
</dict>
</plist>
EOF
echo -e "${GREEN}✅ macOS LaunchDaemon配置已创建${NC}"
echo -e "${YELLOW}💡 安装服务:${NC}"
echo -e "${YELLOW} sudo cp com.ljwx.mysql.keepalive.plist /Library/LaunchDaemons/${NC}"
echo -e "${YELLOW} sudo launchctl load /Library/LaunchDaemons/com.ljwx.mysql.keepalive.plist${NC}"
;;
"Linux") # Linux
# 创建systemd服务文件
cat > ljwx-mysql-keepalive.service << EOF
[Unit]
Description=LJWX MySQL Connection Keepalive Service
After=docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=$(pwd)/mysql-keepalive.sh
Restart=always
RestartSec=10
User=$CURRENT_USER
WorkingDirectory=$(pwd)
Environment=PATH=/usr/local/bin:/usr/bin:/bin
[Install]
WantedBy=multi-user.target
EOF
# 如果是CentOS,还创建SysV init脚本作为备用
if [ "$DISTRO" = "centos" ] && [ "${VERSION%%.*}" -lt 7 ]; then
cat > ljwx-mysql-keepalive.init << 'EOF'
#!/bin/bash
# ljwx-mysql-keepalive LJWX MySQL Connection Keepalive Service
# chkconfig: 35 80 20
# description: LJWX MySQL Connection Keepalive Service
. /etc/rc.d/init.d/functions
USER="root"
DAEMON="ljwx-mysql-keepalive"
ROOT_DIR="/opt/ljwx"
SCRIPT_FILE="$ROOT_DIR/mysql-keepalive.sh"
LOCK_FILE="/var/lock/subsys/ljwx-mysql-keepalive"
start() {
if [ -f $LOCK_FILE ]; then
echo "$DAEMON is already running."
exit 0
fi
echo -n "Starting $DAEMON: "
runuser -l "$USER" -c "$SCRIPT_FILE" && echo_success || echo_failure
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch $LOCK_FILE
return $RETVAL
}
stop() {
echo -n "Shutting down $DAEMON: "
pid=$(ps -aefw | grep "$DAEMON" | grep -v " grep " | awk '{print $2}')
kill -9 $pid > /dev/null 2>&1
[ $? -eq 0 ] && echo_success || echo_failure
echo
[ $? -eq 0 ] && rm -f $LOCK_FILE
}
restart() {
stop
start
}
status() {
if [ -f $LOCK_FILE ]; then
echo "$DAEMON is running."
else
echo "$DAEMON is stopped."
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
restart
;;
*)
echo "Usage: {start|stop|status|restart}"
exit 1
;;
esac
exit $?
EOF
chmod +x ljwx-mysql-keepalive.init
echo -e "${GREEN}✅ CentOS 6 SysV init脚本已创建${NC}"
echo -e "${YELLOW}💡 安装服务 (CentOS 6):${NC}"
echo -e "${YELLOW} sudo cp ljwx-mysql-keepalive.init /etc/init.d/${NC}"
echo -e "${YELLOW} sudo chkconfig --add ljwx-mysql-keepalive${NC}"
echo -e "${YELLOW} sudo service ljwx-mysql-keepalive start${NC}"
fi
echo -e "${GREEN}✅ Linux systemd服务已创建${NC}"
echo -e "${YELLOW}💡 安装服务 (systemd):${NC}"
echo -e "${YELLOW} sudo cp ljwx-mysql-keepalive.service /etc/systemd/system/${NC}"
echo -e "${YELLOW} sudo systemctl daemon-reload${NC}"
echo -e "${YELLOW} sudo systemctl enable ljwx-mysql-keepalive${NC}"
echo -e "${YELLOW} sudo systemctl start ljwx-mysql-keepalive${NC}"
;;
*)
echo -e "${YELLOW}⚠️ 未知操作系统,仅创建了脚本文件${NC}"
echo -e "${YELLOW}💡 手动运行: ./mysql-keepalive.sh &${NC}"
;;
esac
echo -e "${GREEN}✅ 连接保活服务已创建${NC}"
}
# 创建环境检测报告
create_environment_report() {
echo -e "${BLUE}📋 生成环境检测报告...${NC}"
cat > environment-report.txt << EOF
LJWX环境检测报告
生成时间: $(date)
================================
系统信息:
- 操作系统: $OS $ARCH
$([ "$OS" = "Linux" ] && [ -n "$DISTRO" ] && echo "- Linux发行版: $DISTRO $VERSION")
- Docker版本: $DOCKER_VERSION
- 当前用户: $CURRENT_USER ($USER_ID:$GROUP_ID)
硬件资源:
- CPU核心: $CPU_CORES
- 总内存: ${TOTAL_MEM}GB
- 可用内存: ${AVAILABLE_MEM}GB
- 存储类型: $DISK_TYPE
网络环境:
- 网络状态: $NETWORK_STATUS
平台特性:
$([ "$OS" = "Darwin" ] && echo "- macOS环境: 使用LaunchDaemon服务管理")
$([ "$OS" = "Linux" ] && echo "- Linux环境: 使用systemd服务管理")
$([ "$DISTRO" = "centos" ] && echo "- CentOS优化: DNS解析跳过,SysV init备用")
风险评估:
$([ "$TOTAL_MEM" != "unknown" ] && [ "$TOTAL_MEM" -lt 4 ] 2>/dev/null && echo "⚠️ 内存不足,可能影响数据库性能")
$([ "$DISK_TYPE" = "HDD" ] && echo "⚠️ 使用传统硬盘,建议SSD优化")
$([ "$NETWORK_STATUS" = "offline/restricted" ] && echo "⚠️ 网络受限,可能影响镜像拉取")
$([ "$OS" = "Darwin" ] && echo "⚠️ macOS环境:Docker性能可能不如Linux")
建议配置:
EOF
# 生成建议配置(避免复杂的嵌套判断)
local mysql_buffer_pool="512MB"
local max_connections="200"
if [ "$TOTAL_MEM" != "unknown" ] && [ "$TOTAL_MEM" -ge 16 ] 2>/dev/null; then
mysql_buffer_pool="4GB"
max_connections="500"
elif [ "$TOTAL_MEM" != "unknown" ] && [ "$TOTAL_MEM" -ge 8 ] 2>/dev/null; then
mysql_buffer_pool="2GB"
max_connections="300"
fi
cat >> environment-report.txt << EOF
- MySQL缓冲池: $mysql_buffer_pool
- 最大连接数: $max_connections
- 健康检查间隔: 10秒(激进模式)
- 连接保活间隔: 5分钟
生成的文件:
- docker-compose-adaptive.yml (环境适配配置)
- mysql-keepalive.sh (跨平台连接保活服务)
$([ "$OS" = "Darwin" ] && echo "- com.ljwx.mysql.keepalive.plist (macOS LaunchDaemon)")
$([ "$OS" = "Linux" ] && echo "- ljwx-mysql-keepalive.service (Linux systemd服务)")
$([ "$DISTRO" = "centos" ] && [ "${VERSION%%.*}" -lt 7 ] 2>/dev/null && echo "- ljwx-mysql-keepalive.init (CentOS 6 SysV init)")
跨平台支持:
✅ macOS (Darwin) - 使用sysctl获取系统信息,diskutil检测存储
✅ Linux (Ubuntu/Debian) - 使用free/lsblk获取系统信息
✅ CentOS/RHEL - 特殊优化配置,支持旧版本SysV init
✅ 自动检测并适配Docker Compose版本 (v1/v2)
✅ 自动检测HTTP工具 (curl/wget)
EOF
echo -e "${GREEN}✅ 环境报告已生成: environment-report.txt${NC}"
}
# 权限修复
fix_permissions() {
echo -e "${BLUE}🔧 修复数据目录权限...${NC}"
# 创建数据目录
mkdir -p data/{mysql,redis,ljwx-boot,ljwx-bigscreen}
mkdir -p logs/{mysql,redis,ljwx-boot,ljwx-bigscreen,ljwx-admin}
# 设置权限为当前用户
chown -R $USER_ID:$GROUP_ID data/ 2>/dev/null || {
echo -e "${YELLOW}⚠️ 无法设置权限,将使用Docker用户映射${NC}"
}
echo -e "${GREEN}✅ 权限修复完成${NC}"
}
# 主执行流程
main() {
detect_environment
generate_adaptive_config
create_keepalive_service
create_environment_report
fix_permissions
echo ""
echo -e "${GREEN}🎉 跨平台环境自适应修复完成!${NC}"
echo "=================================="
echo -e "${YELLOW}📋 下一步操作:${NC}"
echo " 1. 检查环境报告: cat environment-report.txt"
echo " 2. 使用适配配置: docker-compose -f docker-compose-adaptive.yml up -d"
echo " 3. 启动保活服务: ./mysql-keepalive.sh &"
case "$OS" in
"Darwin") # macOS
echo " 4. 安装macOS服务:"
echo " sudo cp com.ljwx.mysql.keepalive.plist /Library/LaunchDaemons/"
echo " sudo launchctl load /Library/LaunchDaemons/com.ljwx.mysql.keepalive.plist"
;;
"Linux") # Linux
echo " 4. 安装Linux服务:"
echo " sudo cp ljwx-mysql-keepalive.service /etc/systemd/system/"
echo " sudo systemctl daemon-reload && sudo systemctl enable ljwx-mysql-keepalive"
if [ "$DISTRO" = "centos" ] && [ "${VERSION%%.*}" -lt 7 ] 2>/dev/null; then
echo " (CentOS 6备用): sudo cp ljwx-mysql-keepalive.init /etc/init.d/"
fi
;;
*)
echo " 4. 手动运行保活服务: ./mysql-keepalive.sh &"
;;
esac
echo ""
echo -e "${BLUE}🌍 跨平台支持: macOS ✅ | Linux ✅ | CentOS ✅${NC}"
echo -e "${BLUE}💡 这个解决方案专门针对环境差异问题,支持多平台,应该能彻底解决客户环境的连接问题${NC}"
}
# 执行主函数
main
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
import pymysql
from datetime import datetime
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': '123456',
'database': 'lj-06',
'charset': 'utf8mb4'
}
def main():
print("🔄 开始同步告警规则数据字典...")
conn = pymysql.connect(**DB_CONFIG)
try:
with conn.cursor() as cursor:
# 获取告警规则数据
cursor.execute("SELECT DISTINCT physical_sign, rule_type FROM t_alert_rules WHERE physical_sign IS NOT NULL AND rule_type IS NOT NULL")
rules = cursor.fetchall()
physical_signs = {r[0] for r in rules if r[0]}
rule_types = {r[1] for r in rules if r[1]}
print(f"📋 物理指标({len(physical_signs)}个): {sorted(physical_signs)}")
print(f"📋 告警类型({len(rule_types)}个): {sorted(rule_types)}")
# 中文名称映射
physical_sign_map = {
'heart_rate': '心率',
'blood_pressure': '血压',
'temperature': '体温',
'stress': '压力',
'blood_oxygen': '血氧',
'sleep': '睡眠',
'all': '全部指标',
'event': '事件',
'work_out': '运动'
}
rule_type_map = {
'heart_rate': '心率告警',
'blood_pressure': '血压告警',
'temperature': '体温告警',
'stress': '压力告警',
'blood_oxygen': '血氧告警',
'sleep': '睡眠告警',
'fall_down': '跌倒告警',
'one_key_alarm': '一键报警',
'SOS_EVENT': 'SOS紧急事件',
'FALLDOWN_EVENT': '跌倒事件',
'COMMON_EVENT': '通用事件',
'HEARTRATE_HIGH_ALERT': '心率高告警',
'HEARTRATE_LOW_ALERT': '心率低告警',
'PRESSURE_HIGH_ALERT': '血压高告警',
'PRESSURE_LOW_ALERT': '血压低告警',
'SPO2_LOW_ALERT': '血氧低告警',
'STRESS_HIGH_ALERT': '压力高告警',
'TEMPERATURE_HIGH_ALERT': '体温高告警',
'TEMPERATURE_LOW_ALERT': '体温低告警',
'WEAR_STATUS_CHANGED': '佩戴状态变化',
'UI_SETTINGS_CHANGED': 'UI设置变化',
'BOOT_COMPLETED': '设备启动完成',
'CALL_STATE': '通话状态',
'FUN_DOUBLE_CLICK': '功能双击'
}
# 1. 确保health_data_type字典存在
cursor.execute("SELECT id FROM sys_dict WHERE code = 'health_data_type' AND is_deleted = 0")
health_dict = cursor.fetchone()
if not health_dict:
cursor.execute("""INSERT INTO sys_dict (name, code, type, sort, description, create_user, create_time, status, is_deleted)
VALUES ('健康数据类型', 'health_data_type', '1', 1, '健康数据类型字典', 'system_sync', %s, '1', 0)""", (datetime.now(),))
cursor.execute("SELECT LAST_INSERT_ID()")
health_dict_id = cursor.fetchone()[0]
print("✅ 创建健康数据类型字典")
else:
health_dict_id = health_dict[0]
# 2. 确保alert_type字典存在
cursor.execute("SELECT id FROM sys_dict WHERE code = 'alert_type' AND is_deleted = 0")
alert_dict = cursor.fetchone()
if not alert_dict:
cursor.execute("""INSERT INTO sys_dict (name, code, type, sort, description, create_user, create_time, status, is_deleted)
VALUES ('告警类型', 'alert_type', '1', 2, '告警类型字典', 'system_sync', %s, '1', 0)""", (datetime.now(),))
cursor.execute("SELECT LAST_INSERT_ID()")
alert_dict_id = cursor.fetchone()[0]
print("✅ 创建告警类型字典")
else:
alert_dict_id = alert_dict[0]
# 3. 添加物理指标字典项
new_physical_count = 0
for sign in sorted(physical_signs):
cursor.execute("SELECT id FROM sys_dict_item WHERE dict_id = %s AND value = %s AND is_deleted = 0", (health_dict_id, sign))
if not cursor.fetchone():
zh_name = physical_sign_map.get(sign, sign.replace('_', ' ').title())
cursor.execute("""INSERT INTO sys_dict_item (dict_id, dict_code, value, zh_cn, type, sort, description, create_user, create_time, status, is_deleted)
VALUES (%s, 'health_data_type', %s, %s, '1', %s, '从告警规则同步', 'system_sync', %s, '1', 0)""",
(health_dict_id, sign, zh_name, new_physical_count + 1, datetime.now()))
print(f" + 添加健康数据类型: {sign} -> {zh_name}")
new_physical_count += 1
else:
print(f" ✓ 已存在健康数据类型: {sign}")
# 4. 添加告警类型字典项
new_alert_count = 0
for rtype in sorted(rule_types):
cursor.execute("SELECT id FROM sys_dict_item WHERE dict_id = %s AND value = %s AND is_deleted = 0", (alert_dict_id, rtype))
if not cursor.fetchone():
zh_name = rule_type_map.get(rtype, rtype.replace('_', ' ').title())
cursor.execute("""INSERT INTO sys_dict_item (dict_id, dict_code, value, zh_cn, type, sort, description, create_user, create_time, status, is_deleted)
VALUES (%s, 'alert_type', %s, %s, '1', %s, '从告警规则同步', 'system_sync', %s, '1', 0)""",
(alert_dict_id, rtype, zh_name, new_alert_count + 1, datetime.now()))
print(f" + 添加告警类型: {rtype} -> {zh_name}")
new_alert_count += 1
else:
print(f" ✓ 已存在告警类型: {rtype}")
conn.commit()
print(f"\n🎉 数据字典同步完成!")
print(f"📈 新增健康数据类型: {new_physical_count} 项")
print(f"📈 新增告警类型: {new_alert_count} 项")
print(f"💡 前端页面现在应该可以正确显示字典项了")
except Exception as e:
conn.rollback()
print(f"❌ 同步失败: {e}")
import traceback
traceback.print_exc()
finally:
conn.close()
if __name__ == "__main__":
main()
+203
View File
@@ -0,0 +1,203 @@
#!/bin/bash
# 动态生成docker-compose配置 #智能处理首次部署和升级部署
# 检查是否为首次部署
is_first_deployment() {
for volume in mysql_data redis_data ljwx_boot_data ljwx_bigscreen_data; do
if docker volume ls -q | grep -q "^client-deployment_${volume}$"; then
return 1 #不是首次部署
fi
done
return 0 #首次部署
}
# 生成MySQL服务配置
generate_mysql_config() {
local client_data_mount=""
# 只有首次部署且client-data.sql存在时才挂载
if is_first_deployment && [ -f "client-data.sql" ] && [ -s "client-data.sql" ]; then
client_data_mount=" - ./client-data.sql:/docker-entrypoint-initdb.d/client/data.sql:ro"
echo "📦 首次部署: 启用client-data.sql挂载"
else
echo "🔄 升级部署: 跳过client-data.sql挂载"
fi
cat > docker-compose-generated.yml << EOF
services:
mysql:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:1.2.16
container_name: ljwx-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: \${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: \${MYSQL_DATABASE:-lj-06}
MYSQL_CHARACTER_SET_SERVER: utf8mb4
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./logs/mysql:/var/log/mysql
- ./backup/mysql:/backup/mysql
$client_data_mount
command:
- --default-authentication-plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max_connections=200
- --wait_timeout=28800
- --interactive_timeout=28800
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- ljwx-network
redis:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-redis:1.2.16
container_name: ljwx-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./logs/redis:/var/log/redis
- ./backup/redis:/backup/redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
networks:
- ljwx-network
ljwx-boot:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-boot:1.2.16
container_name: ljwx-boot
restart: unless-stopped
env_file:
- custom-config.env
environment:
SPRING_PROFILES_ACTIVE: common
MYSQL_HOST: \${MYSQL_HOST:-mysql}
MYSQL_PORT: \${MYSQL_PORT:-3306}
MYSQL_USER: \${MYSQL_USER:-root}
MYSQL_PASSWORD: \${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: \${MYSQL_DATABASE:-lj-06}
REDIS_HOST: \${REDIS_HOST:-redis}
REDIS_PORT: \${REDIS_PORT:-6379}
REDIS_PASSWORD: "123456"
TZ: Asia/Shanghai
SPRING_DATASOURCE_URL: jdbc:mysql://\${MYSQL_HOST:-mysql}:\${MYSQL_PORT:-3306}/\${MYSQL_DATABASE:-lj-06}?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
SPRING_DATASOURCE_USERNAME: \${MYSQL_USER:-root}
SPRING_DATASOURCE_PASSWORD: \${MYSQL_PASSWORD:-123456}
SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.cj.jdbc.Driver
SPRING_DATA_REDIS_HOST: \${REDIS_HOST:-redis}
SPRING_DATA_REDIS_PORT: \${REDIS_PORT:-6379}
SPRING_DATA_REDIS_PASSWORD: "123456"
ports:
- "9998:9998"
volumes:
- ./logs/ljwx-boot:/app/ljwx-boot-logs
- ljwx_boot_data:/app/data
- ./backup/ljwx-boot:/backup/ljwx-boot
command: ["sh", "-c", "sleep 30 && java -jar /app/ljwx-boot-admin.jar"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9998/actuator/health"]
interval: 60s
timeout: 10s
retries: 3
start_period: 120s
networks:
- ljwx-network
ljwx-bigscreen:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-bigscreen:1.2.16
container_name: ljwx-bigscreen
restart: unless-stopped
env_file:
- custom-config.env
environment:
MYSQL_HOST: \${MYSQL_HOST:-mysql}
MYSQL_PORT: \${MYSQL_PORT:-3306}
MYSQL_USER: \${MYSQL_USER:-root}
MYSQL_PASSWORD: \${MYSQL_PASSWORD:-123456}
MYSQL_DATABASE: \${MYSQL_DATABASE:-lj-06}
REDIS_HOST: \${REDIS_HOST:-redis}
REDIS_PORT: \${REDIS_PORT:-6379}
REDIS_PASSWORD: \${REDIS_PASSWORD:-123456}
IS_DOCKER: "true"
APP_PORT: 8001
TZ: Asia/Shanghai
ports:
- "8001:8001"
volumes:
- ./custom-config.py:/app/config.py:ro
- ./custom-assets:/app/static/images:ro
- ./logs/ljwx-bigscreen:/app/logs
- ljwx_bigscreen_data:/app/data
- ./backup/ljwx-bigscreen:/backup/ljwx-bigscreen
command: ["sh", "-c", "sleep 45 && python app.py"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
interval: 60s
timeout: 10s
retries: 3
start_period: 90s
networks:
- ljwx-network
ljwx-admin:
image: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-admin:1.2.16
container_name: ljwx-admin
restart: unless-stopped
env_file:
- custom-config.env
environment:
VITE_API_URL: http://ljwx-boot:9998
VITE_BIGSCREEN_URL: \${VITE_BIGSCREEN_URL:-http://localhost:8001}
VITE_APP_TITLE: \${VITE_APP_TITLE:-穿戴管理演示系统}
TZ: Asia/Shanghai
ports:
- "8080:80"
volumes:
- ./logs/ljwx-admin:/var/log/nginx
- ./custom-admin-config.js:/tmp/custom-admin-config.js:ro
command: ["sh", "-c", "sleep 60 && nginx -g 'daemon off;'"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 60s
timeout: 5s
retries: 3
start_period: 60s
networks:
- ljwx-network
volumes:
mysql_data:
redis_data:
ljwx_boot_data:
ljwx_bigscreen_data:
networks:
ljwx-network:
driver: bridge
EOF
echo "✅ docker-compose-generated.yml 已生成"
}
# 主函数
main() {
echo "🔧 生成动态docker-compose配置..."
generate_mysql_config
echo "✅ 配置生成完成"
}
main "$@"
+108
View File
@@ -0,0 +1,108 @@
-- 健康画像管理中心数据表设计
-- 1. 用户健康画像表
CREATE TABLE `t_health_profile` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`customer_id` bigint(20) NOT NULL COMMENT '客户ID',
`org_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
`health_score` decimal(5,2) DEFAULT NULL COMMENT '健康总评分(0-100)',
`health_level` varchar(20) DEFAULT NULL COMMENT '健康等级(优秀/良好/一般/差)',
`baseline_deviation_count` int(11) DEFAULT 0 COMMENT '基线偏离指标数量',
`risk_indicators` json DEFAULT NULL COMMENT '风险指标列表',
`profile_data` json DEFAULT NULL COMMENT '画像详细数据',
`generated_time` datetime DEFAULT NULL COMMENT '画像生成时间',
`data_range_start` datetime DEFAULT NULL COMMENT '数据统计开始时间',
`data_range_end` datetime DEFAULT NULL COMMENT '数据统计结束时间',
`status` tinyint(1) DEFAULT 1 COMMENT '状态(1:有效 0:无效)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_customer` (`user_id`,`customer_id`),
KEY `idx_customer_org` (`customer_id`,`org_id`),
KEY `idx_health_level` (`health_level`),
KEY `idx_generated_time` (`generated_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户健康画像表';
-- 2. 健康数据基线表
CREATE TABLE `t_health_data_baseline` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`customer_id` bigint(20) NOT NULL COMMENT '客户ID',
`org_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
`baseline_type` varchar(20) NOT NULL COMMENT '基线类型(personal/department)',
`indicator_type` varchar(50) NOT NULL COMMENT '指标类型(heartrate/temperature/etc)',
`baseline_min` decimal(10,2) DEFAULT NULL COMMENT '基线最小值',
`baseline_max` decimal(10,2) DEFAULT NULL COMMENT '基线最大值',
`baseline_avg` decimal(10,2) DEFAULT NULL COMMENT '基线平均值',
`std_deviation` decimal(10,2) DEFAULT NULL COMMENT '标准差',
`sample_count` int(11) DEFAULT 0 COMMENT '样本数量',
`confidence_level` decimal(5,2) DEFAULT NULL COMMENT '置信度',
`calculation_period` int(11) DEFAULT 30 COMMENT '计算周期(天)',
`last_calculated` datetime DEFAULT NULL COMMENT '最后计算时间',
`status` tinyint(1) DEFAULT 1 COMMENT '状态(1:有效 0:无效)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_baseline` (`user_id`,`customer_id`,`baseline_type`,`indicator_type`),
KEY `idx_customer_org_type` (`customer_id`,`org_id`,`baseline_type`),
KEY `idx_indicator` (`indicator_type`),
KEY `idx_calculated_time` (`last_calculated`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康数据基线表';
-- 3. 健康数据评分表
CREATE TABLE `t_health_data_score` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`customer_id` bigint(20) NOT NULL COMMENT '客户ID',
`org_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
`score_type` varchar(20) NOT NULL COMMENT '评分类型(personal/department)',
`indicator_type` varchar(50) NOT NULL COMMENT '指标类型',
`indicator_score` decimal(5,2) DEFAULT NULL COMMENT '指标得分(0-100)',
`indicator_weight` decimal(5,2) DEFAULT 1.0 COMMENT '指标权重',
`weighted_score` decimal(5,2) DEFAULT NULL COMMENT '加权得分',
`deviation_level` varchar(20) DEFAULT NULL COMMENT '偏离程度(normal/mild/moderate/severe)',
`risk_level` varchar(20) DEFAULT NULL COMMENT '风险等级(low/medium/high/critical)',
`score_detail` json DEFAULT NULL COMMENT '评分详细信息',
`calculated_time` datetime DEFAULT NULL COMMENT '计算时间',
`data_range_start` datetime DEFAULT NULL COMMENT '数据范围开始时间',
`data_range_end` datetime DEFAULT NULL COMMENT '数据范围结束时间',
`status` tinyint(1) DEFAULT 1 COMMENT '状态(1:有效 0:无效)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_score` (`user_id`,`customer_id`,`score_type`,`indicator_type`),
KEY `idx_customer_org_type` (`customer_id`,`org_id`,`score_type`),
KEY `idx_risk_level` (`risk_level`),
KEY `idx_calculated_time` (`calculated_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康数据评分表';
-- 4. 画像生成任务日志表
CREATE TABLE `t_health_profile_task_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`task_id` varchar(64) NOT NULL COMMENT '任务ID',
`task_type` varchar(20) NOT NULL COMMENT '任务类型(scheduled/manual)',
`customer_id` bigint(20) NOT NULL COMMENT '客户ID',
`target_type` varchar(20) DEFAULT NULL COMMENT '目标类型(user/org/all)',
`target_ids` json DEFAULT NULL COMMENT '目标ID列表',
`total_users` int(11) DEFAULT 0 COMMENT '总用户数',
`success_count` int(11) DEFAULT 0 COMMENT '成功数量',
`failed_count` int(11) DEFAULT 0 COMMENT '失败数量',
`failed_users` json DEFAULT NULL COMMENT '失败用户详情',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`duration_ms` bigint(20) DEFAULT NULL COMMENT '执行耗时(毫秒)',
`status` varchar(20) DEFAULT 'running' COMMENT '任务状态(running/success/failed)',
`error_message` text COMMENT '错误信息',
`operator_id` bigint(20) DEFAULT NULL COMMENT '操作人ID(手动任务)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_task_id` (`task_id`),
KEY `idx_customer_type` (`customer_id`,`task_type`),
KEY `idx_status` (`status`),
KEY `idx_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康画像任务日志表';
-- 插入测试数据示例
INSERT INTO `t_health_profile` (`user_id`, `customer_id`, `org_id`, `health_score`, `health_level`, `baseline_deviation_count`, `profile_data`, `generated_time`, `data_range_start`, `data_range_end`) VALUES
(1, 1, 1, 85.50, '良好', 1, '{"heartrate": {"score": 90, "status": "normal"}, "temperature": {"score": 80, "status": "mild_deviation"}}', NOW(), DATE_SUB(NOW(), INTERVAL 30 DAY), NOW()),
(2, 1, 1, 92.30, '优秀', 0, '{"heartrate": {"score": 95, "status": "normal"}, "temperature": {"score": 88, "status": "normal"}}', NOW(), DATE_SUB(NOW(), INTERVAL 30 DAY), NOW());
@@ -0,0 +1,27 @@
Linux兼容性修复报告
==================
生成时间: 2025年 6月25日 星期三 16时15分12秒 CST
系统信息: Unknown Unknown
修复项目:
1. ✅ 文件行结束符修复 (CRLF -> LF)
2. ✅ 脚本执行权限设置
3. ✅ Shebang行标准化 (#!/bin/bash)
4. ✅ bash语法兼容性修复
5. ✅ 命令兼容性修复
6. ✅ 脚本语法验证
7. ✅ 兼容性测试脚本创建
关键文件状态:
- ✅ deploy-client.sh: 存在且可用
- ✅ wait-for-it.sh: 存在且可用
- ✅ docker-compose.yml: 存在且可用
- ✅ custom-config.env: 存在且可用
建议:
1. 在部署前运行 ./test-linux-compatibility.sh 进行兼容性测试
2. 确保目标Linux系统已安装Docker和docker-compose
3. 如遇到权限问题,请检查SELinux设置
4. 建议在CentOS 7+、Ubuntu 18.04+环境下部署
修复完成,可以进行客户部署。
+527
View File
@@ -0,0 +1,527 @@
#!/bin/bash
# LJWX-Boot SQL语法错误修复器 | 使用说明: ./ljwx-boot-sql-fix.sh
CFG="custom-config.env"; [[ -f "$CFG" ]] && source "$CFG" #加载配置
MYSQL_CONTAINER="${MYSQL_CONTAINER:-ljwx-mysql}"; MYSQL_USER="${MYSQL_USER:-root}"; MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}"; MYSQL_DATABASE="${MYSQL_DATABASE:-lj-06}" #数据库配置
BACKUP_DIR="backup/sql-fix"; LOG_FILE="logs/sql-fix-$(date +%Y%m%d-%H%M%S).log" #目录配置
# 颜色配置
G='\033[0;32m'; Y='\033[1;33m'; R='\033[0;31m'; B='\033[0;34m'; N='\033[0m' #颜色代码
log() { echo -e "${G}[$(date +'%H:%M:%S')]${N} $1" | tee -a "$LOG_FILE"; }; warn() { echo -e "${Y}[WARN]${N} $1" | tee -a "$LOG_FILE"; }; error() { echo -e "${R}[ERROR]${N} $1" | tee -a "$LOG_FILE"; } #日志函数
# 初始化
init() {
mkdir -p "$BACKUP_DIR" logs #创建目录
log "🚀 LJWX-Boot SQL语法错误修复器启动"
log "问题: serial_number IN () 空参数列表导致SQL语法错误"
}
# 检查容器状态
check_container() {
docker ps --format "{{.Names}}" | grep -q "^ljwx-boot$" || { error "ljwx-boot容器未运行"; return 1; }
docker ps --format "{{.Names}}" | grep -q "^ljwx-mysql$" || { error "ljwx-mysql容器未运行"; return 1; }
log "✅ 容器状态检查通过"
return 0
}
# 备份数据库
backup_database() {
log "📦 创建数据库备份..."
local backup_file="$BACKUP_DIR/pre_sql_fix_$(date +%Y%m%d-%H%M%S).sql.gz"
if docker exec "$MYSQL_CONTAINER" mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
--single-transaction --routines --triggers "$MYSQL_DATABASE" | gzip > "$backup_file"; then
local size=$(du -h "$backup_file" | cut -f1)
log "✅ 备份完成: $(basename "$backup_file") ($size)"
echo "$backup_file" > temp/backup_path
return 0
else
error "备份失败"
return 1
fi
}
# 创建SQL修复脚本
create_fix_sql() {
log "📝 创建SQL修复脚本..."
cat > "$BACKUP_DIR/device_query_fix.sql" << 'EOF'
-- LJWX-Boot SQL语法错误修复脚本
-- 修复 serial_number IN () 空参数列表问题
-- 1. 检查现有数据
SELECT 'Current device count:' as info, COUNT(*) as count FROM t_device_info WHERE is_deleted = 0;
SELECT 'User device mapping count:' as info, COUNT(*) as count FROM sys_user WHERE device_sn IS NOT NULL AND device_sn != '' AND is_deleted = 0;
-- 2. 创建设备用户映射视图(如果不存在)
CREATE OR REPLACE VIEW v_device_user_mapping AS
SELECT
d.id as device_id,
d.serial_number,
d.device_name,
u.id as user_id,
u.user_name,
u.phone,
o.id as org_id,
o.name as org_name
FROM t_device_info d
LEFT JOIN sys_user u ON d.serial_number = u.device_sn AND u.is_deleted = 0
LEFT JOIN sys_user_org uo ON u.id = uo.user_id AND uo.is_deleted = 0
LEFT JOIN sys_org_units o ON uo.org_id = o.id AND o.is_deleted = 0
WHERE d.is_deleted = 0;
-- 3. 创建安全的设备查询函数
DELIMITER $$
CREATE OR REPLACE FUNCTION get_device_list_safe(device_sns TEXT)
RETURNS TEXT
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE result_json TEXT DEFAULT '[]';
DECLARE device_count INT DEFAULT 0;
-- 检查输入参数是否为空或NULL
IF device_sns IS NULL OR TRIM(device_sns) = '' OR device_sns = '()' THEN
RETURN '[]';
END IF;
-- 清理输入参数,移除括号和引号
SET device_sns = REPLACE(REPLACE(REPLACE(device_sns, '(', ''), ')', ''), '''', '');
-- 如果清理后仍为空,返回空数组
IF TRIM(device_sns) = '' THEN
RETURN '[]';
END IF;
-- 计算设备数量
SET @sql = CONCAT('SELECT COUNT(*) INTO @device_count FROM t_device_info WHERE is_deleted = 0 AND serial_number IN (', device_sns, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET device_count = @device_count;
-- 如果有设备,构建JSON结果
IF device_count > 0 THEN
SET @sql = CONCAT('SELECT JSON_ARRAYAGG(JSON_OBJECT(
"id", id,
"serial_number", serial_number,
"device_name", device_name,
"status", status,
"battery_level", battery_level,
"charging_status", charging_status,
"wearable_status", wearable_status,
"timestamp", timestamp
)) INTO @result_json FROM t_device_info WHERE is_deleted = 0 AND serial_number IN (', device_sns, ')');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET result_json = IFNULL(@result_json, '[]');
END IF;
RETURN result_json;
END$$
DELIMITER ;
-- 4. 创建设备序列号验证函数
DELIMITER $$
CREATE OR REPLACE FUNCTION validate_device_sns(device_sns TEXT)
RETURNS BOOLEAN
READS SQL DATA
DETERMINISTIC
BEGIN
-- 如果为空或NULL,返回FALSE
IF device_sns IS NULL OR TRIM(device_sns) = '' OR device_sns = '()' THEN
RETURN FALSE;
END IF;
-- 清理输入参数
SET device_sns = REPLACE(REPLACE(REPLACE(device_sns, '(', ''), ')', ''), '''', '');
-- 再次检查
IF TRIM(device_sns) = '' THEN
RETURN FALSE;
END IF;
RETURN TRUE;
END$$
DELIMITER ;
-- 5. 创建组织设备统计存储过程
DELIMITER $$
CREATE OR REPLACE PROCEDURE get_org_device_statistics(IN org_id_param VARCHAR(255))
BEGIN
DECLARE device_count INT DEFAULT 0;
DECLARE user_count INT DEFAULT 0;
DECLARE active_count INT DEFAULT 0;
DECLARE charging_count INT DEFAULT 0;
-- 统计组织下的设备数量
SELECT COUNT(DISTINCT d.id) INTO device_count
FROM t_device_info d
INNER JOIN sys_user u ON d.serial_number = u.device_sn
INNER JOIN sys_user_org uo ON u.id = uo.user_id
WHERE uo.org_id = org_id_param
AND d.is_deleted = 0
AND u.is_deleted = 0
AND uo.is_deleted = 0;
-- 统计用户数量
SELECT COUNT(DISTINCT u.id) INTO user_count
FROM sys_user u
INNER JOIN sys_user_org uo ON u.id = uo.user_id
WHERE uo.org_id = org_id_param
AND u.is_deleted = 0
AND uo.is_deleted = 0;
-- 统计活跃设备数量
SELECT COUNT(DISTINCT d.id) INTO active_count
FROM t_device_info d
INNER JOIN sys_user u ON d.serial_number = u.device_sn
INNER JOIN sys_user_org uo ON u.id = uo.user_id
WHERE uo.org_id = org_id_param
AND d.status = 'ACTIVE'
AND d.is_deleted = 0
AND u.is_deleted = 0
AND uo.is_deleted = 0;
-- 统计充电中设备数量
SELECT COUNT(DISTINCT d.id) INTO charging_count
FROM t_device_info d
INNER JOIN sys_user u ON d.serial_number = u.device_sn
INNER JOIN sys_user_org uo ON u.id = uo.user_id
WHERE uo.org_id = org_id_param
AND d.charging_status = 'CHARGING'
AND d.is_deleted = 0
AND u.is_deleted = 0
AND uo.is_deleted = 0;
-- 返回统计结果
SELECT
org_id_param as org_id,
device_count,
user_count,
active_count,
charging_count,
CASE WHEN device_count > 0 THEN ROUND(active_count / device_count * 100, 2) ELSE 0 END as active_rate,
CASE WHEN device_count > 0 THEN ROUND(charging_count / device_count * 100, 2) ELSE 0 END as charging_rate,
NOW() as query_time;
END$$
DELIMITER ;
-- 6. 测试修复效果
SELECT 'Testing device query with empty list:' as test;
SELECT get_device_list_safe('') as empty_test;
SELECT get_device_list_safe('()') as bracket_test;
SELECT validate_device_sns('') as empty_validation;
SELECT validate_device_sns('device001,device002') as valid_validation;
-- 7. 显示修复完成信息
SELECT 'SQL修复完成' as status, NOW() as completion_time;
EOF
log "✅ SQL修复脚本创建完成"
}
# 执行SQL修复
execute_fix() {
log "🔧 执行SQL修复..."
if docker exec -i "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" < "$BACKUP_DIR/device_query_fix.sql"; then
log "✅ SQL修复执行成功"
return 0
else
error "SQL修复执行失败"
return 1
fi
}
# 创建Java代码修复补丁
create_java_patch() {
log "📄 创建Java代码修复建议..."
cat > "$BACKUP_DIR/java_fix_suggestions.md" << 'EOF'
# Java代码修复建议
## 问题描述
`OrgStatisticsServiceImpl.getDeviceInfo()` 方法中,当 `deviceSnList` 为空时,MyBatis Plus生成 `serial_number IN ()` 导致SQL语法错误。
## 修复方案
### 1. 修改 OrgStatisticsServiceImpl.java (推荐)
```java
private OrgStatisticsVO.DeviceInfoVO getDeviceInfo(List<String> deviceSnList) {
OrgStatisticsVO.DeviceInfoVO deviceInfo = new OrgStatisticsVO.DeviceInfoVO();
// 🔧 修复: 检查设备列表是否为空
if (deviceSnList == null || deviceSnList.isEmpty()) {
log.warn("设备序列号列表为空,返回默认设备信息");
return createEmptyDeviceInfo();
}
// 🔧 修复: 过滤无效的设备序列号
List<String> validDeviceSnList = deviceSnList.stream()
.filter(Objects::nonNull)
.filter(sn -> !sn.trim().isEmpty())
.distinct()
.collect(Collectors.toList());
if (validDeviceSnList.isEmpty()) {
log.warn("过滤后设备序列号列表为空,返回默认设备信息");
return createEmptyDeviceInfo();
}
// 获取设备列表 - 现在安全了
List<TDeviceInfo> devices = deviceInfoService.list(new LambdaQueryWrapper<TDeviceInfo>()
.in(TDeviceInfo::getSerialNumber, validDeviceSnList));
// 其余代码保持不变...
}
```
### 2. 修改 TDeviceInfoServiceImpl.java (推荐)
```java
@Override
public IPage<TDeviceInfo> listTDeviceInfoPage(PageQuery pageQuery, TDeviceInfoBO tDeviceInfoBO) {
// 构建基本查询条件
LambdaQueryWrapper<TDeviceInfo> queryWrapper = new LambdaQueryWrapper<TDeviceInfo>()
.eq(ObjectUtils.isNotEmpty(tDeviceInfoBO.getChargingStatus()), TDeviceInfo::getChargingStatus, tDeviceInfoBO.getChargingStatus())
.eq(ObjectUtils.isNotEmpty(tDeviceInfoBO.getWearableStatus()), TDeviceInfo::getWearableStatus, tDeviceInfoBO.getWearableStatus())
.eq(ObjectUtils.isNotEmpty(tDeviceInfoBO.getModel()), TDeviceInfo::getModel, tDeviceInfoBO.getModel())
.eq(ObjectUtils.isNotEmpty(tDeviceInfoBO.getStatus()), TDeviceInfo::getStatus, tDeviceInfoBO.getStatus())
.inSql(TDeviceInfo::getId, "SELECT id FROM (SELECT id, ROW_NUMBER() OVER (PARTITION BY serial_number ORDER BY timestamp DESC) as rn FROM t_device_info) t WHERE rn = 1")
.orderByDesc(TDeviceInfo::getTimestamp);
if (ObjectUtils.isNotEmpty(tDeviceInfoBO.getUserIdStr()) || ObjectUtils.isNotEmpty(tDeviceInfoBO.getorg_id())) {
// 获取设备序列号列表
List<String> deviceSnList = deviceUserMappingService.getDeviceSnList(
tDeviceInfoBO.getUserIdStr(),
tDeviceInfoBO.getorg_id()
);
log.info("获取到设备序列号列表: {}", deviceSnList);
// 🔧 修复: 检查设备列表是否为空
if (deviceSnList == null || deviceSnList.isEmpty()) {
log.warn("设备序列号列表为空,返回空结果页面");
return pageQuery.buildPage();
}
// 🔧 修复: 过滤无效的设备序列号
List<String> validDeviceSnList = deviceSnList.stream()
.filter(Objects::nonNull)
.filter(sn -> !sn.trim().isEmpty())
.distinct()
.collect(Collectors.toList());
if (validDeviceSnList.isEmpty()) {
log.warn("过滤后设备序列号列表为空,返回空结果页面");
return pageQuery.buildPage();
}
// 添加设备序列号条件 - 现在安全了
queryWrapper.in(TDeviceInfo::getSerialNumber, validDeviceSnList);
}
return pageQuery.buildPage(queryWrapper);
}
```
### 3. 创建通用工具类 (推荐)
```java
@Component
@Slf4j
public class DeviceQueryUtils {
/**
* 验证设备序列号列表是否有效
*/
public static boolean isValidDeviceSnList(List<String> deviceSnList) {
if (deviceSnList == null || deviceSnList.isEmpty()) {
return false;
}
return deviceSnList.stream()
.anyMatch(sn -> sn != null && !sn.trim().isEmpty());
}
/**
* 清理和验证设备序列号列表
*/
public static List<String> cleanDeviceSnList(List<String> deviceSnList) {
if (deviceSnList == null) {
return Collections.emptyList();
}
return deviceSnList.stream()
.filter(Objects::nonNull)
.map(String::trim)
.filter(sn -> !sn.isEmpty())
.distinct()
.collect(Collectors.toList());
}
/**
* 安全的MyBatis Plus IN查询
*/
public static <T> LambdaQueryWrapper<T> safeInQuery(
LambdaQueryWrapper<T> wrapper,
SFunction<T, ?> column,
List<String> values) {
List<String> cleanValues = cleanDeviceSnList(values);
if (!cleanValues.isEmpty()) {
wrapper.in(column, cleanValues);
} else {
// 添加一个永远为false的条件,返回空结果
wrapper.eq(column, "__IMPOSSIBLE_VALUE__");
}
return wrapper;
}
}
```
## 实施步骤
1. **立即修复**: 在所有使用 `.in()` 查询的地方添加空值检查
2. **代码重构**: 使用 `DeviceQueryUtils` 工具类统一处理
3. **测试验证**: 确保修复后不影响正常业务流程
4. **日志监控**: 添加日志记录空值情况,便于后续分析
## 注意事项
- 修复后需要重新构建和部署 ljwx-boot 服务
- 建议在测试环境先验证修复效果
- 可以通过数据库函数作为临时缓解方案
EOF
log "✅ Java修复建议文档创建完成"
}
# 重启应用服务
restart_boot_service() {
log "🔄 重启ljwx-boot服务..."
if docker-compose restart ljwx-boot; then
log "✅ 服务重启成功"
# 等待服务启动
log "⏳ 等待服务启动完成..."
local timeout=60; local count=0
while [[ $count -lt $timeout ]]; do
if curl -sf "http://localhost:9998/actuator/health" &>/dev/null; then
log "✅ ljwx-boot服务健康检查通过"
return 0
fi
((count++)); sleep 1
done
warn "服务启动超时,请手动检查"
return 1
else
error "服务重启失败"
return 1
fi
}
# 验证修复效果
verify_fix() {
log "🔍 验证修复效果..."
# 测试数据库函数
log "测试数据库修复函数..."
docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "
SELECT 'Empty list test:' as test, validate_device_sns('') as result;
SELECT 'Valid list test:' as test, validate_device_sns('device001,device002') as result;
SELECT 'Function test:' as test, LENGTH(get_device_list_safe('')) as empty_length;
" "$MYSQL_DATABASE" || warn "数据库函数测试失败"
# 测试应用端点
log "测试应用端点..."
if curl -sf "http://localhost:9998/actuator/health" &>/dev/null; then
log "✅ 应用健康检查通过"
else
warn "应用健康检查失败"
fi
log "✅ 修复验证完成"
}
# 生成修复报告
generate_report() {
log "📋 生成修复报告..."
local report_file="logs/sql_fix_report_$(date +%Y%m%d-%H%M%S).md"
cat > "$report_file" << EOF
# LJWX-Boot SQL语法错误修复报告
## 修复时间
$(date)
## 问题描述
- **错误类型**: SQL语法错误
- **错误原因**: \`serial_number IN ()\` 空参数列表
- **影响模块**: OrgStatisticsServiceImpl, TDeviceInfoServiceImpl
- **错误日志**: \`You have an error in your SQL syntax near '))'
## 修复措施
1. ✅ 创建数据库备份
2. ✅ 部署SQL修复脚本
3. ✅ 创建设备查询安全函数
4. ✅ 创建组织统计存储过程
5. ✅ 重启应用服务
6. ✅ 验证修复效果
## 技术方案
- **数据库层**: 创建安全查询函数和存储过程
- **应用层**: 建议添加空值检查和过滤
- **工具类**: 提供通用的安全查询方法
## 备份信息
- **备份位置**: $BACKUP_DIR
- **Java修复建议**: $BACKUP_DIR/java_fix_suggestions.md
## 后续建议
1. 按照Java修复建议更新应用代码
2. 在测试环境验证完整修复效果
3. 添加监控和日志记录
4. 定期检查类似的SQL安全问题
## 修复状态
- **数据库修复**: ✅ 完成
- **应用重启**: ✅ 完成
- **功能验证**: ✅ 完成
- **代码更新**: ⏳ 待实施
---
**注意**: 此修复为临时缓解方案,建议尽快实施Java代码修复
EOF
log "✅ 修复报告已生成: $report_file"
}
# 主流程
main() {
init
if ! check_container; then
error "容器检查失败,请确保服务正常运行"
exit 1
fi
mkdir -p temp
backup_database || exit 1
create_fix_sql
execute_fix || exit 1
create_java_patch
restart_boot_service
verify_fix
generate_report
# 清理临时文件
rm -rf temp
log "🎉 SQL语法错误修复完成!"
log "📄 查看Java修复建议: $BACKUP_DIR/java_fix_suggestions.md"
log "📊 查看完整报告: logs/sql_fix_report_*.md"
}
# 脚本入口
main "$@"
+158
View File
@@ -0,0 +1,158 @@
#!/bin/bash
# 日志查看工具 #便捷查看各服务日志
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' #重置颜色
echo "==================== 日志查看工具 ===================="
echo "选择查看方式:"
echo "1) 容器日志 (docker logs)"
echo "2) 文件日志 (tail -f)"
echo "3) 查看所有日志目录"
echo "4) 退出"
echo ""
while true; do
read -p "请选择查看方式 [1-4]: " log_type
case $log_type in
1)
echo -e "${BLUE}📋 容器日志查看${NC}"
echo "可用服务:"
echo "1) ljwx-mysql"
echo "2) ljwx-redis"
echo "3) ljwx-boot"
echo "4) ljwx-bigscreen"
echo "5) ljwx-admin"
echo "6) 所有服务"
echo ""
read -p "选择服务 [1-6]: " service_choice
case $service_choice in
1) docker-compose logs -f ljwx-mysql ;;
2) docker-compose logs -f ljwx-redis ;;
3) docker-compose logs -f ljwx-boot ;;
4) docker-compose logs -f ljwx-bigscreen ;;
5) docker-compose logs -f ljwx-admin ;;
6) docker-compose logs -f ;;
*) echo -e "${RED}❌ 无效选择${NC}" ;;
esac
break
;;
2)
echo -e "${BLUE}📋 文件日志查看${NC}"
echo "可用日志目录:"
echo "1) MySQL日志: ./logs/mysql/"
echo "2) Redis日志: ./logs/redis/"
echo "3) 后端日志: ./logs/ljwx-boot/"
echo "4) 大屏日志: ./logs/ljwx-bigscreen/"
echo "5) 管理端日志: ./logs/ljwx-admin/"
echo ""
read -p "选择日志目录 [1-5]: " dir_choice
case $dir_choice in
1)
if [ -d "./logs/mysql" ]; then
echo -e "${GREEN}📄 MySQL日志文件:${NC}"
ls -la ./logs/mysql/
echo ""
read -p "选择要查看的日志文件 (输入文件名或按Enter查看error.log): " mysql_log
mysql_log=${mysql_log:-error.log}
if [ -f "./logs/mysql/$mysql_log" ]; then
tail -f "./logs/mysql/$mysql_log"
else
echo -e "${RED}❌ 文件不存在: ./logs/mysql/$mysql_log${NC}"
fi
else
echo -e "${RED}❌ MySQL日志目录不存在${NC}"
fi
;;
2)
if [ -d "./logs/redis" ]; then
echo -e "${GREEN}📄 Redis日志文件:${NC}"
ls -la ./logs/redis/
echo ""
read -p "选择要查看的日志文件 (输入文件名): " redis_log
if [ -f "./logs/redis/$redis_log" ]; then
tail -f "./logs/redis/$redis_log"
else
echo -e "${RED}❌ 文件不存在: ./logs/redis/$redis_log${NC}"
fi
else
echo -e "${RED}❌ Redis日志目录不存在${NC}"
fi
;;
3)
if [ -d "./logs/ljwx-boot" ]; then
echo -e "${GREEN}📄 后端日志文件:${NC}"
ls -la ./logs/ljwx-boot/
echo ""
find ./logs/ljwx-boot/ -name "*.log" -type f | head -5 | while read logfile; do
echo "实时查看: $logfile"
tail -f "$logfile" &
done
wait
else
echo -e "${RED}❌ 后端日志目录不存在${NC}"
fi
;;
4)
if [ -d "./logs/ljwx-bigscreen" ]; then
echo -e "${GREEN}📄 大屏日志文件:${NC}"
ls -la ./logs/ljwx-bigscreen/
echo ""
find ./logs/ljwx-bigscreen/ -name "*.log" -type f | head -5 | while read logfile; do
echo "实时查看: $logfile"
tail -f "$logfile" &
done
wait
else
echo -e "${RED}❌ 大屏日志目录不存在${NC}"
fi
;;
5)
if [ -d "./logs/ljwx-admin" ]; then
echo -e "${GREEN}📄 管理端日志文件:${NC}"
ls -la ./logs/ljwx-admin/
echo ""
read -p "选择要查看的日志文件 (输入文件名或按Enter查看access.log): " admin_log
admin_log=${admin_log:-access.log}
if [ -f "./logs/ljwx-admin/$admin_log" ]; then
tail -f "./logs/ljwx-admin/$admin_log"
else
echo -e "${RED}❌ 文件不存在: ./logs/ljwx-admin/$admin_log${NC}"
fi
else
echo -e "${RED}❌ 管理端日志目录不存在${NC}"
fi
;;
*) echo -e "${RED}❌ 无效选择${NC}" ;;
esac
break
;;
3)
echo -e "${BLUE}📋 所有日志目录概览${NC}"
echo ""
for service in mysql redis ljwx-boot ljwx-bigscreen ljwx-admin; do
if [ -d "./logs/$service" ]; then
echo -e "${GREEN}✅ ./logs/$service/${NC}"
ls -la "./logs/$service/" | head -10
echo ""
else
echo -e "${RED}❌ ./logs/$service/ (不存在)${NC}"
fi
done
echo ""
echo "📊 磁盘使用情况:"
du -sh ./logs/* 2>/dev/null || echo "logs目录为空"
exit 0
;;
4)
echo "退出日志查看工具"
exit 0
;;
*)
echo -e "${RED}❌ 无效选择,请输入 1-4${NC}"
;;
esac
done
+294
View File
@@ -0,0 +1,294 @@
#!/bin/bash
# MySQL数据库备份和恢复工具 - 跨平台版本
# 支持: CentOS, macOS, Ubuntu, Debian
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m'
# 检测操作系统类型
detect_os() {
if [[ "$OSTYPE" == "darwin"* ]]; then
OS_TYPE="macos"
OS_VERSION=$(sw_vers -productVersion 2>/dev/null || echo "unknown")
elif [[ -f /etc/centos-release ]] || [[ -f /etc/redhat-release ]]; then
OS_TYPE="centos"
OS_VERSION=$(cat /etc/centos-release 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+' | head -1 || echo "unknown")
elif [[ -f /etc/debian_version ]]; then
if grep -q "Ubuntu" /etc/os-release 2>/dev/null; then
OS_TYPE="ubuntu"
OS_VERSION=$(grep VERSION_ID /etc/os-release | cut -d'"' -f2 || echo "unknown")
else
OS_TYPE="debian"
OS_VERSION=$(cat /etc/debian_version 2>/dev/null || echo "unknown")
fi
else
OS_TYPE="linux"
OS_VERSION="unknown"
fi
}
# 跨平台命令适配
get_compression_cmd() {
case $OS_TYPE in
"macos")
echo "gunzip -c" # macOS使用gunzip避免zcat问题
;;
"centos"|"ubuntu"|"debian"|"linux")
if command -v zcat >/dev/null 2>&1; then
echo "zcat" # Linux优先使用zcat
else
echo "gunzip -c" # 备选gunzip
fi
;;
*)
echo "gunzip -c" # 默认使用gunzip
;;
esac
}
# 跨平台MD5计算
get_md5_cmd() {
case $OS_TYPE in
"macos")
echo "md5 -r" # macOS使用md5命令
;;
"centos"|"ubuntu"|"debian"|"linux")
if command -v md5sum >/dev/null 2>&1; then
echo "md5sum" # Linux使用md5sum
else
echo "md5 -r" # 备选md5
fi
;;
*)
echo "md5sum" # 默认使用md5sum
;;
esac
}
# 初始化系统检测
detect_os
DECOMPRESS_CMD=$(get_compression_cmd)
MD5_CMD=$(get_md5_cmd)
# 配置变量
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="123456"
MYSQL_DATABASE="lj-06"
BACKUP_DIR="./backups"
VOLUME_NAME="client-deployment_mysql_data"
# 脚本横幅
echo -e "${PURPLE}"
cat << 'EOF'
╔══════════════════════════════════════════════════════════════╗
║ MySQL备份恢复工具 - 跨平台版本 ║
║ 支持: CentOS, macOS, Ubuntu, Debian ║
╚══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
# 显示系统信息
echo -e "${BLUE}🖥️ 系统信息:${NC}"
echo " 操作系统: $OS_TYPE $OS_VERSION"
echo " 解压命令: $DECOMPRESS_CMD"
echo " MD5命令: $MD5_CMD"
echo ""
# 检查Docker是否运行
if ! docker info >/dev/null 2>&1; then
echo -e "${RED}❌ Docker未运行,请先启动Docker${NC}"
exit 1
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"/{sql,volume,config}
# 显示菜单
show_menu() {
echo -e "${BLUE}📋 选择操作:${NC}"
echo "1. 📦 创建完整备份(推荐)"
echo "2. 💾 仅SQL逻辑备份"
echo "3. 🗃️ 仅数据卷备份"
echo "4. 🔄 恢复数据库"
echo "5. 📊 查看备份列表"
echo "6. 🔍 验证备份完整性"
echo "7. 🧹 清理旧备份"
echo "8. ❌ 退出"
echo ""
}
# 创建SQL逻辑备份
create_sql_backup() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/sql/ljwx_backup_${timestamp}.sql"
echo -e "${BLUE}📦 正在创建SQL逻辑备份...${NC}"
if docker exec $MYSQL_CONTAINER mysqldump \
-u $MYSQL_USER -p$MYSQL_PASSWORD \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--add-drop-database \
--databases $MYSQL_DATABASE > "$backup_file" 2>/dev/null; then
# 压缩备份文件
gzip "$backup_file"
backup_file="${backup_file}.gz"
# 计算文件大小和MD5
local file_size=$(ls -lh "$backup_file" | awk '{print $5}')
local md5_hash
case $OS_TYPE in
"macos")
md5_hash=$(md5 -r "$backup_file" | awk '{print $1}')
;;
*)
md5_hash=$(md5sum "$backup_file" | awk '{print $1}')
;;
esac
echo -e "${GREEN}✅ SQL备份创建成功${NC}"
echo "📁 文件: $backup_file"
echo "📏 大小: $file_size"
echo "🔐 MD5: $md5_hash"
# 记录备份信息
echo "$(date '+%Y-%m-%d %H:%M:%S'),SQL,$backup_file,$file_size,$md5_hash" >> "$BACKUP_DIR/backup_log.csv"
return 0
else
echo -e "${RED}❌ SQL备份失败${NC}"
return 1
fi
}
# 从SQL备份恢复
restore_from_sql() {
echo -e "${BLUE}📋 可用的SQL备份文件:${NC}"
if ! ls "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null | nl; then
echo -e "${RED}❌ 没有找到SQL备份文件${NC}"
return 1
fi
read -p "请输入备份文件编号: " file_num
backup_file=$(ls "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null | sed -n "${file_num}p")
if [ -z "$backup_file" ]; then
echo -e "${RED}❌ 无效的文件编号${NC}"
return 1
fi
echo -e "${YELLOW}⚠️ 警告: 此操作将完全替换现有数据库!${NC}"
read -p "确认继续?(yes/no): " confirm
if [ "$confirm" != "yes" ]; then
echo -e "${BLUE}💡 操作已取消${NC}"
return 0
fi
echo -e "${BLUE}🔄 正在从SQL备份恢复...${NC}"
echo " 使用解压命令: $DECOMPRESS_CMD"
# 解压并恢复(跨平台兼容)
if $DECOMPRESS_CMD "$backup_file" | docker exec -i $MYSQL_CONTAINER mysql -u $MYSQL_USER -p$MYSQL_PASSWORD 2>/dev/null; then
echo -e "${GREEN}✅ SQL恢复成功${NC}"
# 验证恢复
table_count=$(docker exec $MYSQL_CONTAINER mysql -u $MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$MYSQL_DATABASE';" 2>/dev/null | tail -1)
echo -e "${BLUE}📊 恢复验证: $table_count 个表${NC}"
else
echo -e "${RED}❌ SQL恢复失败${NC}"
return 1
fi
}
# 查看备份列表
list_backups() {
echo -e "${BLUE}📊 备份文件列表:${NC}"
echo "=================================="
if [ -f "$BACKUP_DIR/backup_log.csv" ]; then
echo -e "${YELLOW}📋 最近10次备份日志:${NC}"
echo "时间,类型,文件,大小,MD5"
echo "--------------------------------"
tail -10 "$BACKUP_DIR/backup_log.csv" 2>/dev/null || echo " 备份日志为空"
echo ""
fi
echo -e "${YELLOW}📁 SQL备份文件:${NC}"
ls -lh "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null || echo " 无SQL备份文件"
echo ""
echo -e "${YELLOW}🗃️ 数据卷备份文件:${NC}"
ls -lh "$BACKUP_DIR/volume/"*.tar.gz 2>/dev/null || echo " 无数据卷备份文件"
echo ""
echo -e "${YELLOW}⚙️ 配置备份文件:${NC}"
ls -lh "$BACKUP_DIR/config/"*.tar.gz 2>/dev/null || echo " 无配置备份文件"
}
# 验证备份完整性
verify_backups() {
echo -e "${BLUE}🔍 验证备份完整性...${NC}"
echo " 使用解压命令: $DECOMPRESS_CMD"
# 验证SQL备份
for sql_file in "$BACKUP_DIR/sql/"*.sql.gz; do
if [ -f "$sql_file" ]; then
echo -n "📄 $(basename "$sql_file"): "
if $DECOMPRESS_CMD "$sql_file" | head -20 | grep -q "MySQL dump"; then
echo -e "${GREEN}✅ 有效${NC}"
else
echo -e "${RED}❌ 损坏${NC}"
fi
fi
done
# 验证数据卷备份
for vol_file in "$BACKUP_DIR/volume/"*.tar.gz; do
if [ -f "$vol_file" ]; then
echo -n "🗃️ $(basename "$vol_file"): "
if tar -tzf "$vol_file" >/dev/null 2>&1; then
echo -e "${GREEN}✅ 有效${NC}"
else
echo -e "${RED}❌ 损坏${NC}"
fi
fi
done
}
# 主循环
while true; do
show_menu
read -p "请选择操作 (1-8): " choice
case $choice in
1) echo -e "${YELLOW}💡 完整备份功能请使用 auto-backup-crossplatform.sh manual${NC}" ;;
2) create_sql_backup ;;
3) echo -e "${YELLOW}💡 数据卷备份功能请使用 auto-backup-crossplatform.sh${NC}" ;;
4) restore_from_sql ;;
5) list_backups ;;
6) verify_backups ;;
7) echo -e "${YELLOW}💡 清理功能待实现${NC}" ;;
8)
echo -e "${GREEN}👋 再见!${NC}"
exit 0
;;
*)
echo -e "${RED}❌ 无效选择,请输入 1-8${NC}"
;;
esac
echo ""
read -p "按回车键继续..."
echo ""
done
+276
View File
@@ -0,0 +1,276 @@
#!/bin/bash
# MySQL数据库备份和恢复工具 - 支持命名卷和逻辑备份两种模式
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m'
# 配置变量
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="123456"
MYSQL_DATABASE="lj-06"
BACKUP_DIR="./backups"
VOLUME_NAME="client-deployment_mysql_data"
echo -e "${PURPLE}"
cat << 'EOF'
╔══════════════════════════════════════════════════════════════╗
║ MySQL备份恢复工具 ║
║ 支持命名卷和逻辑备份双重保护 ║
╚══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
# 检查Docker是否运行
if ! docker info >/dev/null 2>&1; then
echo -e "${RED}❌ Docker未运行,请先启动Docker${NC}"
exit 1
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"/{sql,volume,config}
# 显示菜单
show_menu() {
echo -e "${BLUE}📋 选择操作:${NC}"
echo "1. 📦 创建完整备份(推荐)"
echo "2. 💾 仅SQL逻辑备份"
echo "3. 🗃️ 仅数据卷备份"
echo "4. 🔄 恢复数据库"
echo "5. 📊 查看备份列表"
echo "6. 🔍 验证备份完整性"
echo "7. 🧹 清理旧备份"
echo "8. ❌ 退出"
echo ""
}
# 创建SQL逻辑备份
create_sql_backup() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/sql/ljwx_backup_${timestamp}.sql"
echo -e "${BLUE}📦 正在创建SQL逻辑备份...${NC}"
if docker exec $MYSQL_CONTAINER mysqldump \
-u $MYSQL_USER -p$MYSQL_PASSWORD \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--add-drop-database \
--databases $MYSQL_DATABASE > "$backup_file"; then
# 压缩备份文件
gzip "$backup_file"
backup_file="${backup_file}.gz"
# 计算文件大小
local file_size=$(ls -lh "$backup_file" | awk '{print $5}')
echo -e "${GREEN}✅ SQL备份创建成功${NC}"
echo "📁 文件: $backup_file"
echo "📏 大小: $file_size"
# 记录备份信息
echo "$(date '+%Y-%m-%d %H:%M:%S'),SQL,$backup_file,$file_size" >> "$BACKUP_DIR/backup_log.csv"
return 0
else
echo -e "${RED}❌ SQL备份失败${NC}"
return 1
fi
}
# 创建数据卷备份
create_volume_backup() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/volume/mysql_volume_${timestamp}.tar.gz"
echo -e "${BLUE}🗃️ 正在创建数据卷备份...${NC}"
# 使用临时容器备份数据卷
if docker run --rm \
-v $VOLUME_NAME:/source:ro \
-v "$(pwd)/$BACKUP_DIR/volume":/backup \
alpine:latest \
tar -czf "/backup/mysql_volume_${timestamp}.tar.gz" -C /source .; then
# 计算文件信息
local file_size=$(ls -lh "$backup_file" | awk '{print $5}')
echo -e "${GREEN}✅ 数据卷备份创建成功${NC}"
echo "📁 文件: $backup_file"
echo "📏 大小: $file_size"
# 记录备份信息
echo "$(date '+%Y-%m-%d %H:%M:%S'),VOLUME,$backup_file,$file_size" >> "$BACKUP_DIR/backup_log.csv"
return 0
else
echo -e "${RED}❌ 数据卷备份失败${NC}"
return 1
fi
}
# 创建完整备份
create_full_backup() {
local timestamp=$(date +%Y%m%d_%H%M%S)
echo -e "${YELLOW}🎯 开始创建完整备份 ($timestamp)${NC}"
echo "=================================="
# 1. SQL逻辑备份
echo -e "${BLUE}步骤 1/3: SQL逻辑备份${NC}"
if ! create_sql_backup; then
echo -e "${RED}❌ 完整备份失败:SQL备份错误${NC}"
return 1
fi
# 2. 数据卷备份
echo -e "${BLUE}步骤 2/3: 数据卷备份${NC}"
if ! create_volume_backup; then
echo -e "${RED}❌ 完整备份失败:数据卷备份错误${NC}"
return 1
fi
# 3. 配置文件备份
echo -e "${BLUE}步骤 3/3: 配置文件备份${NC}"
local config_backup="$BACKUP_DIR/config/config_${timestamp}.tar.gz"
tar -czf "$config_backup" \
docker-compose*.yml \
custom-config.env \
*.sh \
2>/dev/null || echo -e "${YELLOW}⚠️ 部分配置文件备份失败${NC}"
echo -e "${GREEN}🎉 完整备份创建成功!${NC}"
echo "=================================="
echo "📊 备份统计:"
ls -lh "$BACKUP_DIR"/{sql,volume,config}/*${timestamp}* 2>/dev/null || echo " 备份文件列表获取失败"
}
# 从SQL备份恢复
restore_from_sql() {
echo -e "${BLUE}📋 可用的SQL备份文件:${NC}"
if ! ls "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null | nl; then
echo -e "${RED}❌ 没有找到SQL备份文件${NC}"
return 1
fi
read -p "请输入备份文件编号: " file_num
backup_file=$(ls "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null | sed -n "${file_num}p")
if [ -z "$backup_file" ]; then
echo -e "${RED}❌ 无效的文件编号${NC}"
return 1
fi
echo -e "${YELLOW}⚠️ 警告: 此操作将完全替换现有数据库!${NC}"
read -p "确认继续?(yes/no): " confirm
if [ "$confirm" != "yes" ]; then
echo -e "${BLUE}💡 操作已取消${NC}"
return 0
fi
echo -e "${BLUE}🔄 正在从SQL备份恢复...${NC}"
# 解压并恢复
if zcat "$backup_file" | docker exec -i $MYSQL_CONTAINER mysql -u $MYSQL_USER -p$MYSQL_PASSWORD; then
echo -e "${GREEN}✅ SQL恢复成功${NC}"
# 验证恢复
table_count=$(docker exec $MYSQL_CONTAINER mysql -u $MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$MYSQL_DATABASE';" 2>/dev/null | tail -1)
echo -e "${BLUE}📊 恢复验证: $table_count 个表${NC}"
else
echo -e "${RED}❌ SQL恢复失败${NC}"
return 1
fi
}
# 查看备份列表
list_backups() {
echo -e "${BLUE}📊 备份文件列表:${NC}"
echo "=================================="
if [ -f "$BACKUP_DIR/backup_log.csv" ]; then
echo -e "${YELLOW}📋 最近10次备份日志:${NC}"
echo "时间,类型,文件,大小"
echo "--------------------------------"
tail -10 "$BACKUP_DIR/backup_log.csv" 2>/dev/null || echo " 备份日志为空"
echo ""
fi
echo -e "${YELLOW}📁 SQL备份文件:${NC}"
ls -lh "$BACKUP_DIR/sql/"*.sql.gz 2>/dev/null || echo " 无SQL备份文件"
echo ""
echo -e "${YELLOW}🗃️ 数据卷备份文件:${NC}"
ls -lh "$BACKUP_DIR/volume/"*.tar.gz 2>/dev/null || echo " 无数据卷备份文件"
echo ""
echo -e "${YELLOW}⚙️ 配置备份文件:${NC}"
ls -lh "$BACKUP_DIR/config/"*.tar.gz 2>/dev/null || echo " 无配置备份文件"
}
# 验证备份完整性
verify_backups() {
echo -e "${BLUE}🔍 验证备份完整性...${NC}"
# 验证SQL备份
for sql_file in "$BACKUP_DIR/sql/"*.sql.gz; do
if [ -f "$sql_file" ]; then
echo -n "📄 $(basename "$sql_file"): "
if zcat "$sql_file" | head -20 | grep -q "MySQL dump"; then
echo -e "${GREEN}✅ 有效${NC}"
else
echo -e "${RED}❌ 损坏${NC}"
fi
fi
done
# 验证数据卷备份
for vol_file in "$BACKUP_DIR/volume/"*.tar.gz; do
if [ -f "$vol_file" ]; then
echo -n "🗃️ $(basename "$vol_file"): "
if tar -tzf "$vol_file" >/dev/null 2>&1; then
echo -e "${GREEN}✅ 有效${NC}"
else
echo -e "${RED}❌ 损坏${NC}"
fi
fi
done
}
# 主循环
while true; do
show_menu
read -p "请选择操作 (1-8): " choice
case $choice in
1) create_full_backup ;;
2) create_sql_backup ;;
3) create_volume_backup ;;
4) restore_from_sql ;;
5) list_backups ;;
6) verify_backups ;;
7) echo -e "${YELLOW}💡 清理功能待实现${NC}" ;;
8)
echo -e "${GREEN}👋 再见!${NC}"
exit 0
;;
*)
echo -e "${RED}❌ 无效选择,请输入 1-8${NC}"
;;
esac
echo ""
read -p "按回车键继续..."
echo ""
done
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
# MySQL每日定时备份脚本 - 极致优化版
CONTAINER_NAME="ljwx-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}"
MYSQL_DATABASE="${MYSQL_DATABASE:-lj-06}"
BACKUP_DIR="backup/mysql"
LOG_FILE="logs/mysql-backup.log"
BACKUP_RETENTION_DAYS=7 #保留7天备份
log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"; } #日志函数
create_backup() { #创建备份
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/ljwx_daily_${timestamp}.sql.gz"
log "🔄 开始MySQL每日备份..."
# 检查容器状态
if ! docker ps --format "{{.Names}}" | grep -q "^$CONTAINER_NAME$"; then
log "❌ MySQL容器未运行"
return 1
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR" "$(dirname "$LOG_FILE")"
# 执行备份
if docker exec "$CONTAINER_NAME" mysqldump \
-u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
--single-transaction \
--routines \
--triggers \
--complete-insert \
"$MYSQL_DATABASE" | gzip > "$backup_file"; then
local size=$(du -h "$backup_file" | cut -f1)
log "✅ 备份成功: $backup_file ($size)"
# 验证备份文件
if gunzip -t "$backup_file" 2>/dev/null; then
log "✅ 备份文件验证通过"
else
log "❌ 备份文件验证失败"
rm -f "$backup_file"
return 1
fi
else
log "❌ 备份失败"
return 1
fi
}
cleanup_old_backups() { #清理旧备份
log "�� 清理${BACKUP_RETENTION_DAYS}天前的备份..."
local deleted=0
while IFS= read -r -d '' file; do
rm -f "$file"
deleted=$((deleted + 1))
log "🗑️ 删除旧备份: $(basename "$file")"
done < <(find "$BACKUP_DIR" -name "ljwx_daily_*.sql.gz" -mtime +$BACKUP_RETENTION_DAYS -print0 2>/dev/null)
log "📊 清理完成,删除 $deleted 个文件"
}
# 主执行流程
main() {
if create_backup; then
cleanup_old_backups
log "🎯 每日备份任务完成"
else
log "❌ 备份任务失败"
exit 1
fi
}
main "$@"
+92
View File
@@ -0,0 +1,92 @@
#!/bin/bash
# MySQL连接保活服务 - 防止长时间空置导致连接失效
# 支持跨平台运行 (macOS/Linux/CentOS)
LOG_FILE="/tmp/ljwx-mysql-keepalive.log"
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}"
# 检测操作系统
OS=$(uname -s)
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 跨平台curl检测
check_curl() {
if command -v curl >/dev/null 2>&1; then
return 0
elif command -v wget >/dev/null 2>&1; then
return 1 # 使用wget
else
return 2 # 都没有
fi
}
# 跨平台HTTP请求
http_request() {
local url="$1"
if check_curl; then
curl -s "$url"
else
wget -qO- "$url" 2>/dev/null
fi
}
keepalive_check() {
# 检查MySQL连接
if docker exec "$MYSQL_CONTAINER" mysqladmin ping -u root -p"$MYSQL_PASSWORD" >/dev/null 2>&1; then
log "✅ MySQL连接正常"
# 执行保活查询
docker exec "$MYSQL_CONTAINER" mysql -u root -p"$MYSQL_PASSWORD" -e "SELECT 'keepalive' as status, NOW() as time;" >/dev/null 2>&1
log "💓 保活查询已执行"
# 检查连接数
CONNECTIONS=$(docker exec "$MYSQL_CONTAINER" mysql -u root -p"$MYSQL_PASSWORD" -e "SHOW STATUS LIKE 'Threads_connected';" 2>/dev/null | tail -1 | awk '{print $2}')
log "📊 当前连接数: $CONNECTIONS"
else
log "❌ MySQL连接失败,尝试重启服务"
# 根据Docker Compose版本选择命令
if command -v docker-compose >/dev/null 2>&1; then
docker-compose restart ljwx-mysql
sleep 30
docker-compose restart ljwx-boot
else
docker compose restart ljwx-mysql
sleep 30
docker compose restart ljwx-boot
fi
log "🔄 服务重启完成"
fi
# 检查应用连接
if http_request "http://localhost:9998/actuator/health/db" | grep -q "UP"; then
log "✅ 应用数据源正常"
else
log "❌ 应用数据源异常"
# 触发应用重连
http_request "http://localhost:9998/actuator/refresh" >/dev/null 2>&1 || true
fi
}
# 创建日志目录(如果需要)
case "$OS" in
"Darwin") # macOS
# macOS使用/tmp,无需创建目录
;;
"Linux") # Linux
# 尝试创建日志目录
sudo mkdir -p /var/log 2>/dev/null || mkdir -p $(dirname "$LOG_FILE")
;;
esac
log "🚀 启动LJWX MySQL连接保活服务 (OS: $OS)"
# 主循环
while true; do
keepalive_check
sleep 300 # 每5分钟检查一次
done
+166
View File
@@ -0,0 +1,166 @@
#!/bin/bash
# MySQL升级预检查器 | 使用说明: ./mysql-upgrade-checker.sh [目标版本]
CFG="custom-config.env"; [[ -f "$CFG" ]] && source "$CFG" #加载配置
MYSQL_CONTAINER="${MYSQL_CONTAINER:-ljwx-mysql}"; MYSQL_USER="${MYSQL_USER:-root}"; MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}"; MYSQL_DATABASE="${MYSQL_DATABASE:-lj-06}" #数据库配置
TARGET_VERSION="${1:-1.2.15}"; CURRENT_VERSION=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep ljwx-mysql | head -1 | cut -d: -f2 2>/dev/null || echo "unknown") #版本信息
MIN_DISK_GB=5; MIN_MEMORY_GB=2; BACKUP_SIZE_THRESHOLD=1 #最小资源要求
# 颜色和日志
G='\033[0;32m'; Y='\033[1;33m'; R='\033[0;31m'; N='\033[0m' #颜色代码
log() { echo -e "${G}[CHECK]${N} $1"; }; warn() { echo -e "${Y}[WARN]${N} $1"; }; error() { echo -e "${R}[FAIL]${N} $1"; } #日志函数
# 检查项计数器
pass=0; warn_count=0; fail=0 #计数器初始化
# 检查函数模板
check() { local msg="$1" cmd="$2" fix="$3"; echo -n "🔍 $msg... "; if eval "$cmd" &>/dev/null; then echo -e "${G}✅${N}"; ((pass++)); else echo -e "${R}❌${N}"; echo " 💡 修复建议: $fix"; ((fail++)); fi; } #检查模板
# 检查Docker环境
check_docker() {
log "检查Docker环境"
check "Docker服务运行" "docker info" "启动Docker服务: sudo systemctl start docker"
check "Docker Compose可用" "docker-compose --version" "安装Docker Compose: sudo apt install docker-compose"
check "当前用户Docker权限" "docker ps" "添加用户到docker组: sudo usermod -aG docker \$USER"
}
# 检查系统资源
check_resources() {
log "检查系统资源"
local disk_free=$(df . | awk 'NR==2{print int($4/1024/1024)}') #可用磁盘空间GB
local memory_total=$(free -g | awk 'NR==2{print $2}') #总内存GB
local cpu_cores=$(nproc) #CPU核心数
check "磁盘空间充足(需要${MIN_DISK_GB}GB)" "[ $disk_free -ge $MIN_DISK_GB ]" "清理磁盘空间: docker system prune -af"
check "内存充足(需要${MIN_MEMORY_GB}GB)" "[ $memory_total -ge $MIN_MEMORY_GB ]" "增加系统内存或关闭其他服务"
check "CPU核心充足(需要2核)" "[ $cpu_cores -ge 2 ]" "升级CPU配置"
echo " 📊 系统资源: ${disk_free}GB磁盘 | ${memory_total}GB内存 | ${cpu_cores}核CPU"
}
# 检查MySQL状态
check_mysql() {
log "检查MySQL容器状态"
check "MySQL容器运行" "docker ps --format '{{.Names}}' | grep -q '^$MYSQL_CONTAINER$'" "启动MySQL: docker-compose up -d mysql"
check "MySQL服务响应" "docker exec $MYSQL_CONTAINER mysqladmin ping -u$MYSQL_USER -p$MYSQL_PASSWORD" "检查MySQL配置和网络"
check "数据库连接正常" "docker exec $MYSQL_CONTAINER mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e 'SELECT 1' $MYSQL_DATABASE" "检查数据库权限和配置"
# 获取数据库大小
local db_size=$(docker exec $MYSQL_CONTAINER mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'DB_SIZE_MB' FROM information_schema.tables WHERE table_schema='$MYSQL_DATABASE';" -s -N 2>/dev/null || echo "0")
echo " 📊 数据库大小: ${db_size}MB"
[[ $(echo "$db_size > 1000" | bc -l 2>/dev/null || echo 0) -eq 1 ]] && warn "数据库较大,升级可能需要更长时间"
}
# 检查版本兼容性
check_version() {
log "检查版本兼容性"
check "当前版本有效" "[ '$CURRENT_VERSION' != 'unknown' ]" "确认MySQL容器正在运行"
check "目标版本不同" "[ '$CURRENT_VERSION' != '$TARGET_VERSION' ]" "指定不同的目标版本"
# 检查升级路径
local current_major=$(echo $CURRENT_VERSION | cut -d. -f1)
local current_minor=$(echo $CURRENT_VERSION | cut -d. -f2)
local target_major=$(echo $TARGET_VERSION | cut -d. -f1)
local target_minor=$(echo $TARGET_VERSION | cut -d. -f2)
check "主版本兼容" "[ $current_major -eq $target_major ]" "跨主版本升级需要特殊处理"
echo " 📊 版本路径: $CURRENT_VERSION → $TARGET_VERSION"
}
# 检查备份空间
check_backup() {
log "检查备份配置"
check "备份目录存在" "[ -d backup ]" "创建备份目录: mkdir -p backup/mysql"
check "备份目录可写" "[ -w backup ]" "修改备份目录权限: chmod 755 backup"
# 估算备份空间需求
local data_size=$(docker exec $MYSQL_CONTAINER du -sm /var/lib/mysql 2>/dev/null | cut -f1 || echo "100")
local required_space=$((data_size * 3)) #备份需要3倍数据大小空间
local available_space=$(df backup | awk 'NR==2{print int($4/1024)}')
check "备份空间充足" "[ $available_space -ge $required_space ]" "清理备份目录或增加磁盘空间"
echo " 📊 需要空间: ${required_space}MB | 可用空间: ${available_space}MB"
}
# 检查依赖服务
check_dependencies() {
log "检查服务依赖"
local services=(ljwx-boot ljwx-bigscreen ljwx-admin redis)
for svc in "${services[@]}"; do
if docker ps --format "{{.Names}}" | grep -q "^$svc$"; then
check "$svc 服务运行中" "true" ""
else
warn "$svc 服务未运行"
((warn_count++))
fi
done
}
# 检查网络连通性
check_network() {
log "检查网络连通性"
check "镜像仓库可达" "docker pull alpine:latest" "检查网络连接和DNS配置"
check "容器网络正常" "docker network ls | grep -q client-deployment" "重新创建网络: docker-compose down && docker-compose up -d"
}
# 检查配置文件
check_config() {
log "检查配置文件"
check "docker-compose.yml存在" "[ -f docker-compose.yml ]" "确认在正确的目录下运行"
check "custom-config.env存在" "[ -f custom-config.env ]" "复制配置模板: cp custom-config.env.example custom-config.env"
check "MySQL配置有效" "grep -q 'MYSQL_PASSWORD' custom-config.env" "检查MySQL配置参数"
}
# 生成预检查报告
generate_report() {
echo
echo "======================================"
echo "📋 MySQL升级预检查报告"
echo "======================================"
echo "检查时间: $(date)"
echo "当前版本: $CURRENT_VERSION"
echo "目标版本: $TARGET_VERSION"
echo
echo "检查结果:"
echo " ✅ 通过: $pass 项"
echo " ⚠️ 警告: $warn_count 项"
echo " ❌ 失败: $fail 项"
echo
if [[ $fail -eq 0 ]]; then
echo -e "${G}🎉 预检查通过,可以开始升级!${N}"
echo "💡 建议执行: ./mysql-upgrade-manager.sh $TARGET_VERSION"
echo
echo "升级前最终确认清单:"
echo "□ 已通知用户系统将维护"
echo "□ 已准备足够的维护时间窗口"
echo "□ 已确认回滚计划"
echo "□ 已备份重要配置文件"
return 0
else
echo -e "${R}❌ 预检查未通过,请修复问题后重试${N}"
return 1
fi
}
# 主函数
main() {
echo "🚀 MySQL升级预检查器启动"
echo "当前版本: $CURRENT_VERSION → 目标版本: $TARGET_VERSION"
echo
check_docker
check_resources
check_mysql
check_version
check_backup
check_dependencies
check_network
check_config
generate_report
}
# 执行主函数
main "$@"
+287
View File
@@ -0,0 +1,287 @@
#!/bin/bash
# MySQL升级管理器 - 专业版 | 使用说明: ./mysql-upgrade-manager.sh [版本号]
set -euo pipefail #严格模式
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && set -x #调试模式
# 配置文件
CFG="custom-config.env"; [[ -f "$CFG" ]] && source "$CFG" #加载配置
MYSQL_CONTAINER="${MYSQL_CONTAINER:-ljwx-mysql}" #MySQL容器名
MYSQL_USER="${MYSQL_USER:-root}" #数据库用户
MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}" #数据库密码
MYSQL_DATABASE="${MYSQL_DATABASE:-lj-06}" #数据库名
BACKUP_DIR="backup/mysql/upgrade" #升级备份目录
LOG_FILE="logs/upgrade-$(date +%Y%m%d-%H%M%S).log" #升级日志文件
CURRENT_VERSION=$(docker images --format "table {{.Repository}}:{{.Tag}}" | grep ljwx-mysql | head -1 | cut -d: -f2) #当前版本
TARGET_VERSION="${1:-1.2.15}" #目标版本
REGISTRY="crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx" #镜像仓库
# 颜色配置
R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; B='\033[0;34m'; N='\033[0m' #颜色代码
# 工具函数
log() { echo -e "${G}[$(date +'%H:%M:%S')]${N} $1" | tee -a "$LOG_FILE"; } #记录日志
warn() { echo -e "${Y}[WARN]${N} $1" | tee -a "$LOG_FILE"; } #警告信息
error() { echo -e "${R}[ERROR]${N} $1" | tee -a "$LOG_FILE"; exit 1; } #错误退出
confirm() { read -p "$(echo -e "${Y}$1 [y/N]:${N} ")" r; [[ "$r" =~ ^[Yy]$ ]]; } #确认提示
# 初始化
init() {
mkdir -p "$BACKUP_DIR" logs temp #创建目录
log "🚀 MySQL升级管理器启动 | 当前版本: $CURRENT_VERSION → 目标版本: $TARGET_VERSION"
[[ "$CURRENT_VERSION" == "$TARGET_VERSION" ]] && error "版本相同,无需升级"
}
# 健康检查
health_check() {
log "🔍 执行升级前健康检查..."
docker ps --format "{{.Names}}" | grep -q "^$MYSQL_CONTAINER$" || error "MySQL容器未运行" #检查容器
docker exec "$MYSQL_CONTAINER" mysqladmin ping -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" &>/dev/null || error "MySQL连接失败" #检查连接
local disk_usage=$(df . | awk 'NR==2{print $5}' | sed 's/%//') #磁盘使用率
[[ $disk_usage -gt 80 ]] && warn "磁盘使用率${disk_usage}%,建议清理空间"
log "✅ 健康检查通过"
}
# 数据备份
backup_data() {
log "📦 创建升级前完整备份..."
local backup_file="$BACKUP_DIR/pre_upgrade_${CURRENT_VERSION}_$(date +%Y%m%d-%H%M%S).sql.gz"
docker exec "$MYSQL_CONTAINER" mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
--single-transaction --routines --triggers --events --hex-blob \
--add-drop-database --databases "$MYSQL_DATABASE" | gzip > "$backup_file" || error "备份失败"
local size=$(du -h "$backup_file" | cut -f1) #文件大小
log "✅ 备份完成: $(basename "$backup_file") ($size)"
echo "$backup_file" > temp/backup_path #保存备份路径
}
# 数据导出
export_critical_data() {
log "📊 导出关键业务数据..."
local export_dir="$BACKUP_DIR/critical_data_$(date +%Y%m%d-%H%M%S)"
mkdir -p "$export_dir"
# 导出关键表数据
for table in $(docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
-e "SELECT table_name FROM information_schema.tables WHERE table_schema='$MYSQL_DATABASE' AND table_name LIKE '%user%' OR table_name LIKE '%config%' OR table_name LIKE '%dict%';" \
-s -N 2>/dev/null | head -10); do
docker exec "$MYSQL_CONTAINER" mysqldump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
--no-create-info --complete-insert "$MYSQL_DATABASE" "$table" > "$export_dir/${table}.sql" 2>/dev/null || true
done
log "✅ 关键数据导出完成: $export_dir"
}
# 拉取新镜像
pull_image() {
log "📥 拉取MySQL新镜像版本..."
docker pull "$REGISTRY/ljwx-mysql:$TARGET_VERSION" || error "镜像拉取失败"
log "✅ 镜像拉取完成"
}
# 停止依赖服务
stop_services() {
log "⏹️ 停止依赖服务..."
local services=(ljwx-admin ljwx-bigscreen ljwx-boot) #依赖服务列表
for svc in "${services[@]}"; do
docker stop "$svc" &>/dev/null && log " ✓ $svc 已停止" || log " ⚠ $svc 未运行"
done
log "✅ 依赖服务停止完成"
}
# 备份数据卷
backup_volume() {
log "💾 备份MySQL数据卷..."
local volume_backup="$BACKUP_DIR/mysql_volume_${CURRENT_VERSION}_$(date +%Y%m%d-%H%M%S).tar.gz"
docker run --rm -v client-deployment_mysql_data:/source:ro -v "$(pwd)/$BACKUP_DIR":/backup \
alpine:latest tar -czf "/backup/$(basename "$volume_backup")" -C /source . || error "数据卷备份失败"
log "✅ 数据卷备份完成: $(basename "$volume_backup")"
}
# 升级数据库
upgrade_mysql() {
log "🔄 执行MySQL容器升级..."
# 更新docker-compose.yml中的版本
sed -i.bak "s/ljwx-mysql:[0-9]\+\.[0-9]\+\.[0-9]\+/ljwx-mysql:$TARGET_VERSION/g" docker-compose.yml
# 停止并删除旧容器
docker stop "$MYSQL_CONTAINER" && docker rm "$MYSQL_CONTAINER" || error "容器停止失败"
# 启动新版本容器
docker-compose up -d mysql || error "新容器启动失败"
# 等待MySQL启动
log "⏳ 等待MySQL启动完成..."
local timeout=120; local count=0 #超时设置
while [[ $count -lt $timeout ]]; do
if docker exec "$MYSQL_CONTAINER" mysqladmin ping -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" &>/dev/null; then
log "✅ MySQL启动成功"
break
fi
((count++)); sleep 1
done
[[ $count -eq $timeout ]] && error "MySQL启动超时"
}
# 数据库升级脚本
run_upgrade_scripts() {
log "📝 执行数据库升级脚本..."
# 检查是否需要运行升级脚本
if [[ -f "scripts/upgrade-${TARGET_VERSION}.sql" ]]; then
docker exec -i "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE" \
< "scripts/upgrade-${TARGET_VERSION}.sql" || warn "升级脚本执行警告"
log "✅ 升级脚本执行完成"
else
log "ℹ️ 未找到升级脚本文件"
fi
# 运行MySQL升级程序
docker exec "$MYSQL_CONTAINER" mysql_upgrade -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" &>/dev/null || true
log "✅ MySQL升级程序执行完成"
}
# 数据完整性验证
verify_data() {
log "🔍 验证数据完整性..."
# 检查表数量
local table_count=$(docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
-e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$MYSQL_DATABASE';" -s -N 2>/dev/null)
[[ $table_count -gt 0 ]] || error "数据表验证失败"
# 检查关键表
local critical_tables=(sys_user sys_dict health_data device_info) #关键表列表
for table in "${critical_tables[@]}"; do
local count=$(docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
-e "SELECT COUNT(*) FROM $table;" -s -N "$MYSQL_DATABASE" 2>/dev/null || echo "0")
log " 📊 $table: $count 条记录"
done
log "✅ 数据完整性验证通过 (共 $table_count 个表)"
}
# 启动依赖服务
start_services() {
log "▶️ 启动依赖服务..."
docker-compose up -d || error "服务启动失败"
# 等待服务就绪
local services=(ljwx-boot ljwx-bigscreen ljwx-admin) #服务列表
for svc in "${services[@]}"; do
log "⏳ 等待 $svc 服务就绪..."
local timeout=60; local count=0
while [[ $count -lt $timeout ]]; do
if docker ps --format "{{.Names}}" | grep -q "^$svc$" && \
docker exec "$svc" sh -c "exit 0" &>/dev/null; then
log " ✅ $svc 服务就绪"
break
fi
((count++)); sleep 1
done
[[ $count -eq $timeout ]] && warn "$svc 服务启动超时"
done
}
# 服务健康检查
health_test() {
log "🏥 执行升级后健康测试..."
# MySQL连接测试
docker exec "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" \
-e "SELECT 'MySQL OK' as status;" "$MYSQL_DATABASE" &>/dev/null || error "MySQL连接测试失败"
# 应用服务测试
sleep 10 #等待应用启动
curl -sf "http://localhost:9998/actuator/health" &>/dev/null && log " ✅ ljwx-boot 健康" || warn "ljwx-boot 健康检查失败"
curl -sf "http://localhost:8001/health" &>/dev/null && log " ✅ ljwx-bigscreen 健康" || warn "ljwx-bigscreen 健康检查失败"
curl -sf "http://localhost:8080/" &>/dev/null && log " ✅ ljwx-admin 健康" || warn "ljwx-admin 健康检查失败"
log "✅ 健康测试完成"
}
# 清理临时文件
cleanup() {
log "🧹 清理临时文件..."
rm -rf temp/ #删除临时目录
docker image prune -f &>/dev/null || true #清理无用镜像
log "✅ 清理完成"
}
# 回滚功能
rollback() {
error "升级失败,开始回滚..."
warn "⚠️ 回滚操作将恢复到升级前状态"
if [[ -f temp/backup_path ]]; then
local backup_file=$(cat temp/backup_path)
log "🔄 恢复数据库备份..."
# 恢复docker-compose.yml
[[ -f docker-compose.yml.bak ]] && mv docker-compose.yml.bak docker-compose.yml
# 重启旧版本
docker-compose down && docker-compose up -d mysql
# 恢复数据
zcat "$backup_file" | docker exec -i "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD"
log "✅ 回滚完成"
else
error "未找到备份文件,无法回滚"
fi
}
# 生成升级报告
generate_report() {
log "📋 生成升级报告..."
local report_file="logs/upgrade_report_$(date +%Y%m%d-%H%M%S).md"
cat > "$report_file" << EOF
# MySQL数据库升级报告
## 升级信息
- **升级时间**: $(date)
- **原版本**: $CURRENT_VERSION
- **目标版本**: $TARGET_VERSION
- **升级状态**: ✅ 成功
## 验证结果
- **数据完整性**: ✅ 通过
- **服务健康**: ✅ 通过
- **功能测试**: ✅ 通过
## 备份信息
- **备份位置**: $BACKUP_DIR
- **备份大小**: $(du -sh "$BACKUP_DIR" | cut -f1)
## 升级日志
详见: $LOG_FILE
EOF
log "✅ 升级报告已生成: $report_file"
}
# 主流程
main() {
trap 'rollback' ERR #错误时自动回滚
init
health_check
confirm "确认升级MySQL从 $CURRENT_VERSION 到 $TARGET_VERSION?" || exit 0
backup_data
export_critical_data
pull_image
backup_volume
stop_services
upgrade_mysql
run_upgrade_scripts
verify_data
start_services
health_test
generate_report
cleanup
log "🎉 MySQL升级成功完成! 当前版本: $TARGET_VERSION"
}
# 脚本入口
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"
+269
View File
@@ -0,0 +1,269 @@
#!/bin/bash
# LJWX一键修复脚本 - 客户现场部署专用
# 解决MySQL连接问题的完整自动化解决方案
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m'
# 脚本横幅
echo -e "${PURPLE}"
cat << 'EOF'
╔══════════════════════════════════════════════════════════════╗
║ LJWX 一键修复工具 ║
║ 解决MySQL连接问题的完整解决方案 ║
║ 支持 macOS / Linux / CentOS ║
╚══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo -e "${BLUE}🚀 开始一键修复流程...${NC}"
echo "=================================="
# 步骤1: 跨平台兼容性检测
echo -e "${YELLOW}步骤 1/6: 跨平台兼容性检测${NC}"
if [ -f "./test-cross-platform.sh" ]; then
chmod +x test-cross-platform.sh
echo -e "${BLUE}📊 正在检测系统兼容性...${NC}"
if ./test-cross-platform.sh; then
echo -e "${GREEN}✅ 兼容性检测通过${NC}"
else
echo -e "${RED}❌ 兼容性检测失败,但继续执行${NC}"
fi
else
echo -e "${YELLOW}⚠️ 跨平台测试脚本不存在,跳过检测${NC}"
fi
echo ""
# 步骤2: 环境自适应修复
echo -e "${YELLOW}步骤 2/6: 环境自适应修复${NC}"
if [ -f "./environment-adaptive-fix.sh" ]; then
chmod +x environment-adaptive-fix.sh
echo -e "${BLUE}⚙️ 正在执行环境自适应修复...${NC}"
if ./environment-adaptive-fix.sh; then
echo -e "${GREEN}✅ 环境自适应修复完成${NC}"
else
echo -e "${RED}❌ 环境自适应修复失败${NC}"
exit 1
fi
else
echo -e "${RED}❌ 环境自适应修复脚本不存在${NC}"
exit 1
fi
echo ""
# 步骤3: 停止现有服务
echo -e "${YELLOW}步骤 3/6: 停止现有服务${NC}"
echo -e "${BLUE}🛑 正在停止现有Docker服务...${NC}"
# 检测Docker Compose版本
if command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
elif docker compose version >/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
else
echo -e "${RED}❌ Docker Compose不可用${NC}"
exit 1
fi
# 停止现有服务
$COMPOSE_CMD down 2>/dev/null || echo -e "${YELLOW}⚠️ 没有运行中的服务需要停止${NC}"
echo -e "${GREEN}✅ 服务停止完成${NC}"
echo ""
# 步骤4: 启动适配服务
echo -e "${YELLOW}步骤 4/6: 启动环境适配服务${NC}"
echo -e "${BLUE}🚀 正在启动环境适配的Docker服务...${NC}"
# 检查是否有MySQL权限问题,如果有则先修复
if [ -d "data/mysql" ] && [ "$(ls -A data/mysql 2>/dev/null)" ]; then
# 检查MySQL数据目录权限
MYSQL_OWNER=$(ls -ld data/mysql | awk '{print $3":"$4}')
CURRENT_USER="$(id -un):$(id -gn)"
if [ "$MYSQL_OWNER" != "$CURRENT_USER" ] && [ "$MYSQL_OWNER" = "999:999" ]; then
echo -e "${YELLOW}🔧 检测到MySQL权限问题,启用权限修复模式...${NC}"
# 备份现有数据并使用命名卷
BACKUP_DIR="data/mysql_backup_$(date +%Y%m%d_%H%M%S)"
echo -e "${BLUE}📦 备份MySQL数据到: $BACKUP_DIR${NC}"
sudo mv data/mysql "$BACKUP_DIR" 2>/dev/null || {
echo -e "${YELLOW}⚠️ 无法移动数据目录,使用命名卷模式${NC}"
}
# 使用简化的MySQL配置(命名卷)
if [ -f "./docker-compose-mysql-simple.yml" ]; then
echo -e "${BLUE}🚀 使用权限修复配置启动MySQL...${NC}"
$COMPOSE_CMD -f docker-compose-mysql-simple.yml up -d mysql
# 等待MySQL启动
echo -e "${BLUE}⏳ 等待MySQL服务就绪...${NC}"
MAX_WAIT=30
WAIT_COUNT=0
while [ $WAIT_COUNT -lt $MAX_WAIT ]; do
if docker exec ljwx-mysql mysqladmin ping -u root -p123456 >/dev/null 2>&1; then
echo -e "${GREEN}✅ MySQL权限修复成功${NC}"
break
fi
echo -n "."
sleep 2
WAIT_COUNT=$((WAIT_COUNT + 1))
done
fi
fi
fi
# 启动其他服务
if [ -f "./docker-compose-adaptive.yml" ]; then
if $COMPOSE_CMD -f docker-compose-adaptive.yml up -d; then
echo -e "${GREEN}✅ 环境适配服务启动成功${NC}"
else
echo -e "${RED}❌ 环境适配服务启动失败${NC}"
echo -e "${YELLOW}💡 尝试使用标准配置...${NC}"
if $COMPOSE_CMD up -d; then
echo -e "${GREEN}✅ 标准服务启动成功${NC}"
else
echo -e "${RED}❌ 服务启动失败${NC}"
exit 1
fi
fi
else
echo -e "${YELLOW}⚠️ 适配配置不存在,使用标准配置${NC}"
if $COMPOSE_CMD up -d; then
echo -e "${GREEN}✅ 标准服务启动成功${NC}"
else
echo -e "${RED}❌ 服务启动失败${NC}"
exit 1
fi
fi
echo ""
# 步骤5: 等待服务就绪
echo -e "${YELLOW}步骤 5/6: 等待服务就绪${NC}"
echo -e "${BLUE}⏳ 正在等待MySQL和Redis服务启动...${NC}"
# 等待MySQL启动
MAX_WAIT=60
WAIT_COUNT=0
while [ $WAIT_COUNT -lt $MAX_WAIT ]; do
if docker exec ljwx-mysql mysqladmin ping -u root -p"${MYSQL_PASSWORD:-123456}" >/dev/null 2>&1; then
echo -e "${GREEN}✅ MySQL服务已就绪${NC}"
break
fi
echo -n "."
sleep 2
WAIT_COUNT=$((WAIT_COUNT + 1))
done
if [ $WAIT_COUNT -ge $MAX_WAIT ]; then
echo -e "${RED}❌ MySQL服务启动超时${NC}"
echo -e "${YELLOW}💡 请检查日志: docker logs ljwx-mysql${NC}"
else
# 等待应用服务启动
echo -e "${BLUE}⏳ 正在等待应用服务启动...${NC}"
sleep 10
# 检查应用健康状态
if curl -s http://localhost:9998/actuator/health >/dev/null 2>&1; then
echo -e "${GREEN}✅ 应用服务已就绪${NC}"
else
echo -e "${YELLOW}⚠️ 应用服务可能需要更多时间启动${NC}"
fi
fi
echo ""
# 步骤6: 启动保活服务
echo -e "${YELLOW}步骤 6/6: 启动连接保活服务${NC}"
if [ -f "./mysql-keepalive.sh" ]; then
chmod +x mysql-keepalive.sh
echo -e "${BLUE}💓 正在启动MySQL连接保活服务...${NC}"
# 检查是否已有保活进程在运行
if pgrep -f "mysql-keepalive.sh" >/dev/null; then
echo -e "${YELLOW}⚠️ 保活服务已在运行,先停止旧进程${NC}"
pkill -f "mysql-keepalive.sh" 2>/dev/null || true
sleep 2
fi
# 后台启动保活服务
nohup ./mysql-keepalive.sh > mysql-keepalive.log 2>&1 &
KEEPALIVE_PID=$!
# 等待一下确认启动
sleep 3
if kill -0 $KEEPALIVE_PID 2>/dev/null; then
echo -e "${GREEN}✅ 连接保活服务启动成功 (PID: $KEEPALIVE_PID)${NC}"
echo -e "${BLUE}📋 日志文件: mysql-keepalive.log${NC}"
else
echo -e "${RED}❌ 连接保活服务启动失败${NC}"
fi
else
echo -e "${YELLOW}⚠️ mysql-keepalive.sh不存在,跳过保活服务${NC}"
fi
echo ""
# 修复完成报告
echo -e "${GREEN}"
cat << 'EOF'
╔══════════════════════════════════════════════════════════════╗
║ 🎉 修复完成! ║
╚══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo -e "${BLUE}📊 服务状态检查:${NC}"
echo "=================================="
# 检查Docker容器状态
echo -e "${YELLOW}Docker容器状态:${NC}"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep ljwx || echo " 无LJWX相关容器运行"
echo ""
echo -e "${YELLOW}服务访问地址:${NC}"
echo " 🌐 管理后台: http://localhost:8080"
echo " 📊 大屏展示: http://localhost:8001"
echo " 🔧 后端API: http://localhost:9998"
echo " 🗄️ MySQL: localhost:3306"
echo " 🔴 Redis: localhost:6379"
echo ""
echo -e "${YELLOW}日志查看:${NC}"
echo " 📋 环境报告: cat environment-report.txt"
echo " 💓 保活日志: tail -f mysql-keepalive.log"
echo " 🐳 容器日志: docker logs ljwx-mysql"
echo ""
echo -e "${BLUE}💡 如果仍有问题:${NC}"
echo " 1. 检查环境报告中的风险评估"
echo " 2. 查看保活服务日志"
echo " 3. 手动重启: docker-compose restart"
echo " 4. 重新运行: ./one-click-fix.sh"
echo ""
echo -e "${GREEN}🎯 一键修复流程已完成!系统应该能够长期稳定运行。${NC}"
# 可选:安装系统服务提示
echo ""
echo -e "${PURPLE}🔧 可选:安装系统服务(重启后自动启动)${NC}"
OS=$(uname -s)
case "$OS" in
"Darwin") # macOS
if [ -f "com.ljwx.mysql.keepalive.plist" ]; then
echo -e "${YELLOW}macOS系统服务安装:${NC}"
echo " sudo cp com.ljwx.mysql.keepalive.plist /Library/LaunchDaemons/"
echo " sudo launchctl load /Library/LaunchDaemons/com.ljwx.mysql.keepalive.plist"
fi
;;
"Linux") # Linux
if [ -f "ljwx-mysql-keepalive.service" ]; then
echo -e "${YELLOW}Linux系统服务安装:${NC}"
echo " sudo cp ljwx-mysql-keepalive.service /etc/systemd/system/"
echo " sudo systemctl daemon-reload"
echo " sudo systemctl enable ljwx-mysql-keepalive"
fi
;;
esac
+379
View File
@@ -0,0 +1,379 @@
#!/bin/bash
# 客户部署包准备脚本 - 确保Linux兼容性
# 在每次提交客户部署包前运行此脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 日志函数
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${CYAN}[STEP]${NC} $1"; }
# 脚本开始
echo "🚀 客户部署包准备工具"
echo "=================================="
echo "正在准备Linux兼容的客户部署包..."
echo ""
# 检查必需文件
check_required_files() {
log_step "1. 检查必需文件..."
required_files=(
"docker-compose.yml"
"deploy-client.sh"
"wait-for-it.sh"
"custom-config.env"
"validate-config.sh"
"fix-volume-mounts.sh"
)
missing_files=0
for file in "${required_files[@]}"; do
if [ -f "$file" ]; then
log_success "✅ $file 存在"
else
log_error "❌ $file 缺失"
missing_files=$((missing_files + 1))
fi
done
if [ $missing_files -gt 0 ]; then
log_error "缺失 $missing_files 个必需文件,请补充后重试"
exit 1
fi
log_success "所有必需文件检查通过"
}
# 运行Linux兼容性修复
run_compatibility_fix() {
log_step "2. 运行Linux兼容性修复..."
if [ -f "fix-linux-compatibility.sh" ]; then
chmod +x fix-linux-compatibility.sh
./fix-linux-compatibility.sh
log_success "Linux兼容性修复完成"
else
log_warning "fix-linux-compatibility.sh 不存在,跳过兼容性修复"
fi
}
# 验证脚本语法
validate_syntax() {
log_step "3. 验证脚本语法..."
# 查找所有shell脚本
shell_scripts=$(find . -name "*.sh" -type f 2>/dev/null | grep -v ".bak" || true)
syntax_errors=0
for script in $shell_scripts; do
if [ -f "$script" ]; then
if bash -n "$script" 2>/dev/null; then
log_info "✅ $script 语法正确"
else
log_error "❌ $script 语法错误:"
bash -n "$script" 2>&1 | head -3
syntax_errors=$((syntax_errors + 1))
fi
fi
done
if [ $syntax_errors -gt 0 ]; then
log_error "发现 $syntax_errors 个脚本语法错误,请修复后重试"
exit 1
fi
log_success "所有脚本语法验证通过"
}
# 测试关键功能
test_key_functions() {
log_step "4. 测试关键功能..."
# 测试配置文件加载
if [ -f "custom-config.env" ]; then
if . ./custom-config.env 2>/dev/null; then
log_success "✅ 配置文件加载测试通过"
else
log_error "❌ 配置文件加载失败"
exit 1
fi
fi
# 测试wait-for-it.sh基本功能
if [ -f "wait-for-it.sh" ] && [ -x "wait-for-it.sh" ]; then
# 测试参数解析
if ./wait-for-it.sh --help >/dev/null 2>&1; then
log_success "✅ wait-for-it.sh 帮助功能正常"
else
log_warning "⚠️ wait-for-it.sh 帮助功能异常"
fi
# 测试快速连接测试(使用google DNS)
timeout 5 ./wait-for-it.sh 8.8.8.8:53 -t 2 >/dev/null 2>&1 && \
log_success "✅ wait-for-it.sh 网络连接测试通过" || \
log_warning "⚠️ wait-for-it.sh 网络连接测试超时(可能正常)"
else
log_error "❌ wait-for-it.sh 不存在或无执行权限"
exit 1
fi
log_success "关键功能测试完成"
}
# 检查Docker配置
validate_docker_config() {
log_step "5. 验证Docker配置..."
if [ -f "docker-compose.yml" ]; then
# 检查docker-compose.yml语法
if docker-compose -f docker-compose.yml config >/dev/null 2>&1; then
log_success "✅ docker-compose.yml 语法正确"
else
log_error "❌ docker-compose.yml 语法错误:"
docker-compose -f docker-compose.yml config 2>&1 | head -5
exit 1
fi
# 检查是否包含wait-for-it.sh挂载
if grep -q "wait-for-it.sh" docker-compose.yml; then
log_success "✅ docker-compose.yml 包含wait-for-it.sh挂载"
else
log_warning "⚠️ docker-compose.yml 未包含wait-for-it.sh挂载"
fi
# 检查镜像版本一致性
mysql_count=$(grep -c "ljwx-mysql:" docker-compose.yml || echo "0")
if [ "$mysql_count" -eq 1 ]; then
log_success "✅ MySQL镜像版本配置正确"
else
log_warning "⚠️ MySQL镜像版本配置可能有问题"
fi
else
log_error "❌ docker-compose.yml 不存在"
exit 1
fi
log_success "Docker配置验证完成"
}
# 生成部署包信息
generate_deployment_info() {
log_step "6. 生成部署包信息..."
cat > DEPLOYMENT_INFO.md << EOF
# 客户部署包信息
## 生成信息
- 生成时间: $(date)
- 生成系统: $(uname -s) $(uname -r)
- 脚本版本: 1.0.0
## 包含文件
$(find . -type f -name "*.sh" -o -name "*.yml" -o -name "*.env" -o -name "*.py" -o -name "*.js" -o -name "*.svg" -o -name "*.sql" | sort)
## Linux兼容性
- ✅ 文件格式: Unix (LF)
- ✅ 脚本权限: 已设置
- ✅ Shebang标准化: #!/bin/bash
- ✅ 语法兼容性: 已验证
- ✅ 命令兼容性: 已修复
## 支持的Linux发行版
- CentOS 7+
- Ubuntu 18.04+
- Debian 9+
- RHEL 7+
- Amazon Linux 2
## 部署步骤
1. 上传部署包到目标服务器
2. 解压部署包
3. 运行 \`./test-linux-compatibility.sh\` 进行兼容性测试
4. 修改 \`custom-config.env\` 配置文件
5. 运行 \`./deploy-client.sh\` 开始部署
## 故障排除
- 如遇权限问题,请检查SELinux设置
- 如遇网络问题,请检查防火墙配置
- 如遇Docker问题,请确保Docker和docker-compose已正确安装
## 技术支持
如有问题,请联系技术支持团队。
EOF
log_success "部署包信息已生成: DEPLOYMENT_INFO.md"
}
# 创建快速测试脚本
create_quick_test() {
log_step "7. 创建快速测试脚本..."
cat > quick-test.sh << 'EOF'
#!/bin/bash
# 快速测试脚本 - 验证部署包基本功能
echo "🧪 快速功能测试"
echo "================"
# 测试脚本权限
echo "1. 测试脚本权限..."
if [ -x "deploy-client.sh" ] && [ -x "wait-for-it.sh" ]; then
echo "✅ 关键脚本权限正确"
else
echo "❌ 脚本权限异常"
exit 1
fi
# 测试配置文件
echo "2. 测试配置文件..."
if [ -f "custom-config.env" ] && . ./custom-config.env 2>/dev/null; then
echo "✅ 配置文件可正常加载"
else
echo "❌ 配置文件加载失败"
exit 1
fi
# 测试Docker配置
echo "3. 测试Docker配置..."
if docker-compose -f docker-compose.yml config >/dev/null 2>&1; then
echo "✅ Docker Compose配置正确"
else
echo "❌ Docker Compose配置错误"
exit 1
fi
# 测试wait-for-it.sh
echo "4. 测试wait-for-it.sh..."
if timeout 3 ./wait-for-it.sh 127.0.0.1:22 -t 1 >/dev/null 2>&1; then
echo "✅ wait-for-it.sh 功能正常"
else
echo "⚠️ wait-for-it.sh 测试超时(可能正常)"
fi
echo ""
echo "✅ 快速测试完成,部署包可用!"
EOF
chmod +x quick-test.sh
log_success "快速测试脚本已创建: quick-test.sh"
}
# 清理临时文件
cleanup_temp_files() {
log_step "8. 清理临时文件..."
# 删除备份文件
find . -name "*.bak.*" -type f -delete 2>/dev/null || true
# 删除临时测试文件
rm -f test-temp-* 2>/dev/null || true
log_success "临时文件清理完成"
}
# 生成检查清单
generate_checklist() {
log_step "9. 生成部署前检查清单..."
cat > DEPLOYMENT_CHECKLIST.md << EOF
# 客户部署前检查清单
## 服务器环境检查
- [ ] 服务器系统版本: CentOS 7+/Ubuntu 18.04+
- [ ] Docker 已安装且版本 >= 19.03
- [ ] docker-compose 已安装且版本 >= 1.25
- [ ] 服务器可访问互联网(用于拉取镜像)
- [ ] 防火墙已开放必要端口: 8080, 8001, 9998
- [ ] 磁盘空间充足 (建议 >= 20GB)
## 部署包检查
- [ ] 所有脚本文件具有执行权限
- [ ] wait-for-it.sh 功能正常
- [ ] docker-compose.yml 语法正确
- [ ] 配置文件格式正确
## 配置文件检查
- [ ] custom-config.env 已根据客户需求修改
- [ ] 数据库密码已设置
- [ ] 服务器IP地址已配置
- [ ] 大屏标题已自定义
- [ ] Logo文件已准备(如需要)
## 部署步骤
1. [ ] 运行 ./quick-test.sh 验证部署包
2. [ ] 运行 ./test-linux-compatibility.sh 测试兼容性
3. [ ] 修改配置文件
4. [ ] 运行 ./deploy-client.sh 开始部署
5. [ ] 验证服务启动状态
6. [ ] 测试各功能模块
## 验收标准
- [ ] 管理端可正常访问 (http://服务器IP:8080)
- [ ] 大屏端可正常访问 (http://服务器IP:8001)
- [ ] API接口响应正常 (http://服务器IP:9998/actuator/health)
- [ ] 数据库连接正常
- [ ] Redis缓存正常
- [ ] 日志输出正常
## 故障排除
- 如遇到权限问题,检查SELinux设置
- 如遇到网络问题,检查防火墙配置
- 如遇到启动问题,查看容器日志
EOF
log_success "部署检查清单已生成: DEPLOYMENT_CHECKLIST.md"
}
# 主执行流程
main() {
check_required_files
run_compatibility_fix
validate_syntax
test_key_functions
validate_docker_config
generate_deployment_info
create_quick_test
cleanup_temp_files
generate_checklist
# 运行容器权限修复
echo ""
log_step "10. 运行容器权限修复..."
if [ -f "fix-container-permissions.sh" ]; then
./fix-container-permissions.sh
else
log_warning "fix-container-permissions.sh 不存在,跳过权限修复"
fi
echo ""
log_success "🎉 客户部署包准备完成!"
echo ""
echo "生成的文件:"
echo "- DEPLOYMENT_INFO.md (部署包信息)"
echo "- DEPLOYMENT_CHECKLIST.md (部署检查清单)"
echo "- quick-test.sh (快速测试脚本)"
echo "- test-linux-compatibility.sh (兼容性测试)"
echo "- linux-compatibility-report.txt (兼容性报告)"
echo ""
echo "下一步操作:"
echo "1. 运行 ./quick-test.sh 进行最终验证"
echo "2. 打包部署文件发送给客户"
echo "3. 指导客户按照 DEPLOYMENT_CHECKLIST.md 进行部署"
echo ""
}
# 执行主流程
main "$@"
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
echo "🔧 快速修复定制Logo..."
source custom-config.env # 加载配置
if [ "$VITE_CUSTOM_LOGO" = "true" ] && [ -f "custom-logo.svg" ]; then
./replace-logo-correctly.sh
else
echo "❌ Logo配置未启用或文件不存在"
fi
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
echo "🔧 快速修复大屏URL为192.168.1.6:8001..."
CONTAINER_ID=$(docker ps -q -f name=ljwx-admin)
if [ -z "$CONTAINER_ID" ]; then
echo "❌ ljwx-admin容器未运行"
exit 1
fi
echo "📦 容器ID: $CONTAINER_ID"
# 直接替换为192.168.1.6:8001
docker exec "$CONTAINER_ID" sh -c "
find /usr/share/nginx/html -name '*.js' -o -name '*.html' | while read file; do
sed -i 's|192\.168\.1\.7:8001|192.168.1.6:8001|g' \"\$file\"
sed -i 's|localhost:8001|192.168.1.6:8001|g' \"\$file\"
done
"
# 重新加载nginx
docker exec "$CONTAINER_ID" nginx -s reload
echo "✅ URL已修复为192.168.1.6:8001"
echo "💡 请清除浏览器缓存并刷新页面"
+274
View File
@@ -0,0 +1,274 @@
#!/bin/bash
# LJWX快速恢复脚本 - 紧急恢复工具
# 颜色
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
# 配置
MYSQL_CONTAINER="ljwx-mysql"
REDIS_CONTAINER="ljwx-redis"
MYSQL_USER="root"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-123456}"
MYSQL_DATABASE="${MYSQL_DATABASE:-lj-06}"
BACKUP_BASE_DIR="backup"
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Darwin) OS="macOS" ;;
Linux)
if [ -f /etc/centos-release ]; then OS="CentOS"
elif [ -f /etc/ubuntu-release ] || grep -q "Ubuntu" /etc/os-release 2>/dev/null; then OS="Ubuntu"
else OS="Linux"; fi ;;
*) OS="Unknown" ;;
esac
}
log() { echo -e "${GREEN}[$(date +'%H:%M:%S')]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 快速恢复菜单
show_restore_menu() {
echo -e "${RED}
╔══════════════════════════════════════╗
║ 🚨 LJWX 紧急恢复工具 ║
║ OS: $OS ║
╠══════════════════════════════════════╣
║ 1. 快速恢复MySQL (最新备份) ║
║ 2. 快速恢复Redis (最新备份) ║
║ 3. 全量快速恢复 (MySQL+Redis) ║
║ 4. 选择特定MySQL备份恢复 ║
║ 5. 选择特定Redis备份恢复 ║
║ 6. 查看可用备份文件 ║
║ 7. 退出 ║
╚══════════════════════════════════════╝${NC}"
}
# 检查容器状态
check_containers() {
log "🔍 检查容器状态..."
local mysql_running=false
local redis_running=false
if docker ps --format "{{.Names}}" | grep -q "^$MYSQL_CONTAINER$"; then
mysql_running=true
log "✅ MySQL容器运行中"
else
warn "❌ MySQL容器未运行"
fi
if docker ps --format "{{.Names}}" | grep -q "^$REDIS_CONTAINER$"; then
redis_running=true
log "✅ Redis容器运行中"
else
warn "❌ Redis容器未运行"
fi
if ! $mysql_running || ! $redis_running; then
warn "⚠️ 部分容器未运行,是否启动服务?"
read -p "启动Docker Compose服务? [y/N]: " start_services
if [[ $start_services =~ ^[Yy]$ ]]; then
log "🚀 启动Docker Compose服务..."
if [ -f "docker-compose-optimized.yml" ]; then
docker-compose -f docker-compose-optimized.yml up -d
else
docker-compose up -d
fi
sleep 10
fi
fi
}
# 列出备份文件
list_available_backups() {
echo -e "${BLUE}📋 可用的MySQL备份文件:${NC}"
if ls "$BACKUP_BASE_DIR/mysql"/*.sql.gz >/dev/null 2>&1; then
ls -lah "$BACKUP_BASE_DIR/mysql"/*.sql.gz | nl | awk '{print " " $1 ". " $10 " (" $6 ", " $7 " " $8 ")"}'
else
echo " 无可用备份文件"
fi
echo -e "${BLUE}📋 可用的Redis备份文件:${NC}"
if ls "$BACKUP_BASE_DIR/redis"/*.rdb >/dev/null 2>&1; then
ls -lah "$BACKUP_BASE_DIR/redis"/*.rdb | nl | awk '{print " " $1 ". " $10 " (" $6 ", " $7 " " $8 ")"}'
else
echo " 无可用备份文件"
fi
}
# 快速恢复MySQL
quick_mysql_restore() {
local backup_file=$(ls -t "$BACKUP_BASE_DIR/mysql"/*.sql.gz 2>/dev/null | head -1)
if [ -z "$backup_file" ]; then
error "❌ 未找到MySQL备份文件"
return 1
fi
log "🔄 快速恢复MySQL ($(basename "$backup_file"))..."
# 检查容器
if ! docker ps --format "{{.Names}}" | grep -q "^$MYSQL_CONTAINER$"; then
error "❌ MySQL容器未运行"
return 1
fi
# 选择解压命令
local decompress_cmd
case "$OS" in
macOS) decompress_cmd="gunzip -c" ;;
*) decompress_cmd="zcat" ;;
esac
# 执行恢复
if $decompress_cmd "$backup_file" | docker exec -i "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"; then
log "✅ MySQL快速恢复成功"
return 0
else
error "❌ MySQL快速恢复失败"
return 1
fi
}
# 快速恢复Redis
quick_redis_restore() {
local backup_file=$(ls -t "$BACKUP_BASE_DIR/redis"/*.rdb 2>/dev/null | head -1)
if [ -z "$backup_file" ]; then
error "❌ 未找到Redis备份文件"
return 1
fi
log "🔄 快速恢复Redis ($(basename "$backup_file"))..."
# 检查容器
if ! docker ps --format "{{.Names}}" | grep -q "^$REDIS_CONTAINER$"; then
error "❌ Redis容器未运行"
return 1
fi
# 停止Redis写入并恢复数据
if docker exec "$REDIS_CONTAINER" redis-cli FLUSHALL && \
cat "$backup_file" | docker exec -i "$REDIS_CONTAINER" tee /data/dump.rdb >/dev/null && \
docker restart "$REDIS_CONTAINER"; then
log "✅ Redis快速恢复成功"
sleep 3 # 等待Redis重启
return 0
else
error "❌ Redis快速恢复失败"
return 1
fi
}
# 选择特定备份恢复
select_mysql_backup() {
echo -e "${BLUE}📋 选择MySQL备份文件:${NC}"
local backups=($(ls -t "$BACKUP_BASE_DIR/mysql"/*.sql.gz 2>/dev/null))
if [ ${#backups[@]} -eq 0 ]; then
error "❌ 未找到MySQL备份文件"
return 1
fi
for i in "${!backups[@]}"; do
local file="${backups[$i]}"
local size=$(du -h "$file" | cut -f1)
local date=$(date -r "$file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file" 2>/dev/null | cut -d' ' -f1,2)
echo " $((i+1)). $(basename "$file") ($size, $date)"
done
read -p "请选择备份文件编号 [1-${#backups[@]}]: " choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#backups[@]}" ]; then
local selected_file="${backups[$((choice-1))]}"
log "🔄 恢复MySQL备份: $(basename "$selected_file")"
# 选择解压命令
local decompress_cmd
case "$OS" in
macOS) decompress_cmd="gunzip -c" ;;
*) decompress_cmd="zcat" ;;
esac
# 执行恢复
if $decompress_cmd "$selected_file" | docker exec -i "$MYSQL_CONTAINER" mysql -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"; then
log "✅ MySQL恢复成功"
else
error "❌ MySQL恢复失败"
fi
else
error "❌ 无效选择"
fi
}
# 选择特定Redis备份恢复
select_redis_backup() {
echo -e "${BLUE}📋 选择Redis备份文件:${NC}"
local backups=($(ls -t "$BACKUP_BASE_DIR/redis"/*.rdb 2>/dev/null))
if [ ${#backups[@]} -eq 0 ]; then
error "❌ 未找到Redis备份文件"
return 1
fi
for i in "${!backups[@]}"; do
local file="${backups[$i]}"
local size=$(du -h "$file" | cut -f1)
local date=$(date -r "$file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || stat -c %y "$file" 2>/dev/null | cut -d' ' -f1,2)
echo " $((i+1)). $(basename "$file") ($size, $date)"
done
read -p "请选择备份文件编号 [1-${#backups[@]}]: " choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#backups[@]}" ]; then
local selected_file="${backups[$((choice-1))]}"
log "🔄 恢复Redis备份: $(basename "$selected_file")"
# 执行恢复
if docker exec "$REDIS_CONTAINER" redis-cli FLUSHALL && \
cat "$selected_file" | docker exec -i "$REDIS_CONTAINER" tee /data/dump.rdb >/dev/null && \
docker restart "$REDIS_CONTAINER"; then
log "✅ Redis恢复成功"
sleep 3 # 等待Redis重启
else
error "❌ Redis恢复失败"
fi
else
error "❌ 无效选择"
fi
}
# 主函数
main() {
detect_os
echo -e "${RED}🚨 LJWX紧急恢复工具启动 (OS: $OS)${NC}"
check_containers
while true; do
show_restore_menu
read -p "请选择操作 [1-7]: " choice
case $choice in
1) quick_mysql_restore ;;
2) quick_redis_restore ;;
3)
log "🔄 开始全量快速恢复..."
quick_mysql_restore && quick_redis_restore
;;
4) select_mysql_backup ;;
5) select_redis_backup ;;
6) list_available_backups ;;
7) log "👋 退出紧急恢复工具"; exit 0 ;;
*) warn "无效选择,请重新输入" ;;
esac
echo
read -p "按回车键继续..."
done
}
main "$@"
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# 快速测试脚本 - 验证部署包基本功能
echo "🧪 快速功能测试"
echo "================"
# 测试脚本权限
echo "1. 测试脚本权限..."
if [ -x "deploy-client.sh" ] && [ -x "wait-for-it.sh" ]; then
echo "✅ 关键脚本权限正确"
else
echo "❌ 脚本权限异常"
exit 1
fi
# 测试配置文件
echo "2. 测试配置文件..."
if [ -f "custom-config.env" ] && . ./custom-config.env 2>/dev/null; then
echo "✅ 配置文件可正常加载"
else
echo "❌ 配置文件加载失败"
exit 1
fi
# 测试Docker配置
echo "3. 测试Docker配置..."
if docker-compose -f docker-compose.yml config >/dev/null 2>&1; then
echo "✅ Docker Compose配置正确"
else
echo "❌ Docker Compose配置错误"
exit 1
fi
# 测试wait-for-it.sh
echo "4. 测试wait-for-it.sh..."
if timeout 3 ./wait-for-it.sh 127.0.0.1:22 -t 1 >/dev/null 2>&1; then
echo "✅ wait-for-it.sh 功能正常"
else
echo "⚠️ wait-for-it.sh 测试超时(可能正常)"
fi
echo ""
echo "✅ 快速测试完成,部署包可用!"
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# 快速升级启动脚本(支持Docker命名卷) | 使用说明: ./quick-upgrade.sh [目标版本]
TARGET_VERSION="${1:-1.2.15}" #目标版本
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" #脚本目录
# 颜色配置
G='\033[0;32m'; Y='\033[1;33m'; R='\033[0;31m'; B='\033[0;34m'; N='\033[0m' #颜色代码
# 显示升级横幅
show_banner() {
echo -e "${B}"
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ LJWX MySQL升级工具 ║"
echo "║ ║"
echo "║ 专业 • 安全 • 快速 • 可回滚 ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo -e "${N}"
echo "目标版本: ${G}$TARGET_VERSION${N}"
echo "升级时间: $(date)"
echo
}
# 检查必要文件和数据卷迁移状态
check_files() {
local files=("mysql-upgrade-checker.sh" "mysql-upgrade-manager.sh" "docker-compose.yml" "custom-config.env")
for file in "${files[@]}"; do
[[ ! -f "$file" ]] && { echo -e "${R}❌ 缺少文件: $file${N}"; exit 1; }
done
chmod +x mysql-upgrade-*.sh #设置执行权限
# 检查是否需要数据迁移
if [ -d "data" ] || [ -d "logs" ]; then
echo -e "${Y}⚠️ 检测到旧版本bind mount数据目录${N}"
echo "建议先执行数据迁移到Docker命名卷:"
echo " ./upgrade-to-named-volumes.sh"
echo ""
read -p "是否现在执行迁移? (y/N): " migrate_now
if [ "$migrate_now" = "y" ] || [ "$migrate_now" = "Y" ]; then
if [ -f "upgrade-to-named-volumes.sh" ]; then
./upgrade-to-named-volumes.sh
echo "迁移完成,继续升级流程..."
else
echo -e "${R}❌ 未找到迁移脚本 upgrade-to-named-volumes.sh${N}"
exit 1
fi
fi
fi
}
# 用户确认
confirm_upgrade() {
echo -e "${Y}⚠️ 数据库升级重要提醒:${N}"
echo "• 升级过程将暂停所有服务"
echo "• 建议在业务低峰期执行"
echo "• 确保已通知相关用户"
echo "• 升级前将自动创建完整备份"
echo
read -p "$(echo -e "${Y}确认继续升级? [y/N]:${N} ")" -r
[[ ! $REPLY =~ ^[Yy]$ ]] && { echo "升级已取消"; exit 0; }
}
# 执行升级流程
execute_upgrade() {
echo -e "${G}🚀 开始MySQL升级流程...${N}"
echo
# 步骤1: 预检查
echo -e "${B}步骤1/2: 执行升级前检查...${N}"
if ! ./mysql-upgrade-checker.sh "$TARGET_VERSION"; then
echo -e "${R}❌ 预检查失败,请修复问题后重试${N}"
exit 1
fi
echo -e "${G}✅ 预检查通过${N}"
echo
# 步骤2: 执行升级
echo -e "${B}步骤2/2: 执行数据库升级...${N}"
if ./mysql-upgrade-manager.sh "$TARGET_VERSION"; then
echo
echo -e "${G}🎉 MySQL升级成功完成!${N}"
echo -e "${G}当前版本: $TARGET_VERSION${N}"
echo
echo "后续建议:"
echo "• 观察系统运行5-10分钟"
echo "• 执行功能验证测试"
echo "• 查看升级报告: logs/upgrade_report_*.md"
echo "• 监控应用性能指标"
return 0
else
echo -e "${R}❌ 升级失败,系统已自动回滚${N}"
echo "请查看日志文件: logs/upgrade-*.log"
return 1
fi
}
# 显示使用帮助
show_help() {
echo "MySQL数据库升级工具"
echo
echo "用法:"
echo " $0 [目标版本]"
echo
echo "示例:"
echo " $0 1.2.15 # 升级到1.2.15版本"
echo " $0 --help # 显示帮助信息"
echo
echo "相关工具:"
echo " • 预检查: ./mysql-upgrade-checker.sh"
echo " • 手动升级: ./mysql-upgrade-manager.sh"
echo " • 备份恢复: ./backup-restore-manager.sh"
echo
echo "详细文档: MYSQL_UPGRADE_GUIDE.md"
}
# 主函数
main() {
cd "$SCRIPT_DIR" || exit 1 #切换到脚本目录
# 参数处理
case "$1" in
--help|-h) show_help; exit 0 ;;
"") echo -e "${Y}请指定目标版本,如: $0 1.2.15${N}"; exit 1 ;;
esac
show_banner
check_files
confirm_upgrade
execute_upgrade
}
# 脚本入口
main "$@"
+47
View File
@@ -0,0 +1,47 @@
#!/bin/bash
# 替换前端应用中的大屏URL #动态替换硬编码的localhost:8001
set -e
# 加载配置文件
CONFIG_FILE=${1:-"custom-config.env"}
if [ -f "$CONFIG_FILE" ]; then
source $CONFIG_FILE
echo "📋 已加载配置文件: $CONFIG_FILE"
else
echo "❌ 错误: 配置文件 $CONFIG_FILE 不存在"
exit 1
fi
# 检查环境变量
if [ -z "$VITE_BIGSCREEN_URL" ]; then
echo "❌ 错误: VITE_BIGSCREEN_URL 环境变量未设置"
exit 1
fi
echo "🔄 开始替换前端应用中的大屏URL..."
echo " 原地址: localhost:8001"
echo " 新地址: ${VITE_BIGSCREEN_URL#http://}"
# 获取容器ID
CONTAINER_ID=$(docker ps -q -f name=ljwx-admin)
if [ -z "$CONTAINER_ID" ]; then
echo "❌ 错误: ljwx-admin 容器未运行"
exit 1
fi
# 提取新URL的主机和端口部分
NEW_URL=${VITE_BIGSCREEN_URL#http://}
# 在容器中替换所有JavaScript文件中的localhost:8001
docker exec $CONTAINER_ID sh -c "
find /usr/share/nginx/html/assets -name '*.js' -exec sed -i 's/localhost:8001/$NEW_URL/g' {} \;
echo '✅ 已替换所有JavaScript文件中的URL'
"
# 重新加载nginx配置
docker exec $CONTAINER_ID nginx -s reload
echo "✅ Nginx配置已重新加载"
echo "🎉 URL替换完成!"
echo "💡 现在访问 http://localhost:8080/#/home 页面,大屏链接将跳转到 $VITE_BIGSCREEN_URL"
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
source custom-config.env # 加载配置
echo "🔄 正确替换前端应用Logo..."
if [ "$VITE_CUSTOM_LOGO" = "true" ] && [ -f "custom-logo.svg" ]; then
# 查找所有logo相关的SVG文件
echo "📋 查找前端应用中的logo文件..."
LOGO_FILES=$(docker exec ljwx-admin find /usr/share/nginx/html/assets -name "logo*.svg" 2>/dev/null)
if [ -n "$LOGO_FILES" ]; then
echo "找到以下logo文件:"
echo "$LOGO_FILES"
# 备份原始logo文件
for file in $LOGO_FILES; do
docker exec ljwx-admin cp "$file" "$file.backup" 2>/dev/null || true
echo "✅ 已备份: $file"
done
# 用定制logo替换所有logo文件
for file in $LOGO_FILES; do
docker exec ljwx-admin cp /tmp/custom-logo.svg "$file"
echo "✅ 已替换: $file"
done
# 设置权限
for file in $LOGO_FILES; do
docker exec ljwx-admin chmod 644 "$file"
done
echo "🎉 Logo替换完成!"
echo "💡 请强制刷新浏览器页面 (Ctrl+F5) 查看效果"
else
echo "❌ 未找到logo文件"
fi
else
echo "❌ Logo配置未启用或custom-logo.svg文件不存在"
fi
+179
View File
@@ -0,0 +1,179 @@
#!/bin/bash
# 重置数据库脚本 - 清除旧数据,使用新的data.sql初始化
# 使用方法:./reset-database.sh [backup_first]
set -e
# 兼容CentOS - 检查终端颜色支持
if [ -t 1 ] && [ "${TERM:-}" != "" ] && tput colors >/dev/null 2>&1; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
else #CentOS兼容:无颜色支持时使用空值
RED=''
GREEN=''
YELLOW=''
BLUE=''
NC=''
fi
echo -e "${BLUE}🔄 LJWX数据库重置工具${NC}"
echo "=============================================="
# 配置变量
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_VOLUME="client-deployment_mysql_data"
BACKUP_DIR="./backups/reset"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 检查是否需要备份
BACKUP_FIRST=${1:-"yes"}
echo -e "${YELLOW}⚠️ 警告: 此操作将删除所有现有数据库数据!${NC}"
echo ""
echo "当前配置:"
echo "- MySQL容器: $MYSQL_CONTAINER"
echo "- 数据卷: $MYSQL_VOLUME"
echo "- 备份目录: $BACKUP_DIR"
echo ""
if [ "$BACKUP_FIRST" = "yes" ]; then
echo -e "${YELLOW}📦 备份选项: 启用 (推荐)${NC}"
else
echo -e "${RED}📦 备份选项: 跳过${NC}"
fi
echo ""
read -p "确认继续重置数据库? (y/N): " confirm
if [[ $confirm != [yY] ]]; then
echo "操作已取消"
exit 0
fi
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 1. 备份现有数据(如果需要)
if [ "$BACKUP_FIRST" = "yes" ]; then
echo ""
echo -e "${BLUE}📦 步骤1: 备份现有数据${NC}"
echo "创建备份: $BACKUP_DIR/pre_reset_backup_$TIMESTAMP.sql.gz"
if docker ps | grep $MYSQL_CONTAINER > /dev/null; then
docker exec $MYSQL_CONTAINER mysqldump \
-u root -p123456 \
--single-transaction \
--routines \
--triggers \
--events \
--hex-blob \
--databases lj-06 | gzip > "$BACKUP_DIR/pre_reset_backup_$TIMESTAMP.sql.gz"
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ 数据备份完成${NC}"
else
echo -e "${RED}❌ 数据备份失败,继续执行重置...${NC}"
fi
else
echo -e "${YELLOW}⚠️ MySQL容器未运行,跳过备份${NC}"
fi
fi
# 2. 停止服务
echo ""
echo -e "${BLUE}🛑 步骤2: 停止相关服务${NC}"
docker-compose down
# 3. 删除数据卷
echo ""
echo -e "${BLUE}🗑️ 步骤3: 删除旧数据卷${NC}"
if docker volume ls | grep $MYSQL_VOLUME > /dev/null; then
docker volume rm $MYSQL_VOLUME
echo -e "${GREEN}✅ 数据卷已删除${NC}"
else
echo -e "${YELLOW}⚠️ 数据卷不存在${NC}"
fi
# 4. 清理可能存在的旧容器
echo ""
echo -e "${BLUE}🧹 步骤4: 清理旧容器${NC}"
if docker ps -a | grep $MYSQL_CONTAINER > /dev/null; then
docker rm -f $MYSQL_CONTAINER 2>/dev/null || true
echo -e "${GREEN}✅ 旧容器已清理${NC}"
fi
# 5. 重新启动MySQL服务
echo ""
echo -e "${BLUE}🚀 步骤5: 启动MySQL服务(使用新数据)${NC}"
docker-compose up -d mysql
# 6. 等待MySQL初始化完成
echo ""
echo -e "${BLUE}⏳ 步骤6: 等待MySQL初始化...${NC}"
echo "正在等待MySQL服务启动并初始化新数据..."
for i in {1..60}; do
if docker logs $MYSQL_CONTAINER 2>&1 | grep -q "ready for connections"; then
echo -e "${GREEN}✅ MySQL服务启动完成${NC}"
break
fi
if [ $i -eq 60 ]; then
echo -e "${RED}❌ MySQL启动超时${NC}"
echo "请检查日志: docker logs $MYSQL_CONTAINER"
exit 1
fi
echo -n "."
sleep 2
done
# 7. 验证新数据
echo ""
echo -e "${BLUE}🔍 步骤7: 验证新数据库${NC}"
sleep 5
# 检查数据库是否包含新数据
table_count=$(docker exec $MYSQL_CONTAINER mysql -u root -p123456 -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='lj-06';" 2>/dev/null | tail -1)
if [ ! -z "$table_count" ] && [ "$table_count" -gt 0 ]; then
echo -e "${GREEN}✅ 数据库初始化成功${NC}"
echo "📊 检测到 $table_count 个数据表"
# 显示一些关键表的记录数
echo ""
echo "关键表记录统计:"
docker exec $MYSQL_CONTAINER mysql -u root -p123456 lj-06 -e "
SELECT 'sys_user' as table_name, COUNT(*) as record_count FROM sys_user
UNION ALL
SELECT 'sys_dict_data', COUNT(*) FROM sys_dict_data
UNION ALL
SELECT 'sys_org_units', COUNT(*) FROM sys_org_units
UNION ALL
SELECT 't_interface', COUNT(*) FROM t_interface;" 2>/dev/null || echo "📊 部分表可能还在初始化中"
else
echo -e "${RED}❌ 数据库初始化可能失败${NC}"
echo "请检查MySQL日志:"
docker logs $MYSQL_CONTAINER --tail 20
fi
# 8. 启动其他服务
echo ""
echo -e "${BLUE}🚀 步骤8: 启动所有服务${NC}"
docker-compose up -d
echo ""
echo -e "${GREEN}🎉 数据库重置完成!${NC}"
echo ""
echo "📋 操作摘要:"
echo "- 旧数据已备份到: $BACKUP_DIR/pre_reset_backup_$TIMESTAMP.sql.gz"
echo "- 数据卷已重建: $MYSQL_VOLUME"
echo "- 新数据已初始化: data.sql"
echo ""
echo "🔍 验证命令:"
echo "docker exec $MYSQL_CONTAINER mysql -u root -p123456 -e 'SHOW DATABASES;'"
echo ""
echo "📊 服务状态:"
docker-compose ps
+188
View File
@@ -0,0 +1,188 @@
#!/bin/bash
# -*- coding: utf-8 -*-
# 一键恢复所有数据脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
# 配置
BACKUP_BASE_DIR="backup"
MYSQL_BACKUP_DIR="$BACKUP_BASE_DIR/mysql"
REDIS_BACKUP_DIR="$BACKUP_BASE_DIR/redis"
BOOT_BACKUP_DIR="$BACKUP_BASE_DIR/ljwx-boot"
BIGSCREEN_BACKUP_DIR="$BACKUP_BASE_DIR/ljwx-bigscreen"
log_info "=== 灵境万象健康管理系统 - 数据全量恢复 ==="
# 检查备份目录
check_backup_dirs() {
log_step "检查备份目录..."
for dir in "$MYSQL_BACKUP_DIR" "$REDIS_BACKUP_DIR" "$BOOT_BACKUP_DIR" "$BIGSCREEN_BACKUP_DIR"; do
if [ ! -d "$dir" ]; then
log_warn "备份目录不存在: $dir"
else
files_count=$(ls -1 "$dir" 2>/dev/null | wc -l)
log_info " $dir: $files_count 个文件"
fi
done
}
# 检查服务状态
check_services() {
log_step "检查服务状态..."
required_containers=("ljwx-mysql" "ljwx-redis" "ljwx-boot" "ljwx-bigscreen" "ljwx-admin")
for container in "${required_containers[@]}"; do
if docker ps | grep -q $container; then
log_info " ✓ $container 运行中"
else
log_error " ✗ $container 未运行"
exit 1
fi
done
}
# 恢复MySQL数据
restore_mysql() {
log_step "恢复MySQL数据库..."
if [ -f "./restore-database.sh" ]; then
./restore-database.sh
else
# 查找最新的MySQL备份
MYSQL_BACKUP=$(ls -t $MYSQL_BACKUP_DIR/*.sql 2>/dev/null | head -1)
if [ -n "$MYSQL_BACKUP" ]; then
log_info "使用备份文件: $MYSQL_BACKUP"
docker exec -i ljwx-mysql mysql -uroot -p123456 ljwx < $MYSQL_BACKUP
log_info "MySQL数据恢复完成"
else
log_warn "未找到MySQL备份文件,跳过数据库恢复"
fi
fi
}
# 恢复Redis数据
restore_redis() {
log_step "恢复Redis缓存数据..."
if [ -f "./restore-redis.sh" ]; then
./restore-redis.sh
else
REDIS_BACKUP=$(ls -t $REDIS_BACKUP_DIR/*.rdb 2>/dev/null | head -1)
if [ -n "$REDIS_BACKUP" ]; then
log_info "使用备份文件: $REDIS_BACKUP"
docker-compose stop ljwx-redis
cp $REDIS_BACKUP data/redis/dump.rdb
chmod 777 data/redis/dump.rdb
docker-compose start ljwx-redis
sleep 10
log_info "Redis数据恢复完成"
else
log_warn "未找到Redis备份文件,跳过缓存恢复"
fi
fi
}
# 恢复应用文件
restore_app_files() {
log_step "恢复应用文件和配置..."
# 恢复后端文件
if [ -d "$BOOT_BACKUP_DIR" ]; then
log_info "恢复ljwx-boot文件..."
mkdir -p data/ljwx-boot
cp -r $BOOT_BACKUP_DIR/* data/ljwx-boot/ 2>/dev/null || true
log_info "ljwx-boot文件恢复完成"
fi
# 恢复大屏文件
if [ -d "$BIGSCREEN_BACKUP_DIR" ]; then
log_info "恢复ljwx-bigscreen文件..."
mkdir -p data/ljwx-bigscreen
cp -r $BIGSCREEN_BACKUP_DIR/* data/ljwx-bigscreen/ 2>/dev/null || true
log_info "ljwx-bigscreen文件恢复完成"
fi
}
# 验证恢复结果
verify_restore() {
log_step "验证恢复结果..."
# 验证MySQL
USER_COUNT=$(docker exec ljwx-mysql mysql -uroot -p123456 -e "USE ljwx; SELECT COUNT(*) FROM t_user_info;" -N 2>/dev/null || echo "0")
DEVICE_COUNT=$(docker exec ljwx-mysql mysql -uroot -p123456 -e "USE ljwx; SELECT COUNT(*) FROM t_device_info;" -N 2>/dev/null || echo "0")
# 验证Redis
REDIS_KEYS=$(docker exec ljwx-redis redis-cli DBSIZE 2>/dev/null || echo "0")
# 验证服务
BOOT_STATUS=$(curl -s http://localhost:9998/actuator/health 2>/dev/null | grep -o '"status":"UP"' || echo "DOWN")
BIGSCREEN_STATUS=$(curl -s http://localhost:8001 >/dev/null 2>&1 && echo "UP" || echo "DOWN")
ADMIN_STATUS=$(curl -s http://localhost:8080 >/dev/null 2>&1 && echo "UP" || echo "DOWN")
log_info "恢复结果统计:"
log_info " MySQL数据: 用户=$USER_COUNT, 设备=$DEVICE_COUNT"
log_info " Redis缓存: $REDIS_KEYS 个键"
log_info " 服务状态: 后端=$BOOT_STATUS, 大屏=$BIGSCREEN_STATUS, 管理端=$ADMIN_STATUS"
if [ "$BOOT_STATUS" = "UP" ] && [ "$BIGSCREEN_STATUS" = "UP" ] && [ "$ADMIN_STATUS" = "UP" ]; then
log_info "✓ 所有服务恢复正常"
return 0
else
log_error "✗ 部分服务恢复异常"
return 1
fi
}
# 主流程
main() {
log_info "开始全量数据恢复流程..."
check_backup_dirs
check_services
# 执行恢复
restore_mysql
restore_redis
restore_app_files
# 重启服务确保配置生效
log_step "重启服务以应用恢复的配置..."
docker-compose restart ljwx-boot ljwx-bigscreen ljwx-admin
# 等待服务启动
log_info "等待服务完全启动..."
sleep 30
# 验证结果
if verify_restore; then
log_info "🎉 数据全量恢复成功!"
log_info "访问地址:"
log_info " 管理端: http://localhost:8080"
log_info " 大屏: http://localhost:8001"
log_info " API: http://localhost:9998"
else
log_error "❌ 数据恢复过程中出现问题,请检查日志"
exit 1
fi
}
# 确认操作
read -p "确认执行数据全量恢复?这将覆盖当前所有数据 (y/N): " confirm
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
main
else
log_info "操作已取消"
exit 0
fi
+225
View File
@@ -0,0 +1,225 @@
#!/bin/bash
# 数据恢复脚本 #支持SQL备份和卷备份的恢复
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' #重置颜色
echo "==================== 数据恢复工具 ===================="
echo "支持恢复类型:"
echo "1) SQL备份恢复 (auto-backup.sh生成的.sql.gz文件)"
echo "2) 卷备份恢复 (离线卷备份的.tar.gz文件)"
echo "3) 退出"
echo ""
while true; do
read -p "请选择恢复类型 [1-3]: " restore_type
case $restore_type in
1)
echo -e "${BLUE}📦 SQL备份恢复模式${NC}"
restore_mode="sql"
break
;;
2)
echo -e "${BLUE}📦 卷备份恢复模式${NC}"
restore_mode="volume"
break
;;
3)
echo "退出恢复工具"
exit 0
;;
*)
echo -e "${RED}❌ 无效选择,请输入 1-3${NC}"
;;
esac
done
# SQL备份恢复
restore_sql_backup() {
echo ""
echo -e "${BLUE}🔍 查找SQL备份文件...${NC}"
# 查找备份文件
backup_files=$(find . -name "*.sql.gz" -o -name "*.sql" | sort -r)
if [ -z "$backup_files" ]; then
echo -e "${RED}❌ 未找到SQL备份文件${NC}"
exit 1
fi
echo "发现以下备份文件:"
i=1
declare -a file_array
for file in $backup_files; do
echo "$i) $file"
file_array[$i]=$file
i=$((i + 1))
done
echo ""
while true; do
read -p "请选择要恢复的备份文件 [1-$((i-1))]: " file_choice
if [[ "$file_choice" =~ ^[0-9]+$ ]] && [ "$file_choice" -ge 1 ] && [ "$file_choice" -lt "$i" ]; then
selected_file=${file_array[$file_choice]}
break
else
echo -e "${RED}❌ 无效选择${NC}"
fi
done
echo ""
echo -e "${YELLOW}⚠️ 警告: 恢复操作将覆盖现有数据!${NC}"
read -p "确认恢复 $selected_file 吗? (y/N): " confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
echo "恢复已取消"
exit 0
fi
# 检查MySQL容器状态
if ! docker ps | grep -q ljwx-mysql; then
echo -e "${BLUE}🚀 启动MySQL服务...${NC}"
docker-compose up -d mysql
# 等待MySQL启动
echo "等待MySQL启动..."
for i in {1..30}; do
if docker exec ljwx-mysql mysqladmin ping -u root -p123456 >/dev/null 2>&1; then
echo -e "${GREEN}✅ MySQL启动成功${NC}"
break
fi
if [ $i -eq 30 ]; then
echo -e "${RED}❌ MySQL启动超时${NC}"
exit 1
fi
sleep 2
done
fi
# 恢复数据
echo ""
echo -e "${BLUE}📤 恢复数据: $selected_file${NC}"
if [[ "$selected_file" == *.gz ]]; then
# 压缩文件
gunzip -c "$selected_file" | docker exec -i ljwx-mysql mysql -u root -p123456
else
# 普通SQL文件
docker exec -i ljwx-mysql mysql -u root -p123456 < "$selected_file"
fi
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ SQL数据恢复成功${NC}"
else
echo -e "${RED}❌ SQL数据恢复失败${NC}"
exit 1
fi
}
# 卷备份恢复
restore_volume_backup() {
echo ""
echo -e "${BLUE}🔍 查找卷备份目录...${NC}"
# 查找卷备份目录
backup_dirs=$(find . -type d -name "volume_backup_*" | sort -r)
if [ -z "$backup_dirs" ]; then
echo -e "${RED}❌ 未找到卷备份目录${NC}"
exit 1
fi
echo "发现以下备份目录:"
i=1
declare -a dir_array
for dir in $backup_dirs; do
echo "$i) $dir"
dir_array[$i]=$dir
i=$((i + 1))
done
echo ""
while true; do
read -p "请选择要恢复的备份目录 [1-$((i-1))]: " dir_choice
if [[ "$dir_choice" =~ ^[0-9]+$ ]] && [ "$dir_choice" -ge 1 ] && [ "$dir_choice" -lt "$i" ]; then
selected_dir=${dir_array[$dir_choice]}
break
else
echo -e "${RED}❌ 无效选择${NC}"
fi
done
echo ""
echo -e "${BLUE}📋 备份目录内容: $selected_dir${NC}"
ls -la "$selected_dir"
echo ""
echo -e "${YELLOW}⚠️ 警告: 恢复操作将覆盖现有数据卷!${NC}"
read -p "确认恢复 $selected_dir 中的卷备份吗? (y/N): " confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
echo "恢复已取消"
exit 0
fi
# 停止所有服务
echo ""
echo -e "${BLUE}🛑 停止服务...${NC}"
docker-compose down
# 恢复每个卷
echo ""
echo -e "${BLUE}📤 恢复数据卷...${NC}"
for tar_file in "$selected_dir"/*.tar.gz; do
if [ -f "$tar_file" ]; then
volume_name=$(basename "$tar_file" .tar.gz)
full_volume_name="client-deployment_${volume_name}"
echo "恢复卷: $full_volume_name"
# 删除现有卷
docker volume rm "$full_volume_name" 2>/dev/null || true
# 创建新卷并恢复数据
docker volume create "$full_volume_name"
docker run --rm -v "$full_volume_name:/data" -v "$(pwd)/$selected_dir:/backup" alpine sh -c "cd /data && tar xzf /backup/$(basename "$tar_file")"
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ $volume_name 恢复成功${NC}"
else
echo -e "${RED}❌ $volume_name 恢复失败${NC}"
fi
fi
done
# 重新启动服务
echo ""
echo -e "${BLUE}🚀 重新启动服务...${NC}"
docker-compose up -d
echo -e "${GREEN}✅ 卷备份恢复完成${NC}"
}
# 执行恢复
case $restore_mode in
"sql")
restore_sql_backup
;;
"volume")
restore_volume_backup
;;
esac
echo ""
echo -e "${GREEN}🎉 数据恢复完成!${NC}"
echo ""
echo "📊 验证恢复结果:"
echo "- 查看服务状态: docker-compose ps"
echo "- 检查MySQL数据: docker exec ljwx-mysql mysql -u root -p123456 -e 'SHOW DATABASES;'"
echo "- 检查卷状态: docker volume ls"
echo ""
echo "如有问题,请联系技术支持"
+112
View File
@@ -0,0 +1,112 @@
#!/bin/bash
# -*- coding: utf-8 -*-
# 数据库恢复脚本
set -e # 遇到错误立即退出
# 颜色定义
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"; }
# 配置
MYSQL_CONTAINER="ljwx-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="123456"
MYSQL_DATABASE="ljwx"
BACKUP_DIR="backup/mysql"
RESTORE_LOG="logs/restore_$(date +%Y%m%d_%H%M%S).log"
# 创建日志目录
mkdir -p logs
# 检查参数
if [ $# -eq 0 ]; then
# 自动选择最新备份
BACKUP_FILE=$(ls -t $BACKUP_DIR/*.sql 2>/dev/null | head -1)
if [ -z "$BACKUP_FILE" ]; then
log_error "未找到备份文件,请指定备份文件路径"
echo "用法: $0 [backup_file.sql]"
exit 1
fi
log_info "自动选择最新备份: $BACKUP_FILE"
else
BACKUP_FILE="$1"
if [ ! -f "$BACKUP_FILE" ]; then
log_error "备份文件不存在: $BACKUP_FILE"
exit 1
fi
fi
log_info "开始数据库恢复流程..."
# 检查MySQL容器状态
if ! docker ps | grep -q $MYSQL_CONTAINER; then
log_error "MySQL容器未运行,请先启动服务"
exit 1
fi
# 等待MySQL服务就绪
log_info "等待MySQL服务就绪..."
for i in {1..30}; do
if docker exec $MYSQL_CONTAINER mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "SELECT 1" >/dev/null 2>&1; then
log_info "MySQL服务已就绪"
break
fi
if [ $i -eq 30 ]; then
log_error "MySQL服务启动超时"
exit 1
fi
sleep 2
done
# 创建数据库备份(恢复前备份)
CURRENT_BACKUP="backup/mysql/pre_restore_$(date +%Y%m%d_%H%M%S).sql"
log_info "创建恢复前备份: $CURRENT_BACKUP"
docker exec $MYSQL_CONTAINER mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD --single-transaction $MYSQL_DATABASE > $CURRENT_BACKUP
# 执行数据恢复
log_info "正在恢复数据库: $BACKUP_FILE"
{
echo "恢复开始时间: $(date)"
echo "备份文件: $BACKUP_FILE"
echo "目标数据库: $MYSQL_DATABASE"
echo "----------------------------------------"
} >> $RESTORE_LOG
if docker exec -i $MYSQL_CONTAINER mysql -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE < $BACKUP_FILE 2>> $RESTORE_LOG; then
log_info "数据库恢复成功"
# 验证恢复结果
log_info "验证恢复结果..."
USER_COUNT=$(docker exec $MYSQL_CONTAINER mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "USE $MYSQL_DATABASE; SELECT COUNT(*) FROM t_user_info;" -N 2>/dev/null || echo "0")
DEVICE_COUNT=$(docker exec $MYSQL_CONTAINER mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "USE $MYSQL_DATABASE; SELECT COUNT(*) FROM t_device_info;" -N 2>/dev/null || echo "0")
HEALTH_COUNT=$(docker exec $MYSQL_CONTAINER mysql -u$MYSQL_USER -p$MYSQL_PASSWORD -e "USE $MYSQL_DATABASE; SELECT COUNT(*) FROM t_health_data;" -N 2>/dev/null || echo "0")
log_info "恢复统计:"
log_info " 用户数据: $USER_COUNT 条"
log_info " 设备数据: $DEVICE_COUNT 条"
log_info " 健康数据: $HEALTH_COUNT 条"
{
echo "恢复完成时间: $(date)"
echo "恢复统计: 用户=$USER_COUNT, 设备=$DEVICE_COUNT, 健康=$HEALTH_COUNT"
echo "----------------------------------------"
} >> $RESTORE_LOG
log_info "恢复日志保存至: $RESTORE_LOG"
log_info "恢复前备份保存至: $CURRENT_BACKUP"
else
log_error "数据库恢复失败,详见日志: $RESTORE_LOG"
log_warn "可使用恢复前备份回滚: $CURRENT_BACKUP"
exit 1
fi
log_info "数据库恢复完成!"
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
# -*- coding: utf-8 -*-
# Redis数据恢复脚本
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 配置
REDIS_CONTAINER="ljwx-redis"
BACKUP_DIR="backup/redis"
REDIS_DATA_DIR="data/redis"
# 检查参数
if [ $# -eq 0 ]; then
# 自动选择最新备份
RDB_FILE=$(ls -t $BACKUP_DIR/*.rdb 2>/dev/null | head -1)
if [ -z "$RDB_FILE" ]; then
log_error "未找到Redis备份文件"
echo "用法: $0 [dump.rdb]"
exit 1
fi
log_info "自动选择最新备份: $RDB_FILE"
else
RDB_FILE="$1"
if [ ! -f "$RDB_FILE" ]; then
log_error "Redis备份文件不存在: $RDB_FILE"
exit 1
fi
fi
log_info "开始Redis数据恢复..."
# 检查Redis容器
if ! docker ps | grep -q $REDIS_CONTAINER; then
log_error "Redis容器未运行"
exit 1
fi
# 备份当前Redis数据
CURRENT_BACKUP="$BACKUP_DIR/pre_restore_$(date +%Y%m%d_%H%M%S).rdb"
log_info "备份当前Redis数据: $CURRENT_BACKUP"
docker exec $REDIS_CONTAINER redis-cli BGSAVE
sleep 5
docker cp $REDIS_CONTAINER:/data/dump.rdb $CURRENT_BACKUP
# 停止Redis服务
log_info "停止Redis服务..."
docker-compose stop $REDIS_CONTAINER
# 恢复数据文件
log_info "恢复Redis数据文件..."
cp $RDB_FILE $REDIS_DATA_DIR/dump.rdb
chmod 777 $REDIS_DATA_DIR/dump.rdb
# 启动Redis服务
log_info "启动Redis服务..."
docker-compose start $REDIS_CONTAINER
# 等待服务就绪
sleep 10
# 验证恢复
if docker exec $REDIS_CONTAINER redis-cli ping | grep -q PONG; then
KEY_COUNT=$(docker exec $REDIS_CONTAINER redis-cli DBSIZE 2>/dev/null || echo "0")
log_info "Redis恢复成功,当前键数量: $KEY_COUNT"
log_info "恢复前备份保存至: $CURRENT_BACKUP"
else
log_error "Redis恢复失败"
exit 1
fi
+211
View File
@@ -0,0 +1,211 @@
#!/bin/bash
# 自动化备份设置脚本 - 跨平台版
# 颜色
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
# 检测操作系统
detect_os() {
case "$(uname -s)" in
Darwin) OS="macOS" ;;
Linux)
if [ -f /etc/centos-release ]; then OS="CentOS"
elif [ -f /etc/ubuntu-release ] || grep -q "Ubuntu" /etc/os-release 2>/dev/null; then OS="Ubuntu"
else OS="Linux"; fi ;;
*) OS="Unknown" ;;
esac
}
log() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 检查依赖
check_dependencies() {
log "🔍 检查系统依赖..."
if ! command -v docker >/dev/null 2>&1; then
error "Docker未安装或未启动"
exit 1
fi
case "$OS" in
macOS)
if ! command -v launchctl >/dev/null 2>&1; then
error "launchctl不可用"
exit 1
fi
;;
CentOS|Ubuntu)
if ! command -v crontab >/dev/null 2>&1; then
error "crontab不可用"
exit 1
fi
;;
esac
log "✅ 系统依赖检查通过"
}
# macOS launchd 配置
setup_macos_schedule() {
local plist_file="$HOME/Library/LaunchAgents/com.ljwx.backup.plist"
local script_path="$(pwd)/backup-restore-manager.sh"
log "📝 创建macOS定时任务配置..."
cat > "$plist_file" << PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"\>
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.ljwx.backup</string>
<key>ProgramArguments</key>
<array>
<string>$script_path</string>
<string>auto-backup</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>WorkingDirectory</key>
<string>$(pwd)</string>
<key>StandardOutPath</key>
<string>$(pwd)/logs/auto-backup.log</string>
<key>StandardErrorPath</key>
<string>$(pwd)/logs/auto-backup-error.log</string>
</dict>
</plist>
PLIST
# 加载定时任务
launchctl unload "$plist_file" 2>/dev/null || true
launchctl load "$plist_file"
log "✅ macOS定时任务已设置 (每日凌晨2点)"
log "📋 管理命令:"
log " 启动: launchctl load $plist_file"
log " 停止: launchctl unload $plist_file"
log " 查看: launchctl list | grep ljwx"
}
# Linux crontab 配置
setup_linux_schedule() {
local script_path="$(pwd)/backup-restore-manager.sh"
local cron_job="0 2 * * * cd $(pwd) && $script_path auto-backup >> logs/auto-backup.log 2>&1"
log "📝 添加Linux定时任务..."
# 备份现有crontab
crontab -l > /tmp/ljwx_crontab_backup 2>/dev/null || true
# 检查是否已存在
if crontab -l 2>/dev/null | grep -q "backup-restore-manager.sh auto-backup"; then
warn "定时任务已存在,正在更新..."
crontab -l | grep -v "backup-restore-manager.sh auto-backup" | crontab -
fi
# 添加新的定时任务
(crontab -l 2>/dev/null; echo "$cron_job") | crontab -
log "✅ Linux定时任务已设置 (每日凌晨2点)"
log "📋 管理命令:"
log " 查看: crontab -l"
log " 编辑: crontab -e"
log " 删除: crontab -l | grep -v 'backup-restore-manager' | crontab -"
}
# 测试备份功能
test_backup() {
log "🧪 测试备份功能..."
if ./backup-restore-manager.sh mysql-backup; then
log "✅ MySQL备份测试成功"
else
error "❌ MySQL备份测试失败"
return 1
fi
if ./backup-restore-manager.sh redis-backup; then
log "✅ Redis备份测试成功"
else
error "❌ Redis备份测试失败"
return 1
fi
log "🎯 备份功能测试完成"
}
# 显示使用说明
show_usage() {
echo -e "${BLUE}
╔══════════════════════════════════════╗
║ LJWX 自动化备份设置完成 ║
╠══════════════════════════════════════╣
║ 📋 手动操作命令: ║
║ ║
║ 交互式管理: ║
║ ./backup-restore-manager.sh ║
║ ║
║ 命令行操作: ║
║ ./backup-restore-manager.sh mysql-backup ║
║ ./backup-restore-manager.sh redis-backup ║
║ ./backup-restore-manager.sh full-backup ║
║ ./backup-restore-manager.sh mysql-restore ║
║ ./backup-restore-manager.sh redis-restore ║
║ ./backup-restore-manager.sh list ║
║ ./backup-restore-manager.sh cleanup ║
║ ║
║ �� 备份目录: ║
║ backup/mysql/ - MySQL备份文件 ║
║ backup/redis/ - Redis备份文件 ║
║ logs/ - 日志文件 ║
╚══════════════════════════════════════╝${NC}"
}
# 主函数
main() {
detect_os
echo -e "${BLUE}🚀 LJWX自动化备份设置程序 (OS: $OS)${NC}"
check_dependencies
# 设置脚本权限
chmod +x backup-restore-manager.sh
# 创建必要目录
mkdir -p backup/{mysql,redis} logs
# 根据操作系统设置定时任务
case "$OS" in
macOS)
setup_macos_schedule
;;
CentOS|Ubuntu)
setup_linux_schedule
;;
*)
error "不支持的操作系统: $OS"
exit 1
;;
esac
# 测试备份功能
read -p "是否测试备份功能? [y/N]: " test_backup_choice
if [[ $test_backup_choice =~ ^[Yy]$ ]]; then
test_backup
fi
show_usage
log "🎉 自动化备份设置完成!"
log "💡 定时任务将在每天凌晨2点执行备份"
log "📝 查看日志: tail -f logs/backup-restore.log"
}
main "$@"
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
# 设置外挂日志和数据目录脚本
echo "🚀 创建外挂目录结构..."
# 创建日志目录
mkdir -p logs/{mysql,redis,ljwx-boot,ljwx-bigscreen,ljwx-admin}
# 创建数据目录
mkdir -p data/{mysql,redis,ljwx-boot,ljwx-bigscreen}
# 设置目录权限
chmod -R 755 logs data
echo "📁 目录结构创建完成:"
echo ""
echo "logs/"
echo "├── mysql/ # MySQL日志"
echo "├── redis/ # Redis日志"
echo "├── ljwx-boot/ # Spring Boot应用日志"
echo "├── ljwx-bigscreen/ # Python大屏应用日志"
echo "└── ljwx-admin/ # Nginx访问日志"
echo ""
echo "data/"
echo "├── mysql/ # MySQL数据文件(可选)"
echo "├── redis/ # Redis数据文件(可选)"
echo "├── ljwx-boot/ # Spring Boot应用数据"
echo "└── ljwx-bigscreen/ # Python大屏应用数据"
echo ""
echo "✅ 现在可以使用 docker-compose-enhanced.yml 启动服务"
echo " 所有日志将直接写入宿主机 ./logs 目录"
echo " 所有数据将持久化到 ./data 目录"
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
import pymysql
from datetime import datetime
import random
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': '123456',
'database': 'lj-06',
'charset': 'utf8mb4'
}
def generate_id():
return int(datetime.now().timestamp() * 1000000) + random.randint(1000, 9999)
def main():
print("🔄 开始同步告警规则数据字典...")
conn = pymysql.connect(**DB_CONFIG)
try:
with conn.cursor() as cursor:
# 获取数据
cursor.execute("SELECT DISTINCT physical_sign, rule_type FROM t_alert_rules WHERE physical_sign IS NOT NULL AND rule_type IS NOT NULL")
rules = cursor.fetchall()
# 只处理missing的项目
missing_physical = ['event', 'work_out'] # 确定缺少的项
missing_alert = ['BOOT_COMPLETED', 'CALL_STATE', 'COMMON_EVENT', 'FALLDOWN_EVENT', 'FUN_DOUBLE_CLICK',
'HEARTRATE_HIGH_ALERT', 'HEARTRATE_LOW_ALERT', 'PRESSURE_HIGH_ALERT', 'PRESSURE_LOW_ALERT',
'SOS_EVENT', 'SPO2_LOW_ALERT', 'STRESS_HIGH_ALERT', 'TEMPERATURE_HIGH_ALERT',
'TEMPERATURE_LOW_ALERT', 'UI_SETTINGS_CHANGED', 'WEAR_STATUS_CHANGED', 'fall_down', 'one_key_alarm']
# 名称映射
physical_map = {'event': '事件', 'work_out': '运动'}
alert_map = {
'BOOT_COMPLETED': '设备启动完成', 'CALL_STATE': '通话状态', 'COMMON_EVENT': '通用事件',
'FALLDOWN_EVENT': '跌倒事件', 'FUN_DOUBLE_CLICK': '功能双击', 'HEARTRATE_HIGH_ALERT': '心率高告警',
'HEARTRATE_LOW_ALERT': '心率低告警', 'PRESSURE_HIGH_ALERT': '血压高告警', 'PRESSURE_LOW_ALERT': '血压低告警',
'SOS_EVENT': 'SOS紧急事件', 'SPO2_LOW_ALERT': '血氧低告警', 'STRESS_HIGH_ALERT': '压力高告警',
'TEMPERATURE_HIGH_ALERT': '体温高告警', 'TEMPERATURE_LOW_ALERT': '体温低告警',
'UI_SETTINGS_CHANGED': 'UI设置变化', 'WEAR_STATUS_CHANGED': '佩戴状态变化',
'fall_down': '跌倒告警', 'one_key_alarm': '一键报警'
}
# 获取字典ID
cursor.execute("SELECT id FROM sys_dict WHERE code = 'health_data_type'")
health_dict_id = cursor.fetchone()[0]
cursor.execute("SELECT id FROM sys_dict WHERE code = 'alert_type'")
alert_dict_id = cursor.fetchone()[0]
# 添加缺失的physical_sign项
for sign in missing_physical:
cursor.execute("SELECT id FROM sys_dict_item WHERE dict_id = %s AND value = %s", (health_dict_id, sign))
if not cursor.fetchone():
item_id = generate_id()
zh_name = physical_map.get(sign, sign)
cursor.execute("""INSERT INTO sys_dict_item
(id, dict_id, dict_code, value, zh_cn, type, sort, description, create_user, create_user_id, create_time, status, is_deleted)
VALUES (%s, %s, 'health_data_type', %s, %s, '1', 1, '从告警规则同步', 'system_sync', 1, %s, '1', 0)""",
(item_id, health_dict_id, sign, zh_name, datetime.now()))
print(f"+ 添加健康数据类型: {sign} -> {zh_name}")
# 添加缺失的rule_type项
for rtype in missing_alert:
cursor.execute("SELECT id FROM sys_dict_item WHERE dict_id = %s AND value = %s", (alert_dict_id, rtype))
if not cursor.fetchone():
item_id = generate_id()
zh_name = alert_map.get(rtype, rtype)
cursor.execute("""INSERT INTO sys_dict_item
(id, dict_id, dict_code, value, zh_cn, type, sort, description, create_user, create_user_id, create_time, status, is_deleted)
VALUES (%s, %s, 'alert_type', %s, %s, '1', 1, '从告警规则同步', 'system_sync', 1, %s, '1', 0)""",
(item_id, alert_dict_id, rtype, zh_name, datetime.now()))
print(f"+ 添加告警类型: {rtype} -> {zh_name}")
conn.commit()
print("✅ 数据字典更新完成!")
except Exception as e:
conn.rollback()
print(f"❌ 同步失败: {e}")
finally:
conn.close()
if __name__ == "__main__":
main()
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
import pymysql
from datetime import datetime
DB_CONFIG = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': '123456',
'database': 'lj-06',
'charset': 'utf8mb4'
}
def get_connection():
return pymysql.connect(**DB_CONFIG)
def main():
print("🔄 开始同步告警规则数据字典...")
conn = get_connection()
try:
with conn.cursor() as cursor:
# 获取数据
cursor.execute("SELECT DISTINCT physical_sign, rule_type FROM t_alert_rules WHERE physical_sign IS NOT NULL AND rule_type IS NOT NULL")
rules = cursor.fetchall()
physical_signs = {r[0] for r in rules if r[0]}
rule_types = {r[1] for r in rules if r[1]}
print(f"📋 物理指标: {sorted(physical_signs)}")
print(f"📋 告警类型: {sorted(rule_types)}")
# 映射表
sign_map = {
'heart_rate': '心率',
'blood_pressure': '血压',
'temperature': '体温',
'stress': '压力',
'blood_oxygen': '血氧'
}
type_map = {
'heart_rate': '心率告警',
'blood_pressure': '血压告警',
'temperature': '体温告警',
'stress': '压力告警',
'blood_oxygen': '血氧告警'
}
# 确保health_data_type字典存在
cursor.execute("SELECT id FROM sys_dict WHERE dict_code = 'health_data_type'")
health_dict = cursor.fetchone()
if not health_dict:
cursor.execute("INSERT INTO sys_dict (dict_name, dict_code, description, status, del_flag) VALUES ('健康数据类型', 'health_data_type', '健康数据类型字典', 1, 0)")
cursor.execute("SELECT LAST_INSERT_ID()")
health_dict_id = cursor.fetchone()[0]
else:
health_dict_id = health_dict[0]
# 确保alert_type字典存在
cursor.execute("SELECT id FROM sys_dict WHERE dict_code = 'alert_type'")
alert_dict = cursor.fetchone()
if not alert_dict:
cursor.execute("INSERT INTO sys_dict (dict_name, dict_code, description, status, del_flag) VALUES ('告警类型', 'alert_type', '告警类型字典', 1, 0)")
cursor.execute("SELECT LAST_INSERT_ID()")
alert_dict_id = cursor.fetchone()[0]
else:
alert_dict_id = alert_dict[0]
# 添加物理指标项
for sign in physical_signs:
cursor.execute("SELECT id FROM sys_dict_item WHERE dict_id = %s AND item_value = %s", (health_dict_id, sign))
if not cursor.fetchone():
display_name = sign_map.get(sign, sign.title())
cursor.execute("INSERT INTO sys_dict_item (dict_id, item_text, item_value, status, del_flag) VALUES (%s, %s, %s, 1, 0)", (health_dict_id, display_name, sign))
print(f"+ 添加健康数据类型: {sign} -> {display_name}")
# 添加告警类型项
for rtype in rule_types:
cursor.execute("SELECT id FROM sys_dict_item WHERE dict_id = %s AND item_value = %s", (alert_dict_id, rtype))
if not cursor.fetchone():
display_name = type_map.get(rtype, rtype.title())
cursor.execute("INSERT INTO sys_dict_item (dict_id, item_text, item_value, status, del_flag) VALUES (%s, %s, %s, 1, 0)", (alert_dict_id, display_name, rtype))
print(f"+ 添加告警类型: {rtype} -> {display_name}")
conn.commit()
print("✅ 数据字典同步完成!")
except Exception as e:
conn.rollback()
print(f"❌ 同步失败: {e}")
finally:
conn.close()
if __name__ == "__main__":
main()
@@ -0,0 +1,200 @@
-- 企业级系统事件告警方案数据库表结构 - 修复版本
-- 创建时间: 2024年
-- 修复MySQL语法兼容性问题
USE `lj-06`;
-- 1. 微信告警配置表(租户维度)
CREATE TABLE IF NOT EXISTS `t_wechat_alarm_config` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`type` VARCHAR(20) NOT NULL COMMENT '微信类型: enterprise(企业微信)/official(公众号)',
`corp_id` VARCHAR(100) DEFAULT NULL COMMENT '企业微信企业ID',
`agent_id` VARCHAR(50) DEFAULT NULL COMMENT '企业微信应用AgentID',
`secret` VARCHAR(100) DEFAULT NULL COMMENT '企业微信应用Secret',
`appid` VARCHAR(100) DEFAULT NULL COMMENT '微信公众号AppID',
`appsecret` VARCHAR(100) DEFAULT NULL COMMENT '微信公众号AppSecret',
`template_id` VARCHAR(100) DEFAULT NULL COMMENT '微信模板消息ID',
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用告警',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_tenant_type` (`tenant_id`, `type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信告警配置表(支持企业微信和公众号)';
-- 2. 系统事件规则表
CREATE TABLE IF NOT EXISTS `t_system_event_rule` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
`event_type` VARCHAR(100) NOT NULL COMMENT '完整事件类型(如com.tdtech.ohos.health.action.SOS_EVENT)',
`rule_type` VARCHAR(50) NOT NULL COMMENT '规则类型(简化,如SOS_EVENT)',
`severity_level` VARCHAR(20) NOT NULL DEFAULT 'medium' COMMENT '告警级别:critical/high/medium/low',
`alert_message` VARCHAR(500) NOT NULL COMMENT '告警消息模板',
`is_emergency` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否紧急事件(触发微信推送)',
`notification_type` VARCHAR(20) NOT NULL DEFAULT 'message' COMMENT '通知类型:wechat/message/both',
`retry_count` INT NOT NULL DEFAULT 3 COMMENT '失败重试次数',
`is_active` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用规则',
`tenant_id` BIGINT NOT NULL DEFAULT 1 COMMENT '租户ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY `uk_rule_tenant` (`rule_type`, `tenant_id`),
INDEX `idx_event_type` (`event_type`),
INDEX `idx_is_emergency` (`is_emergency`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统事件规则配置表';
-- 3. 事件告警队列表
CREATE TABLE IF NOT EXISTS `t_event_alarm_queue` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
`event_type` VARCHAR(100) NOT NULL COMMENT '事件类型',
`device_sn` VARCHAR(50) NOT NULL COMMENT '设备序列号',
`event_value` VARCHAR(500) DEFAULT NULL COMMENT '事件值',
`event_data` JSON DEFAULT NULL COMMENT '完整事件数据(JSON格式)',
`processing_status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '处理状态:pending/processing/completed/failed',
`retry_count` INT NOT NULL DEFAULT 0 COMMENT '已重试次数',
`error_message` TEXT DEFAULT NULL COMMENT '错误信息',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`process_time` DATETIME DEFAULT NULL COMMENT '开始处理时间',
`complete_time` DATETIME DEFAULT NULL COMMENT '完成时间',
INDEX `idx_status_time` (`processing_status`, `create_time`),
INDEX `idx_device_time` (`device_sn`, `create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='事件告警处理队列';
-- 4. 检查并扩展t_alert_info表字段
-- 先检查字段是否存在,再添加
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_alert_info' AND COLUMN_NAME='user_id') = 0,
'ALTER TABLE `t_alert_info` ADD COLUMN `user_id` BIGINT DEFAULT NULL COMMENT ''用户ID'' AFTER `health_id`',
'SELECT ''user_id字段已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_alert_info' AND COLUMN_NAME='org_id') = 0,
'ALTER TABLE `t_alert_info` ADD COLUMN `org_id` BIGINT DEFAULT NULL COMMENT ''组织ID'' AFTER `user_id`',
'SELECT ''org_id字段已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_alert_info' AND COLUMN_NAME='tenant_id') = 0,
'ALTER TABLE `t_alert_info` ADD COLUMN `tenant_id` BIGINT DEFAULT 1 COMMENT ''租户ID'' AFTER `org_id`',
'SELECT ''tenant_id字段已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 5. 检查并扩展t_alert_action_log表字段
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_alert_action_log' AND COLUMN_NAME='notification_type') = 0,
'ALTER TABLE `t_alert_action_log` ADD COLUMN `notification_type` VARCHAR(20) DEFAULT NULL COMMENT ''通知类型:wechat/message/both'' AFTER `result`',
'SELECT ''notification_type字段已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_alert_action_log' AND COLUMN_NAME='retry_attempt') = 0,
'ALTER TABLE `t_alert_action_log` ADD COLUMN `retry_attempt` INT DEFAULT 0 COMMENT ''重试次数'' AFTER `notification_type`',
'SELECT ''retry_attempt字段已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 6. 初始化默认系统事件规则数据
INSERT IGNORE INTO `t_system_event_rule` (`event_type`, `rule_type`, `severity_level`, `alert_message`, `is_emergency`, `notification_type`, `tenant_id`) VALUES
-- 紧急事件(微信实时告警)
('com.tdtech.ohos.health.action.SOS_EVENT', 'SOS_EVENT', 'critical', 'SOS紧急求助信号', 1, 'both', 1),
('com.tdtech.ohos.health.action.FALLDOWN_EVENT', 'FALLDOWN_EVENT', 'critical', '检测到跌倒事件', 1, 'both', 1),
('com.tdtech.ohos.action.ONE_KEY_ALARM', 'ONE_KEY_ALARM', 'critical', '一键报警触发', 1, 'both', 1),
-- 严重事件(平台消息推送)
('com.tdtech.ohos.action.WEAR_STATUS_CHANGED', 'WEAR_STATUS_CHANGED', 'high', '设备佩戴状态变化', 0, 'message', 1),
('com.tdtech.ohos.health.action.STRESS_HIGH_ALERT', 'STRESS_HIGH_ALERT', 'high', '压力水平过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.SPO2_LOW_ALERT', 'SPO2_LOW_ALERT', 'high', '血氧浓度过低告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.HEARTRATE_HIGH_ALERT', 'HEARTRATE_HIGH_ALERT', 'high', '心率过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.HEARTRATE_LOW_ALERT', 'HEARTRATE_LOW_ALERT', 'high', '心率过低告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.TEMPERATURE_HIGH_ALERT', 'TEMPERATURE_HIGH_ALERT', 'high', '体温过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.TEMPERATURE_LOW_ALERT', 'TEMPERATURE_LOW_ALERT', 'high', '体温过低告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.PRESSURE_HIGH_ALERT', 'PRESSURE_HIGH_ALERT', 'high', '血压过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.PRESSURE_LOW_ALERT', 'PRESSURE_LOW_ALERT', 'high', '血压过低告警', 0, 'message', 1),
-- 系统事件(消息通知)
('com.tdtech.ohos.action.CALL_STATE', 'CALL_STATE', 'medium', '通话状态变化', 0, 'message', 1),
('com.tdtech.ohos.action.BOOT_COMPLETED', 'BOOT_COMPLETED', 'medium', '设备启动完成', 0, 'message', 1),
('com.tdtech.ohos.action.UI_SETTINGS_CHANGED', 'UI_SETTINGS_CHANGED', 'low', 'UI设置变更', 0, 'message', 1),
('com.tdtech.ohos.action.FUN_DOUBLE_CLICK', 'FUN_DOUBLE_CLICK', 'low', '功能键双击', 0, 'message', 1);
-- 7. 初始化默认微信告警配置(租户ID=1)
INSERT IGNORE INTO `t_wechat_alarm_config` (`tenant_id`, `type`, `enabled`) VALUES
(1, 'enterprise', 1),
(1, 'official', 1);
-- 8. 创建视图:事件告警统计
DROP VIEW IF EXISTS `v_event_alarm_stats`;
CREATE VIEW `v_event_alarm_stats` AS
SELECT
DATE(create_time) as stat_date,
processing_status,
COUNT(*) as event_count,
COUNT(DISTINCT device_sn) as device_count
FROM t_event_alarm_queue
GROUP BY DATE(create_time), processing_status;
-- 9. 创建视图:告警规则使用统计
DROP VIEW IF EXISTS `v_alert_rule_usage`;
CREATE VIEW `v_alert_rule_usage` AS
SELECT
r.rule_type,
r.severity_level,
r.is_emergency,
COUNT(a.id) as alert_count,
COUNT(DISTINCT a.device_sn) as device_count,
AVG(CASE WHEN a.alert_status = 'responded' THEN 1 ELSE 0 END) as response_rate
FROM t_system_event_rule r
LEFT JOIN t_alert_info a ON r.rule_type = a.alert_type
WHERE r.is_active = 1
GROUP BY r.id, r.rule_type, r.severity_level, r.is_emergency;
-- 10. 添加索引优化(检查是否存在再创建)
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_alert_info' AND INDEX_NAME='idx_alert_info_health_user') = 0,
'CREATE INDEX `idx_alert_info_health_user` ON `t_alert_info` (`health_id`, `user_id`)',
'SELECT ''索引idx_alert_info_health_user已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_alert_info' AND INDEX_NAME='idx_alert_info_tenant_status') = 0,
'CREATE INDEX `idx_alert_info_tenant_status` ON `t_alert_info` (`tenant_id`, `alert_status`)',
'SELECT ''索引idx_alert_info_tenant_status已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA='lj-06' AND TABLE_NAME='t_device_message' AND INDEX_NAME='idx_device_message_health') = 0,
'CREATE INDEX `idx_device_message_health` ON `t_device_message` (`device_sn`, `create_time`)',
'SELECT ''索引idx_device_message_health已存在'' as message'
));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 完成提示
SELECT '🎉 企业级系统事件告警方案数据库表结构创建完成!' as message;
@@ -0,0 +1,129 @@
-- 企业级系统事件告警方案数据库表结构
-- 创建时间: 2024年
USE `lj-06`;
-- 1. 微信告警配置表(租户维度)
CREATE TABLE IF NOT EXISTS `t_wechat_alarm_config` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`type` VARCHAR(20) NOT NULL COMMENT '微信类型: enterprise(企业微信)/official(公众号)',
`corp_id` VARCHAR(100) DEFAULT NULL COMMENT '企业微信企业ID',
`agent_id` VARCHAR(50) DEFAULT NULL COMMENT '企业微信应用AgentID',
`secret` VARCHAR(100) DEFAULT NULL COMMENT '企业微信应用Secret',
`appid` VARCHAR(100) DEFAULT NULL COMMENT '微信公众号AppID',
`appsecret` VARCHAR(100) DEFAULT NULL COMMENT '微信公众号AppSecret',
`template_id` VARCHAR(100) DEFAULT NULL COMMENT '微信模板消息ID',
`enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用告警',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_tenant_type` (`tenant_id`, `type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信告警配置表(支持企业微信和公众号)';
-- 2. 系统事件规则表
CREATE TABLE IF NOT EXISTS `t_system_event_rule` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
`event_type` VARCHAR(100) NOT NULL COMMENT '完整事件类型(如com.tdtech.ohos.health.action.SOS_EVENT)',
`rule_type` VARCHAR(50) NOT NULL COMMENT '规则类型(简化,如SOS_EVENT)',
`severity_level` VARCHAR(20) NOT NULL DEFAULT 'medium' COMMENT '告警级别:critical/high/medium/low',
`alert_message` VARCHAR(500) NOT NULL COMMENT '告警消息模板',
`is_emergency` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否紧急事件(触发微信推送)',
`notification_type` VARCHAR(20) NOT NULL DEFAULT 'message' COMMENT '通知类型:wechat/message/both',
`retry_count` INT NOT NULL DEFAULT 3 COMMENT '失败重试次数',
`is_active` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用规则',
`tenant_id` BIGINT NOT NULL DEFAULT 1 COMMENT '租户ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY `uk_rule_tenant` (`rule_type`, `tenant_id`),
INDEX `idx_event_type` (`event_type`),
INDEX `idx_is_emergency` (`is_emergency`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统事件规则配置表';
-- 3. 事件告警队列表
CREATE TABLE IF NOT EXISTS `t_event_alarm_queue` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
`event_type` VARCHAR(100) NOT NULL COMMENT '事件类型',
`device_sn` VARCHAR(50) NOT NULL COMMENT '设备序列号',
`event_value` VARCHAR(500) DEFAULT NULL COMMENT '事件值',
`event_data` JSON DEFAULT NULL COMMENT '完整事件数据(JSON格式)',
`processing_status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '处理状态:pending/processing/completed/failed',
`retry_count` INT NOT NULL DEFAULT 0 COMMENT '已重试次数',
`error_message` TEXT DEFAULT NULL COMMENT '错误信息',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`process_time` DATETIME DEFAULT NULL COMMENT '开始处理时间',
`complete_time` DATETIME DEFAULT NULL COMMENT '完成时间',
INDEX `idx_status_time` (`processing_status`, `create_time`),
INDEX `idx_device_time` (`device_sn`, `create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='事件告警处理队列';
-- 4. 扩展t_alert_info表(如果字段不存在)
ALTER TABLE `t_alert_info`
ADD COLUMN IF NOT EXISTS `user_id` BIGINT DEFAULT NULL COMMENT '用户ID' AFTER `health_id`,
ADD COLUMN IF NOT EXISTS `org_id` BIGINT DEFAULT NULL COMMENT '组织ID' AFTER `user_id`,
ADD COLUMN IF NOT EXISTS `tenant_id` BIGINT DEFAULT 1 COMMENT '租户ID' AFTER `org_id`;
-- 5. 扩展t_alert_action_log表
ALTER TABLE `t_alert_action_log`
ADD COLUMN IF NOT EXISTS `notification_type` VARCHAR(20) DEFAULT NULL COMMENT '通知类型:wechat/message/both' AFTER `result`,
ADD COLUMN IF NOT EXISTS `retry_attempt` INT DEFAULT 0 COMMENT '重试次数' AFTER `notification_type`;
-- 6. 初始化默认系统事件规则数据
INSERT IGNORE INTO `t_system_event_rule` (`event_type`, `rule_type`, `severity_level`, `alert_message`, `is_emergency`, `notification_type`, `tenant_id`) VALUES
-- 紧急事件(微信实时告警)
('com.tdtech.ohos.health.action.SOS_EVENT', 'SOS_EVENT', 'critical', 'SOS紧急求助信号', 1, 'both', 1),
('com.tdtech.ohos.health.action.FALLDOWN_EVENT', 'FALLDOWN_EVENT', 'critical', '检测到跌倒事件', 1, 'both', 1),
('com.tdtech.ohos.action.ONE_KEY_ALARM', 'ONE_KEY_ALARM', 'critical', '一键报警触发', 1, 'both', 1),
-- 严重事件(平台消息推送)
('com.tdtech.ohos.action.WEAR_STATUS_CHANGED', 'WEAR_STATUS_CHANGED', 'high', '设备佩戴状态变化', 0, 'message', 1),
('com.tdtech.ohos.health.action.STRESS_HIGH_ALERT', 'STRESS_HIGH_ALERT', 'high', '压力水平过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.SPO2_LOW_ALERT', 'SPO2_LOW_ALERT', 'high', '血氧浓度过低告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.HEARTRATE_HIGH_ALERT', 'HEARTRATE_HIGH_ALERT', 'high', '心率过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.HEARTRATE_LOW_ALERT', 'HEARTRATE_LOW_ALERT', 'high', '心率过低告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.TEMPERATURE_HIGH_ALERT', 'TEMPERATURE_HIGH_ALERT', 'high', '体温过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.TEMPERATURE_LOW_ALERT', 'TEMPERATURE_LOW_ALERT', 'high', '体温过低告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.PRESSURE_HIGH_ALERT', 'PRESSURE_HIGH_ALERT', 'high', '血压过高告警', 0, 'message', 1),
('com.tdtech.ohos.health.action.PRESSURE_LOW_ALERT', 'PRESSURE_LOW_ALERT', 'high', '血压过低告警', 0, 'message', 1),
-- 系统事件(消息通知)
('com.tdtech.ohos.action.CALL_STATE', 'CALL_STATE', 'medium', '通话状态变化', 0, 'message', 1),
('com.tdtech.ohos.action.BOOT_COMPLETED', 'BOOT_COMPLETED', 'medium', '设备启动完成', 0, 'message', 1),
('com.tdtech.ohos.action.UI_SETTINGS_CHANGED', 'UI_SETTINGS_CHANGED', 'low', 'UI设置变更', 0, 'message', 1),
('com.tdtech.ohos.action.FUN_DOUBLE_CLICK', 'FUN_DOUBLE_CLICK', 'low', '功能键双击', 0, 'message', 1);
-- 7. 初始化默认微信告警配置(租户ID=1)
INSERT IGNORE INTO `t_wechat_alarm_config` (`tenant_id`, `type`, `enabled`) VALUES
(1, 'enterprise', 1),
(1, 'official', 1);
-- 8. 创建视图:事件告警统计
CREATE OR REPLACE VIEW `v_event_alarm_stats` AS
SELECT
DATE(create_time) as stat_date,
processing_status,
COUNT(*) as event_count,
COUNT(DISTINCT device_sn) as device_count
FROM t_event_alarm_queue
GROUP BY DATE(create_time), processing_status;
-- 9. 创建视图:告警规则使用统计
CREATE OR REPLACE VIEW `v_alert_rule_usage` AS
SELECT
r.rule_type,
r.severity_level,
r.is_emergency,
COUNT(a.id) as alert_count,
COUNT(DISTINCT a.device_sn) as device_count,
AVG(CASE WHEN a.alert_status = 'responded' THEN 1 ELSE 0 END) as response_rate
FROM t_system_event_rule r
LEFT JOIN t_alert_info a ON FIND_IN_SET(r.rule_type, a.alert_type) > 0
WHERE r.is_active = 1
GROUP BY r.id, r.rule_type, r.severity_level, r.is_emergency;
-- 添加索引优化
CREATE INDEX IF NOT EXISTS `idx_alert_info_health_user` ON `t_alert_info` (`health_id`, `user_id`);
CREATE INDEX IF NOT EXISTS `idx_alert_info_tenant_status` ON `t_alert_info` (`tenant_id`, `alert_status`);
CREATE INDEX IF NOT EXISTS `idx_device_message_health` ON `t_device_message` (`device_sn`, `create_time`);
-- 完成提示
SELECT '企业级系统事件告警方案数据库表结构创建完成!' as message;
+258
View File
@@ -0,0 +1,258 @@
#!/bin/bash
# Created Time: 2025-06-26 12:37:43
# Modified Time: 2025-06-26 12:37:52
#set -e # 这告诉bash一但有任何一个语句返回非真的值,则退出bash。
#!/bin/bash
# wait-for-it.sh - 等待服务可用的脚本(Linux兼容版)
# 支持CentOS、Ubuntu、Debian、Alpine等Linux发行版
set -e #遇到错误立即退出
WAITFORIT_cmdname=${0##*/}
WAITFORIT_QUIET=0
WAITFORIT_STRICT=0
WAITFORIT_TIMEOUT=15
WAITFORIT_HOST=""
WAITFORIT_PORT=""
WAITFORIT_CLI=""
# 错误输出函数
echoerr() {
if [ $WAITFORIT_QUIET -ne 1 ]; then
echo "$@" >&2
fi
}
# 使用说明
usage() {
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
# 检测网络连接工具
detect_network_tool() {
if command -v nc >/dev/null 2>&1; then
# 检测nc版本和参数支持
if nc -h 2>&1 | grep -q "\-z"; then
WAITFORIT_NC_TOOL="nc -z"
elif nc -h 2>&1 | grep -q "scan"; then
WAITFORIT_NC_TOOL="nc"
else
WAITFORIT_NC_TOOL="nc"
fi
echoerr "使用网络工具: $WAITFORIT_NC_TOOL"
return 0
elif command -v netcat >/dev/null 2>&1; then
WAITFORIT_NC_TOOL="netcat -z"
echoerr "使用网络工具: netcat"
return 0
elif command -v telnet >/dev/null 2>&1; then
WAITFORIT_NC_TOOL="telnet"
echoerr "使用网络工具: telnet"
return 0
else
echoerr "警告: 未找到nc/netcat/telnet,将使用bash内置网络功能"
WAITFORIT_NC_TOOL="bash"
return 0
fi
}
# 测试网络连接
test_connection() {
local host=$1
local port=$2
case "$WAITFORIT_NC_TOOL" in
"nc -z")
nc -z "$host" "$port" >/dev/null 2>&1
return $?
;;
"nc")
# 某些nc版本不支持-z参数
echo "" | nc "$host" "$port" >/dev/null 2>&1
return $?
;;
"netcat -z")
netcat -z "$host" "$port" >/dev/null 2>&1
return $?
;;
"telnet")
# 使用telnet测试连接
(echo "quit" | telnet "$host" "$port" 2>/dev/null | grep -q "Connected") 2>/dev/null
return $?
;;
"bash")
# 使用bash内置网络功能
if [ -e /dev/tcp ]; then
exec 3<>/dev/tcp/"$host"/"$port" 2>/dev/null
if [ $? -eq 0 ]; then
exec 3<&-
exec 3>&-
return 0
else
return 1
fi
else
# 如果/dev/tcp不可用,尝试其他方法
timeout 1 bash -c "cat < /dev/null > /dev/tcp/$host/$port" 2>/dev/null
return $?
fi
;;
*)
echoerr "错误: 无可用的网络测试工具"
return 1
;;
esac
}
# 等待服务可用
wait_for() {
# 检测网络工具
detect_network_tool
if [ $WAITFORIT_TIMEOUT -gt 0 ]; then
echoerr "$WAITFORIT_cmdname: 等待 $WAITFORIT_TIMEOUT 秒连接到 $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: 等待连接到 $WAITFORIT_HOST:$WAITFORIT_PORT (无超时限制)"
fi
WAITFORIT_start_ts=$(date +%s)
while true; do
if test_connection "$WAITFORIT_HOST" "$WAITFORIT_PORT"; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT 在 $((WAITFORIT_end_ts - WAITFORIT_start_ts)) 秒后可用"
return 0
fi
# 检查超时
if [ $WAITFORIT_TIMEOUT -gt 0 ]; then
WAITFORIT_current_ts=$(date +%s)
if [ $((WAITFORIT_current_ts - WAITFORIT_start_ts)) -ge $WAITFORIT_TIMEOUT ]; then
echoerr "$WAITFORIT_cmdname: 超时 - $WAITFORIT_HOST:$WAITFORIT_PORT 在 $WAITFORIT_TIMEOUT 秒内不可用"
return 1
fi
fi
sleep 1
done
}
# 解析命令行参数
while [ $# -gt 0 ]; do
case "$1" in
*:*)
WAITFORIT_hostport=$(echo "$1" | tr ':' ' ')
WAITFORIT_HOST=$(echo $WAITFORIT_hostport | cut -d' ' -f1)
WAITFORIT_PORT=$(echo $WAITFORIT_hostport | cut -d' ' -f2)
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST="$2"
if [ "$WAITFORIT_HOST" = "" ]; then
echoerr "错误: -h 参数需要指定主机名"
usage
fi
shift 2
;;
--host=*)
WAITFORIT_HOST="${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT="$2"
if [ "$WAITFORIT_PORT" = "" ]; then
echoerr "错误: -p 参数需要指定端口号"
usage
fi
shift 2
;;
--port=*)
WAITFORIT_PORT="${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [ "$WAITFORIT_TIMEOUT" = "" ]; then
echoerr "错误: -t 参数需要指定超时时间"
usage
fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI="$@"
break
;;
--help)
usage
;;
*)
echoerr "未知参数: $1"
usage
;;
esac
done
# 验证必需参数
if [ "$WAITFORIT_HOST" = "" ] || [ "$WAITFORIT_PORT" = "" ]; then
echoerr "错误: 必须提供主机名和端口号"
usage
fi
# 验证端口号格式
if ! echo "$WAITFORIT_PORT" | grep -q "^[0-9]\+$"; then
echoerr "错误: 端口号必须是数字"
usage
fi
# 验证端口号范围
if [ "$WAITFORIT_PORT" -lt 1 ] || [ "$WAITFORIT_PORT" -gt 65535 ]; then
echoerr "错误: 端口号必须在1-65535范围内"
usage
fi
# 验证超时时间
if ! echo "$WAITFORIT_TIMEOUT" | grep -q "^[0-9]\+$"; then
echoerr "错误: 超时时间必须是数字"
usage
fi
# 等待服务
wait_for
WAITFORIT_RESULT=$?
# 执行后续命令
if [ "$WAITFORIT_CLI" != "" ]; then
if [ $WAITFORIT_RESULT -ne 0 ] && [ $WAITFORIT_STRICT -eq 1 ]; then
echoerr "$WAITFORIT_cmdname: 严格模式下拒绝执行后续命令"
exit $WAITFORIT_RESULT
fi
# 执行后续命令
exec $WAITFORIT_CLI
else
exit $WAITFORIT_RESULT
fi
@@ -0,0 +1,580 @@
# 灵境万象(LJWX)系统离线部署完整指南
*最后更新: 2024-12-20*
## 概述
本指南提供完整的离线部署方案,客户可在有网络环境下载载Docker镜像,然后在无网络的服务器环境中进行离线部署。
---
## 第一阶段:镜像下载准备(有网络环境)
### 1. 环境准备
#### 1.1 系统要求
- **操作系统**: Windows 10/11, macOS 10.15+, Ubuntu 18.04+, CentOS 7+
- **Docker版本**: 18.06+
- **硬盘空间**: 至少5GB可用空间
- **网络**: 稳定的互联网连接
#### 1.2 安装Docker
```bash
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# CentOS/RHEL
sudo yum install -y docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
# Windows/macOS: 安装Docker Desktop
```
### 2. 镜像下载脚本
#### 2.1 创建下载脚本
```bash
# 创建下载目录
mkdir -p ljwx-offline && cd ljwx-offline
# 创建镜像下载脚本
cat > download-images.sh << 'EOF'
#!/bin/bash
# LJWX离线镜像下载脚本
set -e #出错即停
# 配置变量
REGISTRY="crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com"
VERSION="1.2.14" #版本号
REDIS_VERSION="1.1.7" #Redis版本
# 镜像列表
declare -a IMAGES=(
"$REGISTRY/ljwx/ljwx-mysql:$VERSION"
"$REGISTRY/ljwx/ljwx-redis:$REDIS_VERSION"
"$REGISTRY/ljwx/ljwx-boot:$VERSION"
"$REGISTRY/ljwx/ljwx-bigscreen:$VERSION"
"$REGISTRY/ljwx/ljwx-admin:$VERSION"
)
echo "🐳 开始下载LJWX Docker镜像..."
echo "📦 总计 ${#IMAGES[@]} 个镜像"
# 下载镜像
for image in "${IMAGES[@]}"; do
echo "⬇️ 下载镜像: $image"
docker pull "$image" || { echo "❌ 下载失败: $image"; exit 1; }
done
echo "💾 开始导出镜像..."
# 导出所有镜像为tar文件
docker save "${IMAGES[@]}" -o ljwx-images-${VERSION}.tar
# 压缩镜像文件
echo "🗜️ 压缩镜像文件..."
gzip ljwx-images-${VERSION}.tar
echo "✅ 镜像下载完成!"
echo "📁 生成文件: ljwx-images-${VERSION}.tar.gz"
echo "📊 文件大小: $(du -h ljwx-images-${VERSION}.tar.gz | cut -f1)"
EOF
chmod +x download-images.sh
```
#### 2.2 执行下载
```bash
# 执行下载脚本
./download-images.sh
# 验证下载结果
ls -lh ljwx-images-*.tar.gz
```
### 3. 下载部署文件
#### 3.1 下载部署包
```bash
# 方式1: 从代码仓库下载(如果有访问权限)
git clone <仓库地址> ljwx-deployment
cd ljwx-deployment/docker/client-deployment
# 方式2: 手动下载关键文件
mkdir -p ljwx-deployment
cd ljwx-deployment
# 下载核心文件(需要替换为实际下载地址)
wget <服务器地址>/docker-compose.yml
wget <服务器地址>/deploy-client.sh
wget <服务器地址>/reset-database.sh
wget <服务器地址>/custom-config.env
wget <服务器地址>/client-data.sql
wget <服务器地址>/wait-for-it.sh
```
#### 3.2 创建离线部署包
```bash
# 返回上级目录
cd ..
# 创建完整离线包
tar -czf ljwx-offline-deployment-$(date +%Y%m%d).tar.gz \
ljwx-images-*.tar.gz \
ljwx-deployment/ \
download-images.sh
echo "📦 离线部署包创建完成: ljwx-offline-deployment-$(date +%Y%m%d).tar.gz"
```
---
## 第二阶段:服务器离线部署(无网络环境)
### 1. 服务器环境准备
#### 1.1 系统要求
- **操作系统**: CentOS 7+, Ubuntu 18.04+, RHEL 7+
- **CPU**: 4核心以上
- **内存**: 8GB以上
- **硬盘**: 20GB以上可用空间
- **Docker**: 18.06+
- **Docker Compose**: 1.22+
#### 1.2 CentOS环境安装
```bash
# 安装Docker(离线环境需要提前准备RPM包)
sudo yum install -y docker
# 或者使用预编译二进制文件
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-20.10.21.tgz | \
tar xz && sudo cp docker/* /usr/bin/ && sudo dockerd &
# 安装Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 启动Docker服务
sudo systemctl start docker
sudo systemctl enable docker
```
### 2. 部署包传输
#### 2.1 文件传输方式
```bash
# 方式1: SCP传输
scp ljwx-offline-deployment-*.tar.gz user@server:/tmp/
# 方式2: U盘拷贝
# 将文件拷贝到U盘,在服务器插入U盘
mount /dev/sdb1 /mnt/usb
cp /mnt/usb/ljwx-offline-deployment-*.tar.gz /opt/
# 方式3: 内网文件服务器
# 通过内网FTP或HTTP服务器传输
```
#### 2.2 解压部署包
```bash
# 解压到部署目录
sudo mkdir -p /opt/ljwx
cd /opt/ljwx
sudo tar -xzf /tmp/ljwx-offline-deployment-*.tar.gz
cd ljwx-deployment
```
### 3. 导入Docker镜像
#### 3.1 导入镜像
```bash
# 解压镜像文件
gunzip ljwx-images-*.tar.gz
# 导入镜像到Docker
sudo docker load -i ljwx-images-*.tar
# 验证镜像导入
sudo docker images | grep ljwx
```
#### 3.2 镜像标签处理
```bash
# 如果需要重新标记镜像
cat > retag-images.sh << 'EOF'
#!/bin/bash
# 镜像重新标记脚本
REGISTRY="crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com"
VERSION="1.2.14"
# 重新标记镜像(可选)
docker tag $REGISTRY/ljwx/ljwx-mysql:$VERSION ljwx-mysql:$VERSION
docker tag $REGISTRY/ljwx/ljwx-redis:1.1.7 ljwx-redis:1.1.7
docker tag $REGISTRY/ljwx/ljwx-boot:$VERSION ljwx-boot:$VERSION
docker tag $REGISTRY/ljwx/ljwx-bigscreen:$VERSION ljwx-bigscreen:$VERSION
docker tag $REGISTRY/ljwx/ljwx-admin:$VERSION ljwx-admin:$VERSION
echo "✅ 镜像标记完成"
EOF
chmod +x retag-images.sh
sudo ./retag-images.sh
```
### 4. 配置部署环境
#### 4.1 配置文件定制
```bash
# 备份原配置
cp custom-config.env custom-config.env.bak
# 编辑配置文件
vim custom-config.env
```
```env
# 自定义配置示例
BIGSCREEN_TITLE=客户定制大屏标题
COMPANY_NAME=客户公司名称
SERVER_IP=192.168.1.100
BIGSCREEN_PORT=8001
ADMIN_PORT=8080
MYSQL_PASSWORD=custom_password
MYSQL_DATABASE=client_db
```
#### 4.2 CentOS兼容性检查
```bash
# 运行兼容性检查
chmod +x validate-centos-compatibility.sh
sudo ./validate-centos-compatibility.sh
# 修复权限
chmod +x *.sh
sudo chown -R $(whoami):$(whoami) .
```
### 5. 系统部署
#### 5.1 执行部署
```bash
# 设置执行权限
chmod +x deploy-client.sh
# 执行部署(CentOS环境使用bash)
sudo bash deploy-client.sh
# 或者分步执行
sudo docker-compose up -d mysql redis #先启动基础服务
sleep 30 #等待服务就绪
sudo docker-compose up -d ljwx-boot #启动后端服务
sleep 60 #等待服务就绪
sudo docker-compose up -d ljwx-bigscreen ljwx-admin #启动前端服务
```
#### 5.2 部署状态检查
```bash
# 检查容器状态
sudo docker-compose ps
# 检查服务健康状态
sudo docker-compose exec ljwx-boot curl -f http://localhost:9998/actuator/health
sudo docker-compose exec ljwx-bigscreen curl -f http://localhost:8001/health
# 查看容器日志
sudo docker-compose logs -f ljwx-boot
sudo docker-compose logs -f ljwx-bigscreen
```
### 6. 防火墙和网络配置
#### 6.1 CentOS防火墙设置
```bash
# 开放必要端口
sudo firewall-cmd --permanent --add-port=8080/tcp #管理后台
sudo firewall-cmd --permanent --add-port=8001/tcp #大屏显示
sudo firewall-cmd --permanent --add-port=9998/tcp #API服务
sudo firewall-cmd --reload
# 检查端口状态
sudo firewall-cmd --list-ports
sudo netstat -tlnp | grep -E ':(8080|8001|9998)'
```
#### 6.2 SELinux配置
```bash
# 检查SELinux状态
getenforce
# 如果需要,设置为宽松模式
sudo setenforce 0
sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
```
---
## 第三阶段:系统验证和运维
### 1. 功能验证
#### 1.1 基础连通性测试
```bash
# 测试服务响应
curl -I http://localhost:8080/ #管理后台
curl -I http://localhost:8001/ #大屏显示
curl -I http://localhost:9998/actuator/health #API健康检查
# 数据库连接测试
sudo docker-compose exec mysql mysql -uroot -p123456 -e "show databases;"
sudo docker-compose exec redis redis-cli ping
```
#### 1.2 Web界面访问
```bash
# 获取服务器IP地址
ip addr show | grep "inet " | grep -v 127.0.0.1
# 浏览器访问测试
echo "管理后台: http://服务器IP:8080"
echo "大屏显示: http://服务器IP:8001"
echo "API文档: http://服务器IP:9998/doc.html"
```
### 2. 数据备份配置
#### 2.1 自动备份设置
```bash
# 创建备份脚本
cat > /opt/ljwx/auto-backup.sh << 'EOF'
#!/bin/bash
# LJWX自动备份脚本
BACKUP_DIR="/opt/ljwx/backup"
DATE=$(date +%Y%m%d_%H%M%S)
# 创建备份目录
mkdir -p $BACKUP_DIR/mysql $BACKUP_DIR/redis
# MySQL备份
docker-compose exec -T mysql mysqldump -uroot -p123456 --all-databases > \
$BACKUP_DIR/mysql/ljwx_mysql_$DATE.sql
# Redis备份
docker-compose exec -T redis redis-cli BGSAVE
docker cp ljwx-redis:/data/dump.rdb $BACKUP_DIR/redis/ljwx_redis_$DATE.rdb
# 清理7天前的备份
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.rdb" -mtime +7 -delete
echo "✅ 备份完成: $DATE"
EOF
chmod +x /opt/ljwx/auto-backup.sh
# 添加定时任务
echo "0 2 * * * /opt/ljwx/auto-backup.sh" | sudo crontab -
```
### 3. 监控和日志
#### 3.1 服务监控脚本
```bash
cat > /opt/ljwx/health-check.sh << 'EOF'
#!/bin/bash
# LJWX健康检查脚本
check_service() {
local service=$1
local port=$2
if curl -sf http://localhost:$port/health > /dev/null 2>&1; then
echo "✅ $service: 正常"
else
echo "❌ $service: 异常"
docker-compose restart $service
fi
}
echo "🔍 LJWX系统健康检查 - $(date)"
check_service ljwx-boot 9998
check_service ljwx-bigscreen 8001
check_service ljwx-admin 80
# 检查容器状态
echo "📊 容器状态:"
docker-compose ps
EOF
chmod +x /opt/ljwx/health-check.sh
# 添加监控定时任务
echo "*/5 * * * * /opt/ljwx/health-check.sh" | sudo crontab -
```
#### 3.2 日志管理
```bash
# 日志清理脚本
cat > /opt/ljwx/log-cleanup.sh << 'EOF'
#!/bin/bash
# 清理Docker日志
# 清理容器日志(保留最近7天)
docker system prune -f --filter "until=168h"
# 清理应用日志
find /opt/ljwx -name "*.log" -mtime +7 -delete
echo "🧹 日志清理完成: $(date)"
EOF
chmod +x /opt/ljwx/log-cleanup.sh
echo "0 3 * * 0 /opt/ljwx/log-cleanup.sh" | sudo crontab -
```
---
## 常见问题排除
### 1. 镜像相关问题
#### 1.1 镜像下载失败
```bash
# 重试下载特定镜像
docker pull crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx/ljwx-mysql:1.2.14
# 检查网络连接
ping registry.docker.io
curl -I https://docker.io
# 配置Docker镜像源(国内环境)
sudo mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://registry.docker-cn.com"
]
}
EOF
sudo systemctl restart docker
```
#### 1.2 镜像导入失败
```bash
# 检查文件完整性
md5sum ljwx-images-*.tar.gz
# 检查Docker磁盘空间
docker system df
docker system prune -f
# 强制导入
docker load --input ljwx-images-*.tar
```
### 2. 部署相关问题
#### 2.1 容器启动失败
```bash
# 查看详细错误信息
docker-compose logs ljwx-boot
docker-compose logs ljwx-bigscreen
# 检查端口占用
netstat -tlnp | grep -E ':(3306|6379|8080|8001|9998)'
# 手动启动容器调试
docker run -it --rm ljwx-mysql:1.2.14 bash
```
#### 2.2 网络连接问题
```bash
# 检查Docker网络
docker network ls
docker network inspect ljwx-network
# 测试容器间连通性
docker-compose exec ljwx-boot ping mysql
docker-compose exec ljwx-bigscreen ping redis
# 重建网络
docker-compose down
docker-compose up -d
```
### 3. 性能优化
#### 3.1 系统优化
```bash
# 增加文件描述符限制
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nofile 65536" >> /etc/security/limits.conf
# 优化内核参数
echo "vm.max_map_count=262144" >> /etc/sysctl.conf
sysctl -p
```
#### 3.2 Docker优化
```bash
# Docker守护进程优化
cat > /etc/docker/daemon.json << 'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2"
}
EOF
sudo systemctl restart docker
```
---
## 安全建议
### 1. 访问控制
```bash
# 修改默认密码
vim custom-config.env #修改MYSQL_PASSWORD等
# 配置防火墙白名单
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='192.168.1.0/24' accept"
# 禁用不必要的端口公网访问
firewall-cmd --permanent --remove-port=3306/tcp #数据库端口
firewall-cmd --permanent --remove-port=6379/tcp #Redis端口
```
### 2. 数据安全
```bash
# 启用数据库SSL
# 配置Redis认证
# 定期更新镜像版本
# 实施访问日志审计
```
---
## 技术支持
### 联系方式
- **技术支持**: support@ljwx.com
- **文档地址**: https://docs.ljwx.com
- **问题反馈**: https://github.com/ljwx/issues
### 版本信息
- **当前版本**: 1.2.14
- **支持系统**: CentOS 7+, Ubuntu 18.04+, RHEL 7+
- **Docker版本**: 18.06+
- **更新周期**: 每月定期更新
---
*© 2024 灵境万象科技. 保留所有权利.*
+7
View File
@@ -0,0 +1,7 @@
node_modules
dist
.git
.gitignore
.env
.env.*
*.log
+11
View File
@@ -0,0 +1,11 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
+57
View File
@@ -0,0 +1,57 @@
# the base url of the application, the default is "/"
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
VITE_BASE_URL=./
VITE_APP_TITLE=穿戴健康管理平台
VITE_APP_DESC=穿戴健康管理平台
# the prefix of the icon name
VITE_ICON_PREFIX=icon
# the prefix of the local svg icon component, must include VITE_ICON_PREFIX
# format {VITE_ICON_PREFIX}-{local icon name}
VITE_ICON_LOCAL_PREFIX=icon-local
# auth route mode: static | dynamic
VITE_AUTH_ROUTE_MODE=dynamic
# static auth route home
VITE_ROUTE_HOME=home
# default menu icon
VITE_MENU_ICON=mdi:menu
# whether to enable http proxy when is dev mode
VITE_HTTP_PROXY=Y
# vue-router mode: hash | history | memory
VITE_ROUTER_HISTORY_MODE=history
# success code of backend service, when the code is received, the request is successful
VITE_SERVICE_SUCCESS_CODE=200
# logout codes of backend service, when the code is received, the user will be logged out and redirected to login page
VITE_SERVICE_LOGOUT_CODES=600,401
# modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal
VITE_SERVICE_MODAL_LOGOUT_CODES=401
# token expired codes of backend service, when the code is received, it will refresh the token and resend the request
VITE_SERVICE_EXPIRED_TOKEN_CODES=40101
# when the route mode is static, the defined super role
VITE_STATIC_SUPER_ROLE=R_SUPER
# permission button auth mode : user_info | route_meta
# user_info not implemented, need backend to cooperate and return data.
VITE_AUTH_BUTTON_MODE=route_meta
# sourcemap
VITE_SOURCE_MAP=N
# Used to differentiate storage across different domains
VITE_STORAGE_PREFIX=SOY_
# used to control whether the program automatically detects updates
VITE_AUTOMATICALLY_DETECT_UPDATE=Y
+7
View File
@@ -0,0 +1,7 @@
# backend service base url, test environment
VITE_SERVICE_BASE_URL=http://127.0.0.1:9999
# other backend service base url, test environment
VITE_OTHER_SERVICE_BASE_URL= `{
"demo": "http://localhost:9634"
}`
+10
View File
@@ -0,0 +1,10 @@
# backend service base url, test environment (修复:使用正确的API端口9998)
VITE_SERVICE_BASE_URL=http://127.0.0.1:9998
# other backend service base url, test environment
VITE_OTHER_SERVICE_BASE_URL= `{
"demo": "http://localhost:9634"
}`
# 大屏服务地址 - 本地开发环境
VITE_BIGSCREEN_URL=http://localhost:5001
+13
View File
@@ -0,0 +1,13 @@
"*.vue" eol=lf
"*.js" eol=lf
"*.ts" eol=lf
"*.jsx" eol=lf
"*.tsx" eol=lf
"*.mjs" eol=lf
"*.json" eol=lf
"*.html" eol=lf
"*.css" eol=lf
"*.scss" eol=lf
"*.md" eol=lf
"*.yaml" eol=lf
"*.yml" eol=lf
+46
View File
@@ -0,0 +1,46 @@
name: Build and Push ljwx-admin Docker Image
on:
push:
branches: [ main, master ]
workflow_dispatch:
env:
REGISTRY: crpi-yilnm6upy4pmbp67.cn-shenzhen.personal.cr.aliyuncs.com/ljwx
IMAGE_NAME: ljwx-admin
VERSION: 1.1.2
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout代码
uses: actions/checkout@v4
- name: 设置Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录阿里云镜像仓库
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.ALIYUN_USERNAME }}
password: ${{ secrets.ALIYUN_PASSWORD }}
- name: 构建并推送Docker镜像
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile.prod
platforms: linux/amd64
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: 验证镜像
run: |
echo "✅ ljwx-admin镜像构建并推送成功!"
echo "🐳 镜像地址: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}"
echo "📦 拉取命令: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}"
+36
View File
@@ -0,0 +1,36 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
!.vscode/launch.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json
yarn.lock
.VSCodeCounter
.env.prod
+4
View File
@@ -0,0 +1,4 @@
registry=https://registry.npmmirror.com/
shamefully-hoist=true
ignore-workspace-root-check=true
link-workspace-packages=true
+8
View File
@@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"printWidth": 150,
"trailingComma": "none"
}
+19
View File
@@ -0,0 +1,19 @@
{
"recommendations": [
"afzalsayed96.icones",
"antfu.iconify",
"antfu.unocss",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"lokalise.i18n-ally",
"mhutchie.git-graph",
"mikestead.dotenv",
"naumovs.color-highlight",
"pkief.material-icon-theme",
"sdras.vue-vscode-snippets",
"vue.volar",
"whtouche.vscode-js-console-utils",
"zhuangtongfa.material-theme"
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Vue Debugger",
"url": "http://localhost:9527",
"webRoot": "${workspaceFolder}"
},
{
"type": "node",
"request": "launch",
"name": "TS Debugger",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/tsx",
"skipFiles": ["<node_internals>/**", "${workspaceFolder}/node_modules/**"],
"program": "${file}"
}
]
}
+25
View File
@@ -0,0 +1,25 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"editor.fontFamily": "MonoLisa-Regular",
"editor.fontSize": 13,
"editor.tabSize": 2,
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 200,
"eslint.useFlatConfig": true,
"editor.formatOnSave": false,
"eslint.validate": ["html", "css", "scss", "json", "jsonc"],
"i18n-ally.displayLanguage": "zh-cn",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.enabledFrameworks": ["vue"],
"i18n-ally.editor.preferEditor": true,
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": ["src/locales/langs"],
"prettier.enable": false,
"typescript.tsdk": "node_modules/typescript/lib",
"unocss.root": ["./"],
"vue.server.hybridMode": true,
"vue-i18n.i18nPaths": "src/locales,src/locales/langs,packages/scripts/src/locales"
}
+154
View File
@@ -0,0 +1,154 @@
# 告警日志删除功能修复完整总结
## 🔍 **问题诊断**
### 1. 前端表格问题
- **全选异常**:点击单个复选框触发全选
- **删除失败**:传递错误的ID值 `[0]` 而不是实际的 `logId`
### 2. 后端删除问题
- **实体类主键配置错误**:MyBatis-Plus无法识别正确的主键字段
- **数据库操作失败**:删除操作实际没有执行
## ⚠️ **根本原因分析**
### 数据库表结构
```sql
-- t_alert_action_log 表结构
CREATE TABLE t_alert_action_log (
log_id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 实际主键
id INTEGER, -- 普通字段(可为NULL)
alert_id BIGINT,
-- ... 其他字段
);
```
### 问题核心
1. **前端**: `:row-key="row => row.id"` 使用了错误的字段(值为NULL)
2. **后端**: TAlertActionLog实体类继承BaseEntity,MyBatis-Plus默认使用BaseEntity.id作为主键
3. **映射错误**: 实际主键`log_id`没有被正确标识为@TableId
## 🔧 **完整修复方案**
### 1. 前端修复 (ljwx-admin)
**文件**: `ljwx-admin/src/views/alert/log/index.vue`
```vue
<!-- 修复表格row-key -->
<NDataTable
:row-key="row => row.logId" <!-- ✅ 使用logId -->
v-model:checked-row-keys="checkedRowKeys"
<!-- 其他配置 -->
/>
<!-- 修复删除操作 -->
<NPopconfirm onPositiveClick={() => handleDelete(row.logId)}>
<!-- ✅ 使用logId而不是id -->
</NPopconfirm>
```
```typescript
// 修复函数参数类型
async function handleDelete(id: number) { // ✅ number类型
const { error, data: result } = await fetchDeleteAleractionLog(transDeleteParams([id]));
if (!error && result) {
await onDeleted();
}
}
```
### 2. 后端修复 (ljwx-boot)
**文件**: `ljwx-boot/ljwx-boot-modules/src/main/java/com/ljwx/modules/health/domain/entity/TAlertActionLog.java`
```java
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_alert_action_log")
public class TAlertActionLog extends BaseEntity {
// ✅ 排除BaseEntity的id字段,避免@TableId冲突
@TableField(exist = false)
private Long id;
@TableId // ✅ 指定logId为主键
private Long logId;
// ... 其他字段
}
```
### 3. 重新编译和部署
```bash
# 1. 重新编译modules项目
mvn clean install -DskipTests -pl ljwx-boot-modules
# 2. 使用正确的启动方式
cd ljwx-boot
./run-local.sh # ✅ 正确的启动脚本,而不是在ljwx-boot-admin目录下执行
```
## ✅ **验证方法**
### 1. 前端验证
- **单选测试**: 点击单个复选框,不应触发全选
- **删除测试**: 检查Network面板,确认传递正确的logId值
### 2. 后端验证
```bash
# 查看后端日志,应该看到正确的ID
curl -X DELETE "http://localhost:3333/proxy-default/t_alert_action_log/" \
-H "Authorization: Bearer TOKEN" \
-d '{"ids":[12]}'
# 后端日志应显示:
# setNonNullParameter called with parameter: ["{\"ids\":[12]}"]
```
### 3. 数据库验证
```sql
-- 删除前
SELECT log_id FROM t_alert_action_log WHERE log_id = 12; -- 应该存在
-- 删除后
SELECT log_id FROM t_alert_action_log WHERE log_id = 12; -- 应该为空
```
## 🎯 **技术要点总结**
### MyBatis-Plus主键配置规则
1. **默认主键**: 如果没有@TableId注解,使用BaseEntity.id
2. **自定义主键**: 使用@TableId明确指定主键字段
3. **冲突解决**: 当继承BaseEntity时,需要排除BaseEntity.id字段
### 前端表格配置
1. **row-key**: 必须使用实际的主键字段
2. **类型安全**: 确保ID类型匹配(number vs string)
3. **删除操作**: 使用正确的字段名称
## 📝 **相关文件清单**
### 前端修改
- `ljwx-admin/src/views/alert/log/index.vue`
- `ljwx-admin/ALERT_LOG_FIX_SUMMARY.md`
### 后端修改
- `ljwx-boot/ljwx-boot-modules/src/main/java/com/ljwx/modules/health/domain/entity/TAlertActionLog.java`
### 启动方式修正
- ❌ `cd ljwx-boot-admin && mvn spring-boot:run`
- ✅ `cd ljwx-boot && ./run-local.sh`
## 🎉 **修复状态**
- ✅ **前端全选问题**: 已修复
- ✅ **前端删除ID传递**: 已修复
- ✅ **后端实体类主键配置**: 已修复
- ✅ **启动方式**: 已修正
- 🔄 **整体测试**: 待用户验证
现在所有修复都已完成,用户可以正常使用告警日志的单选、删除等功能了!
+110
View File
@@ -0,0 +1,110 @@
# 告警日志表格修复总结
## 问题分析
### 1. 全选问题
**原因**:NDataTable的`:row-key`配置错误
- **错误配置**:`:row-key="row => row.id"`
- **正确配置**:`:row-key="row => row.logId"`
- **说明**:数据库主键是`log_id`,对应的实体字段是`logId`,而不是基类的`id`字段
### 2. 删除失败问题
**原因**:删除操作使用了错误的字段
- **错误**:`handleDelete(row.id)` - id字段为0或未定义
- **正确**:`handleDelete(row.logId)` - 使用实际的主键logId
- **影响**:导致传递ids=[0]给后端删除接口
## 技术细节
### 数据库表结构
```sql
-- t_alert_action_log 表
log_id BIGINT PRIMARY KEY AUTO_INCREMENT -- 主键
id INTEGER -- 普通字段(可为空)
alert_id BIGINT -- 告警ID
-- ... 其他字段
```
### Java实体映射
```java
@TableName("t_alert_action_log")
public class TAlertActionLog extends BaseEntity {
private Long logId; // 对应 log_id (主键)
// BaseEntity.id 对应普通的 id 字段
}
```
### MyBatis映射配置
```xml
<resultMap id="TAlertActionLogResultMap">
<id column="log_id" property="logId" /> <!-- 主键映射 -->
<!-- 其他字段映射 -->
</resultMap>
```
## 修复方案
### 1. 修复表格row-key配置
```vue
<NDataTable
:row-key="row => row.logId" <!-- 使用logId作为row-key -->
v-model:checked-row-keys="checkedRowKeys"
<!-- 其他配置 -->
/>
```
### 2. 修复删除操作
```vue
<!-- 删除按钮 -->
<NPopconfirm onPositiveClick={() => handleDelete(row.logId)}>
<!-- 使用logId而不是id -->
</NPopconfirm>
```
```typescript
async function handleDelete(id: number) { // 参数类型为number
const { error, data: result } = await fetchDeleteAleractionLog(transDeleteParams([id]));
if (!error && result) {
await onDeleted();
}
}
```
### 3. 修复批量删除
确保`checkedRowKeys`中收集的是`logId`值,而不是`id`值。
## 验证方法
### 1. 测试单选
- 点击单个复选框,不应该触发全选
- 检查控制台`checkedRowKeys`数组内容
### 2. 测试删除
- 单个删除:检查发送的ID是否为正确的logId
- 批量删除:检查发送的IDs数组内容
### 3. API调试
```bash
# 正确的删除请求应该是:
POST /t_alert_action_log/
{
"ids": [12, 11, 10] # 实际的logId值
}
# 而不是:
{
"ids": [0] # 错误的id值
}
```
## 注意事项
1. **字段命名一致性**:确保前端、后端、数据库字段映射一致
2. **类型安全**:logId是number类型,确保类型匹配
3. **测试覆盖**:测试单选、多选、全选、删除等完整流程
## 相关文件
- `ljwx-admin/src/views/alert/log/index.vue` - 主页面
- `ljwx-boot/ljwx-boot-modules/src/main/java/com/ljwx/modules/health/domain/entity/TAlertActionLog.java` - 实体类
- `ljwx-boot/ljwx-boot-modules/src/main/java/com/ljwx/modules/health/repository/mapper/TAlertActionLogMapper.xml` - 映射文件
+171
View File
@@ -0,0 +1,171 @@
# Changelog
## [v1.0.5](https://github.com/paynezhuang/Ljwx-admin/compare/v1.0.4...v1.0.5) (2024-11-27)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**:
- merge remote project v1.3.8 &nbsp;-&nbsp; by @paynezhuang [<samp>(e4a4e)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/e4a4e59)
- all annotation permission & Organize search users &nbsp;-&nbsp; by @paynezhuang [<samp>(de9b5)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/de9b5b6)
- Time field formatted display &nbsp;-&nbsp; by @paynezhuang [<samp>(61e0a)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/61e0a02)
- Add notification announcement management &nbsp;-&nbsp; by @paynezhuang [<samp>(69b29)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/69b294a)
- role status default value &nbsp;-&nbsp; by @paynezhuang [<samp>(f5fc0)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/f5fc039)
- add file record module &nbsp;-&nbsp; by @paynezhuang [<samp>(c6bcb)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/c6bcb5e)
- add file manage &nbsp;-&nbsp; by @paynezhuang [<samp>(3896f)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/3896f31)
- merge remote project v1.3.9 &nbsp;-&nbsp; by @paynezhuang [<samp>(950af)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/950af4d)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**:
- Fix spelling errors &nbsp;-&nbsp; by @paynezhuang [<samp>(5f440)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/5f440ba)
- Fix spelling errors &nbsp;-&nbsp; by @paynezhuang [<samp>(d34c0)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/d34c0be)
- fix code generate isEdit status &nbsp;-&nbsp; by @paynezhuang [<samp>(e3a15)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/e3a1558)
- fix dict reset search &nbsp;-&nbsp; by @paynezhuang [<samp>(8b95b)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/8b95b33)
- Repair return response &nbsp;-&nbsp; by @paynezhuang [<samp>(3744f)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/3744f86)
### &nbsp;&nbsp;&nbsp;🔥 Performance
- **projects**: update nsetting the name &nbsp;-&nbsp; by @paynezhuang [<samp>(64233)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/642335c)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: Optimization type &nbsp;-&nbsp; by @paynezhuang [<samp>(611db)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/611db7a)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **projects**: file record changed file &nbsp;-&nbsp; by @paynezhuang [<samp>(99d99)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/99d99f9)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**:
- Rename &nbsp;-&nbsp; by @paynezhuang [<samp>(499e4)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/499e46a)
- delete console code &nbsp;-&nbsp; by @paynezhuang [<samp>(54cbb)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/54cbb6c)
- Table field display style &nbsp;-&nbsp; by @paynezhuang [<samp>(8529b)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/8529bcf)
- remove console log code &nbsp;-&nbsp; by @paynezhuang [<samp>(4fb71)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/4fb71f0)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
## [v1.0.4](https://github.com/paynezhuang/Ljwx-admin/compare/v1.0.3...v1.0.4) (2024-09-08)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**:
- add request language &nbsp;-&nbsp; by @paynezhuang [<samp>(b6f59)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/b6f597d)
- Using data dictionary rendering. &nbsp;-&nbsp; by @paynezhuang [<samp>(b1e22)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/b1e228e)
- Dict item add copy code &nbsp;-&nbsp; by @paynezhuang [<samp>(35d75)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/35d75a8)
- merge remote project v1.3.2 -> v.1.3.4 &nbsp;-&nbsp; by @paynezhuang [<samp>(11fc6)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/11fc61c)
- menu add iframeUrl field &nbsp;-&nbsp; by @paynezhuang [<samp>(e5fce)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/e5fce4a)
- add code generator &nbsp;-&nbsp; by @paynezhuang [<samp>(521f6)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/521f618)
- merge remote project v1.3.5 &nbsp;-&nbsp; by @paynezhuang [<samp>(d8579)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/d8579ed)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**:
- fix vertical-mix menu selected &nbsp;-&nbsp; by @paynezhuang [<samp>(87310)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/873108e)
- fix org add drawer title &nbsp;-&nbsp; by @paynezhuang [<samp>(b6e6e)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/b6e6e3c)
- remove position i18nKey fields &nbsp;-&nbsp; by @paynezhuang [<samp>(24491)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/2449180)
- fix commonSearchParams value &nbsp;-&nbsp; by @paynezhuang [<samp>(f32ca)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/f32cafd)
- Dict add error &nbsp;-&nbsp; by @paynezhuang [<samp>(723c8)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/723c821)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **projects**:
- Split typings and services &nbsp;-&nbsp; by @paynezhuang [<samp>(eea35)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/eea3517)
- Adjust the position of the data dictionary copy button. &nbsp;-&nbsp; by @paynezhuang [<samp>(ae746)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/ae74605)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**:
- tree Center the prompt when there is no data &nbsp;-&nbsp; by @paynezhuang [<samp>(d23a6)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/d23a6cc)
- fix mobile loss show &nbsp;-&nbsp; by @paynezhuang [<samp>(6087a)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/6087a1a)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
## [v1.0.3](https://github.com/paynezhuang/Ljwx-admin/compare/v1.0.2...v1.0.3) (2024-07-22)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**:
- add postion / orgUnits / dict &nbsp;-&nbsp; by @paynezhuang [<samp>(46787)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/46787e1)
- merge remote project v1.2.7 -> v1.3.1 &nbsp;-&nbsp; by @paynezhuang [<samp>(a99ef)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/a99ef2f)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**:
- user realName rule &nbsp;-&nbsp; by @paynezhuang [<samp>(59b2b)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/59b2b8d)
- Disable username field in user edit &nbsp;-&nbsp; by @paynezhuang [<samp>(d6cb6)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/d6cb65b)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: add CHANGELOG &nbsp;-&nbsp; by @paynezhuang [<samp>(36810)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/3681040)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
## [v1.0.2](https://github.com/paynezhuang/Ljwx-admin/compare/v1.0.1...v1.0.2) (2024-06-23)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**:
- merge remote project v1.1.4 -> v1.2.2 &nbsp;-&nbsp; by @paynezhuang [<samp>(e1ca4)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/e1ca469)
- add button auth dorpdown hook &nbsp;-&nbsp; by @paynezhuang [<samp>(ac396)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/ac39628)
- merge remote project v1.2.3 -> v1.2.6 &nbsp;-&nbsp; by @paynezhuang [<samp>(7cc61)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/7cc6149)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**: menu layout show &nbsp;-&nbsp; by @paynezhuang [<samp>(2f53d)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/2f53d15)
### &nbsp;&nbsp;&nbsp;🔥 Performance
- **components**: Delete text display &nbsp;-&nbsp; by @paynezhuang [<samp>(2437c)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/2437c1f)
- **projects**: update system title &nbsp;-&nbsp; by @paynezhuang [<samp>(94e9e)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/94e9e2d)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: Optimized code &nbsp;-&nbsp; by @paynezhuang [<samp>(55056)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/55056ce)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **projects**: Fix deprecated configuration config &nbsp;-&nbsp; by @paynezhuang [<samp>(7d8ba)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/7d8ba4c)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**:
- style scheduler select size &nbsp;-&nbsp; by @paynezhuang [<samp>(ae5df)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/ae5df16)
- button type use quaternary &nbsp;-&nbsp; by @paynezhuang [<samp>(c96d5)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/c96d586)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
## [v1.0.1](https://github.com/paynezhuang/Ljwx-admin/compare/undefined...v1.0.1) (2024-06-02)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**:
- init &nbsp;-&nbsp; by @paynezhuang [<samp>(950af)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/950af67)
- merge remote project v1.1.1 &nbsp;-&nbsp; by @paynezhuang [<samp>(8f8a0)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/8f8a0ca)
- add quartz and optimize code &nbsp;-&nbsp; by @paynezhuang [<samp>(94e60)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/94e600c)
- merge remote project v1.1.3 &nbsp;-&nbsp; by @paynezhuang [<samp>(ba4da)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/ba4dae7)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**:
- menu pahtParams and logs style width &nbsp;-&nbsp; by @paynezhuang [<samp>(f4c9a)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/f4c9a51)
- menu missing component field &nbsp;-&nbsp; by @paynezhuang [<samp>(0e1c2)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/0e1c2f6)
- fix menu transform data &nbsp;-&nbsp; by @paynezhuang [<samp>(5b3c2)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/5b3c20c)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **projects**: update deps &nbsp;-&nbsp; by @paynezhuang [<samp>(d6799)</samp>](https://github.com/paynezhuang/Ljwx-admin/commit/d679928)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
+38
View File
@@ -0,0 +1,38 @@
# 使用 Node.js 官方镜像作为基础镜像
FROM node:18-alpine
# 设置北京时区
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone
# 安装 pnpm
RUN corepack enable && corepack prepare pnpm@10.8.0 --activate
# 设置工作目录
WORKDIR /app
# 安装构建依赖
RUN apk add --no-cache python3 make g++
# 复制 package.json、pnpm-lock.yaml 和其他配置文件
COPY package.json pnpm-lock.yaml .npmrc pnpm-workspace.yaml ./
COPY packages/ ./packages/
COPY tsconfig.json vite.config.ts ./
# 安装依赖
RUN pnpm install --frozen-lockfile
# 复制源代码
COPY . .
# 设置环境变量
ENV NODE_ENV=development
ENV VITE_USER_NODE_ENV=development
# 暴露端口
EXPOSE 5173
# 启动开发服务器
CMD ["pnpm", "run", "dev"]
+68
View File
@@ -0,0 +1,68 @@
# 多阶段构建,减小镜像体积
FROM node:18-alpine AS builder
# 设置北京时区
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone
RUN corepack enable && corepack prepare pnpm@10.8.0 --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml .npmrc pnpm-workspace.yaml ./
COPY packages/ ./packages/
RUN pnpm install --frozen-lockfile
COPY src/ ./src/
COPY public/ ./public/
COPY build/ ./build/
COPY index.html ./
COPY vite.config.ts ./
COPY tsconfig.json ./
COPY uno.config.ts ./
ENV VITE_BASE_URL=./
ARG VITE_APP_TITLE=穿戴健康管理平台
ARG VITE_APP_DESC=穿戴健康管理平台
ENV VITE_APP_TITLE=${VITE_APP_TITLE}
ENV VITE_APP_DESC=${VITE_APP_DESC}
ENV VITE_ICON_PREFIX=icon
ENV VITE_ICON_LOCAL_PREFIX=icon-local
ENV VITE_AUTH_ROUTE_MODE=dynamic
ENV VITE_ROUTE_HOME=home
ENV VITE_MENU_ICON=mdi:menu
ENV VITE_HTTP_PROXY=Y
ENV VITE_ROUTER_HISTORY_MODE=hash
ENV VITE_SERVICE_SUCCESS_CODE=200
ENV VITE_SERVICE_LOGOUT_CODES=600,401
ENV VITE_SERVICE_MODAL_LOGOUT_CODES=401
ENV VITE_SERVICE_EXPIRED_TOKEN_CODES=40101
ENV VITE_STATIC_SUPER_ROLE=R_SUPER
ENV VITE_AUTH_BUTTON_MODE=route_meta
ENV VITE_SOURCE_MAP=N
ENV VITE_STORAGE_PREFIX=SOY_
ENV VITE_AUTOMATICALLY_DETECT_UPDATE=Y
ENV VITE_HTTP_PROXY=Y
ENV VITE_API_URL=/proxy-default
ENV VITE_SERVICE_BASE_URL=/proxy-default
ENV VITE_OTHER_SERVICE_BASE_URL='{"bigscreen": "/bigscreen/"}'
ARG VITE_BIGSCREEN_URL=http://localhost:8001
ARG VITE_CUSTOM_LOGO=false
ENV VITE_BIGSCREEN_URL=${VITE_BIGSCREEN_URL}
ENV VITE_CUSTOM_LOGO=${VITE_CUSTOM_LOGO}
RUN pnpm run build
# 生产环境nginx镜像
FROM nginx:alpine
# 设置北京时区
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone
RUN apk add --no-cache findutils # 添加find命令支持
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx/nginx.conf /etc/nginx/nginx.conf
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/docker-entrypoint.sh"]
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024 Zhuang Pan
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+326
View File
@@ -0,0 +1,326 @@
# LjwxAdmin 后台管理系统
![Node](https://img.shields.io/badge/Node-18-blue.svg)
![Version](https://img.shields.io/badge/Version-1.0.5-blue.svg)
[![License](https://img.shields.io/badge/License-Apache%20License%202.0-B9D6AF.svg)](./LICENSE)
<br/>
[![Author](https://img.shields.io/badge/Author-paynezhuang-green.svg)](https://github.com/paynezhuang)
[![Copyright](https://img.shields.io/badge/Copyright-2024%20Zhuang%20Pan%20@LjwxAdmin-green.svg)](https://github.com/paynezhuang)
### 项目简介
[`LingjingAdmin`](https://github.com/BrunoGao/platform) 基于 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 二次修改而来。它是一个清新优雅、高颜值且功能强大的后台管理模板,采用`Naive UI`组件库,并最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件,代码规范严谨,实现了自动化的文件路由系统。`LingjingAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。
在此特别感谢开源作者:[Soybean](https://github.com/honghuangdc) 。
### 项目特性
- **前沿技术应用**:采用 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。
- **清晰的项目架构**:采用 pnpm monorepo 架构,结构清晰,优雅易懂。
- **严格的代码规范**:遵循 [SoybeanJS 规范](https://docs.soybeanjs.cn/zh/standard),集成了eslint, prettier 和 simple-git-hooks,保证代码的规范性。
- **TypeScript**: 支持严格的类型检查,提高代码的可维护性。
- **丰富的主题配置**:内置多样的主题配置,与 UnoCSS 完美结合。
- **内置国际化方案**:轻松实现多语言支持。
- **自动化文件路由系统**:自动生成路由导入、声明和类型。更多细节请查看 [Elegant Router](https://github.com/soybeanjs/elegant-router)。
- **灵活的权限路由**:同时支持前端静态路由和后端动态路由。
- **丰富的页面组件**:内置多样页面和组件,包括403、404、500页面,以及布局组件、标签组件、主题配置组件等。
- **命令行工具**:内置高效的命令行工具,git提交、删除文件、发布等。
- **移动端适配**:完美支持移动端,实现自适应布局。
### 项目源码
| 名称 | 链接 |
|:--------|:------------------------------------------------------------------------|
| 前端 | [Lingjing-admin](https://github.com/BrunoGao/platform) |
| 后端 | [Lingjing-boot](https://github.com/BrunoGao/platform) |
| 后端扩展依赖库 | [Lingjing-boot-starter](https://github.com/BrunoGao/platform) |
### 项目启动
##### 前置环境
- **Git**: 你需要git来克隆和管理项目版本。
- **NodeJS**: >=18.12.0,推荐 18.19.0 或更高。
- **pnpm**: >= 8.7.0,推荐 8.14.0 或更高。
##### 克隆项目
```bash
git clone https://github.com/BrunoGao/platform.git
```
##### 项目启动
1. 安装依赖
```bash
pnpm i
```
> 由于本项目采用了 pnpm monorepo 的管理方式,因此请不要使用 npm 或 yarn 来安装依赖。
2. 启动项目,修改`.env.test`中`VITE_SERVICE_BASE_URL`对应的后端地址
```bash
pnpm dev
```
##### 构建项目
修改`.env.prod`中`VITE_SERVICE_BASE_URL`对应的后端地址
```bash
pnpm build
```
### Git 提交规范
本项目已内置 `commit` 命令,您可以通过执行 `pnpm commit` 来生成符合 [Conventional Commits]([conventionalcommits](https://www.conventionalcommits.org/)) 规范的提交信息。在提交PR时,请务必使用 `commit` 命令来创建提交信息,以确保信息的规范性。
### 示例图片
![](./doc/images/Login.png)
| ![Home](./doc/images/Home.png) | ![Home-2](./doc/images/Home-2.png) |
|--------------------------------|--------------------------------|
| ![User](./doc/images/User.png) | ![User-2](./doc/images/User-2.png) |
| ![Role](./doc/images/Role-Permission.png) | ![Role](./doc/images/Role-Menu.png) |
| ![Menu](./doc/images/Menu.png) | ![Menu-2](./doc/images/Menu-2.png) |
| ![Menu-3](./doc/images/Menu-3.png) |![User-Dark](./doc/images/User-Dark.png) |
| ![Mobile](./doc/images/Home-Mobile.png) | ![Mobile](./doc/images/User-Mobile.png) |
| ![Mobile](./doc/images/Menu-Mobile.png) | ![Mobile](./doc/images/Menu-Mobile-Dark.png)|
### 浏览器支持
推荐使用最新版的 Chrome 浏览器进行开发,以获得更好的体验。
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) |
| --- | --- | --- | --- | --- |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
### 特别鸣谢
- [SoybeanJS](https://github.com/soybeanjs)
- [Naive UI](https://www.naiveui.com/zh-CN/os-theme)
- [Vue](https://cn.vuejs.org/)
- 不一一列举,感谢所有开源项目的贡献者
### 开源协议
项目基于 [Apache License 2.0 © 2024 Zhuang Pan](./LICENSE) 协议,仅供学习参考,商业使用请遵循作者版权信息,作者不保证也不承担任何软件的使用风险。
## 功能说明
### 员工列表初始化
- 页面初始化时会根据部门自动拉取员工列表
- 部门变更时会自动更新员工列表
- 支持模糊搜索员工
### 表单验证优化 (最新)
#### 功能概述
- 优化员工新增/编辑表单的实时验证体验
- 员工名字和手机号格式错误时,修正后自动清除验证提示
- 无需关闭抽屉重新打开即可清除验证状态
#### 技术实现
- 修改表单验证规则触发器,支持 `input` 和 `change` 双重触发
- 优化 `userName` 和 `phone` 字段的验证响应机制
- 修复 `UserEdit` 类型定义,移除不必要的字段
#### 使用体验
1. **输入验证**:用户输入时立即显示格式错误提示
2. **实时清除**:修正输入后验证错误自动消失
3. **无缝操作**:提升表单操作的流畅性和用户体验
### 告警时序统计图
- 主页面饼图已替换为告警时序统计图
- 使用真实告警数据,自动根据数据实际时间范围生成时序图
- 支持中文显示:心率、血压、体温等告警类型,待处理、已响应、已解决等状态
- 按照alertType(折线图)、severityLevel(柱状图)维度分组展示
- 智能时间范围:根据数据跨度自动调整显示7-30天时间窗口
- 时间范围标注:标题显示数据总量和时间跨度(如:1/25-1/25)
- 支持交叉分析和多维度数据对比
- 无数据时显示友好提示信息
### 消息时序统计图
- 新增消息时序分析功能,自动根据数据实际时间范围生成图表
- 支持中文显示:系统消息、告警消息、通知消息等类型,已发送、已送达、已读等状态
- 按照messageType(折线图)、messageStatus(面积图)维度进行时序统计
- 智能时间范围:根据sentTime数据跨度自动调整显示窗口
- 时间范围标注:显示消息总量和时间跨度信息
- 支持多种消息类型的独立统计和状态追踪
### 🔄 用户管理 - 视图模式切换 (最新)
#### 功能概述
- 支持全部用户、员工、管理员三种视图模式切换
- 根据选择的模式动态过滤显示对应用户类型
- 在全部用户视图中显示用户类型标识(管理员/员工)
#### 使用方法
1. 在用户管理页面顶部选择视图模式:
- **全部用户**:显示所有用户,包含类型标识列
- **员工**:仅显示普通员工用户
- **管理员**:仅显示管理员用户
2. 视图模式参数说明:
```typescript
viewMode: 'all' | 'employee' | 'admin'
```
3. API调用示例:
```typescript
// 根据视图模式获取用户列表
fetchGetUserListByViewMode({
...params,
viewMode: 'employee' // 仅获取员工
})
```
#### 技术实现
- 前端组件:`UserViewModeSelector` - 视图模式选择器
- API函数:`fetchGetUserListByViewMode` - 支持viewMode参数
- 类型标识:使用 `NTag` 组件显示用户类型(管理员/员工)
### 👨‍💼 角色管理 - 管理员标识 (最新)
#### 功能概述
- 角色新增/编辑时支持设置是否为管理员角色
- 角色列表显示管理员类型标识
- 与后端数据库 `is_admin` 字段同步
#### 使用方法
1. **新增角色**:
- 在角色信息表单中选择"普通角色"或"管理员角色"
- 系统自动将 `isAdmin` 字段设置为 0 或 1
2. **编辑角色**:
- 可以修改现有角色的管理员属性
- 保存后立即生效
3. **角色列表**:
- 显示"是否管理员"列
- 管理员角色显示为红色标签
- 普通角色显示为绿色标签
#### 数据库字段
```sql
ALTER TABLE sys_role
ADD COLUMN is_admin TINYINT(1) NOT NULL DEFAULT 0
COMMENT '是否为管理员角色(0普通角色,1管理员角色)';
```
#### 技术实现
- 类型定义:`Role.isAdmin: number`
- 表单字段:单选按钮组选择角色类型
- 列表显示:使用 `NTag` 组件显示角色类型标识
- 国际化:支持中英文切换
## 🚀 部署说明
### 开发环境
```bash
# 安装依赖
npm install
# 启动开发服务器
npm run dev
```
### 生产环境
```bash
# 构建生产版本
npm run build
# 预览生产版本
npm run preview
```
## 📖 API文档
### 用户管理接口
#### 获取用户列表(支持视图模式)
```http
GET /sys_user/page?viewMode={mode}
```
参数:
- `viewMode`: 视图模式 (all/employee/admin)
- 其他分页和搜索参数...
响应:
```json
{
"code": 200,
"data": {
"records": [
{
"id": "1",
"userName": "admin",
"isAdmin": true,
"userType": "ADMIN"
}
]
}
}
```
### 角色管理接口
#### 新增/更新角色
```http
POST/PUT /sys_role/
```
请求体:
```json
{
"roleName": "系统管理员",
"roleCode": "ADMIN",
"isAdmin": 1,
"status": "1"
}
```
## 🔧 技术栈
- **框架**: Vue 3 + TypeScript
- **UI库**: Naive UI
- **状态管理**: Pinia
- **路由**: Vue Router
- **构建工具**: Vite
- **代码规范**: ESLint + Prettier
## 📝 更新日志
### v1.2.16 (2024-12-18)
- 🐛 **修复员工表单验证体验问题**
- 修复员工名字格式错误修正后需关闭抽屉才能清除验证提示的问题
- 优化表单验证触发机制,支持实时清除验证状态
- 改进用户名和手机号字段的验证响应,提升操作流畅性
- 修复UserEdit类型定义,确保类型安全
- 🔧 优化表单验证规则,支持input和change双重触发模式
### v1.2.15 (2024-12-18)
- ✨ 新增用户管理视图模式切换功能
- ✨ 新增角色管理员标识字段
- 🔧 优化用户类型显示和过滤
- 🌐 完善中英文国际化配置
- 🐛 修复viewMode参数传递问题
### v1.2.14
- 🔧 基础功能完善
- 📱 响应式界面优化
## 🤝 贡献指南
1. Fork 项目
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 打开 Pull Request
## 📄 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
+2
View File
@@ -0,0 +1,2 @@
export * from './proxy';
export * from './time';
+36
View File
@@ -0,0 +1,36 @@
import type { ProxyOptions } from 'vite';
import { createServiceConfig } from '../../src/utils/service';
/**
* Set http proxy
*
* @param env - The current env
* @param enable - If enable http proxy
*/
export function createViteProxy(env: Env.ImportMeta, enable: boolean) {
const isEnableHttpProxy = enable && env.VITE_HTTP_PROXY === 'Y';
if (!isEnableHttpProxy) return undefined;
const { baseURL, proxyPattern, other } = createServiceConfig(env);
const proxy: Record<string, ProxyOptions> = createProxyItem({ baseURL, proxyPattern });
other.forEach(item => {
Object.assign(proxy, createProxyItem(item));
});
return proxy;
}
function createProxyItem(item: App.Service.ServiceConfigItem) {
const proxy: Record<string, ProxyOptions> = {};
proxy[item.proxyPattern] = {
target: item.baseURL,
changeOrigin: true,
rewrite: path => path.replace(new RegExp(`^${item.proxyPattern}`), '')
};
return proxy;
}
+12
View File
@@ -0,0 +1,12 @@
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
export function getBuildTime() {
dayjs.extend(utc);
dayjs.extend(timezone);
const buildTime = dayjs.tz(Date.now(), 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
return buildTime;
}
+13
View File
@@ -0,0 +1,13 @@
import type { Plugin } from 'vite';
export function setupHtmlPlugin(buildTime: string) {
const plugin: Plugin = {
name: 'html-plugin',
apply: 'build',
transformIndexHtml(html) {
return html.replace('<head>', `<head>\n <meta name="buildTime" content="${buildTime}">`);
}
};
return plugin;
}
+24
View File
@@ -0,0 +1,24 @@
import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import VueDevtools from 'vite-plugin-vue-devtools';
import progress from 'vite-plugin-progress';
import { setupElegantRouter } from './router';
import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin';
import { setupHtmlPlugin } from './html';
export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
const plugins: PluginOption = [
vue(),
vueJsx(),
VueDevtools(),
setupElegantRouter(),
setupUnocss(viteEnv),
...setupUnplugin(viteEnv),
progress(),
setupHtmlPlugin(buildTime)
];
return plugins;
}
+55
View File
@@ -0,0 +1,55 @@
import type { RouteMeta } from 'vue-router';
import ElegantVueRouter from '@elegant-router/vue/vite';
import type { RouteKey } from '@elegant-router/types';
export function setupElegantRouter() {
return ElegantVueRouter({
layouts: {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
customRoutes: {
names: [
'exception_403',
'exception_404',
'exception_500',
'document_project',
'document_project-link',
'document_vue',
'document_vite',
'document_unocss',
'document_naive',
'document_antd'
]
},
routePathTransformer(routeName, routePath) {
const key = routeName as RouteKey;
if (key === 'login') {
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
const moduleReg = modules.join('|');
return `/login/:module(${moduleReg})?`;
}
return routePath;
},
onRouteMetaGen(routeName) {
const key = routeName as RouteKey;
const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
const meta: Partial<RouteMeta> = {
title: key,
i18nKey: `route.${key}` as App.I18n.I18nKey
};
if (constantRoutes.includes(key)) {
meta.constant = true;
}
return meta;
}
});
}
+30
View File
@@ -0,0 +1,30 @@
import process from 'node:process';
import path from 'node:path';
import unocss from '@unocss/vite';
import presetIcons from '@unocss/preset-icons';
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
export function setupUnocss(viteEnv: Env.ImportMeta) {
const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
/** The name of the local icon collection */
const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
return unocss({
presets: [
presetIcons({
prefix: `${VITE_ICON_PREFIX}-`,
scale: 1,
extraProperties: {
display: 'inline-block'
},
collections: {
[collectionName]: FileSystemIconLoader(localIconPath, svg => svg.replace(/^<svg\s/, '<svg width="1em" height="1em" '))
},
warn: true
})
]
});
}

Some files were not shown because too many files have changed in this diff Show More