This commit is contained in:
zlwu committed 2025-12-26 00:44:19 +08:00
1 parent 34a843e790
commit abfc5cd746
14 files changed
+1595 -1148

No files matched your search

-102
View File
@@ -1,102 +0,0 @@
# 学生换班级模块
## 📁 文件夹说明
本文件夹包含学生换班级业务模块的相关设计文档和数据库脚本。
## 📋 文件清单
### 🗄️ 数据库设计
- **[学生换班级记录表.sql](学生换班级记录表.sql)** - 学生换班级记录表的完整建表SQL脚本
- **[学生换班级记录表设计说明.md](学生换班级记录表设计说明.md)** - 详细的表结构设计说明文档
### 🔧 处理脚本(新增)
- **[process_student_class_change.sh](process_student_class_change.sh)** - 准实时处理换班级工单的主脚本
- **[control_scripts.sh](control_scripts.sh)** - 脚本控制程序,支持启动、停止、重启等操作
- **[health_check.sh](health_check.sh)** - 服务健康检查和监控脚本
- **[data.conf](data.conf)** - 数据库连接和脚本运行配置
- **[README_换班级脚本.md](README_换班级脚本.md)** - 换班级处理脚本的详细技术文档
## 🎯 业务概述
学生换班级记录表用于记录学校所有学生换班级的操作历史,包括:
- 学生主动申请换班级
- 学校根据教学需要调整班级
- 学生转专业导致的班级变化
- 其他特殊情况的换班级操作
## 🔑 核心功能
### 1. 完整的换班级记录
- 记录学生的原班级和新班级信息
- 保存换班级的原因和类型
- 记录换班级的时间和生效时间
- 状态管理:未处理/已处理
### 2. 前端界面支持
- 学生信息管理页面集成换班级功能
- 单选学生进行换班级操作
- 三级级联选择新班级(学院→专业→班级)
- 实时展示学生原班级信息
### 3. 数据追溯和审计
- 完整的操作历史记录
- 支持数据追溯和审计
- 提供统计分析基础数据
## 📊 数据结构特点
### 遵循项目规范
- ✅ 包含所有强制性基础字段
- ✅ 使用雪花ID作为主键
- ✅ 支持多租户数据隔离
- ✅ 逻辑删除,避免物理删除
- ✅ 新增状态字段:0-未处理,1-已处理
### 关联关系
- 学生信息:关联 `xg_student_msg` 表
- 班级信息:关联 `xg_class_msg` 表
- 专业信息:关联 `xg_major_msg` 表
- 学院信息:关联 `sys_dept` 表(dept_category区分层级)
### 索引优化
- 基础索引:租户、删除标志、创建时间
- 业务索引:学生ID、班级ID、时间等
- 唯一约束:防止重复记录
## 🔄 业务流程
```
学生申请 → 提交申请 → 辅导员审批 → 院系领导审批 → 教务处审批 → 执行换班级 → 生效
```
## 📈 统计分析支持
支持多种维度的统计分析:
- 按时间统计换班级趋势
- 按原因分析换班级分布
- 按班级统计换班级情况
- 按审批状态统计处理情况
## 🚀 使用建议
### 开发注意事项
1. **数据一致性**: 换班级操作必须确保相关数据的一致性
2. **业务规则**: 严格遵守学校的换班级管理规定
3. **审批流程**: 必须经过完整的审批流程
4. **文档同步**: 代码变更及时更新相关文档
### 性能优化
1. **分页查询**: 大数据量查询必须分页
2. **索引使用**: 充分利用设计的索引
3. **缓存策略**: 对常用数据进行适当缓存
## 📞 技术支持
如有疑问或需要技术支持,请联系项目技术团队。
---
**创建时间**: 2025-01-25
**维护人员**: AI Assistant
**版本**: v1.0.0
-248
View File
@@ -1,248 +0,0 @@
# 学生换班级处理脚本
## 📁 概述
本脚本用于准实时处理学生换班级工单,实现学生信息的自动更新和干部信息的终止处理。参考退学、休学、复学、毕业脚本的架构设计,提供完整的换班级业务处理能力。
## 🎯 业务需求
### 1. 干部信息终止
- 参考退学等脚本思路,终止学生的干部职务
- 设置干部状态为"换班级终止"(cadre_status = '5')
- 记录终止原因和时间
### 2. 学生信息更新
- 更新学生基本班级信息(学院、专业、班级、年级等)
- 更新预留字段,从新班级表获取完整信息
- 特别处理 `academic_level` 字段,对应 `xg_class_msg.class_type`
## 📋 文件清单
### 🔧 核心脚本
- **[process_student_class_change.sh](process_student_class_change.sh)** - 主处理脚本,实现换班级业务逻辑
- **[control_scripts.sh](control_scripts.sh)** - 脚本控制程序,支持启动、停止、重启等操作
- **[health_check.sh](health_check.sh)** - 服务健康检查脚本
### ⚙️ 配置文件
- **[data.conf](data.conf)** - 数据库连接配置
### 📊 数据库设计
- **[学生换班级记录表.sql](学生换班级记录表.sql)** - 换班级记录表结构
- **[学生换班级记录表设计说明.md](学生换班级记录表设计说明.md)** - 详细设计文档
## 🔄 处理流程
```
换班级工单提交 → 脚本检测 → 获取新班级信息 → 终止干部职务 → 更新学生信息 → 标记处理完成
```
### 详细步骤
1. **工单检测**: 扫描 `status = '0'` 的换班级记录
2. **班级信息获取**: 从 `xg_class_msg` 表获取新班级完整信息
3. **干部信息终止**:
- 设置 `is_current = 0`
- 设置 `cadre_status = '5'`(换班级终止)
- 记录终止日期和原因
4. **学生信息更新**:
- 更新基础班级信息(college_id, major_id, class_id等)
- 更新预留字段(school_system, academic_level, training_type等)
- academic_level 对应 class_type
5. **工单状态更新**: 设置 `status = '1'` 标记处理完成
## 🗄️ 数据映射
### 新班级信息获取
```sql
SELECT
college_id, college_name, major_id, major_name,
class_code, class_name, class_type, grade,
training_type, counselor_id, counselor_name,
class_teacher_id, class_teacher_name, school_system
FROM xg_class_msg
WHERE id = {new_class_id}
```
### 学生信息更新字段
```sql
UPDATE xg_student_msg SET
-- 基础班级信息
college_id = '{college_id}',
college_name = '{college_name}',
major_id = '{major_id}',
major_name = '{major_name}',
class_id = '{new_class_id}',
class_name = '{class_name}',
grade = '{grade}',
-- 预留字段 (7182-7195行)
school_system = '{school_system}',
academic_level = '{class_type}', -- 关键映射
training_type = '{training_type}',
counselor_id = {counselor_id},
counselor_name = '{counselor_name}',
class_teacher_id = {class_teacher_id},
class_teacher_name = '{class_teacher_name}',
-- 更新信息
update_by = {operator_id},
update_time = NOW()
WHERE id = {student_id}
```
## 🚀 部署使用
### 1. 配置数据库连接
```bash
vim data.conf
# 修改数据库连接信息
```
### 2. 启动服务
```bash
# 启动换班级处理服务
./control_scripts.sh start class_change
# 或启动所有服务
./control_scripts.sh startall
```
### 3. 查看服务状态
```bash
# 查看所有服务状态
./control_scripts.sh statusall
# 查看特定服务
./control_scripts.sh status class_change
```
### 4. 健康检查
```bash
# 执行健康检查
./health_check.sh
# 生成详细报告
./health_check.sh --report
```
### 5. 停止服务
```bash
# 停止换班级处理服务
./control_scripts.sh stop class_change
# 或停止所有服务
./control_scripts.sh stopall
```
## 📊 监控日志
### 日志位置
- **主日志**: `./student_class_change/process_student_class_change_*.log`
- **包含信息**: 处理详情、错误信息、性能统计
### 日志内容示例
```
[2025-01-25 10:00:00] [INFO] Starting student class change processing daemon
[2025-01-25 10:00:00] [INFO] Processing student change - ID: 123, Student: 20240001 (456), New Class ID: 789
[2025-01-25 10:00:00] [INFO] Successfully processed student class change - ID: 123, Student: 20240001
```
## ⚙️ 配置参数
### data.conf 配置项
```bash
# 数据库配置
DB_HOST="localhost"
DB_PORT="3306"
DB_NAME="hzdyjs_xg"
DB_USER="root"
DB_PASS="password"
# 脚本配置
LOG_DIR="./student_class_change"
BATCH_SIZE=100 # 每批处理数量
MAX_RETRY=3 # 最大重试次数
RETRY_DELAY=5 # 重试间隔(秒)
CYCLE_INTERVAL=600 # 检查周期(秒)
```
## 🔧 故障排除
### 常见问题
1. **数据库连接失败**
```bash
# 检查配置
cat data.conf
# 测试连接
mysql -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASS $DB_NAME -e "SELECT 1;"
```
2. **服务无法启动**
```bash
# 检查脚本权限
ls -la *.sh
# 设置执行权限
chmod +x *.sh
```
3. **处理失败**
```bash
# 查看日志
tail -f ./student_class_change/*.log
# 检查数据
mysql -e "SELECT * FROM xg_student_class_change WHERE status = '0';"
```
### 性能优化
1. **索引检查**: 确保相关表的索引完整
2. **批量处理**: 每次处理适当数量的记录
3. **日志轮转**: 定期清理旧日志文件
4. **监控资源**: 监控CPU、内存、磁盘使用情况
## 📈 统计监控
### 处理统计
```sql
-- 查看处理统计
SELECT
COUNT(*) as total_records,
SUM(CASE WHEN status = '0' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = '1' THEN 1 ELSE 0 END) as processed
FROM xg_student_class_change
WHERE deleted = 0;
-- 查看最近处理情况
SELECT
DATE(update_time) as process_date,
COUNT(*) as processed_count
FROM xg_student_class_change
WHERE status = '1'
AND update_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(update_time)
ORDER BY process_date DESC;
```
## 🔄 扩展开发
### 添加新的业务逻辑
1. 在 `process_student_class_change()` 函数中添加处理步骤
2. 更新事务SQL,确保数据一致性
3. 添加相应的日志记录
4. 更新健康检查逻辑
### 自定义配置
1. 在 `data.conf` 中添加新配置项
2. 在脚本中读取和使用配置
3. 更新文档说明
## 📞 技术支持
如有疑问或需要技术支持,请联系项目技术团队。
---
**创建时间**: 2025-01-25
**维护人员**: AI Assistant
**版本**: v1.0.0
-332
View File
@@ -1,332 +0,0 @@
#!/bin/bash
# ===========================================
# 学生换班级处理脚本控制程序
# 用于启动、停止、重启处理脚本
# ===========================================
set -euo pipefail
# Load configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/data.conf"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "错误:配置文件 $CONFIG_FILE 未找到!"
exit 1
fi
source "$CONFIG_FILE"
# Script definitions
declare -A SCRIPTS=(
["class_change"]="process_student_class_change.sh"
)
# PID files
PID_DIR="${SCRIPT_DIR}/pids"
mkdir -p "$PID_DIR"
# Functions
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message"
}
get_pid_file() {
local script_name="$1"
echo "${PID_DIR}/${script_name}.pid"
}
get_lock_file() {
local script_name="$1"
echo "${SCRIPT_DIR}/${script_name}.lock"
}
is_running() {
local script_name="$1"
local pid_file=$(get_pid_file "$script_name")
local lock_file=$(get_lock_file "$script_name")
# Check PID file
if [[ -f "$pid_file" ]]; then
local pid=$(cat "$pid_file")
if kill -0 "$pid" 2>/dev/null; then
return 0 # Running
else
log "WARN" "删除脚本 $script_name 的过期PID文件"
rm -f "$pid_file"
fi
fi
# Check lock file
if [[ -f "$lock_file" ]]; then
local lock_pid=$(cat "$lock_file")
if kill -0 "$lock_pid" 2>/dev/null; then
return 0 # Running
else
log "WARN" "删除脚本 $script_name 的过期锁定文件"
rm -f "$lock_file"
fi
fi
return 1 # Not running
}
start_script() {
local script_name="$1"
local script_file="${SCRIPT_DIR}/${SCRIPTS[$script_name]}"
local pid_file=$(get_pid_file "$script_name")
if [[ ! -f "$script_file" ]]; then
log "ERROR" "脚本文件未找到: $script_file"
return 1
fi
if is_running "$script_name"; then
log "INFO" "$script_name 已在运行"
return 0
fi
log "INFO" "正在启动 $script_name..."
chmod +x "$script_file"
# Start script in background
nohup "$script_file" > /dev/null 2>&1 &
local pid=$!
# Save PID
echo $pid > "$pid_file"
# Wait a moment and check if it's still running
sleep 2
if kill -0 "$pid" 2>/dev/null; then
log "INFO" "$script_name 启动成功 (PID: $pid)"
return 0
else
log "ERROR" "$script_name 启动失败"
rm -f "$pid_file"
return 1
fi
}
stop_script() {
local script_name="$1"
local pid_file=$(get_pid_file "$script_name")
local lock_file=$(get_lock_file "$script_name")
if ! is_running "$script_name"; then
log "INFO" "$script_name 未运行"
return 0
fi
log "INFO" "正在停止 $script_name..."
# Try graceful shutdown first
local pids=()
[[ -f "$pid_file" ]] && pids+=($(cat "$pid_file"))
[[ -f "$lock_file" ]] && pids+=($(cat "$lock_file"))
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
fi
done
# Wait for graceful shutdown
local count=0
while [[ $count -lt 10 ]] && is_running "$script_name"; do
sleep 1
((count++))
done
# Force kill if still running
if is_running "$script_name"; then
log "WARN" "Graceful shutdown failed, force killing $script_name..."
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
kill -9 "$pid" 2>/dev/null || true
fi
done
sleep 1
fi
# Clean up files
rm -f "$pid_file" "$lock_file"
if is_running "$script_name"; then
log "ERROR" "Failed to stop $script_name"
return 1
else
log "INFO" "$script_name stopped successfully"
return 0
fi
}
restart_script() {
local script_name="$1"
log "INFO" "正在重启 $script_name..."
if stop_script "$script_name" && start_script "$script_name"; then
log "INFO" "$script_name 重启成功"
return 0
else
log "ERROR" "$script_name 重启失败"
return 1
fi
}
status_script() {
local script_name="$1"
if is_running "$script_name"; then
local pid_file=$(get_pid_file "$script_name")
local lock_file=$(get_lock_file "$script_name")
local pid=""
if [[ -f "$pid_file" ]]; then
pid=$(cat "$pid_file")
elif [[ -f "$lock_file" ]]; then
pid=$(cat "$lock_file")
fi
echo "$script_name 正在运行 (PID: $pid)"
return 0
else
echo "$script_name 未运行"
return 1
fi
}
usage() {
echo "用法: $0 {start|stop|restart|status} {script_name}"
echo " $0 {startall|stopall|restartall|statusall}"
echo ""
echo "可用脚本:"
for script in "${!SCRIPTS[@]}"; do
echo " $script - ${SCRIPTS[$script]}"
done
echo ""
echo "示例:"
echo " $0 start class_change # 启动换班级处理"
echo " $0 stop class_change # 停止换班级处理"
echo " $0 restart class_change # 重启换班级处理"
echo " $0 status class_change # 检查换班级处理状态"
echo " $0 startall # 启动所有脚本"
echo " $0 stopall # 停止所有脚本"
echo " $0 statusall # 检查所有脚本状态"
}
# Main logic
case "${1:-}" in
start)
if [[ -z "${2:-}" ]]; then
usage
exit 1
fi
if [[ -n "${SCRIPTS[$2]:-}" ]]; then
start_script "$2"
else
echo "未知脚本: $2"
usage
exit 1
fi
;;
stop)
if [[ -z "${2:-}" ]]; then
usage
exit 1
fi
if [[ -n "${SCRIPTS[$2]:-}" ]]; then
stop_script "$2"
else
echo "未知脚本: $2"
usage
exit 1
fi
;;
restart)
if [[ -z "${2:-}" ]]; then
usage
exit 1
fi
if [[ -n "${SCRIPTS[$2]:-}" ]]; then
restart_script "$2"
else
echo "未知脚本: $2"
usage
exit 1
fi
;;
status)
if [[ -z "${2:-}" ]]; then
usage
exit 1
fi
if [[ -n "${SCRIPTS[$2]:-}" ]]; then
status_script "$2"
else
echo "未知脚本: $2"
usage
exit 1
fi
;;
startall)
log "INFO" "正在启动所有脚本..."
local failed=()
for script in "${!SCRIPTS[@]}"; do
if ! start_script "$script"; then
failed+=("$script")
fi
done
if [[ ${#failed[@]} -eq 0 ]]; then
log "INFO" "所有脚本启动成功"
else
log "ERROR" "启动脚本失败: ${failed[*]}"
exit 1
fi
;;
stopall)
log "INFO" "正在停止所有脚本..."
local failed=()
for script in "${!SCRIPTS[@]}"; do
if ! stop_script "$script"; then
failed+=("$script")
fi
done
if [[ ${#failed[@]} -eq 0 ]]; then
log "INFO" "所有脚本停止成功"
else
log "ERROR" "停止脚本失败: ${failed[*]}"
exit 1
fi
;;
restartall)
log "INFO" "正在重启所有脚本..."
local failed=()
for script in "${!SCRIPTS[@]}"; do
if ! restart_script "$script"; then
failed+=("$script")
fi
done
if [[ ${#failed[@]} -eq 0 ]]; then
log "INFO" "所有脚本重启成功"
else
log "ERROR" "重启脚本失败: ${failed[*]}"
exit 1
fi
;;
statusall)
log "INFO" "正在检查所有脚本的状态..."
for script in "${!SCRIPTS[@]}"; do
echo -n "$script: "
status_script "$script"
done
;;
*)
usage
exit 1
;;
esac
+17
View File
@@ -0,0 +1,17 @@
# Database configuration
DB_HOST="172.16.0.67"
DB_PORT="3306"
DB_NAME="hzdyjs_xg"
DB_USER="root"
DB_PASS="Hzdyjs@Root@123456"
# Script configuration
LOG_DIR="${SCRIPT_DIR}/student_class_change"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOCK_FILE="${SCRIPT_DIR}/script.lock"
# Processing configuration
BATCH_SIZE=100
MAX_RETRY=3
RETRY_DELAY=5
CYCLE_INTERVAL=600 # 循环间隔时间(秒),默认10分钟
-348
View File
@@ -1,348 +0,0 @@
#!/bin/bash
# ===========================================
# 学生换班级处理服务健康检查脚本
# 用于监控服务运行状态和数据库连接
# ===========================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/data.conf"
CONTROL_SCRIPT="${SCRIPT_DIR}/control_scripts.sh"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Load configuration
if [[ ! -f "$CONFIG_FILE" ]]; then
echo -e "${RED}错误:配置文件 $CONFIG_FILE 未找到!${NC}"
exit 1
fi
source "$CONFIG_FILE"
# Functions
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo -e "${timestamp} [$level] $message"
}
success() {
echo -e "${GREEN}✓ $1${NC}"
}
error() {
echo -e "${RED}✗ $1${NC}"
}
warning() {
echo -e "${YELLOW}⚠ $1${NC}"
}
info() {
echo -e "${BLUE}ℹ $1${NC}"
}
# 检查数据库连接
check_database() {
info "正在检查数据库连接..."
local db_test
if db_test=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SELECT 1 as test;" 2>/dev/null); then
if echo "$db_test" | grep -q "test"; then
success "数据库连接成功"
return 0
else
error "数据库查询失败"
return 1
fi
else
error "数据库连接失败"
return 1
fi
}
# 检查服务状态
check_service() {
local service_name="$1"
local service_desc="$2"
info "正在检查 $service_desc..."
if [[ ! -x "$CONTROL_SCRIPT" ]]; then
error "控制脚本未找到或不可执行: $CONTROL_SCRIPT"
return 1
fi
local status_output
status_output=$("$CONTROL_SCRIPT" status "$service_name" 2>/dev/null)
if echo "$status_output" | grep -q "is running"; then
success "$service_desc 正在运行"
# 提取PID(如果可用)
local pid
pid=$(echo "$status_output" | grep -o "PID: [0-9]*" | cut -d' ' -f2)
if [[ -n "$pid" ]]; then
info " 进程ID: $pid"
fi
return 0
else
error "$service_desc 未运行"
return 1
fi
}
# 检查待处理记录
check_pending_records() {
info "正在检查待处理的换班级记录..."
local pending_count
if pending_count=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "
SELECT COUNT(*) as pending
FROM xg_student_class_change
WHERE status = '0' AND deleted = 0;
" 2>/dev/null | tail -n1); then
if [[ "$pending_count" -eq 0 ]]; then
success "无待处理的换班级记录"
elif [[ "$pending_count" -lt 10 ]]; then
warning "$pending_count 条待处理的换班级记录"
else
error "$pending_count 条待处理的换班级记录 (积压严重)"
fi
return 0
else
error "检查待处理记录失败"
return 1
fi
}
# 检查最近的处理活动
check_recent_processing() {
info "正在检查最近的处理活动..."
local recent_count
if recent_count=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "
SELECT COUNT(*) as recent
FROM xg_student_class_change
WHERE status = '1'
AND update_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
AND deleted = 0;
" 2>/dev/null | tail -n1); then
if [[ "$recent_count" -gt 0 ]]; then
success "最近一小时处理了 $recent_count 条记录"
else
warning "最近一小时未处理任何记录"
fi
return 0
else
error "检查最近处理活动失败"
return 1
fi
}
# 检查日志文件
check_logs() {
info "正在检查日志文件..."
if [[ -d "$LOG_DIR" ]]; then
local log_count
log_count=$(find "$LOG_DIR" -name "*.log" -type f -mtime -1 2>/dev/null | wc -l)
if [[ "$log_count" -gt 0 ]]; then
success "发现最近的日志文件 ($log_count 个文件来自最近24小时)"
else
warning "未发现最近的日志文件"
fi
# 检查错误日志
local error_count
error_count=$(find "$LOG_DIR" -name "*.log" -type f -exec grep -l "ERROR" {} \; 2>/dev/null | wc -l)
if [[ "$error_count" -gt 0 ]]; then
warning "$error_count 个日志文件包含错误"
else
success "最近日志中未发现错误消息"
fi
else
error "日志目录未找到: $LOG_DIR"
return 1
fi
}
# 检查磁盘空间
check_disk_space() {
info "正在检查磁盘空间..."
local disk_usage
disk_usage=$(df "$SCRIPT_DIR" | tail -1 | awk '{print $5}' | sed 's/%//')
if [[ "$disk_usage" -lt 80 ]]; then
success "磁盘使用率: ${disk_usage}%"
elif [[ "$disk_usage" -lt 90 ]]; then
warning "磁盘使用率: ${disk_usage}% (接近限制)"
else
error "磁盘使用率: ${disk_usage}% (严重)"
return 1
fi
}
# 检查内存使用情况
check_memory() {
info "正在检查内存使用情况..."
local mem_usage
mem_usage=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100.0}')
if [[ "$mem_usage" -lt 80 ]]; then
success "内存使用率: ${mem_usage}%"
elif [[ "$mem_usage" -lt 90 ]]; then
warning "内存使用率: ${mem_usage}% (接近限制)"
else
error "内存使用率: ${mem_usage}% (严重)"
return 1
fi
}
# Generate health report
generate_report() {
local report_file="${SCRIPT_DIR}/health_report_$(date +%Y%m%d_%H%M%S).txt"
{
echo "=== 学生换班级处理服务健康检查报告 ==="
echo "检查时间: $(date)"
echo "服务器信息: $(hostname)"
echo ""
echo "=== 数据库连接 ==="
if check_database; then
echo "✓ 数据库连接正常"
else
echo "✗ 数据库连接异常"
fi
echo ""
echo "=== 服务状态 ==="
if check_service "class_change" "学生换班级处理服务"; then
echo "✓ 学生换班级处理服务运行正常"
else
echo "✗ 学生换班级处理服务未运行"
fi
echo ""
echo "=== 业务数据检查 ==="
check_pending_records
echo ""
check_recent_processing
echo ""
echo "=== 系统资源 ==="
check_disk_space
echo ""
check_memory
echo ""
echo "=== 日志检查 ==="
check_logs
echo ""
echo "报告生成完成"
} > "$report_file" 2>/dev/null
success "健康检查报告已生成: $report_file"
}
# Main function
main() {
echo ""
echo "=== 学生换班级处理服务健康检查 ==="
echo "检查时间: $(date)"
echo "服务器信息: $(hostname)"
echo ""
local checks_passed=0
local total_checks=0
# Database connection
echo "=== 数据库连接 ==="
((total_checks++))
check_database && ((checks_passed++))
echo ""
# Service status
echo "=== 服务状态 ==="
((total_checks++))
check_service "class_change" "学生换班级处理服务" && ((checks_passed++))
echo ""
# Business data
echo "=== 业务数据检查 ==="
((total_checks++))
check_pending_records && ((checks_passed++))
echo ""
((total_checks++))
check_recent_processing && ((checks_passed++))
echo ""
# System resources
echo "=== 系统资源 ==="
((total_checks++))
check_disk_space && ((checks_passed++))
echo ""
((total_checks++))
check_memory && ((checks_passed++))
echo ""
# Logs
echo "=== 日志检查 ==="
((total_checks++))
check_logs && ((checks_passed++))
echo ""
# 总结
echo "=== 检查总结 ==="
local success_rate=$((checks_passed * 100 / total_checks))
echo "通过检查: $checks_passed/$total_checks ($success_rate%)"
if [[ $success_rate -eq 100 ]]; then
success "所有检查通过,服务运行正常"
exit 0
elif [[ $success_rate -ge 80 ]]; then
warning "大部分检查通过,服务运行基本正常"
exit 0
else
error "多项检查失败,建议立即检查服务状态"
exit 1
fi
}
# 命令行选项
case "${1:-}" in
--report)
generate_report
;;
--help|-h)
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " --report 生成详细的健康检查报告"
echo " --help 显示此帮助信息"
echo ""
echo "无选项时,执行标准健康检查。"
;;
*)
main "$@"
;;
esac
@@ -0,0 +1,337 @@
#!/bin/bash
# ===========================================
# 学生换班级处理脚本 (修复版本)
# 处理类型: CLASS_CHANGE(换班级)
# ===========================================
set -euo pipefail
# Force C locale to ensure consistent behavior across systems
export LANG=C
export LC_ALL=C
# Load configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/data.conf"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "错误:配置文件 $CONFIG_FILE 未找到!"
exit 1
fi
source "$CONFIG_FILE"
# Create log directory
mkdir -p "$LOG_DIR"
# Log file
LOG_FILE="${LOG_DIR}/process_student_class_change_fixed_$(date +%Y%m%d_%H%M%S).log"
# Lock file to prevent multiple instances
LOCK_FILE="${SCRIPT_DIR}/process_student_class_change_fixed.lock"
# Functions
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
error_exit() {
local message="$1"
log "ERROR" "$message"
# Remove lock file
[[ -f "$LOCK_FILE" ]] && rm -f "$LOCK_FILE"
exit 1
}
# Database functions - Fixed version
execute_sql() {
local sql="$1"
local result
result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$sql" 2>&1)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "SQL执行失败 (退出码: $exit_code)"
log "ERROR" "数据库连接: $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME"
log "ERROR" "失败的SQL: $sql"
log "ERROR" "MySQL错误详情: $result"
return 1
fi
echo "$result"
}
# Database query function - Fixed version for cross-platform compatibility
execute_query() {
local sql="$1"
local result
# Use -ss for ultra-silent mode to ensure clean tab-separated output
result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -ss "$DB_NAME" -e "$sql" 2>&1)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "数据库查询执行失败 (退出码: $exit_code)"
log "ERROR" "数据库连接: $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME"
log "ERROR" "失败的SQL: $sql"
log "ERROR" "MySQL错误详情: $result"
return 1
fi
echo "$result"
}
# Process student class change
process_student_class_change() {
local record_id="$1"
local student_id="$2"
local student_no="$3"
local new_class_id="$4"
local operator_id="${5:-1}"
# Validate input parameters
if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$new_class_id" ]]; then
log "ERROR" "参数不完整 - record_id: '$record_id', student_id: '$student_id', student_no: '$student_no', new_class_id: '$new_class_id'"
return 1
fi
log "INFO" "Processing student class change - ID: $record_id, Student: $student_no ($student_id), New Class ID: $new_class_id"
# Get new class information
local class_info_sql="
SELECT
c.college_id, c.college_name, c.major_id, c.major_name,
c.class_code, c.class_name, c.class_type, c.grade,
c.training_type, c.counselor_id, c.counselor_name,
c.class_teacher_id, c.class_teacher_name, c.school_system
FROM xg_class_msg c
WHERE c.id = $new_class_id AND c.deleted = 0
"
local class_info
class_info=$(execute_query "$class_info_sql")
if [[ -z "$class_info" || "$class_info" == *"Empty set"* ]]; then
log "ERROR" "获取班级ID为 $new_class_id 的班级信息失败"
return 1
fi
# Parse class information - Fixed parsing logic
local class_data
class_data=$(echo "$class_info" | grep -v "^mysql:" | grep -v "^$" | head -n 1)
if [[ -z "$class_data" ]]; then
log "ERROR" "解析班级信息失败,无法获取数据行"
return 1
fi
# Use cut to extract specific fields reliably
local college_id=$(echo "$class_data" | cut -f1)
local college_name=$(echo "$class_data" | cut -f2)
local major_id=$(echo "$class_data" | cut -f3)
local major_name=$(echo "$class_data" | cut -f4)
local class_code=$(echo "$class_data" | cut -f5)
local class_name=$(echo "$class_data" | cut -f6)
local class_type=$(echo "$class_data" | cut -f7)
local grade=$(echo "$class_data" | cut -f8)
local training_type=$(echo "$class_data" | cut -f9)
local counselor_id=$(echo "$class_data" | cut -f10)
local counselor_name=$(echo "$class_data" | cut -f11)
local class_teacher_id=$(echo "$class_data" | cut -f12)
local class_teacher_name=$(echo "$class_data" | cut -f13)
local school_system=$(echo "$class_data" | cut -f14)
# Handle NULL values for IDs
local counselor_id_value class_teacher_id_value
if [[ "$counselor_id" == "NULL" || -z "$counselor_id" ]]; then
counselor_id_value="NULL"
else
counselor_id_value="'$counselor_id'"
fi
if [[ "$class_teacher_id" == "NULL" || -z "$class_teacher_id" ]]; then
class_teacher_id_value="NULL"
else
class_teacher_id_value="'$class_teacher_id'"
fi
# Start transaction
local transaction_sql="
START TRANSACTION;
-- 1. Backup original data to his tables (ignore if already exists)
INSERT IGNORE INTO xg_student_msg_his SELECT * FROM xg_student_msg WHERE id = $student_id;
INSERT IGNORE INTO xg_class_pos_msg_his SELECT * FROM xg_class_pos_msg WHERE student_id = $student_id;
-- 2. Terminate cadre positions (干部信息终止)
UPDATE xg_class_pos_msg SET
is_current = 0,
cadre_status = '4', -- 免职
dismissal_date = CURDATE(),
dismissal_reason = '学生换班级',
term_end_date = CURDATE(),
term_duration = TIMESTAMPDIFF(MONTH, term_start_date, CURDATE()),
update_by = $operator_id,
update_time = NOW()
WHERE student_id = $student_id AND is_current = 1;
-- 3. Update student information with new class data
UPDATE xg_student_msg SET
-- 基础班级信息
college_id = '$college_id',
college_name = '$college_name',
major_id = '$major_id',
major_name = '$major_name',
class_id = '$new_class_id',
class_name = '$class_name',
grade = '$grade',
-- 预留字段从班级表关联获取的数据 (7182-7195行)
school_system = '$school_system',
academic_level = '$class_type', -- class_type 对应 academic_level
training_type = '$training_type',
counselor_id = $counselor_id_value,
counselor_name = '$counselor_name',
class_teacher_id = $class_teacher_id_value,
class_teacher_name = '$class_teacher_name',
-- 更新信息
update_by = $operator_id,
update_time = NOW()
WHERE id = $student_id;
-- 4. Update class change record status
UPDATE xg_student_class_change SET
status = '1', -- 已处理
update_by = $operator_id,
update_time = NOW()
WHERE id = $record_id;
COMMIT;
"
# 执行事务
if execute_sql "$transaction_sql"; then
log "INFO" "成功处理学生换班级 - ID: $record_id, 学生: $student_no"
return 0
else
log "ERROR" "处理学生换班级失败 - ID: $record_id, 学生: $student_no"
return 1
fi
}
# Main function
main() {
log "INFO" "启动学生换班级处理守护进程 (修复版本)"
log "INFO" "处理类型: CLASS_CHANGE(换班级)"
log "INFO" "以连续模式运行,间隔10分钟"
# 测试数据库连接
log "INFO" "正在测试数据库连接..."
log "INFO" "连接信息: $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME"
if ! execute_sql "SELECT 1;" >/dev/null 2>&1; then
error_exit "数据库连接失败 - 请检查连接参数和网络连接"
fi
log "INFO" "数据库连接测试成功"
# Main processing loop
while true; do
log "INFO" "开始新的处理周期"
# 获取未处理的记录
local query_sql="
SELECT id, student_id, student_no, new_class_id, update_by
FROM xg_student_class_change
WHERE status = '0'
AND deleted = 0
AND effective_time <= NOW()
ORDER BY create_time ASC
LIMIT $BATCH_SIZE;
"
local records
if ! records=$(execute_query "$query_sql"); then
log "ERROR" "获取待处理记录失败,${CYCLE_INTERVAL}秒后重试"
sleep $CYCLE_INTERVAL
continue
fi
# 检查是否有记录需要处理(过滤掉空行、MySQL警告信息和表头)
local clean_records
# 过滤掉MySQL警告、空行,然后排除表头行(第一行通常是列名)
local filtered_records
filtered_records=$(echo "$records" | grep -v "^mysql:" | grep -v "^$" | sed '1d')
clean_records=$(echo "$filtered_records" | wc -l)
# 去除可能的空白字符
clean_records=$(echo "$clean_records" | tr -d ' ')
if [[ $clean_records -eq 0 ]]; then
log "INFO" "未找到未处理的记录,正在等待..."
else
log "INFO" "找到 $clean_records 条待处理记录"
# 调试:显示前几条记录的信息
if [[ $clean_records -gt 0 ]]; then
log "DEBUG" "待处理记录预览:"
echo "$filtered_records" | head -3 | while read -r line; do
log "DEBUG" " $line"
done
fi
local processed_count=0
local failed_count=0
# Create a temporary file to store records for processing
local temp_records_file="${SCRIPT_DIR}/temp_class_change_records_fixed_$$.txt"
echo "$filtered_records" > "$temp_records_file"
# Process each record from the temporary file
while IFS=$'\t' read -r record_id student_id student_no new_class_id operator_id; do
# Skip empty lines or invalid records
[[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$new_class_id" ]] && continue
# Log the record being processed for debugging
log "INFO" "读取到记录 - ID: $record_id, 学生ID: $student_id, 学号: $student_no, 新班级ID: $new_class_id, 操作员: ${operator_id:-1}"
local retry_count=0
local success=false
while [[ $retry_count -lt $MAX_RETRY && $success == false ]]; do
if process_student_class_change "$record_id" "$student_id" "$student_no" "$new_class_id" "$operator_id"; then
success=true
((processed_count++))
else
((retry_count++))
if [[ $retry_count -lt $MAX_RETRY ]]; then
log "WARN" "处理失败,$RETRY_DELAY 秒后重试... (尝试 $retry_count/$MAX_RETRY)"
sleep $RETRY_DELAY
fi
fi
done
if [[ $success == false ]]; then
((failed_count++))
log "ERROR" "$MAX_RETRY 次尝试后仍处理失败 - ID: $record_id"
fi
done < "$temp_records_file"
# Clean up temporary file
rm -f "$temp_records_file"
log "INFO" "处理周期完成 - 已处理: $processed_count, 失败: $failed_count"
fi
# 等待配置的间隔时间后开始下一个周期
log "INFO" "等待 $CYCLE_INTERVAL 秒后开始下一个处理周期..."
sleep $CYCLE_INTERVAL
done
}
# Run main function
main "$@"
@@ -0,0 +1,220 @@
# 学生状态变更处理系统 - 生产环境部署指南
## 📋 概述
本系统包含两个核心处理脚本,已修复Ubuntu环境兼容性问题:
- `process_student_changes_fixed.sh` - 学生状态变更处理(休学/退学/毕业)
- `process_student_resume_fixed.sh` - 学生复学处理
## 🚀 快速启动
### 1. 启动所有服务
```bash
cd /path/to/scripts
./control_scripts.sh startall
```
### 2. 检查运行状态
```bash
./control_scripts.sh statusall
```
### 3. 健康检查
```bash
./control_scripts.sh health all
```
### 4. 查看系统资源
```bash
./control_scripts.sh info
```
## 📁 文件结构
```
/scripts/
├── control_scripts.sh # 生产环境控制脚本
├── process_student_changes_fixed.sh # 状态变更处理脚本
├── process_student_resume_fixed.sh # 复学处理脚本
├── data.conf # 配置文件
├── logs/ # 控制脚本日志目录
│ └── control_scripts_YYYYMMDD.log
└── student_status_change/ # 处理脚本日志目录
└── process_student_*_YYYYMMDD_HHMMSS.log
```
## ⚙️ 配置要求
### 数据库配置 (data.conf)
```bash
# Database configuration
DB_HOST="your_db_host"
DB_PORT="3306"
DB_NAME="your_db_name"
DB_USER="your_db_user"
DB_PASS="your_db_password"
# Processing configuration
BATCH_SIZE=100 # 每批处理记录数
MAX_RETRY=3 # 最大重试次数
RETRY_DELAY=5 # 重试间隔(秒)
CYCLE_INTERVAL=600 # 处理周期间隔(秒)
```
### 系统要求
- Ubuntu 18.04+ / CentOS 7+
- Bash 4.0+
- MySQL 5.7+ / MariaDB 10.0+
- 标准系统工具 (ps, kill, chmod等)
## 🎯 控制脚本命令
### 基础操作
```bash
# 启动/停止/重启
./control_scripts.sh start changes # 启动状态变更处理
./control_scripts.sh stop resume # 停止复学处理
./control_scripts.sh restart changes # 重启状态变更处理
# 状态检查
./control_scripts.sh status changes # 查看状态变更脚本状态
./control_scripts.sh statusall # 查看所有脚本状态
```
### 批量操作
```bash
# 启动所有脚本
./control_scripts.sh startall
# 停止所有脚本
./control_scripts.sh stopall
# 重启所有脚本 (需要时使用)
./control_scripts.sh restart all
```
### 监控和维护
```bash
# 健康检查
./control_scripts.sh health changes # 检查单个脚本
./control_scripts.sh health all # 检查所有脚本
# 系统资源监控
./control_scripts.sh info
```
## 🔍 故障排除
### 常见问题
#### 1. 脚本无法启动
**症状**: `start` 命令失败
**检查**:
```bash
# 检查脚本权限
ls -la *.sh
# 检查配置文件
cat data.conf
# 检查数据库连接
mysql -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASS $DB_NAME -e "SELECT 1;"
```
#### 2. 脚本状态显示异常
**症状**: `status` 显示未知状态
**解决**:
```bash
# 清理可能的残留文件
rm -f *.lock pids/*.pid
# 重启脚本
./control_scripts.sh restart all
```
#### 3. 数据库连接失败
**症状**: 健康检查显示数据库连接失败
**检查**:
- 确认数据库服务运行
- 验证连接参数
- 检查网络连接
#### 4. 日志文件过大
**解决**:
```bash
# 清理旧日志 (保留最近7天)
find logs/ -name "*.log" -mtime +7 -delete
find student_status_change/ -name "*.log" -mtime +7 -delete
```
### 日志位置
- **控制脚本日志**: `logs/control_scripts_YYYYMMDD.log`
- **处理脚本日志**: `student_status_change/process_student_*_YYYYMMDD_HHMMSS.log`
## 📊 监控要点
### 关键指标
1. **脚本运行状态**: 确保两个处理脚本都在运行
2. **数据库连接**: 定期检查数据库连接性
3. **处理效率**: 观察日志中的处理记录数和耗时
4. **系统资源**: CPU/内存/磁盘使用率
5. **错误日志**: 关注ERROR和WARN级别的日志
### 自动化监控脚本
```bash
#!/bin/bash
# 监控脚本示例
SCRIPT_DIR="/path/to/scripts"
LOG_FILE="/var/log/student_monitor.log"
echo "$(date): 检查脚本状态" >> "$LOG_FILE"
cd "$SCRIPT_DIR"
# 检查脚本状态
if ! ./control_scripts.sh health all >> "$LOG_FILE" 2>&1; then
echo "$(date): 健康检查失败,发送告警" >> "$LOG_FILE"
# 发送告警邮件/短信等
fi
```
## 🔄 升级部署
### 停机升级
```bash
# 1. 停止所有服务
./control_scripts.sh stopall
# 2. 备份当前脚本
cp *.sh backup/
# 3. 部署新版本脚本
# (复制新版本文件)
# 4. 启动服务
./control_scripts.sh startall
# 5. 验证功能
./control_scripts.sh health all
```
### 热升级 (推荐)
```bash
# 逐个重启脚本,最小化服务中断
./control_scripts.sh restart changes
sleep 30
./control_scripts.sh restart resume
```
## 📞 支持
如遇到问题,请检查:
1. 日志文件中的错误信息
2. 系统资源使用情况
3. 数据库连接状态
4. 脚本执行权限
---
**最后更新**: 2025-12-26
**版本**: Ubuntu兼容修复版本
@@ -1,11 +1,40 @@
#!/bin/bash
# ===========================================
# 学生状态变更处理脚本控制程序
# 用于启动、停止、重启处理脚本
# 学生状态变更处理脚本控制程序 - 生产环境版本
# 用于启动、停止、重启处理脚本 (Ubuntu兼容性修复版本)
# ===========================================
#
# 生产环境部署指南:
# 1. 确保所有修复版本脚本存在:
# - process_student_changes_fixed.sh (状态变更处理)
# - process_student_resume_fixed.sh (复学处理)
#
# 2. 确保配置文件存在:
# - data.conf (数据库配置和脚本参数)
#
# 3. 启动所有服务:
# ./control_scripts.sh startall
#
# 4. 监控运行状态:
# ./control_scripts.sh statusall
#
# 5. 健康检查:
# ./control_scripts.sh health all
#
# 6. 查看系统资源:
# ./control_scripts.sh info
#
# 日志文件位置:
# - 控制脚本日志: logs/control_scripts_YYYYMMDD.log
# - 处理脚本日志: student_status_change/process_student_*_YYYYMMDD_HHMMSS.log
set -euo pipefail
# Disable strict error checking for better production resilience
# set -euo pipefail # Temporarily disabled for production robustness
# Force C locale for consistent behavior across systems
export LANG=C
export LC_ALL=C
# Load configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -13,16 +42,33 @@ CONFIG_FILE="${SCRIPT_DIR}/data.conf"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "错误:配置文件 $CONFIG_FILE 未找到!"
echo "请确保 data.conf 文件存在于脚本目录中"
exit 1
fi
source "$CONFIG_FILE"
# Script definitions
declare -A SCRIPTS=(
["changes"]="process_student_changes.sh"
["resume"]="process_student_resume.sh"
)
# Production environment setup
PRODUCTION_LOG_DIR="${SCRIPT_DIR}/logs"
mkdir -p "$PRODUCTION_LOG_DIR"
PRODUCTION_LOG_FILE="${PRODUCTION_LOG_DIR}/control_scripts_$(date +%Y%m%d).log"
# Script definitions - Production versions with Ubuntu compatibility fixes
# Using indexed arrays for better bash compatibility
SCRIPT_NAMES=("changes" "resume")
SCRIPT_FILES=("process_student_changes_fixed.sh" "process_student_resume_fixed.sh")
# Function to get script file by name
get_script_file() {
local script_name="$1"
for i in "${!SCRIPT_NAMES[@]}"; do
if [[ "${SCRIPT_NAMES[$i]}" == "$script_name" ]]; then
echo "${SCRIPT_FILES[$i]}"
return 0
fi
done
return 1
}
# PID files
PID_DIR="${SCRIPT_DIR}/pids"
@@ -33,7 +79,13 @@ log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message"
local log_message="[$timestamp] [$level] $message"
# Output to console
echo "$log_message"
# Output to production log file
echo "$log_message" >> "$PRODUCTION_LOG_FILE"
}
get_pid_file() {
@@ -76,9 +128,64 @@ is_running() {
return 1 # Not running
}
# Health check function for production monitoring
health_check() {
local script_name="$1"
local script_file="${SCRIPT_DIR}/$(get_script_file "$script_name")"
log "INFO" "执行 $script_name 的健康检查..."
# Check if script file exists and is executable
if [[ ! -f "$script_file" ]]; then
log "ERROR" "$script_name 脚本文件不存在: $script_file"
return 1
fi
if [[ ! -x "$script_file" ]]; then
log "WARN" "$script_name 脚本文件没有执行权限,正在修复..."
chmod +x "$script_file"
fi
# Check database connectivity
if ! mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SELECT 1;" >/dev/null 2>&1; then
log "ERROR" "$script_name 数据库连接失败"
return 1
fi
# Check if script is running
if ! is_running "$script_name"; then
log "WARN" "$script_name 脚本未运行"
return 1
fi
log "INFO" "$script_name 健康检查通过"
return 0
}
# Get system resource usage
get_system_info() {
log "INFO" "=== 系统资源信息 ==="
# CPU usage
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
log "INFO" "CPU使用率: ${cpu_usage}%"
# Memory usage
local mem_info=$(free -h | grep "^Mem:")
log "INFO" "内存信息: $mem_info"
# Disk usage
local disk_info=$(df -h "$SCRIPT_DIR" | tail -1 | awk '{print $1 ": " $5 " used (" $4 " free)"}')
log "INFO" "磁盘使用: $disk_info"
# Process count
local process_count=$(pgrep -f "process_student.*\.sh" | wc -l)
log "INFO" "运行中的学生处理进程数: $process_count"
}
start_script() {
local script_name="$1"
local script_file="${SCRIPT_DIR}/${SCRIPTS[$script_name]}"
local script_file="${SCRIPT_DIR}/$(get_script_file "$script_name")"
local pid_file=$(get_pid_file "$script_name")
if [[ ! -f "$script_file" ]]; then
@@ -189,38 +296,74 @@ show_usage() {
stop 停止指定的脚本
restart 重启指定的脚本
status 显示指定脚本的状态
health 执行脚本健康检查
startall 启动所有脚本
stopall 停止所有脚本
statusall 显示所有脚本的状态
info 显示系统资源信息
脚本名称:
changes 学生状态变更脚本 (休学/退学/毕业)
resume 学生复学脚本 (复学)
changes 学生状态变更脚本 (休学/退学/毕业) - Ubuntu兼容修复版本
resume 学生复学脚本 (复学) - Ubuntu兼容修复版本
all 所有脚本
示例:
生产环境示例:
$0 startall # 启动所有学生处理脚本
$0 statusall # 检查所有脚本运行状态
$0 health all # 执行所有脚本的健康检查
$0 info # 查看系统资源使用情况
$0 restart changes # 重启状态变更处理脚本
$0 stopall # 停止所有处理脚本
传统示例:
$0 start changes # 启动状态变更处理脚本
$0 stop resume # 停止复学处理脚本
$0 restart changes # 重启状态变更处理脚本
$0 status all # 显示所有脚本的状态
$0 startall # 启动所有脚本
$0 stopall # 停止所有脚本
$0 status changes # 显示状态变更脚本状态
注意:
本脚本使用修复版本,已适配Ubuntu环境,解决了循环处理和数据库查询的兼容性问题
EOF
}
# Production environment initialization
init_production_env() {
log "INFO" "初始化生产环境..."
# Check script files exist
for script_name in "${!SCRIPTS[@]}"; do
local script_file="${SCRIPT_DIR}/${SCRIPTS[$script_name]}"
if [[ ! -f "$script_file" ]]; then
log "ERROR" "脚本文件不存在: $script_file"
log "ERROR" "请确保已部署修复版本的脚本文件"
exit 1
fi
done
# Test database connectivity
if ! mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SELECT 1;" >/dev/null 2>&1; then
log "ERROR" "数据库连接失败,请检查配置"
exit 1
fi
log "INFO" "生产环境初始化完成"
}
# Main function
main() {
# Initialize production environment
init_production_env
local command="${1:-}"
local script_name="${2:-}"
case "$command" in
start)
if [[ "$script_name" == "all" ]]; then
for script in "${!SCRIPTS[@]}"; do
for script in "${SCRIPT_NAMES[@]}"; do
start_script "$script"
done
elif [[ -n "${SCRIPTS[$script_name]}" ]]; then
elif get_script_file "$script_name" >/dev/null 2>&1; then
start_script "$script_name"
else
log "ERROR" "未知的脚本名称: $script_name"
@@ -231,10 +374,10 @@ main() {
stop)
if [[ "$script_name" == "all" ]]; then
for script in "${!SCRIPTS[@]}"; do
for script in "${SCRIPT_NAMES[@]}"; do
stop_script "$script"
done
elif [[ -n "${SCRIPTS[$script_name]}" ]]; then
elif get_script_file "$script_name" >/dev/null 2>&1; then
stop_script "$script_name"
else
log "ERROR" "未知的脚本名称: $script_name"
@@ -245,10 +388,10 @@ main() {
restart)
if [[ "$script_name" == "all" ]]; then
for script in "${!SCRIPTS[@]}"; do
for script in "${SCRIPT_NAMES[@]}"; do
restart_script "$script"
done
elif [[ -n "${SCRIPTS[$script_name]}" ]]; then
elif get_script_file "$script_name" >/dev/null 2>&1; then
restart_script "$script_name"
else
log "ERROR" "未知的脚本名称: $script_name"
@@ -259,10 +402,10 @@ main() {
status)
if [[ "$script_name" == "all" ]]; then
for script in "${!SCRIPTS[@]}"; do
for script in "${SCRIPT_NAMES[@]}"; do
status_script "$script"
done
elif [[ -n "${SCRIPTS[$script_name]}" ]]; then
elif get_script_file "$script_name" >/dev/null 2>&1; then
status_script "$script_name"
else
log "ERROR" "未知的脚本名称: $script_name"
@@ -273,25 +416,59 @@ main() {
startall)
log "INFO" "正在启动所有脚本..."
for script in "${!SCRIPTS[@]}"; do
for script in "${SCRIPT_NAMES[@]}"; do
start_script "$script"
done
;;
stopall)
log "INFO" "正在停止所有脚本..."
for script in "${!SCRIPTS[@]}"; do
for script in "${SCRIPT_NAMES[@]}"; do
stop_script "$script"
done
;;
statusall)
log "INFO" "正在检查所有脚本的状态..."
for script in "${!SCRIPTS[@]}"; do
for script in "${SCRIPT_NAMES[@]}"; do
status_script "$script"
done
;;
health)
if [[ "$script_name" == "all" ]]; then
local all_healthy=true
for script in "${SCRIPT_NAMES[@]}"; do
if ! health_check "$script"; then
all_healthy=false
fi
done
if [[ "$all_healthy" == true ]]; then
log "INFO" "所有脚本健康检查通过"
exit 0
else
log "ERROR" "部分脚本健康检查失败"
exit 1
fi
elif get_script_file "$script_name" >/dev/null 2>&1; then
if health_check "$script_name"; then
log "INFO" "$script_name 健康检查通过"
exit 0
else
log "ERROR" "$script_name 健康检查失败"
exit 1
fi
else
log "ERROR" "未知的脚本名称: $script_name"
show_usage
exit 1
fi
;;
info)
get_system_info
;;
*)
log "ERROR" "未知命令: $command"
show_usage
@@ -0,0 +1,17 @@
# Database configuration
DB_HOST="172.16.0.67"
DB_PORT="3306"
DB_NAME="hzdyjs_xg"
DB_USER="root"
DB_PASS="Hzdyjs@Root@123456"
# Script configuration
LOG_DIR="${SCRIPT_DIR}/student_status_change"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOCK_FILE="${SCRIPT_DIR}/script.lock"
# Processing configuration
BATCH_SIZE=100
MAX_RETRY=3
RETRY_DELAY=5
CYCLE_INTERVAL=600 # 循环间隔时间(秒),默认10分钟
@@ -1,69 +0,0 @@
# ===========================================
# 学生状态变更处理脚本 - 可选的Crontab配置
# ===========================================
# 注意:脚本现在采用持续运行模式,默认每10分钟自动处理一次
# 此配置文件仅供特殊情况下的备用配置使用
# 如果不使用持续运行模式,可以使用以下crontab配置:
# 1. 将以下内容添加到系统的 crontab 中:crontab -e
# 2. 根据实际需要调整执行时间和频率
# 3. 确保脚本路径正确
# 4. 确保用户有执行权限
# 脚本所在目录(请根据实际情况修改)
SCRIPT_DIR="/path/to/student_status_scripts"
# 每天早上8点执行学生状态变更处理
0 8 * * * $SCRIPT_DIR/process_student_changes.sh
# 每天早上8点30分执行学生复学处理
30 8 * * * $SCRIPT_DIR/process_student_resume.sh
# 每小时执行一次健康检查
0 * * * * $SCRIPT_DIR/health_check.sh report
# 每周一早上6点重启所有服务(可选,用于定期重启确保稳定性)
0 6 * * 1 $SCRIPT_DIR/stop_services.sh && sleep 5 && $SCRIPT_DIR/start_services.sh
# 每天晚上10点清理旧日志文件(保留7天)
0 22 * * * find $SCRIPT_DIR/logs -name "*.log" -type f -mtime +7 -delete
# 每分钟检查服务状态,如果服务停止则自动重启(可选)
# */1 * * * * $SCRIPT_DIR/control_scripts.sh statusall >/dev/null 2>&1 || ($SCRIPT_DIR/stop_services.sh force && $SCRIPT_DIR/start_services.sh)
# ===========================================
# Crontab 语法说明
# ===========================================
# 分 时 日 月 周 命令
# * * * * * command
#
# 字段说明:
# 分 - 0-59
# 时 - 0-23
# 日 - 1-31
# 月 - 1-12
# 周 - 0-7 (0和7都表示星期日)
#
# 常用示例:
# 0 8 * * * - 每天早上8点
# */30 * * * * - 每30分钟
# 0 */2 * * * - 每2小时
# 0 9-17 * * 1-5 - 周一到周五的9点到17点,每小时执行一次
# @reboot - 系统重启时执行
# ===========================================
# 监控和告警建议
# ===========================================
# 1. 监控日志中的ERROR关键字
# 可以使用logwatch或自定义脚本监控日志
# 2. 监控数据库连接
# */5 * * * * mysql -h host -u user -p pass -e "SELECT 1;" || echo "DB connection failed" | mail -s "DB Alert" admin@example.com
# 3. 监控磁盘空间
# */30 * * * * df /var/log | awk 'NR==2 {if($5>90) print "Disk usage high: "$5}' | mail -s "Disk Alert" admin@example.com
# 4. 监控进程状态
# */5 * * * * pgrep -f "process_student_changes.sh" >/dev/null || echo "Process died" | mail -s "Process Alert" admin@example.com
@@ -82,6 +82,25 @@ execute_sql() {
echo "$result"
}
# Database query function - Fixed version for cross-platform compatibility
execute_query() {
local sql="$1"
local result
# Use -ss for ultra-silent mode to ensure clean tab-separated output
# Redirect stderr to /dev/null to discard warnings, only capture actual data
result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -ss "$DB_NAME" -e "$sql" 2>/dev/null)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "查询执行失败: $sql"
log "ERROR" "MySQL错误码: $exit_code"
return 1
fi
echo "$result"
}
# Process student status change
process_student_change() {
local record_id="$1"
@@ -105,20 +124,16 @@ process_student_change() {
local bed_ids=""
# Execute dormitory query with error handling
if dorm_info=$(execute_sql "$dorm_query" 2>/dev/null); then
# Parse the result (skip header if present)
if [[ -n "$dorm_info" ]]; then
# Remove warnings, keep only data rows
dorm_info=$(echo "$dorm_info" | grep -v "mysql:" | grep -E "^[0-9]+\t" | head -n 1)
if [[ -n "$dorm_info" && "$dorm_info" != "NULL" && "$dorm_info" != "NULL\tNULL" ]]; then
# Split by tab and handle NULL values
IFS=$'\t' read -r dorm_ids_raw bed_ids_raw <<< "$dorm_info"
if [[ "$dorm_ids_raw" != "NULL" && -n "$dorm_ids_raw" ]]; then
dorm_ids="$dorm_ids_raw"
fi
if [[ "$bed_ids_raw" != "NULL" && -n "$bed_ids_raw" ]]; then
bed_ids="$bed_ids_raw"
fi
if dorm_info=$(execute_query "$dorm_query" 2>/dev/null); then
# Parse the result - handle NULL values properly
if [[ -n "$dorm_info" && "$dorm_info" != "NULL" && "$dorm_info" != "NULL\tNULL" ]]; then
# Split by tab and handle NULL values
IFS=$'\t' read -r dorm_ids_raw bed_ids_raw <<< "$dorm_info"
if [[ "$dorm_ids_raw" != "NULL" && -n "$dorm_ids_raw" ]]; then
dorm_ids="$dorm_ids_raw"
fi
if [[ "$bed_ids_raw" != "NULL" && -n "$bed_ids_raw" ]]; then
bed_ids="$bed_ids_raw"
fi
fi
else
@@ -285,18 +300,32 @@ main() {
if [[ $record_count -le 1 ]]; then
log "INFO" "未找到未处理的记录,正在等待..."
else
# 移除标题行和可能的警告信息
records=$(echo "$records" | grep -v "mysql:" | grep -v "id\t" | grep -v "^$")
local processed_count=0
local failed_count=0
# Process each record
# Process records directly, skip header and invalid lines
local is_first_line=true
while IFS=$'\t' read -r record_id student_id student_no change_type operator_id; do
# Skip empty lines
[[ -z "$record_id" ]] && continue
# Skip completely empty lines and MySQL warnings
[[ -z "$record_id$student_id$student_no$change_type$operator_id" || "$record_id" =~ ^mysql: ]] && continue
# Skip header line (first non-empty line that looks like column names)
if [[ $is_first_line == true ]]; then
is_first_line=false
# Check if this looks like a header line
if [[ "$record_id" == "id" || "$record_id" =~ ^[[:space:]]*id[[:space:]]*$ ]]; then
log "INFO" "跳过标题行"
continue
fi
fi
# Validate required fields
if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$change_type" ]]; then
log "WARN" "跳过无效记录 - ID: '$record_id', 学生ID: '$student_id', 学号: '$student_no', 类型: '$change_type'"
continue
fi
log "INFO" "读取到记录 - ID: $record_id, 学生ID: $student_id, 学号: $student_no, 类型: $change_type, 操作员: ${operator_id:-1}"
local retry_count=0
local success=false
@@ -0,0 +1,357 @@
#!/bin/bash
# ===========================================
# 学生状态变更处理脚本 - 毕业/退学/休学 (修复版本)
# 处理类型: XIU_XUE(休学), TUI_XUE(退学), BI_YE(毕业)
# ===========================================
# set -euo pipefail # Temporarily disable to prevent script exit on errors
# Force C locale to ensure consistent behavior across systems
export LANG=C
export LC_ALL=C
# Load configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/data.conf"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "错误:配置文件 $CONFIG_FILE 未找到!"
exit 1
fi
source "$CONFIG_FILE"
# Create log directory
mkdir -p "$LOG_DIR"
# Log file
LOG_FILE="${LOG_DIR}/process_student_changes_fixed_$(date +%Y%m%d_%H%M%S).log"
# Lock file to prevent multiple instances
LOCK_FILE="${SCRIPT_DIR}/process_student_changes_fixed.lock"
# Functions
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
error_exit() {
local message="$1"
log "ERROR" "$message"
# Remove lock file
[[ -f "$LOCK_FILE" ]] && rm -f "$LOCK_FILE"
exit 1
}
# Database functions - Fixed version
execute_sql() {
local sql="$1"
local result
result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$sql" 2>&1)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "SQL执行失败: $sql"
log "ERROR" "MySQL错误: $result"
return 1
fi
echo "$result"
}
# Database query function - Fixed version for cross-platform compatibility
execute_query() {
local sql="$1"
local result
# Use -ss for ultra-silent mode to ensure clean tab-separated output
# Redirect stderr to /dev/null to discard warnings, only capture actual data
result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -ss "$DB_NAME" -e "$sql" 2>/dev/null)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "查询执行失败: $sql"
log "ERROR" "MySQL错误码: $exit_code"
return 1
fi
echo "$result"
}
# Process student status change
process_student_change() {
local record_id="$1"
local student_id="$2"
local student_no="$3"
local change_type="$4"
local operator_id="${5:-1}"
# Validate input parameters
if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$change_type" ]]; then
log "ERROR" "参数不完整 - record_id: '$record_id', student_id: '$student_id', student_no: '$student_no', change_type: '$change_type'"
return 1
fi
log "INFO" "正在处理学生状态变更 - ID: $record_id, 学生: $student_no ($student_id), 类型: $change_type"
# Get dormitory assignment info before transaction
local dorm_query="
SELECT GROUP_CONCAT(id), GROUP_CONCAT(bed_id)
FROM xg_dorm_bed_student
WHERE student_id = $student_id AND status = '0' AND deleted = 0;
"
local dorm_info=""
local dorm_ids=""
local bed_ids=""
# Execute dormitory query with error handling
if dorm_info=$(execute_query "$dorm_query" 2>/dev/null); then
# Parse the result - handle NULL values properly
if [[ -n "$dorm_info" && "$dorm_info" != "NULL" && "$dorm_info" != "NULL\tNULL" ]]; then
# Split by tab and handle NULL values
IFS=$'\t' read -r dorm_ids_raw bed_ids_raw <<< "$dorm_info"
if [[ "$dorm_ids_raw" != "NULL" && -n "$dorm_ids_raw" ]]; then
dorm_ids="$dorm_ids_raw"
fi
if [[ "$bed_ids_raw" != "NULL" && -n "$bed_ids_raw" ]]; then
bed_ids="$bed_ids_raw"
fi
fi
else
log "WARN" "查询宿舍分配信息失败,将跳过宿舍处理 - 学生ID: $student_id"
fi
log "INFO" "找到宿舍分配 - ID: ${dorm_ids:-无}, 床位: ${bed_ids:-无}"
# Start transaction
local transaction_sql="
START TRANSACTION;
-- 1. Backup original data to his tables (ignore if already exists)
INSERT IGNORE INTO xg_student_msg_his SELECT * FROM xg_student_msg WHERE id = $student_id;
INSERT IGNORE INTO sys_user_his SELECT * FROM sys_user WHERE user_id = $student_id;
INSERT IGNORE INTO xg_class_pos_msg_his SELECT * FROM xg_class_pos_msg WHERE student_id = $student_id;
INSERT IGNORE INTO club_member_his SELECT * FROM club_member WHERE user_id = $student_id;
INSERT IGNORE INTO stgl_member_his SELECT * FROM stgl_member WHERE user_id = $student_id;
INSERT IGNORE INTO xg_dorm_bed_student_his SELECT * FROM xg_dorm_bed_student WHERE student_id = $student_id;
-- 2. Update student status
UPDATE xg_student_msg SET
student_status = CASE
WHEN '$change_type' = 'XIU_XUE' THEN '2'
WHEN '$change_type' = 'TUI_XUE' THEN '4'
WHEN '$change_type' = 'BI_YE' THEN '5'
ELSE student_status
END,
update_by = $operator_id,
update_time = NOW()
WHERE id = $student_id;
-- 3. Disable user account
UPDATE sys_user SET
status = '1',
update_by = $operator_id,
update_time = NOW()
WHERE user_id = $student_id AND account_type = '2';
-- 4. Process class position (cadre) - only for XIU_XUE, TUI_XUE, BI_YE
UPDATE xg_class_pos_msg SET
is_current = 0,
cadre_status = CASE
WHEN '$change_type' = 'XIU_XUE' THEN '6'
WHEN '$change_type' IN ('TUI_XUE', 'BI_YE') THEN '4'
ELSE cadre_status
END,
dismissal_date = CURDATE(),
dismissal_reason = CASE
WHEN '$change_type' = 'XIU_XUE' THEN '学生休学'
WHEN '$change_type' = 'TUI_XUE' THEN '学生退学'
WHEN '$change_type' = 'BI_YE' THEN '学生毕业'
ELSE dismissal_reason
END,
term_end_date = CURDATE(),
term_duration = TIMESTAMPDIFF(MONTH, term_start_date, CURDATE()),
update_by = $operator_id,
update_time = NOW()
WHERE student_id = $student_id AND is_current = 1;
-- 5. Process club membership - only for XIU_XUE, TUI_XUE, BI_YE
UPDATE club_member SET
member_status = '4',
update_by = $operator_id,
update_time = NOW()
WHERE user_id = $student_id AND member_status IN ('0', '1', '2');
-- 6. Process volunteer membership - only for XIU_XUE, TUI_XUE, BI_YE
UPDATE stgl_member SET
status = 4,
update_by = $operator_id,
update_time = NOW()
WHERE user_id = $student_id AND status IN (0, 1, 2);
-- 7. Process dormitory - only for TUI_XUE, BI_YE
-- First, clear dormitory info from student record
UPDATE xg_student_msg SET
dormitory_building = NULL,
dormitory_room = NULL,
ext_field1 = NULL,
update_time = NOW(),
update_by = $operator_id
WHERE id = $student_id;"
# Add dormitory processing only if assignments exist and are valid
if [[ -n "$dorm_ids" && "$dorm_ids" != "NULL" && -n "$bed_ids" && "$bed_ids" != "NULL" ]]; then
transaction_sql+="
-- Process dormitory checkout for specific assignments
UPDATE xg_dorm_bed_student SET
status = '1',
checkout_time = NOW(),
update_time = NOW(),
update_by = $operator_id
WHERE FIND_IN_SET(id, '$dorm_ids') AND deleted = 0;
-- Update bed status for specific beds
UPDATE xg_dorm_bed SET
bed_status = '0'
WHERE FIND_IN_SET(id, '$bed_ids') AND deleted = 0;"
log "INFO" "将处理宿舍退宿 - 分配ID: $dorm_ids, 床位ID: $bed_ids"
else
log "INFO" "无需处理宿舍退宿(无有效宿舍分配)"
fi
transaction_sql+="
-- 8. Update change record status
UPDATE xg_student_status_change SET
process_status = 1,
update_by = $operator_id,
update_time = NOW()
WHERE id = $record_id;
COMMIT;
"
# Execute transaction (using execute_sql to allow error output for debugging)
if execute_sql "$transaction_sql"; then
log "INFO" "成功处理学生状态变更 - ID: $record_id, 学生: $student_no"
return 0
else
log "ERROR" "处理学生状态变更失败 - ID: $record_id, 学生: $student_no"
return 1
fi
}
# Main function
main() {
log "INFO" "启动学生状态变更处理守护进程 (修复版本)"
log "INFO" "处理类型: XIU_XUE(休学), TUI_XUE(退学), BI_YE(毕业)"
log "INFO" "以连续模式运行,间隔10分钟"
# 测试数据库连接
log "INFO" "正在测试数据库连接..."
if ! execute_sql "SELECT 1;" >/dev/null 2>&1; then
error_exit "数据库连接失败"
fi
log "INFO" "数据库连接成功"
# Main processing loop
while true; do
log "INFO" "开始新的处理周期"
# 获取未处理的记录
local query_sql="
SELECT id, student_id, student_no, change_type, operator_id
FROM xg_student_status_change
WHERE process_status = 0
AND change_type IN ('XIU_XUE', 'TUI_XUE', 'BI_YE')
AND deleted = 0
AND effective_date <= NOW()
ORDER BY create_time ASC
LIMIT $BATCH_SIZE;
"
local records
records=$(execute_query "$query_sql")
if [[ $? -ne 0 ]]; then
log "ERROR" "获取待处理记录失败"
sleep $CYCLE_INTERVAL
continue
fi
# 检查是否有记录需要处理(过滤掉空行和MySQL警告信息)
local clean_records
clean_records=$(echo "$records" | grep -v "^mysql:" | grep -v "^$" | wc -l)
if [[ $clean_records -eq 0 ]]; then
log "INFO" "未找到未处理的记录,正在等待..."
else
log "INFO" "找到 $clean_records 条待处理记录"
local processed_count=0
local failed_count=0
# Process records directly from the query result, skip header and empty lines
local is_first_line=true
while IFS=$'\t' read -r record_id student_id student_no change_type operator_id; do
# Skip completely empty lines
[[ -z "$record_id$student_id$student_no$change_type$operator_id" ]] && continue
# Skip header line (first non-empty line that looks like column names)
if [[ $is_first_line == true ]]; then
is_first_line=false
# Check if this looks like a header line
if [[ "$record_id" == "id" || "$record_id" =~ ^[[:space:]]*id[[:space:]]*$ ]]; then
log "INFO" "跳过标题行"
continue
fi
fi
# Validate required fields
if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$change_type" ]]; then
log "WARN" "跳过无效记录 - ID: '$record_id', 学生ID: '$student_id', 学号: '$student_no', 类型: '$change_type'"
continue
fi
# Log the record being processed for debugging
log "INFO" "读取到记录 - ID: $record_id, 学生ID: $student_id, 学号: $student_no, 类型: $change_type, 操作员: ${operator_id:-1}"
local retry_count=0
local success=false
while [[ $retry_count -lt $MAX_RETRY && $success == false ]]; do
if process_student_change "$record_id" "$student_id" "$student_no" "$change_type" "$operator_id"; then
success=true
((processed_count++))
else
((retry_count++))
if [[ $retry_count -lt $MAX_RETRY ]]; then
log "WARN" "处理失败,$RETRY_DELAY 秒后重试... (尝试 $retry_count/$MAX_RETRY)"
sleep $RETRY_DELAY
fi
fi
done
if [[ $success == false ]]; then
((failed_count++))
log "ERROR" "$MAX_RETRY 次尝试后仍处理失败 - ID: $record_id"
fi
done <<< "$(echo "$records" | grep -v "^mysql:" | grep -v "^$")"
log "INFO" "处理周期完成 - 已处理: $processed_count, 失败: $failed_count"
fi
# 等待配置的间隔时间后开始下一个周期
log "INFO" "等待 $CYCLE_INTERVAL 秒后开始下一个处理周期..."
sleep $CYCLE_INTERVAL
done
}
# Run main function
main "$@"
@@ -0,0 +1,252 @@
#!/bin/bash
# ===========================================
# 学生状态变更处理脚本 - 复学 (修复版本)
# 处理类型: FU_XUE(复学)
# ===========================================
# set -euo pipefail # Temporarily disable to prevent script exit on errors
# Force C locale to ensure consistent behavior across systems
export LANG=C
export LC_ALL=C
# Load configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/data.conf"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "错误:配置文件 $CONFIG_FILE 未找到!"
exit 1
fi
source "$CONFIG_FILE"
# Create log directory
mkdir -p "$LOG_DIR"
# Log file
LOG_FILE="${LOG_DIR}/process_student_resume_fixed_$(date +%Y%m%d_%H%M%S).log"
# Lock file to prevent multiple instances
LOCK_FILE="${SCRIPT_DIR}/process_student_resume_fixed.lock"
# Functions
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
error_exit() {
local message="$1"
log "ERROR" "$message"
# Remove lock file
[[ -f "$LOCK_FILE" ]] && rm -f "$LOCK_FILE"
exit 1
}
# Database functions - Fixed version
execute_sql() {
local sql="$1"
local result
result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$sql" 2>&1)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "SQL执行失败: $sql"
log "ERROR" "MySQL错误: $result"
return 1
fi
echo "$result"
}
# Database query function - Fixed version for cross-platform compatibility
execute_query() {
local sql="$1"
local result
# Use -ss for ultra-silent mode to ensure clean tab-separated output
# Redirect stderr to /dev/null to discard warnings, only capture actual data
result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -ss "$DB_NAME" -e "$sql" 2>/dev/null)
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "查询执行失败: $sql"
log "ERROR" "MySQL错误码: $exit_code"
return 1
fi
echo "$result"
}
# Process student resume
process_student_resume() {
local record_id="$1"
local student_id="$2"
local student_no="$3"
local change_type="$4"
local operator_id="${5:-1}"
# Validate input parameters
if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$change_type" ]]; then
log "ERROR" "参数不完整 - record_id: '$record_id', student_id: '$student_id', student_no: '$student_no', change_type: '$change_type'"
return 1
fi
log "INFO" "正在处理学生复学 - ID: $record_id, 学生: $student_no ($student_id), 类型: $change_type"
# Start transaction
local transaction_sql="
START TRANSACTION;
-- 1. Backup original data to his tables (ignore if already exists)
INSERT IGNORE INTO xg_student_msg_his SELECT * FROM xg_student_msg WHERE id = $student_id;
INSERT IGNORE INTO sys_user_his SELECT * FROM sys_user WHERE user_id = $student_id;
-- 2. Update student status to resume (复学)
UPDATE xg_student_msg SET
student_status = '1',
update_by = $operator_id,
update_time = NOW()
WHERE id = $student_id;
-- 3. Enable user account
UPDATE sys_user SET
status = '0',
update_by = $operator_id,
update_time = NOW()
WHERE user_id = $student_id AND account_type = '2';
-- 4. Update change record status
UPDATE xg_student_status_change SET
process_status = 1,
update_by = $operator_id,
update_time = NOW()
WHERE id = $record_id;
COMMIT;
"
# Execute transaction (using execute_sql to allow error output for debugging)
if execute_sql "$transaction_sql"; then
log "INFO" "成功处理学生复学 - ID: $record_id, 学生: $student_no"
return 0
else
log "ERROR" "处理学生复学失败 - ID: $record_id, 学生: $student_no"
return 1
fi
}
# Main function
main() {
log "INFO" "启动学生复学处理守护进程 (修复版本)"
log "INFO" "处理类型: FU_XUE(复学)"
log "INFO" "以连续模式运行,间隔10分钟"
# 测试数据库连接
log "INFO" "正在测试数据库连接..."
if ! execute_sql "SELECT 1;" >/dev/null 2>&1; then
error_exit "数据库连接失败"
fi
log "INFO" "数据库连接成功"
# Main processing loop
while true; do
log "INFO" "开始新的复学处理周期"
# 获取未处理的记录
local query_sql="
SELECT id, student_id, student_no, change_type, operator_id
FROM xg_student_status_change
WHERE process_status = 0
AND change_type = 'FU_XUE'
AND deleted = 0
AND effective_date <= NOW()
ORDER BY create_time ASC
LIMIT $BATCH_SIZE;
"
local records
records=$(execute_query "$query_sql")
if [[ $? -ne 0 ]]; then
log "ERROR" "获取待处理记录失败"
sleep $CYCLE_INTERVAL
continue
fi
# 检查是否有记录需要处理(过滤掉空行和MySQL警告信息)
local clean_records
clean_records=$(echo "$records" | grep -v "^mysql:" | grep -v "^$" | wc -l)
if [[ $clean_records -eq 0 ]]; then
log "INFO" "未找到未处理的复学记录,正在等待..."
else
log "INFO" "找到 $clean_records 条待处理复学记录"
local processed_count=0
local failed_count=0
# Process records directly from the query result, skip header and empty lines
local is_first_line=true
while IFS=$'\t' read -r record_id student_id student_no change_type operator_id; do
# Skip completely empty lines
[[ -z "$record_id$student_id$student_no$change_type$operator_id" ]] && continue
# Skip header line (first non-empty line that looks like column names)
if [[ $is_first_line == true ]]; then
is_first_line=false
# Check if this looks like a header line
if [[ "$record_id" == "id" || "$record_id" =~ ^[[:space:]]*id[[:space:]]*$ ]]; then
log "INFO" "跳过标题行"
continue
fi
fi
# Validate required fields
if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$change_type" ]]; then
log "WARN" "跳过无效记录 - ID: '$record_id', 学生ID: '$student_id', 学号: '$student_no', 类型: '$change_type'"
continue
fi
# Log the record being processed for debugging
log "INFO" "读取到复学记录 - ID: $record_id, 学生ID: $student_id, 学号: $student_no, 类型: $change_type, 操作员: ${operator_id:-1}"
local retry_count=0
local success=false
while [[ $retry_count -lt $MAX_RETRY && $success == false ]]; do
if process_student_resume "$record_id" "$student_id" "$student_no" "$change_type" "$operator_id"; then
success=true
((processed_count++))
else
((retry_count++))
if [[ $retry_count -lt $MAX_RETRY ]]; then
log "WARN" "处理失败,$RETRY_DELAY 秒后重试... (尝试 $retry_count/$MAX_RETRY)"
sleep $RETRY_DELAY
fi
fi
done
if [[ $success == false ]]; then
((failed_count++))
log "ERROR" "$MAX_RETRY 次尝试后仍处理失败 - ID: $record_id"
fi
done <<< "$(echo "$records" | grep -v "^mysql:" | grep -v "^$")"
log "INFO" "复学处理周期完成 - 已处理: $processed_count, 失败: $failed_count"
fi
# 等待配置的间隔时间后开始下一个周期
log "INFO" "等待 $CYCLE_INTERVAL 秒后开始下一个复学处理周期..."
sleep $CYCLE_INTERVAL
done
}
# Run main function
main "$@"
@@ -0,0 +1,140 @@
#!/bin/bash
# Production environment debug script for student status change scripts
# This script helps diagnose MySQL output format issues
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/data.conf"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "错误:配置文件 $CONFIG_FILE 未找到!"
exit 1
fi
source "$CONFIG_FILE"
echo "=== 生产环境调试信息 ==="
echo "时间: $(date)"
echo "脚本目录: $SCRIPT_DIR"
echo "SHELL: $SHELL"
echo "BASH_VERSION: $BASH_VERSION"
echo
echo "=== MySQL版本信息 ==="
mysql --version
echo
echo "=== 系统信息 ==="
uname -a
echo
# Test basic connection
echo "=== 测试数据库连接 ==="
if mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "SELECT 1;" >/dev/null 2>&1; then
echo "数据库连接成功"
else
echo "数据库连接失败"
exit 1
fi
echo
# Test query formats
echo "=== 测试MySQL查询输出格式 ==="
QUERY="SELECT id, student_id, student_no, change_type, operator_id FROM xg_student_status_change WHERE process_status = 0 AND change_type IN ('XIU_XUE', 'TUI_XUE', 'BI_YE') AND deleted = 0 AND effective_date <= NOW() ORDER BY create_time ASC LIMIT 2;"
echo "查询SQL: $QUERY"
echo
echo "1. 标准mysql -e 输出 (可能包含表格格式):"
echo "----------------------------------------"
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$QUERY" 2>&1
echo "----------------------------------------"
echo
echo "2. mysql -B 输出 (批量模式,无表格格式):"
echo "----------------------------------------"
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -B "$DB_NAME" -e "$QUERY" 2>&1
echo "----------------------------------------"
echo
echo "3. mysql -ss 输出 (超静默模式):"
echo "----------------------------------------"
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -ss "$DB_NAME" -e "$QUERY" 2>&1
echo "----------------------------------------"
echo
echo "=== 详细分析 -B 参数输出 ==="
RESULT_B=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -B "$DB_NAME" -e "$QUERY" 2>&1)
echo "原始输出 (包含警告):"
echo "\"$RESULT_B\""
echo
echo "按行分割查看:"
echo "$RESULT_B" | while IFS= read -r line; do
echo "行: '$line'"
done
echo
echo "过滤MySQL警告后的结果:"
FILTERED_RESULT=$(echo "$RESULT_B" | grep -v "^mysql:" | grep -v "^$")
echo "\"$FILTERED_RESULT\""
echo
echo "解析为变量 (使用tab分隔):"
if [[ -n "$FILTERED_RESULT" ]]; then
echo "$FILTERED_RESULT" | while IFS=$'\t' read -r record_id student_id student_no change_type operator_id; do
echo "解析结果:"
echo " record_id: '$record_id'"
echo " student_id: '$student_id'"
echo " student_no: '$student_no'"
echo " change_type: '$change_type'"
echo " operator_id: '$operator_id'"
echo
# Test if variables are numeric
echo "变量类型检查:"
echo " record_id 是数字: $([[ '$record_id' =~ ^[0-9]+$ ]] && echo '是' || echo '否')"
echo " student_id 是数字: $([[ '$student_id' =~ ^[0-9]+$ ]] && echo '是' || echo '否')"
echo " operator_id 是数字或空: $([[ -z '$operator_id' || '$operator_id' =~ ^[0-9]+$ ]] && echo '是' || echo '否')"
echo
# Simulate SQL construction
echo "模拟SQL构造:"
TEST_SQL="UPDATE xg_student_msg SET student_status = '2' WHERE id = '$student_id';"
echo " $TEST_SQL"
echo
# Test SQL execution (dry run - just show what would be executed)
echo "SQL执行测试 (仅显示,不实际执行):"
echo " mysql -h'$DB_HOST' -P'$DB_PORT' -u'$DB_USER' -p'***' '$DB_NAME' -e \"$TEST_SQL\""
done
else
echo "没有找到数据行,检查是否有待处理的记录"
fi
echo
echo "=== 脚本兼容性检查 ==="
# Check bash features
echo "Bash特性检查:"
echo " 支持pipefail: $(set -o | grep pipefail)"
echo " 支持nounset: $(set -o | grep nounset)"
echo
echo "=== 环境变量检查 ==="
echo "PATH: $PATH"
echo "LD_LIBRARY_PATH: ${LD_LIBRARY_PATH:-未设置}"
echo
echo "=== 建议的修复方案 ==="
echo "如果上述解析失败,请尝试以下修改:"
echo "1. 在脚本开头添加: export LANG=C"
echo "2. 修改MySQL查询使用 -ss 参数而不是 -B"
echo "3. 检查数据表中是否有实际的待处理记录"
echo "4. 验证operator_id字段是否可能为NULL"
echo
echo "=== 调试完成 ==="
echo "请将上述输出结果发送给开发人员"