From abfc5cd746161b1640729706dfc049742f7ddc3d Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 26 Dec 2025 00:44:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 学生换班级/README.md | 102 ----- 学生换班级/README_换班级脚本.md | 248 ------------ 学生换班级/control_scripts.sh | 332 ---------------- 学生换班级/data.conf.prod | 17 + 学生换班级/health_check.sh | 348 ----------------- .../process_student_class_change_fixed.sh | 337 +++++++++++++++++ .../PRODUCTION_DEPLOYMENT.md | 220 +++++++++++ 退学、休学、复学、毕业/control_scripts.sh | 233 ++++++++++-- 退学、休学、复学、毕业/data.conf.prod | 17 + .../optional_crontab_config.txt | 69 ---- .../process_student_changes.sh | 71 ++-- .../process_student_changes_fixed.sh | 357 ++++++++++++++++++ .../process_student_resume_fixed.sh | 252 +++++++++++++ 退学、休学、复学、毕业/prod_debug.sh | 140 +++++++ 14 files changed, 1595 insertions(+), 1148 deletions(-) delete mode 100644 学生换班级/README.md delete mode 100644 学生换班级/README_换班级脚本.md delete mode 100755 学生换班级/control_scripts.sh create mode 100644 学生换班级/data.conf.prod delete mode 100755 学生换班级/health_check.sh create mode 100644 学生换班级/process_student_class_change_fixed.sh create mode 100644 退学、休学、复学、毕业/PRODUCTION_DEPLOYMENT.md create mode 100644 退学、休学、复学、毕业/data.conf.prod delete mode 100644 退学、休学、复学、毕业/optional_crontab_config.txt create mode 100755 退学、休学、复学、毕业/process_student_changes_fixed.sh create mode 100755 退学、休学、复学、毕业/process_student_resume_fixed.sh create mode 100644 退学、休学、复学、毕业/prod_debug.sh diff --git a/学生换班级/README.md b/学生换班级/README.md deleted file mode 100644 index 5e00a763..00000000 --- a/学生换班级/README.md +++ /dev/null @@ -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 diff --git a/学生换班级/README_换班级脚本.md b/学生换班级/README_换班级脚本.md deleted file mode 100644 index b37f4d7c..00000000 --- a/学生换班级/README_换班级脚本.md +++ /dev/null @@ -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 diff --git a/学生换班级/control_scripts.sh b/学生换班级/control_scripts.sh deleted file mode 100755 index e7386065..00000000 --- a/学生换班级/control_scripts.sh +++ /dev/null @@ -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 diff --git a/学生换班级/data.conf.prod b/学生换班级/data.conf.prod new file mode 100644 index 00000000..0db2162d --- /dev/null +++ b/学生换班级/data.conf.prod @@ -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分钟 diff --git a/学生换班级/health_check.sh b/学生换班级/health_check.sh deleted file mode 100755 index fc0170b6..00000000 --- a/学生换班级/health_check.sh +++ /dev/null @@ -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 diff --git a/学生换班级/process_student_class_change_fixed.sh b/学生换班级/process_student_class_change_fixed.sh new file mode 100644 index 00000000..6a62c270 --- /dev/null +++ b/学生换班级/process_student_class_change_fixed.sh @@ -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 "$@" diff --git a/退学、休学、复学、毕业/PRODUCTION_DEPLOYMENT.md b/退学、休学、复学、毕业/PRODUCTION_DEPLOYMENT.md new file mode 100644 index 00000000..3403fd84 --- /dev/null +++ b/退学、休学、复学、毕业/PRODUCTION_DEPLOYMENT.md @@ -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兼容修复版本 diff --git a/退学、休学、复学、毕业/control_scripts.sh b/退学、休学、复学、毕业/control_scripts.sh index f40918d6..eff833cb 100755 --- a/退学、休学、复学、毕业/control_scripts.sh +++ b/退学、休学、复学、毕业/control_scripts.sh @@ -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 diff --git a/退学、休学、复学、毕业/data.conf.prod b/退学、休学、复学、毕业/data.conf.prod new file mode 100644 index 00000000..20e71632 --- /dev/null +++ b/退学、休学、复学、毕业/data.conf.prod @@ -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分钟 diff --git a/退学、休学、复学、毕业/optional_crontab_config.txt b/退学、休学、复学、毕业/optional_crontab_config.txt deleted file mode 100644 index eaaa4bd5..00000000 --- a/退学、休学、复学、毕业/optional_crontab_config.txt +++ /dev/null @@ -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 diff --git a/退学、休学、复学、毕业/process_student_changes.sh b/退学、休学、复学、毕业/process_student_changes.sh index fdc0ae3b..f8ab2c80 100755 --- a/退学、休学、复学、毕业/process_student_changes.sh +++ b/退学、休学、复学、毕业/process_student_changes.sh @@ -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 diff --git a/退学、休学、复学、毕业/process_student_changes_fixed.sh b/退学、休学、复学、毕业/process_student_changes_fixed.sh new file mode 100755 index 00000000..ad8e0d0b --- /dev/null +++ b/退学、休学、复学、毕业/process_student_changes_fixed.sh @@ -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 "$@" diff --git a/退学、休学、复学、毕业/process_student_resume_fixed.sh b/退学、休学、复学、毕业/process_student_resume_fixed.sh new file mode 100755 index 00000000..ebb6662e --- /dev/null +++ b/退学、休学、复学、毕业/process_student_resume_fixed.sh @@ -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 "$@" diff --git a/退学、休学、复学、毕业/prod_debug.sh b/退学、休学、复学、毕业/prod_debug.sh new file mode 100644 index 00000000..c2c11e6d --- /dev/null +++ b/退学、休学、复学、毕业/prod_debug.sh @@ -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 "请将上述输出结果发送给开发人员"