process_student_class_change_fixed.sh 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. #!/bin/bash
  2. # ===========================================
  3. # 学生换班级处理脚本 (修复版本)
  4. # 处理类型: CLASS_CHANGE(换班级)
  5. # ===========================================
  6. # set -euo pipefail # Temporarily disable to prevent script exit on errors
  7. # Force C locale to ensure consistent behavior across systems
  8. export LANG=C
  9. export LC_ALL=C
  10. # Load configuration
  11. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  12. CONFIG_FILE="${SCRIPT_DIR}/data.conf"
  13. if [[ ! -f "$CONFIG_FILE" ]]; then
  14. echo "错误:配置文件 $CONFIG_FILE 未找到!"
  15. exit 1
  16. fi
  17. source "$CONFIG_FILE"
  18. # Create log directory
  19. mkdir -p "$LOG_DIR"
  20. # Log file
  21. LOG_FILE="${LOG_DIR}/process_student_class_change_fixed_$(date +%Y%m%d_%H%M%S).log"
  22. # Lock file to prevent multiple instances
  23. LOCK_FILE="${SCRIPT_DIR}/process_student_class_change_fixed.lock"
  24. # Functions
  25. log() {
  26. local level="$1"
  27. local message="$2"
  28. local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
  29. echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
  30. }
  31. error_exit() {
  32. local message="$1"
  33. log "ERROR" "$message"
  34. # Remove lock file
  35. [[ -f "$LOCK_FILE" ]] && rm -f "$LOCK_FILE"
  36. exit 1
  37. }
  38. # Database functions - Fixed version
  39. execute_sql() {
  40. local sql="$1"
  41. local result
  42. result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e "$sql" 2>&1)
  43. local exit_code=$?
  44. if [[ $exit_code -ne 0 ]]; then
  45. log "ERROR" "SQL执行失败 (退出码: $exit_code)"
  46. log "ERROR" "数据库连接: $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME"
  47. log "ERROR" "失败的SQL: $sql"
  48. log "ERROR" "MySQL错误详情: $result"
  49. return 1
  50. fi
  51. echo "$result"
  52. }
  53. # Database query function - Fixed version for cross-platform compatibility
  54. execute_query() {
  55. local sql="$1"
  56. local result
  57. # Use -ss for ultra-silent mode to ensure clean tab-separated output
  58. result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -ss "$DB_NAME" -e "$sql" 2>&1)
  59. local exit_code=$?
  60. if [[ $exit_code -ne 0 ]]; then
  61. log "ERROR" "数据库查询执行失败 (退出码: $exit_code)"
  62. log "ERROR" "数据库连接: $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME"
  63. log "ERROR" "失败的SQL: $sql"
  64. log "ERROR" "MySQL错误详情: $result"
  65. return 1
  66. fi
  67. echo "$result"
  68. }
  69. # Process student class change
  70. process_student_class_change() {
  71. local record_id="$1"
  72. local student_id="$2"
  73. local student_no="$3"
  74. local new_class_id="$4"
  75. local operator_id="${5:-1}"
  76. # Validate input parameters
  77. if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$new_class_id" ]]; then
  78. log "ERROR" "参数不完整 - record_id: '$record_id', student_id: '$student_id', student_no: '$student_no', new_class_id: '$new_class_id'"
  79. return 1
  80. fi
  81. log "INFO" "Processing student class change - ID: $record_id, Student: $student_no ($student_id), New Class ID: $new_class_id"
  82. # Get new class information
  83. local class_info_sql="
  84. SELECT
  85. c.college_id, c.college_name, c.major_id, c.major_name,
  86. c.class_code, c.class_name, c.class_type, c.grade,
  87. c.training_type, c.counselor_id, c.counselor_name,
  88. c.class_teacher_id, c.class_teacher_name, c.school_system
  89. FROM xg_class_msg c
  90. WHERE c.id = $new_class_id AND c.deleted = 0
  91. "
  92. local class_info
  93. class_info=$(execute_query "$class_info_sql")
  94. if [[ -z "$class_info" || "$class_info" == *"Empty set"* ]]; then
  95. log "ERROR" "获取班级ID为 $new_class_id 的班级信息失败"
  96. return 1
  97. fi
  98. # Parse class information - Fixed parsing logic
  99. local class_data
  100. class_data=$(echo "$class_info" | grep -v "^mysql:" | grep -v "^$" | head -n 1)
  101. if [[ -z "$class_data" ]]; then
  102. log "ERROR" "解析班级信息失败,无法获取数据行"
  103. return 1
  104. fi
  105. # Use cut to extract specific fields reliably
  106. local college_id=$(echo "$class_data" | cut -f1)
  107. local college_name=$(echo "$class_data" | cut -f2)
  108. local major_id=$(echo "$class_data" | cut -f3)
  109. local major_name=$(echo "$class_data" | cut -f4)
  110. local class_code=$(echo "$class_data" | cut -f5)
  111. local class_name=$(echo "$class_data" | cut -f6)
  112. local class_type=$(echo "$class_data" | cut -f7)
  113. local grade=$(echo "$class_data" | cut -f8)
  114. local training_type=$(echo "$class_data" | cut -f9)
  115. local counselor_id=$(echo "$class_data" | cut -f10)
  116. local counselor_name=$(echo "$class_data" | cut -f11)
  117. local class_teacher_id=$(echo "$class_data" | cut -f12)
  118. local class_teacher_name=$(echo "$class_data" | cut -f13)
  119. local school_system=$(echo "$class_data" | cut -f14)
  120. # Handle NULL values for IDs
  121. local counselor_id_value class_teacher_id_value
  122. if [[ "$counselor_id" == "NULL" || -z "$counselor_id" ]]; then
  123. counselor_id_value="NULL"
  124. else
  125. counselor_id_value="'$counselor_id'"
  126. fi
  127. if [[ "$class_teacher_id" == "NULL" || -z "$class_teacher_id" ]]; then
  128. class_teacher_id_value="NULL"
  129. else
  130. class_teacher_id_value="'$class_teacher_id'"
  131. fi
  132. # Start transaction
  133. local transaction_sql="
  134. START TRANSACTION;
  135. -- 1. Backup original data to his tables (ignore if already exists)
  136. INSERT IGNORE INTO xg_student_msg_his SELECT * FROM xg_student_msg WHERE id = $student_id;
  137. INSERT IGNORE INTO xg_class_pos_msg_his SELECT * FROM xg_class_pos_msg WHERE student_id = $student_id;
  138. -- 2. Terminate cadre positions (干部信息终止)
  139. UPDATE xg_class_pos_msg SET
  140. is_current = 0,
  141. cadre_status = '4', -- 免职
  142. dismissal_date = CURDATE(),
  143. dismissal_reason = '学生换班级',
  144. term_end_date = CURDATE(),
  145. term_duration = TIMESTAMPDIFF(MONTH, term_start_date, CURDATE()),
  146. update_by = $operator_id,
  147. update_time = NOW()
  148. WHERE student_id = $student_id AND is_current = 1;
  149. -- 3. Update student information with new class data
  150. UPDATE xg_student_msg SET
  151. -- 基础班级信息
  152. college_id = '$college_id',
  153. college_name = '$college_name',
  154. major_id = '$major_id',
  155. major_name = '$major_name',
  156. class_id = '$new_class_id',
  157. class_name = '$class_name',
  158. grade = '$grade',
  159. -- 预留字段从班级表关联获取的数据 (7182-7195行)
  160. school_system = '$school_system',
  161. academic_level = '$class_type', -- class_type 对应 academic_level
  162. training_type = '$training_type',
  163. counselor_id = $counselor_id_value,
  164. counselor_name = '$counselor_name',
  165. class_teacher_id = $class_teacher_id_value,
  166. class_teacher_name = '$class_teacher_name',
  167. -- 更新信息
  168. update_by = $operator_id,
  169. update_time = NOW()
  170. WHERE id = $student_id;
  171. -- 4. Update class change record status
  172. UPDATE xg_student_class_change SET
  173. status = '1', -- 已处理
  174. update_by = $operator_id,
  175. update_time = NOW()
  176. WHERE id = $record_id;
  177. COMMIT;
  178. "
  179. # 执行事务
  180. if execute_sql "$transaction_sql"; then
  181. log "INFO" "成功处理学生换班级 - ID: $record_id, 学生: $student_no"
  182. return 0
  183. else
  184. log "ERROR" "处理学生换班级失败 - ID: $record_id, 学生: $student_no"
  185. return 1
  186. fi
  187. }
  188. # Main function
  189. main() {
  190. log "INFO" "启动学生换班级处理守护进程 (修复版本)"
  191. log "INFO" "处理类型: CLASS_CHANGE(换班级)"
  192. log "INFO" "以连续模式运行,间隔10分钟"
  193. # 测试数据库连接
  194. log "INFO" "正在测试数据库连接..."
  195. log "INFO" "连接信息: $DB_USER@$DB_HOST:$DB_PORT/$DB_NAME"
  196. if ! execute_sql "SELECT 1;" >/dev/null 2>&1; then
  197. error_exit "数据库连接失败 - 请检查连接参数和网络连接"
  198. fi
  199. log "INFO" "数据库连接测试成功"
  200. # Main processing loop
  201. while true; do
  202. log "INFO" "开始新的处理周期"
  203. # 获取未处理的记录
  204. local query_sql="
  205. SELECT id, student_id, student_no, new_class_id, update_by
  206. FROM xg_student_class_change
  207. WHERE status = '0'
  208. AND deleted = 0
  209. AND effective_time <= NOW()
  210. ORDER BY create_time ASC
  211. LIMIT $BATCH_SIZE;
  212. "
  213. local records
  214. if ! records=$(execute_query "$query_sql"); then
  215. log "ERROR" "获取待处理记录失败,${CYCLE_INTERVAL}秒后重试"
  216. sleep $CYCLE_INTERVAL
  217. continue
  218. fi
  219. # 检查是否有记录需要处理(过滤掉空行和MySQL警告信息)
  220. local clean_records
  221. clean_records=$(echo "$records" | grep -v "^mysql:" | grep -v "^$" | wc -l)
  222. # 去除可能的空白字符
  223. clean_records=$(echo "$clean_records" | tr -d ' ')
  224. if [[ $clean_records -le 1 ]]; then
  225. log "INFO" "未找到未处理的记录,正在等待..."
  226. else
  227. # 排除标题行后的实际记录数
  228. local actual_records=$((clean_records - 1))
  229. log "INFO" "找到 $actual_records 条待处理记录"
  230. # 调试:显示前几条记录的信息
  231. if [[ $actual_records -gt 0 ]]; then
  232. log "DEBUG" "将处理 $actual_records 条记录"
  233. fi
  234. local processed_count=0
  235. local failed_count=0
  236. # Process records line by line using a simple loop
  237. local filtered_data
  238. filtered_data=$(echo "$records" | grep -v "^mysql:" | grep -v "^$")
  239. log "DEBUG" "过滤后数据行数: $(echo "$filtered_data" | wc -l)"
  240. log "DEBUG" "过滤后数据内容预览:"
  241. echo "$filtered_data" | head -3 | while read -r preview_line; do
  242. log "DEBUG" " DATA: $preview_line"
  243. done
  244. # Create temporary file for processing to avoid subshell variable scope issues
  245. local temp_file="/tmp/student_class_change_process_$$.txt"
  246. echo "$filtered_data" > "$temp_file"
  247. # Process records from temporary file (variables will be in same shell)
  248. local is_first_line=true
  249. while IFS= read -r line; do
  250. # Parse the tab-separated values for processing
  251. IFS=$'\t' read -r record_id student_id student_no new_class_id operator_id <<< "$line"
  252. # Skip completely empty lines
  253. if [[ -z "$record_id$student_id$student_no$new_class_id$operator_id" ]]; then
  254. continue
  255. fi
  256. # Skip header line (first non-empty line that looks like column names)
  257. if [[ $is_first_line == true ]]; then
  258. is_first_line=false
  259. # Check if this looks like a header line
  260. if [[ "$record_id" == "id" || "$record_id" =~ ^[[:space:]]*id[[:space:]]*$ ]]; then
  261. log "INFO" "跳过标题行"
  262. continue
  263. fi
  264. fi
  265. # Validate required fields
  266. if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$new_class_id" ]]; then
  267. log "WARN" "跳过无效记录 - ID: '$record_id'"
  268. continue
  269. fi
  270. # Log the record being processed
  271. log "INFO" "处理记录 - ID: $record_id, 学号: $student_no"
  272. local retry_count=0
  273. local success=false
  274. while [[ $retry_count -lt $MAX_RETRY && $success == false ]]; do
  275. log "DEBUG" "开始调用process_student_class_change函数,参数: $record_id, $student_id, $student_no, $new_class_id, $operator_id"
  276. if process_student_class_change "$record_id" "$student_id" "$student_no" "$new_class_id" "$operator_id"; then
  277. success=true
  278. ((processed_count++))
  279. log "INFO" "成功处理记录 $record_id (总计: $processed_count)"
  280. log "DEBUG" "函数调用成功,success=$success, processed_count=$processed_count"
  281. else
  282. ((retry_count++))
  283. log "DEBUG" "函数调用失败,retry_count=$retry_count, MAX_RETRY=$MAX_RETRY"
  284. if [[ $retry_count -lt $MAX_RETRY ]]; then
  285. log "WARN" "处理失败,重试中..."
  286. sleep $RETRY_DELAY
  287. fi
  288. fi
  289. done
  290. if [[ $success == false ]]; then
  291. ((failed_count++))
  292. log "ERROR" "记录 $record_id 处理失败"
  293. fi
  294. ((line_count++))
  295. echo "DEBUG: 处理第 $line_count 行: $line" >&2
  296. log "DEBUG" "处理第 $line_count 行: $line"
  297. # Parse the tab-separated values for processing
  298. IFS=$'\t' read -r record_id student_id student_no new_class_id operator_id <<< "$line"
  299. # Skip completely empty lines
  300. if [[ -z "$record_id$student_id$student_no$new_class_id$operator_id" ]]; then
  301. continue
  302. fi
  303. # Skip header line (first non-empty line that looks like column names)
  304. if [[ $is_first_line == true ]]; then
  305. is_first_line=false
  306. if [[ "$record_id" == "id" || "$record_id" =~ ^[[:space:]]*id[[:space:]]*$ ]]; then
  307. continue
  308. fi
  309. fi
  310. # Validate required fields
  311. if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$new_class_id" ]]; then
  312. continue
  313. fi
  314. # Log the record being processed
  315. log "INFO" "处理记录 - ID: $record_id, 学号: $student_no"
  316. local retry_count=0
  317. local success=false
  318. while [[ $retry_count -lt $MAX_RETRY && $success == false ]]; do
  319. log "DEBUG" "开始调用process_student_class_change函数,参数: $record_id, $student_id, $student_no, $new_class_id, $operator_id"
  320. if process_student_class_change "$record_id" "$student_id" "$student_no" "$new_class_id" "$operator_id"; then
  321. success=true
  322. ((processed_count++))
  323. log "INFO" "成功处理记录 $record_id (总计: $processed_count)"
  324. log "DEBUG" "函数调用成功,success=$success, processed_count=$processed_count"
  325. else
  326. ((retry_count++))
  327. log "DEBUG" "函数调用失败,retry_count=$retry_count, MAX_RETRY=$MAX_RETRY"
  328. if [[ $retry_count -lt $MAX_RETRY ]]; then
  329. log "WARN" "处理失败,重试中..."
  330. sleep $RETRY_DELAY
  331. fi
  332. fi
  333. done
  334. if [[ $success == false ]]; then
  335. ((failed_count++))
  336. log "ERROR" "记录 $record_id 处理失败"
  337. fi
  338. log "DEBUG" "完成处理记录 $record_id,继续下一条"
  339. done < "$temp_file"
  340. log "DEBUG" "所有记录处理完成,processed_count=$processed_count, failed_count=$failed_count"
  341. # Clean up temporary file
  342. rm -f "$temp_file"
  343. log "INFO" "处理周期完成 - 已处理: $processed_count, 失败: $failed_count"
  344. log "INFO" "处理周期完成 - 已处理: $processed_count, 失败: $failed_count"
  345. fi
  346. # 等待配置的间隔时间后开始下一个周期
  347. log "INFO" "等待 $CYCLE_INTERVAL 秒后开始下一个处理周期..."
  348. sleep $CYCLE_INTERVAL
  349. done
  350. }
  351. # Run main function
  352. main "$@"