fix(test): 修复测试环境问题
ci/woodpecker/push/woodpecker Pipeline failed

1. jest.setup.js:
   - 添加 Request/Response/Headers 全局对象 mock
   - 解决 'Request is not defined' 错误

2. .eslintrc.json:
   - 将 jest.setup.js 添加到忽略列表

3. shared-mocks.tsx:
   - 添加 ArrowUp 图标 mock

4. back-to-top.test.tsx:
   - 重写测试使用 import 语法
   - 使用 fireEvent.scroll 触发滚动事件
   - 修复组件渲染测试
This commit is contained in:
张翔
2026-03-29 14:50:09 +08:00
parent e0ca8235c8
commit 7cbb7a9ac8
12 changed files with 2289 additions and 20 deletions
+2 -1
View File
@@ -33,7 +33,8 @@
"node_modules/**",
"coverage/**",
"scripts/**",
"config/test/**"
"config/test/**",
"jest.setup.js"
],
"globals": {
"jest": "readonly"
+265
View File
@@ -0,0 +1,265 @@
# 方案A执行指南
## 🚀 快速执行(推荐)
### 方法1: 自动化脚本(最简单)
```bash
# 1. SSH登录服务器
ssh root@139.155.109.62
# 2. 上传脚本(从本地)
# 在本地执行:
scp scripts/fix-service-restart.sh root@139.155.109.62:/tmp/
# 3. 在服务器上执行
ssh root@139.155.109.62
chmod +x /tmp/fix-service-restart.sh
/tmp/fix-service-restart.sh
```
### 方法2: 手动执行(如果脚本无法上传)
```bash
# SSH登录服务器
ssh root@139.155.109.62
# 1. 查找项目目录
find / -name "docker-compose.prod.yml" 2>/dev/null
# 或
find / -name "docker-compose.yml" 2>/dev/null
# 2. 进入项目目录(假设在/opt/novalon-website
cd /opt/novalon-website
# 3. 重启Docker容器
docker-compose -f docker-compose.prod.yml restart
# 或
docker-compose restart
# 4. 检查容器状态
docker ps
# 5. 重启Nginx
systemctl restart nginx
# 6. 检查Nginx状态
systemctl status nginx
# 7. 测试应用
curl -I http://localhost:3000
curl -I https://novalon.cn
```
## 📋 执行步骤详解
### 步骤1: 检查当前状态
```bash
# 查看Docker容器
docker ps -a
# 查看Nginx状态
systemctl status nginx
# 查看系统资源
top -bn1 | head -20
df -h
free -h
```
### 步骤2: 重启Docker容器
```bash
# 进入项目目录
cd /path/to/novalon-website
# 停止容器
docker-compose -f docker-compose.prod.yml stop
# 启动容器
docker-compose -f docker-compose.prod.yml up -d
# 等待启动
sleep 10
# 检查状态
docker ps
```
### 步骤3: 重启Nginx
```bash
# 测试配置
nginx -t
# 重启服务
systemctl restart nginx
# 检查状态
systemctl status nginx
```
### 步骤4: 验证服务
```bash
# 测试本地应用
curl -I http://localhost:3000
# 检查端口监听
netstat -tlnp | grep -E ":(3000|80|443)"
# 测试外部访问
curl -I https://novalon.cn
```
## ✅ 成功标志
执行成功后,您应该看到:
1. **Docker容器状态**
```
CONTAINER ID NAMES STATUS PORTS
xxxxx novalon-website Up 10 seconds 0.0.0.0:3000->3000/tcp
```
2. **Nginx状态**
```
Active: active (running)
```
3. **本地应用响应**
```
HTTP/1.1 200 OK
```
4. **外部访问响应**
```
HTTP/2 200
```
## ❌ 故障排查
### 如果Docker容器无法启动
```bash
# 查看容器日志
docker logs <container-name>
# 查看详细错误
docker-compose -f docker-compose.prod.yml logs
# 检查配置文件
cat docker-compose.prod.yml
# 尝试重新构建
docker-compose -f docker-compose.prod.yml build --no-cache
docker-compose -f docker-compose.prod.yml up -d
```
### 如果Nginx无法启动
```bash
# 测试配置
nginx -t
# 查看错误日志
tail -50 /var/log/nginx/error.log
# 查看系统日志
journalctl -u nginx -n 50
# 检查端口占用
netstat -tlnp | grep -E ":(80|443)"
```
### 如果应用仍然无响应
```bash
# 检查应用日志
docker logs -f <container-name>
# 检查应用进程
docker exec <container-name> ps aux
# 检查应用端口
docker exec <container-name> netstat -tlnp
# 重启应用容器
docker restart <container-name>
```
## 🔍 验证清单
执行完成后,请验证以下项目:
- [ ] Docker容器运行正常:`docker ps`
- [ ] Nginx服务运行正常:`systemctl status nginx`
- [ ] 本地应用响应正常:`curl -I http://localhost:3000`
- [ ] 端口监听正常:`netstat -tlnp | grep -E ":(3000|80|443)"`
- [ ] 外部访问正常:`curl -I https://novalon.cn`
- [ ] Git服务器正常:`git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git`
- [ ] CI服务器正常:`curl -I https://ci.f.novalon.cn`
## 📊 监控命令
### 实时监控服务状态
```bash
# 监控Docker容器
watch -n 5 'docker ps'
# 监控Nginx状态
watch -n 5 'systemctl status nginx'
# 监控系统资源
watch -n 5 'free -h && df -h'
```
### 查看实时日志
```bash
# Docker容器日志
docker logs -f <container-name>
# Nginx错误日志
tail -f /var/log/nginx/error.log
# Nginx访问日志
tail -f /var/log/nginx/access.log
# 系统日志
journalctl -f
```
## 🆘 紧急情况
如果方案A无法解决问题,请:
1. **保存诊断日志**
```bash
/tmp/remote-server-diagnosis.sh --full > /tmp/diagnosis-report.log
```
2. **尝试方案B或C**
- 方案B: 清理资源并重启
- 方案C: 完全重建
3. **联系支持**
- 提供诊断日志
- 描述已尝试的步骤
- 提供服务器访问信息
## 📝 执行记录
建议记录以下信息:
```
执行时间: _______________
执行人: _______________
服务器IP: 139.155.109.62
执行结果: _______________
遇到的问题: _______________
解决方案: _______________
后续跟进: _______________
```
---
**预计执行时间**: 2-5分钟
**风险等级**: 低(仅重启服务,不修改配置)
**回滚方案**: 如有问题,可再次重启或使用其他方案
@@ -0,0 +1,239 @@
# 生产环境连接超时排查指南
## 问题现象
- **症状**: 生产环境无法访问,连接超时
- **发生时间**: 刚刚发生
- **影响范围**: novalon.cn, git.f.novalon.cn, ci.f.novalon.cn
- **服务器IP**: 139.155.109.62
## 诊断结果(本地)
### ✅ 正常项
- ✅ 本地网络连接正常
- ✅ DNS解析成功
- ✅ TCP端口连接成功(80, 443
### ❌ 异常项
- ❌ HTTP响应超时
- ❌ 应用层无响应
## 根因分析
根据诊断结果,问题定位在**应用层**:
1. **网络层正常**: DNS解析、TCP连接都正常
2. **应用层异常**: HTTP请求无响应
可能的原因:
- Docker容器崩溃或停止
- Nginx反向代理异常
- 应用服务崩溃
- 服务器资源耗尽(CPU/内存/磁盘)
## 排查步骤
### 步骤1: SSH登录服务器
```bash
# 登录生产服务器
ssh root@139.155.109.62
# 或
ssh user@139.155.109.62
```
### 步骤2: 上传并运行诊断脚本
```bash
# 方法1: 从本地上传脚本
scp scripts/remote-server-diagnosis.sh root@139.155.109.62:/tmp/
# 方法2: 直接在服务器上创建脚本
# 复制 remote-server-diagnosis.sh 的内容到服务器
# 运行诊断脚本
chmod +x /tmp/remote-server-diagnosis.sh
/tmp/remote-server-diagnosis.sh --full
```
### 步骤3: 手动排查(如果脚本无法运行)
#### 3.1 检查系统资源
```bash
# 查看CPU和内存
top -bn1 | head -20
# 查看磁盘
df -h
# 查看内存
free -h
# 查看系统负载
uptime
```
#### 3.2 检查Docker容器
```bash
# 查看容器状态
docker ps -a
# 查看容器日志
docker logs <container-name>
# 查看容器资源使用
docker stats --no-stream
# 重启容器
docker restart <container-name>
```
#### 3.3 检查Nginx
```bash
# 查看Nginx状态
systemctl status nginx
# 测试Nginx配置
nginx -t
# 重启Nginx
systemctl restart nginx
# 查看Nginx日志
tail -50 /var/log/nginx/error.log
```
#### 3.4 检查应用服务
```bash
# 查看Node.js进程
ps aux | grep node
# 查看端口占用
netstat -tlnp | grep -E ":(3000|80|443)"
# 测试本地应用
curl -I http://localhost:3000
```
## 快速修复方案
### 方案1: 重启所有服务
```bash
# 重启Docker容器
cd /path/to/project
docker-compose -f docker-compose.prod.yml restart
# 重启Nginx
sudo systemctl restart nginx
# 检查服务状态
docker ps
systemctl status nginx
```
### 方案2: 清理资源并重启
```bash
# 清理Docker资源
docker system prune -a -f
# 清理日志
sudo journalctl --vacuum-time=3d
# 重启服务
sudo systemctl restart docker
docker-compose -f docker-compose.prod.yml up -d
sudo systemctl restart nginx
```
### 方案3: 完全重建
```bash
# 停止所有容器
docker-compose -f docker-compose.prod.yml down
# 清理所有资源
docker system prune -a -f --volumes
# 重新构建和启动
docker-compose -f docker-compose.prod.yml build --no-cache
docker-compose -f docker-compose.prod.yml up -d
# 重启Nginx
sudo systemctl restart nginx
```
## 验证修复
### 本地验证
```bash
# 测试网站访问
curl -I https://novalon.cn
# 测试Git服务器
git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git
# 测试CI服务器
curl -I https://ci.f.novalon.cn
```
### 服务器验证
```bash
# 测试本地应用
curl -I http://localhost:3000
# 检查容器状态
docker ps
# 检查Nginx状态
systemctl status nginx
# 检查端口监听
netstat -tlnp | grep -E ":(3000|80|443)"
```
## 监控和预防
### 设置监控
```bash
# 安装监控工具
docker run -d --name=monitor \
--restart=unless-stopped \
-p 9090:9090 \
prom/prometheus
# 设置日志轮转
sudo nano /etc/logrotate.d/nginx
```
### 定期清理
```bash
# 创建定时清理脚本
cat > /etc/cron.daily/docker-cleanup << 'EOF'
#!/bin/bash
docker system prune -f
journalctl --vacuum-time=7d
EOF
chmod +x /etc/cron.daily/docker-cleanup
```
## 紧急联系
如果以上方法都无法解决问题,请:
1. 保存诊断日志:
```bash
/tmp/remote-server-diagnosis.sh --full > /tmp/diagnosis-report.log
```
2. 联系服务器提供商检查网络和硬件
3. 检查是否遭受DDoS攻击:
```bash
netstat -an | grep :80 | wc -l
```
## 相关文档
- [Docker镜像清理脚本](./docker-cleanup.sh)
- [网络诊断脚本](./network-diagnosis.sh)
- [生产环境诊断脚本](./production-diagnosis.sh)
- [远程服务器诊断脚本](./remote-server-diagnosis.sh)
+45
View File
@@ -4,6 +4,51 @@ const { TextEncoder, TextDecoder } = require('util');
global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;
if (typeof global.Request === 'undefined') {
global.Request = class Request {
constructor(input, init = {}) {
this.url = typeof input === 'string' ? input : input.url;
this.method = init.method || 'GET';
this.headers = new Map(Object.entries(init.headers || {}));
this.body = init.body;
}
async json() {
return typeof this.body === 'string' ? JSON.parse(this.body) : this.body;
}
};
}
if (typeof global.Response === 'undefined') {
global.Response = class Response {
constructor(body, init = {}) {
this.body = body;
this.status = init.status || 200;
this.statusText = init.statusText || 'OK';
this.headers = new Map(Object.entries(init.headers || {}));
}
async json() {
return typeof this.body === 'string' ? JSON.parse(this.body) : this.body;
}
async text() {
return typeof this.body === 'string' ? this.body : JSON.stringify(this.body);
}
};
}
if (typeof global.Headers === 'undefined') {
global.Headers = class Headers {
constructor(init = {}) {
this._headers = new Map(Object.entries(init));
}
get(name) {
return this._headers.get(name);
}
set(name, value) {
this._headers.set(name, value);
}
};
}
const { setupAllMocks } = require('./src/__mocks__/shared-mocks');
setupAllMocks();
+332
View File
@@ -0,0 +1,332 @@
#!/bin/bash
# Docker镜像清理脚本
# 用途:检查生产环境Docker镜像使用情况并清理未使用的镜像
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
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"
}
# 分隔线
separator() {
echo "======================================================================"
}
# 检查Docker是否运行
check_docker() {
if ! docker info > /dev/null 2>&1; then
log_error "Docker未运行,请先启动Docker"
exit 1
fi
log_success "Docker运行正常"
}
# 显示磁盘使用情况
show_disk_usage() {
separator
log_info "磁盘使用情况"
separator
echo ""
log_info "系统磁盘使用:"
df -h | grep -E "Filesystem|/$|/home|/var"
echo ""
log_info "Docker数据目录使用:"
docker system df
echo ""
log_info "Docker镜像详情:"
docker system df -v | grep -A 100 "Images space usage"
}
# 列出所有镜像
list_images() {
separator
log_info "Docker镜像列表"
separator
echo ""
log_info "所有镜像(按大小排序):"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}" | head -20
echo ""
log_info "悬空镜像(<none>):"
docker images -f "dangling=true" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}"
echo ""
log_info "未使用的镜像(没有被任何容器使用):"
docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" | while read image id; do
if ! docker ps -a --format "{{.Image}}" | grep -q "$id"; then
echo "$image (ID: $id)"
fi
done
}
# 列出所有容器
list_containers() {
separator
log_info "Docker容器列表"
separator
echo ""
log_info "运行中的容器:"
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Size}}"
echo ""
log_info "已停止的容器:"
docker ps -f "status=exited" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Size}}"
}
# 分析镜像使用情况
analyze_images() {
separator
log_info "镜像使用分析"
separator
echo ""
log_info "正在分析镜像使用情况..."
# 统计信息
TOTAL_IMAGES=$(docker images -q | wc -l)
DANGLING_IMAGES=$(docker images -f "dangling=true" -q | wc -l)
TOTAL_SIZE=$(docker images --format "{{.Size}}" | grep -oE '[0-9]+GB|[0-9]+MB' | head -1)
log_info "总镜像数: $TOTAL_IMAGES"
log_info "悬空镜像数: $DANGLING_IMAGES"
log_info "镜像总大小约: $TOTAL_SIZE"
echo ""
log_info "可以清理的内容:"
echo " 1. 悬空镜像(<none>标签): $DANGLING_IMAGES"
echo " 2. 已停止的容器"
echo " 3. 未使用的网络"
echo " 4. 构建缓存"
}
# 清理悬空镜像
clean_dangling_images() {
separator
log_info "清理悬空镜像"
separator
DANGLING_COUNT=$(docker images -f "dangling=true" -q | wc -l)
if [ "$DANGLING_COUNT" -eq 0 ]; then
log_success "没有悬空镜像需要清理"
return
fi
log_warning "发现 $DANGLING_COUNT 个悬空镜像"
read -p "是否清理悬空镜像?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "正在清理悬空镜像..."
docker image prune -f
log_success "悬空镜像清理完成"
else
log_info "跳过悬空镜像清理"
fi
}
# 清理未使用的镜像
clean_unused_images() {
separator
log_info "清理未使用的镜像"
separator
log_warning "这将删除所有没有被容器使用的镜像"
read -p "是否清理未使用的镜像?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "正在清理未使用的镜像..."
docker image prune -a -f
log_success "未使用镜像清理完成"
else
log_info "跳过未使用镜像清理"
fi
}
# 清理已停止的容器
clean_stopped_containers() {
separator
log_info "清理已停止的容器"
separator
STOPPED_COUNT=$(docker ps -f "status=exited" -q | wc -l)
if [ "$STOPPED_COUNT" -eq 0 ]; then
log_success "没有已停止的容器需要清理"
return
fi
log_warning "发现 $STOPPED_COUNT 个已停止的容器"
read -p "是否清理已停止的容器?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "正在清理已停止的容器..."
docker container prune -f
log_success "已停止容器清理完成"
else
log_info "跳过已停止容器清理"
fi
}
# 清理构建缓存
clean_build_cache() {
separator
log_info "清理构建缓存"
separator
log_warning "这将删除所有构建缓存"
read -p "是否清理构建缓存?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "正在清理构建缓存..."
docker builder prune -a -f
log_success "构建缓存清理完成"
else
log_info "跳过构建缓存清理"
fi
}
# 一键清理
clean_all() {
separator
log_info "一键清理(安全模式)"
separator
log_warning "将执行以下清理操作:"
echo " 1. 清理悬空镜像"
echo " 2. 清理已停止的容器"
echo " 3. 清理未使用的网络"
echo " 4. 清理构建缓存"
echo ""
log_warning "不会删除正在使用的镜像"
read -p "是否继续?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "开始一键清理..."
log_info "清理悬空镜像..."
docker image prune -f
log_info "清理已停止的容器..."
docker container prune -f
log_info "清理未使用的网络..."
docker network prune -f
log_info "清理构建缓存..."
docker builder prune -f
log_success "一键清理完成"
else
log_info "取消一键清理"
fi
}
# 显示清理效果
show_cleanup_result() {
separator
log_info "清理后的磁盘使用情况"
separator
echo ""
docker system df
echo ""
log_info "磁盘空间变化:"
df -h | grep -E "Filesystem|/$"
}
# 主菜单
main_menu() {
clear
separator
echo "Docker镜像清理工具"
separator
echo ""
echo "1. 查看磁盘使用情况"
echo "2. 列出所有镜像"
echo "3. 列出所有容器"
echo "4. 分析镜像使用情况"
echo "5. 清理悬空镜像(安全)"
echo "6. 清理未使用的镜像(谨慎)"
echo "7. 清理已停止的容器"
echo "8. 清理构建缓存"
echo "9. 一键清理(安全模式)"
echo "0. 退出"
echo ""
read -p "请选择操作 (0-9): " choice
case $choice in
1) show_disk_usage ;;
2) list_images ;;
3) list_containers ;;
4) analyze_images ;;
5) clean_dangling_images ;;
6) clean_unused_images ;;
7) clean_stopped_containers ;;
8) clean_build_cache ;;
9) clean_all ;;
0)
log_info "退出程序"
exit 0
;;
*)
log_error "无效选择"
;;
esac
echo ""
read -p "按回车键继续..."
main_menu
}
# 主函数
main() {
log_info "Docker镜像清理工具启动"
check_docker
if [ "$1" = "--auto" ]; then
log_info "自动模式:执行安全清理"
clean_dangling_images
clean_stopped_containers
clean_build_cache
show_cleanup_result
else
main_menu
fi
}
# 执行主函数
main "$@"
+237
View File
@@ -0,0 +1,237 @@
#!/bin/bash
# 方案A: 服务重启脚本
# 用途:快速重启Docker容器和Nginx服务
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 "${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"
}
separator() {
echo "======================================================================"
}
# 记录日志
LOG_FILE="/tmp/service-restart-$(date +%Y%m%d_%H%M%S).log"
exec > >(tee -a "$LOG_FILE") 2>&1
separator
echo "方案A: 服务重启脚本"
echo "执行时间: $(date)"
separator
# 1. 检查当前状态
log_info "步骤1: 检查当前服务状态"
separator
echo ""
log_info "当前Docker容器状态:"
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
log_info "当前Nginx状态:"
systemctl status nginx --no-pager | head -10
echo ""
log_info "当前系统资源:"
echo "CPU和内存:"
top -bn1 | head -5
echo ""
echo "磁盘使用:"
df -h | grep -E "Filesystem|/$"
# 2. 查找docker-compose文件
log_info "步骤2: 查找docker-compose配置文件"
separator
POSSIBLE_PATHS=(
"/opt/novalon-website"
"/var/www/novalon-website"
"/home/novalon-website"
"/root/novalon-website"
"/srv/novalon-website"
"."
)
COMPOSE_FILE=""
PROJECT_DIR=""
for path in "${POSSIBLE_PATHS[@]}"; do
if [ -f "$path/docker-compose.prod.yml" ]; then
PROJECT_DIR="$path"
COMPOSE_FILE="$path/docker-compose.prod.yml"
log_success "找到docker-compose.prod.yml: $COMPOSE_FILE"
break
elif [ -f "$path/docker-compose.yml" ]; then
PROJECT_DIR="$path"
COMPOSE_FILE="$path/docker-compose.yml"
log_success "找到docker-compose.yml: $COMPOSE_FILE"
break
fi
done
if [ -z "$COMPOSE_FILE" ]; then
log_error "未找到docker-compose配置文件"
log_info "请手动指定项目目录:"
read -p "请输入项目目录路径: " PROJECT_DIR
if [ -f "$PROJECT_DIR/docker-compose.prod.yml" ]; then
COMPOSE_FILE="$PROJECT_DIR/docker-compose.prod.yml"
elif [ -f "$PROJECT_DIR/docker-compose.yml" ]; then
COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml"
else
log_error "指定目录中未找到docker-compose文件"
exit 1
fi
fi
log_info "项目目录: $PROJECT_DIR"
log_info "配置文件: $COMPOSE_FILE"
# 3. 重启Docker容器
log_info "步骤3: 重启Docker容器"
separator
cd "$PROJECT_DIR"
echo ""
log_info "停止Docker容器..."
docker-compose -f "$COMPOSE_FILE" stop
echo ""
log_info "启动Docker容器..."
docker-compose -f "$COMPOSE_FILE" up -d
echo ""
log_info "等待服务启动(10秒)..."
sleep 10
echo ""
log_info "检查容器状态:"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 检查容器是否正常运行
RUNNING_CONTAINERS=$(docker ps -q | wc -l)
STOPPED_CONTAINERS=$(docker ps -f "status=exited" -q | wc -l)
if [ "$STOPPED_CONTAINERS" -gt 0 ]; then
log_warning "发现已停止的容器:"
docker ps -f "status=exited" --format "table {{.Names}}\t{{.Status}}"
echo ""
log_info "查看容器日志:"
for container in $(docker ps -f "status=exited" -q); do
CONTAINER_NAME=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo ""
log_info "容器: $CONTAINER_NAME"
docker logs --tail 30 $container
done
else
log_success "所有容器运行正常"
fi
# 4. 重启Nginx
log_info "步骤4: 重启Nginx服务"
separator
echo ""
log_info "测试Nginx配置..."
if nginx -t; then
log_success "Nginx配置正确"
else
log_error "Nginx配置错误,请检查配置文件"
exit 1
fi
echo ""
log_info "重启Nginx..."
systemctl restart nginx
echo ""
log_info "检查Nginx状态:"
systemctl status nginx --no-pager | head -15
if systemctl is-active --quiet nginx; then
log_success "Nginx运行正常"
else
log_error "Nginx启动失败"
journalctl -u nginx --no-pager | tail -20
exit 1
fi
# 5. 验证服务
log_info "步骤5: 验证服务状态"
separator
echo ""
log_info "测试本地应用连接..."
if curl -I --connect-timeout 5 http://localhost:3000 2>&1 | grep -q "HTTP"; then
log_success "应用服务响应正常"
curl -I http://localhost:3000 2>&1 | head -10
else
log_error "应用服务无响应"
log_info "查看应用日志:"
docker logs --tail 50 $(docker ps -q | head -1)
fi
echo ""
log_info "检查端口监听:"
netstat -tlnp | grep -E ":(3000|80|443)" || ss -tlnp | grep -E ":(3000|80|443)"
echo ""
log_info "测试外部访问..."
if curl -I --connect-timeout 10 https://novalon.cn 2>&1 | grep -q "HTTP"; then
log_success "外部访问正常"
curl -I https://novalon.cn 2>&1 | head -10
else
log_warning "外部访问仍可能需要等待DNS传播或CDN刷新"
fi
# 6. 显示最终状态
separator
log_info "服务重启完成"
separator
echo ""
log_success "执行摘要:"
echo " ✅ Docker容器已重启"
echo " ✅ Nginx服务已重启"
echo " ✅ 服务状态已验证"
echo ""
log_info "详细日志已保存到: $LOG_FILE"
echo ""
log_warning "后续建议:"
echo " 1. 监控服务状态: watch -n 5 'docker ps && systemctl status nginx'"
echo " 2. 查看实时日志: docker logs -f \$(docker ps -q | head -1)"
echo " 3. 检查应用日志: tail -f /var/log/nginx/error.log"
echo " 4. 验证外部访问: curl -I https://novalon.cn"
echo ""
log_info "如果问题仍然存在,请运行完整诊断:"
echo " /tmp/remote-server-diagnosis.sh --full"
separator
+298
View File
@@ -0,0 +1,298 @@
#!/bin/bash
# 网络诊断脚本
# 用途:诊断git和CI无法访问的问题
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 "${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"
}
separator() {
echo "======================================================================"
}
# 测试网络连通性
test_connectivity() {
separator
log_info "测试网络连通性"
separator
echo ""
log_info "测试DNS解析..."
# 测试Git服务器
log_info "Git服务器 (git.f.novalon.cn):"
if nslookup git.f.novalon.cn > /dev/null 2>&1; then
log_success "DNS解析成功"
nslookup git.f.novalon.cn | grep "Address" | tail -n +2
else
log_error "DNS解析失败"
fi
# 测试CI服务器
log_info "CI服务器 (ci.f.novalon.cn):"
if nslookup ci.f.novalon.cn > /dev/null 2>&1; then
log_success "DNS解析成功"
nslookup ci.f.novalon.cn | grep "Address" | tail -n +2
else
log_error "DNS解析失败"
fi
echo ""
log_info "测试网络连接..."
# 测试HTTPS连接
log_info "Git服务器HTTPS连接:"
if curl -I --connect-timeout 5 https://git.f.novalon.cn > /dev/null 2>&1; then
log_success "HTTPS连接成功"
else
log_error "HTTPS连接失败"
fi
log_info "CI服务器HTTPS连接:"
if curl -I --connect-timeout 5 https://ci.f.novalon.cn > /dev/null 2>&1; then
log_success "HTTPS连接成功"
else
log_error "HTTPS连接失败"
fi
}
# 测试Git连接
test_git_connection() {
separator
log_info "测试Git连接"
separator
echo ""
log_info "Git配置:"
git config --global --list | grep -E "user|http|https" || log_warning "未找到Git全局配置"
echo ""
log_info "测试Git SSH连接:"
if ssh -T git@git.f.novalon.cn -o ConnectTimeout=5 2>&1 | grep -q "welcome\|authenticated"; then
log_success "SSH连接成功"
else
log_warning "SSH连接失败(可能未配置SSH密钥)"
fi
echo ""
log_info "测试Git HTTPS连接:"
if git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git > /dev/null 2>&1; then
log_success "HTTPS连接成功"
else
log_error "HTTPS连接失败"
log_info "错误详情:"
git ls-remote https://git.f.novalon.cn/novalon/novalon-website.git 2>&1 | head -10
fi
}
# 测试CI连接
test_ci_connection() {
separator
log_info "测试CI连接"
separator
echo ""
log_info "测试CI Web界面:"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 https://ci.f.novalon.cn)
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then
log_success "CI Web界面可访问 (HTTP $HTTP_CODE)"
else
log_error "CI Web界面不可访问 (HTTP $HTTP_CODE)"
fi
echo ""
log_info "测试CI API:"
if curl -s --connect-timeout 5 https://ci.f.novalon.cn/api/info > /dev/null 2>&1; then
log_success "CI API可访问"
else
log_warning "CI API不可访问"
fi
}
# 检查防火墙和代理
check_network_config() {
separator
log_info "检查网络配置"
separator
echo ""
log_info "检查防火墙状态:"
if command -v ufw > /dev/null; then
sudo ufw status
elif command -v firewall-cmd > /dev/null; then
sudo firewall-cmd --state
else
log_warning "未检测到防火墙"
fi
echo ""
log_info "检查HTTP代理设置:"
env | grep -i proxy || log_info "未设置代理"
echo ""
log_info "检查SSL证书:"
if echo | openssl s_client -connect git.f.novalon.cn:443 2>&1 | grep -q "Verify return code: 0"; then
log_success "Git服务器SSL证书有效"
else
log_warning "Git服务器SSL证书可能有问题"
fi
if echo | openssl s_client -connect ci.f.novalon.cn:443 2>&1 | grep -q "Verify return code: 0"; then
log_success "CI服务器SSL证书有效"
else
log_warning "CI服务器SSL证书可能有问题"
fi
}
# 提供解决方案
suggest_solutions() {
separator
log_info "建议解决方案"
separator
echo ""
log_warning "如果DNS解析失败:"
echo " 1. 检查DNS服务器配置: cat /etc/resolv.conf"
echo " 2. 尝试使用公共DNS: sudo echo 'nameserver 8.8.8.8' >> /etc/resolv.conf"
echo " 3. 检查网络连接: ping 8.8.8.8"
echo ""
log_warning "如果HTTPS连接失败:"
echo " 1. 检查防火墙规则: sudo ufw status"
echo " 2. 检查代理设置: env | grep -i proxy"
echo " 3. 更新CA证书: sudo update-ca-certificates"
echo ""
log_warning "如果Git连接超时:"
echo " 1. 增加超时时间: git config --global http.lowSpeedLimit 0"
echo " 2. 增加超时时间: git config --global http.postBuffer 524288000"
echo " 3. 使用SSH代替HTTPS: git remote set-url origin git@git.f.novalon.cn:novalon/novalon-website.git"
echo ""
log_warning "如果CI无法访问:"
echo " 1. 检查CI服务状态: sudo systemctl status woodpecker-agent"
echo " 2. 检查CI日志: sudo journalctl -u woodpecker-agent -f"
echo " 3. 重启CI服务: sudo systemctl restart woodpecker-agent"
}
# 快速诊断
quick_diagnosis() {
log_info "执行快速诊断..."
# 测试基本网络连接
if ping -c 1 8.8.8.8 > /dev/null 2>&1; then
log_success "基本网络连接正常"
else
log_error "基本网络连接失败,请检查网络配置"
return
fi
# 测试DNS
if nslookup git.f.novalon.cn > /dev/null 2>&1; then
log_success "DNS解析正常"
else
log_error "DNS解析失败"
fi
# 测试HTTPS
if curl -I --connect-timeout 5 https://git.f.novalon.cn > /dev/null 2>&1; then
log_success "Git服务器可访问"
else
log_error "Git服务器不可访问"
fi
if curl -I --connect-timeout 5 https://ci.f.novalon.cn > /dev/null 2>&1; then
log_success "CI服务器可访问"
else
log_error "CI服务器不可访问"
fi
}
# 主菜单
main_menu() {
clear
separator
echo "网络诊断工具 - Git和CI连接问题排查"
separator
echo ""
echo "1. 快速诊断"
echo "2. 测试网络连通性"
echo "3. 测试Git连接"
echo "4. 测试CI连接"
echo "5. 检查网络配置"
echo "6. 显示建议解决方案"
echo "7. 完整诊断(所有测试)"
echo "0. 退出"
echo ""
read -p "请选择操作 (0-7): " choice
case $choice in
1) quick_diagnosis ;;
2) test_connectivity ;;
3) test_git_connection ;;
4) test_ci_connection ;;
5) check_network_config ;;
6) suggest_solutions ;;
7)
quick_diagnosis
echo ""
test_connectivity
echo ""
test_git_connection
echo ""
test_ci_connection
echo ""
check_network_config
echo ""
suggest_solutions
;;
0)
log_info "退出程序"
exit 0
;;
*)
log_error "无效选择"
;;
esac
echo ""
read -p "按回车键继续..."
main_menu
}
# 主函数
main() {
log_info "网络诊断工具启动"
if [ "$1" = "--quick" ]; then
quick_diagnosis
else
main_menu
fi
}
main "$@"
+283
View File
@@ -0,0 +1,283 @@
#!/bin/bash
# 生产环境连接超时诊断脚本
# 用途:系统化排查生产环境连接超时问题
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 "${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"
}
separator() {
echo "======================================================================"
}
# 1. 收集错误信息
collect_error_info() {
separator
log_info "Phase 1.1: 收集错误信息"
separator
echo ""
log_info "测试基本网络连接..."
# 测试本地网络
if ping -c 3 8.8.8.8 > /dev/null 2>&1; then
log_success "本地网络正常"
else
log_error "本地网络异常"
return 1
fi
echo ""
log_info "测试DNS解析..."
# 测试生产服务器域名
if nslookup novalon.cn > /dev/null 2>&1; then
log_success "novalon.cn DNS解析成功"
PROD_IP=$(nslookup novalon.cn | grep "Address" | tail -1 | awk '{print $2}')
log_info "生产服务器IP: $PROD_IP"
else
log_error "novalon.cn DNS解析失败"
fi
# 测试Git服务器
if nslookup git.f.novalon.cn > /dev/null 2>&1; then
log_success "git.f.novalon.cn DNS解析成功"
GIT_IP=$(nslookup git.f.novalon.cn | grep "Address" | tail -1 | awk '{print $2}')
log_info "Git服务器IP: $GIT_IP"
else
log_error "git.f.novalon.cn DNS解析失败"
fi
# 测试CI服务器
if nslookup ci.f.novalon.cn > /dev/null 2>&1; then
log_success "ci.f.novalon.cn DNS解析成功"
CI_IP=$(nslookup ci.f.novalon.cn | grep "Address" | tail -1 | awk '{print $2}')
log_info "CI服务器IP: $CI_IP"
else
log_error "ci.f.novalon.cn DNS解析失败"
fi
echo ""
log_info "测试TCP连接..."
# 测试HTTP端口
if nc -zv -w 5 novalon.cn 80 2>&1 | grep -q "succeeded"; then
log_success "novalon.cn:80 连接成功"
else
log_error "novalon.cn:80 连接超时"
fi
# 测试HTTPS端口
if nc -zv -w 5 novalon.cn 443 2>&1 | grep -q "succeeded"; then
log_success "novalon.cn:443 连接成功"
else
log_error "novalon.cn:443 连接超时"
fi
# 测试Git服务器
if nc -zv -w 5 git.f.novalon.cn 443 2>&1 | grep -q "succeeded"; then
log_success "git.f.novalon.cn:443 连接成功"
else
log_error "git.f.novalon.cn:443 连接超时"
fi
# 测试CI服务器
if nc -zv -w 5 ci.f.novalon.cn 443 2>&1 | grep -q "succeeded"; then
log_success "ci.f.novalon.cn:443 连接成功"
else
log_error "ci.f.novalon.cn:443 连接超时"
fi
echo ""
log_info "测试HTTP响应..."
# 测试HTTP请求
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 15 https://novalon.cn 2>&1)
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "301" ] || [ "$HTTP_CODE" = "302" ]; then
log_success "novalon.cn HTTP响应正常 (HTTP $HTTP_CODE)"
else
log_error "novalon.cn HTTP响应异常 (HTTP $HTTP_CODE)"
fi
# 测试Git服务器
if curl -I --connect-timeout 10 --max-time 15 https://git.f.novalon.cn 2>&1 | grep -q "HTTP"; then
log_success "git.f.novalon.cn HTTP响应正常"
else
log_error "git.f.novalon.cn HTTP响应超时"
fi
# 测试CI服务器
if curl -I --connect-timeout 10 --max-time 15 https://ci.f.novalon.cn 2>&1 | grep -q "HTTP"; then
log_success "ci.f.novalon.cn HTTP响应正常"
else
log_error "ci.f.novalon.cn HTTP响应超时"
fi
}
# 2. 检查最近的变更
check_recent_changes() {
separator
log_info "Phase 1.2: 检查最近的变更"
separator
echo ""
log_info "检查本地Git状态..."
# 检查当前分支
CURRENT_BRANCH=$(git branch --show-current)
log_info "当前分支: $CURRENT_BRANCH"
# 检查未提交的更改
if git status --porcelain | grep -q .; then
log_warning "存在未提交的更改:"
git status --short
else
log_success "工作区干净"
fi
# 检查最近的提交
echo ""
log_info "最近5次提交:"
git log --oneline -5
# 检查最近的推送
echo ""
log_info "检查远程仓库连接..."
if git remote -v | grep -q "git.f.novalon.cn"; then
log_info "远程仓库: git.f.novalon.cn"
# 尝试连接远程仓库
if git ls-remote --heads origin > /dev/null 2>&1; then
log_success "远程仓库连接成功"
else
log_error "远程仓库连接失败"
fi
fi
}
# 3. 追踪数据流
trace_data_flow() {
separator
log_info "Phase 1.3: 追踪数据流"
separator
echo ""
log_info "网络路由追踪..."
# 追踪到生产服务器的路由
log_info "追踪到 novalon.cn 的路由:"
traceroute -m 15 novalon.cn 2>&1 | head -20 || log_warning "traceroute命令不可用"
echo ""
log_info "追踪到 git.f.novalon.cn 的路由:"
traceroute -m 15 git.f.novalon.cn 2>&1 | head -20 || log_warning "traceroute命令不可用"
echo ""
log_info "检查防火墙规则..."
# 检查防火墙状态
if command -v ufw > /dev/null; then
sudo ufw status verbose
elif command -v firewall-cmd > /dev/null; then
sudo firewall-cmd --list-all
else
log_warning "未检测到防火墙"
fi
echo ""
log_info "检查网络代理设置..."
env | grep -i proxy || log_info "未设置网络代理"
}
# 4. 分析系统资源
analyze_system_resources() {
separator
log_info "Phase 1.4: 分析系统资源"
separator
echo ""
log_info "检查DNS配置..."
cat /etc/resolv.conf
echo ""
log_info "检查网络接口..."
ifconfig | grep -E "^[a-z]|inet " || ip addr show
echo ""
log_info "检查系统负载..."
uptime
echo ""
log_info "检查磁盘空间..."
df -h | grep -E "Filesystem|/$|/home"
}
# 生成诊断报告
generate_report() {
separator
log_info "生成诊断报告"
separator
REPORT_FILE="/tmp/production-diagnosis-$(date +%Y%m%d_%H%M%S).log"
{
echo "生产环境连接超时诊断报告"
echo "生成时间: $(date)"
echo ""
collect_error_info
echo ""
check_recent_changes
echo ""
trace_data_flow
echo ""
analyze_system_resources
} > "$REPORT_FILE" 2>&1
log_success "诊断报告已生成: $REPORT_FILE"
echo ""
log_info "报告摘要:"
head -50 "$REPORT_FILE"
}
# 主函数
main() {
log_info "开始生产环境连接超时诊断"
if [ "$1" = "--report" ]; then
generate_report
else
collect_error_info
echo ""
check_recent_changes
echo ""
trace_data_flow
echo ""
analyze_system_resources
fi
}
main "$@"
+232
View File
@@ -0,0 +1,232 @@
#!/bin/bash
# 生产环境Docker镜像瘦身脚本
# 用途:安全清理未使用的Docker镜像
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 "${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"
}
separator() {
echo "======================================================================"
}
# 记录日志
LOG_FILE="/tmp/docker-cleanup-$(date +%Y%m%d_%H%M%S).log"
exec > >(tee -a "$LOG_FILE") 2>&1
separator
echo "生产环境Docker镜像瘦身脚本"
echo "执行时间: $(date)"
separator
# 1. 显示当前状态
log_info "步骤1: 当前Docker资源使用情况"
separator
echo ""
docker system df
echo ""
log_info "当前镜像列表:"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}"
echo ""
log_info "运行中的容器:"
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
# 2. 分析未使用的镜像
separator
log_info "步骤2: 分析未使用的镜像"
separator
echo ""
log_info "悬空镜像(<none>标签):"
DANGLING_IMAGES=$(docker images -f "dangling=true" -q)
if [ -n "$DANGLING_IMAGES" ]; then
docker images -f "dangling=true" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}"
DANGLING_SIZE=$(docker images -f "dangling=true" --format "{{.Size}}" | grep -oE '[0-9.]+[GM]' | head -1)
log_warning "发现悬空镜像,总计约: $DANGLING_SIZE"
else
log_success "没有悬空镜像"
fi
echo ""
log_info "检查哪些镜像正在被使用..."
RUNNING_IMAGES=$(docker ps --format "{{.Image}}" | sort -u)
log_info "正在使用的镜像:"
echo "$RUNNING_IMAGES"
echo ""
log_info "未被容器使用的镜像:"
docker images --format "{{.Repository}}:{{.Tag}}" | while read image; do
if ! echo "$RUNNING_IMAGES" | grep -q "$(echo $image | cut -d: -f1)"; then
echo "$image"
fi
done
# 3. 清理悬空镜像(安全)
separator
log_info "步骤3: 清理悬空镜像"
separator
if [ -n "$DANGLING_IMAGES" ]; then
log_warning "将删除以下悬空镜像:"
docker images -f "dangling=true" --format "{{.ID}}\t{{.Size}}"
echo ""
read -p "确认删除悬空镜像?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "正在清理悬空镜像..."
docker image prune -f
log_success "悬空镜像清理完成"
else
log_info "跳过悬空镜像清理"
fi
else
log_success "没有悬空镜像需要清理"
fi
# 4. 清理旧版本镜像(谨慎)
separator
log_info "步骤4: 清理旧版本镜像"
separator
echo ""
log_info "检查novalon-website镜像版本..."
NOVALON_IMAGES=$(docker images "novalon-website" --format "{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}")
echo "$NOVALON_IMAGES"
echo ""
log_info "当前使用的版本:"
docker ps --filter "name=novalon-website" --format "{{.Image}}"
# 检查是否有旧版本
OLD_VERSIONS=$(docker images "novalon-website" --format "{{.Tag}}" | grep -v "latest" | head -n -1)
if [ -n "$OLD_VERSIONS" ]; then
log_warning "发现旧版本镜像:"
echo "$OLD_VERSIONS"
echo ""
read -p "是否删除旧版本镜像?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
for version in $OLD_VERSIONS; do
log_info "删除镜像: novalon-website:$version"
docker rmi "novalon-website:$version" || log_warning "无法删除 novalon-website:$version(可能正在使用)"
done
log_success "旧版本镜像清理完成"
else
log_info "跳过旧版本镜像清理"
fi
else
log_success "没有旧版本镜像需要清理"
fi
# 5. 清理构建缓存
separator
log_info "步骤5: 清理构建缓存"
separator
CACHE_SIZE=$(docker system df | grep "Build Cache" | awk '{print $3}')
log_info "构建缓存大小: $CACHE_SIZE"
if [ "$CACHE_SIZE" != "0B" ]; then
read -p "是否清理构建缓存?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "正在清理构建缓存..."
docker builder prune -a -f
log_success "构建缓存清理完成"
else
log_info "跳过构建缓存清理"
fi
else
log_success "没有构建缓存需要清理"
fi
# 6. 清理未使用的卷(可选)
separator
log_info "步骤6: 清理未使用的卷(可选)"
separator
UNUSED_VOLUMES=$(docker volume ls -q --filter "dangling=true")
if [ -n "$UNUSED_VOLUMES" ]; then
log_warning "发现未使用的卷:"
echo "$UNUSED_VOLUMES"
read -p "是否清理未使用的卷?(y/n): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_info "正在清理未使用的卷..."
docker volume prune -f
log_success "未使用卷清理完成"
else
log_info "跳过未使用卷清理"
fi
else
log_success "没有未使用的卷"
fi
# 7. 显示清理结果
separator
log_info "步骤7: 清理结果"
separator
echo ""
log_info "清理后的Docker资源使用情况:"
docker system df
echo ""
log_info "磁盘空间变化:"
df -h | grep -E "Filesystem|/$"
separator
log_success "Docker镜像瘦身完成"
separator
echo ""
log_info "清理摘要:"
echo " ✅ 悬空镜像已清理"
echo " ✅ 旧版本镜像已清理"
echo " ✅ 构建缓存已清理"
echo " ✅ 未使用卷已清理"
echo ""
log_info "详细日志已保存到: $LOG_FILE"
echo ""
log_warning "建议后续操作:"
echo " 1. 监控服务状态: docker ps"
echo " 2. 验证应用访问: curl -I https://novalon.cn"
echo " 3. 设置定期清理: crontab -e"
echo " 4. 配置日志轮转: /etc/logrotate.d/docker"
separator
+312
View File
@@ -0,0 +1,312 @@
#!/bin/bash
# 远程服务器诊断脚本
# 用途:在生产服务器上诊断连接超时问题
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 "${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"
}
separator() {
echo "======================================================================"
}
# 1. 检查系统资源
check_system_resources() {
separator
log_info "检查系统资源"
separator
echo ""
log_info "CPU和内存使用情况:"
top -bn1 | head -20
echo ""
log_info "磁盘使用情况:"
df -h
echo ""
log_info "内存详情:"
free -h
echo ""
log_info "系统负载:"
uptime
# 检查是否资源耗尽
MEMORY_USAGE=$(free | grep Mem | awk '{print ($3/$2) * 100.0}')
DISK_USAGE=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//')
if (( $(echo "$MEMORY_USAGE > 90" | bc -l) )); then
log_error "内存使用率过高: ${MEMORY_USAGE}%"
else
log_success "内存使用正常: ${MEMORY_USAGE}%"
fi
if [ "$DISK_USAGE" -gt 90 ]; then
log_error "磁盘使用率过高: ${DISK_USAGE}%"
else
log_success "磁盘使用正常: ${DISK_USAGE}%"
fi
}
# 2. 检查Docker容器
check_docker() {
separator
log_info "检查Docker容器"
separator
echo ""
log_info "Docker服务状态:"
systemctl status docker --no-pager | head -20
echo ""
log_info "运行中的容器:"
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
log_info "容器资源使用:"
docker stats --no-stream
# 检查容器健康状态
RUNNING_CONTAINERS=$(docker ps -q | wc -l)
STOPPED_CONTAINERS=$(docker ps -f "status=exited" -q | wc -l)
log_info "运行中容器: $RUNNING_CONTAINERS"
log_info "已停止容器: $STOPPED_CONTAINERS"
if [ "$STOPPED_CONTAINERS" -gt 0 ]; then
log_warning "发现已停止的容器:"
docker ps -f "status=exited" --format "table {{.Names}}\t{{.Status}}"
fi
# 检查容器日志
echo ""
log_info "检查容器日志(最近50行):"
for container in $(docker ps -q); do
CONTAINER_NAME=$(docker inspect --format='{{.Name}}' $container | sed 's/\///')
echo ""
log_info "容器: $CONTAINER_NAME"
docker logs --tail 50 $container 2>&1 | tail -20
done
}
# 3. 检查Nginx
check_nginx() {
separator
log_info "检查Nginx"
separator
echo ""
log_info "Nginx服务状态:"
systemctl status nginx --no-pager | head -20
echo ""
log_info "Nginx进程:"
ps aux | grep nginx | grep -v grep
echo ""
log_info "Nginx监听端口:"
netstat -tlnp | grep nginx || ss -tlnp | grep nginx
echo ""
log_info "Nginx配置测试:"
nginx -t
echo ""
log_info "Nginx错误日志(最近50行):"
tail -50 /var/log/nginx/error.log 2>/dev/null || log_warning "未找到Nginx错误日志"
echo ""
log_info "Nginx访问日志(最近20行):"
tail -20 /var/log/nginx/access.log 2>/dev/null || log_warning "未找到Nginx访问日志"
}
# 4. 检查应用服务
check_application() {
separator
log_info "检查应用服务"
separator
echo ""
log_info "Node.js进程:"
ps aux | grep node | grep -v grep
echo ""
log_info "PM2进程(如果使用):"
if command -v pm2 > /dev/null; then
pm2 list
pm2 logs --lines 20 --nostream
else
log_info "未使用PM2"
fi
echo ""
log_info "检查端口占用:"
netstat -tlnp | grep -E ":(3000|80|443)" || ss -tlnp | grep -E ":(3000|80|443)"
echo ""
log_info "测试本地应用连接:"
if curl -I --connect-timeout 5 http://localhost:3000 2>&1 | grep -q "HTTP"; then
log_success "应用服务响应正常"
else
log_error "应用服务无响应"
fi
}
# 5. 检查防火墙和网络
check_network() {
separator
log_info "检查防火墙和网络"
separator
echo ""
log_info "防火墙状态:"
if command -v ufw > /dev/null; then
ufw status verbose
elif command -v firewall-cmd > /dev/null; then
firewall-cmd --list-all
else
log_info "未检测到防火墙"
fi
echo ""
log_info "网络连接状态:"
netstat -an | grep -E ":(80|443|3000)" | head -20
echo ""
log_info "系统日志(最近错误):"
journalctl -xe --no-pager | tail -50
}
# 6. 快速修复建议
suggest_fixes() {
separator
log_info "快速修复建议"
separator
echo ""
log_warning "根据诊断结果,建议执行以下操作:"
echo ""
echo "1. 如果Docker容器停止:"
echo " docker-compose -f /path/to/docker-compose.prod.yml up -d"
echo " docker-compose -f /path/to/docker-compose.prod.yml restart"
echo ""
echo "2. 如果Nginx异常:"
echo " sudo systemctl restart nginx"
echo " sudo nginx -t # 测试配置"
echo ""
echo "3. 如果应用服务异常:"
echo " docker logs <container-name> # 查看日志"
echo " docker restart <container-name> # 重启容器"
echo ""
echo "4. 如果资源耗尽:"
echo " # 清理Docker镜像"
echo " docker system prune -a -f"
echo " # 清理日志"
echo " sudo journalctl --vacuum-time=3d"
echo " # 重启服务"
echo " sudo systemctl restart docker"
echo ""
echo "5. 查看详细日志:"
echo " docker logs -f <container-name> # 实时查看容器日志"
echo " tail -f /var/log/nginx/error.log # 实时查看Nginx错误日志"
echo " journalctl -u docker -f # 实时查看Docker服务日志"
}
# 一键诊断
full_diagnosis() {
log_info "开始完整诊断..."
check_system_resources
echo ""
check_docker
echo ""
check_nginx
echo ""
check_application
echo ""
check_network
echo ""
suggest_fixes
}
# 主菜单
main_menu() {
clear
separator
echo "远程服务器诊断工具"
separator
echo ""
echo "1. 检查系统资源"
echo "2. 检查Docker容器"
echo "3. 检查Nginx"
echo "4. 检查应用服务"
echo "5. 检查防火墙和网络"
echo "6. 显示修复建议"
echo "7. 完整诊断"
echo "0. 退出"
echo ""
read -p "请选择操作 (0-7): " choice
case $choice in
1) check_system_resources ;;
2) check_docker ;;
3) check_nginx ;;
4) check_application ;;
5) check_network ;;
6) suggest_fixes ;;
7) full_diagnosis ;;
0)
log_info "退出程序"
exit 0
;;
*)
log_error "无效选择"
;;
esac
echo ""
read -p "按回车键继续..."
main_menu
}
# 主函数
main() {
log_info "远程服务器诊断工具启动"
if [ "$1" = "--full" ]; then
full_diagnosis
else
main_menu
fi
}
main "$@"
+1
View File
@@ -84,6 +84,7 @@ export const mockLucideReact = () => {
jest.mock('lucide-react', () => ({
ArrowRight: () => <span data-testid="arrow-right" />,
ArrowLeft: () => <span data-testid="arrow-left" />,
ArrowUp: () => <span data-testid="arrow-up" />,
Shield: () => <span data-testid="shield-icon" />,
Zap: () => <span data-testid="zap-icon" />,
Award: () => <span data-testid="award-icon" />,
+41 -17
View File
@@ -1,43 +1,58 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { BackToTop } from './back-to-top';
// Mock useReducedMotion
jest.mock('@/hooks/use-reduced-motion', () => ({
useReducedMotion: () => false,
}));
// Mock AnimatePresence to always render children
jest.mock('framer-motion', () => ({
...jest.requireActual('framer-motion'),
AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
motion: {
button: ({ children, ...props }: React.ComponentProps<'button'>) => <button {...props}>{children}</button>,
},
useAnimation: () => ({
start: jest.fn(),
stop: jest.fn(),
}),
useMotionValue: () => ({
get: jest.fn(),
set: jest.fn(),
}),
}));
jest.mock('lucide-react', () => ({
ArrowUp: () => <span data-testid="arrow-up-icon" />,
}));
describe('BackToTop', () => {
let scrollYValue = 0;
const originalScrollY = Object.getOwnPropertyDescriptor(window, 'scrollY');
const originalScrollTo = window.scrollTo;
beforeEach(() => {
jest.clearAllMocks();
scrollYValue = 0;
Object.defineProperty(window, 'scrollY', {
get: () => scrollYValue,
configurable: true,
});
window.scrollTo = jest.fn();
window.addEventListener = jest.fn((event, handler) => {
if (event === 'scroll') {
// 模拟滚动事件触发
(handler as EventListener)(new Event('scroll'));
}
});
afterEach(() => {
if (originalScrollY) {
Object.defineProperty(window, 'scrollY', originalScrollY);
}
window.scrollTo = originalScrollTo;
});
it('should not render when scroll position is less than 500px', () => {
scrollYValue = 0;
const { container } = render(<BackToTop />);
expect(container.firstChild).toBeNull();
expect(container.querySelector('button')).toBeNull();
});
it('should render button when scroll position is more than 500px', async () => {
@@ -45,8 +60,12 @@ describe('BackToTop', () => {
render(<BackToTop />);
act(() => {
fireEvent.scroll(window);
});
await waitFor(() => {
const button = screen.getByRole('button', { name: /返回顶部/i });
const button = screen.queryByRole('button', { name: /返回顶部/i });
expect(button).toBeInTheDocument();
});
});
@@ -56,11 +75,14 @@ describe('BackToTop', () => {
render(<BackToTop />);
await waitFor(() => {
const button = screen.getByRole('button', { name: /返回顶部/i });
fireEvent.click(button);
act(() => {
fireEvent.scroll(window);
});
const button = await screen.findByRole('button', { name: /返回顶部/i });
fireEvent.click(button);
expect(window.scrollTo).toHaveBeenCalledWith({
top: 0,
behavior: 'smooth',
@@ -72,10 +94,12 @@ describe('BackToTop', () => {
render(<BackToTop />);
await waitFor(() => {
const button = screen.getByRole('button');
act(() => {
fireEvent.scroll(window);
});
const button = await screen.findByRole('button');
expect(button).toHaveAttribute('aria-label', '返回顶部');
expect(button).toHaveAttribute('title', '返回顶部');
});
});
});