process_student_changes_fixed.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. #!/bin/bash
  2. # ===========================================
  3. # 学生状态变更处理脚本 - 毕业/退学/休学 (修复版本)
  4. # 处理类型: XIU_XUE(休学), TUI_XUE(退学), BI_YE(毕业)
  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_changes_fixed_$(date +%Y%m%d_%H%M%S).log"
  22. # Lock file to prevent multiple instances
  23. LOCK_FILE="${SCRIPT_DIR}/process_student_changes_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执行失败: $sql"
  46. log "ERROR" "MySQL错误: $result"
  47. return 1
  48. fi
  49. echo "$result"
  50. }
  51. # Database query function - Fixed version for cross-platform compatibility
  52. execute_query() {
  53. local sql="$1"
  54. local result
  55. # Use -ss for ultra-silent mode to ensure clean tab-separated output
  56. # Redirect stderr to /dev/null to discard warnings, only capture actual data
  57. result=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -ss "$DB_NAME" -e "$sql" 2>/dev/null)
  58. local exit_code=$?
  59. if [[ $exit_code -ne 0 ]]; then
  60. log "ERROR" "查询执行失败: $sql"
  61. log "ERROR" "MySQL错误码: $exit_code"
  62. return 1
  63. fi
  64. echo "$result"
  65. }
  66. # Process student status change
  67. process_student_change() {
  68. local record_id="$1"
  69. local student_id="$2"
  70. local student_no="$3"
  71. local change_type="$4"
  72. local operator_id="${5:-1}"
  73. # Validate input parameters
  74. if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$change_type" ]]; then
  75. log "ERROR" "参数不完整 - record_id: '$record_id', student_id: '$student_id', student_no: '$student_no', change_type: '$change_type'"
  76. return 1
  77. fi
  78. log "INFO" "正在处理学生状态变更 - ID: $record_id, 学生: $student_no ($student_id), 类型: $change_type"
  79. # Get dormitory assignment info before transaction
  80. local dorm_query="
  81. SELECT GROUP_CONCAT(id), GROUP_CONCAT(bed_id)
  82. FROM xg_dorm_bed_student
  83. WHERE student_id = $student_id AND status = '0' AND deleted = 0;
  84. "
  85. local dorm_info=""
  86. local dorm_ids=""
  87. local bed_ids=""
  88. # Execute dormitory query with error handling
  89. if dorm_info=$(execute_query "$dorm_query" 2>/dev/null); then
  90. # Parse the result - handle NULL values properly
  91. if [[ -n "$dorm_info" && "$dorm_info" != "NULL" && "$dorm_info" != "NULL\tNULL" ]]; then
  92. # Split by tab and handle NULL values
  93. IFS=$'\t' read -r dorm_ids_raw bed_ids_raw <<< "$dorm_info"
  94. if [[ "$dorm_ids_raw" != "NULL" && -n "$dorm_ids_raw" ]]; then
  95. dorm_ids="$dorm_ids_raw"
  96. fi
  97. if [[ "$bed_ids_raw" != "NULL" && -n "$bed_ids_raw" ]]; then
  98. bed_ids="$bed_ids_raw"
  99. fi
  100. fi
  101. else
  102. log "WARN" "查询宿舍分配信息失败,将跳过宿舍处理 - 学生ID: $student_id"
  103. fi
  104. log "INFO" "找到宿舍分配 - ID: ${dorm_ids:-无}, 床位: ${bed_ids:-无}"
  105. # Start transaction
  106. local transaction_sql="
  107. START TRANSACTION;
  108. -- 1. Backup original data to his tables (ignore if already exists)
  109. INSERT IGNORE INTO xg_student_msg_his SELECT * FROM xg_student_msg WHERE id = $student_id;
  110. INSERT IGNORE INTO sys_user_his SELECT * FROM sys_user WHERE user_id = $student_id;
  111. INSERT IGNORE INTO xg_class_pos_msg_his SELECT * FROM xg_class_pos_msg WHERE student_id = $student_id;
  112. INSERT IGNORE INTO club_member_his SELECT * FROM club_member WHERE user_id = $student_id;
  113. INSERT IGNORE INTO stgl_member_his SELECT * FROM stgl_member WHERE user_id = $student_id;
  114. INSERT IGNORE INTO xg_dorm_bed_student_his SELECT * FROM xg_dorm_bed_student WHERE student_id = $student_id;
  115. -- 2. Update student status
  116. UPDATE xg_student_msg SET
  117. student_status = CASE
  118. WHEN '$change_type' = 'XIU_XUE' THEN '2'
  119. WHEN '$change_type' = 'TUI_XUE' THEN '4'
  120. WHEN '$change_type' = 'BI_YE' THEN '5'
  121. ELSE student_status
  122. END,
  123. update_by = $operator_id,
  124. update_time = NOW()
  125. WHERE id = $student_id;
  126. -- 3. Disable user account
  127. UPDATE sys_user SET
  128. status = '1',
  129. update_by = $operator_id,
  130. update_time = NOW()
  131. WHERE user_id = $student_id AND account_type = '2';
  132. -- 4. Process class position (cadre) - only for XIU_XUE, TUI_XUE, BI_YE
  133. UPDATE xg_class_pos_msg SET
  134. is_current = 0,
  135. cadre_status = CASE
  136. WHEN '$change_type' = 'XIU_XUE' THEN '6'
  137. WHEN '$change_type' IN ('TUI_XUE', 'BI_YE') THEN '4'
  138. ELSE cadre_status
  139. END,
  140. dismissal_date = CURDATE(),
  141. dismissal_reason = CASE
  142. WHEN '$change_type' = 'XIU_XUE' THEN '学生休学'
  143. WHEN '$change_type' = 'TUI_XUE' THEN '学生退学'
  144. WHEN '$change_type' = 'BI_YE' THEN '学生毕业'
  145. ELSE dismissal_reason
  146. END,
  147. term_end_date = CURDATE(),
  148. term_duration = TIMESTAMPDIFF(MONTH, term_start_date, CURDATE()),
  149. update_by = $operator_id,
  150. update_time = NOW()
  151. WHERE student_id = $student_id AND is_current = 1;
  152. -- 5. Process club membership - only for XIU_XUE, TUI_XUE, BI_YE
  153. UPDATE club_member SET
  154. member_status = '4',
  155. update_by = $operator_id,
  156. update_time = NOW()
  157. WHERE user_id = $student_id AND member_status IN ('0', '1', '2');
  158. -- 6. Process volunteer membership - only for XIU_XUE, TUI_XUE, BI_YE
  159. UPDATE stgl_member SET
  160. status = 4,
  161. update_by = $operator_id,
  162. update_time = NOW()
  163. WHERE user_id = $student_id AND status IN (0, 1, 2);
  164. -- 7. Process dormitory - only for TUI_XUE, BI_YE
  165. -- First, clear dormitory info from student record
  166. UPDATE xg_student_msg SET
  167. dormitory_building = NULL,
  168. dormitory_room = NULL,
  169. ext_field1 = NULL,
  170. update_time = NOW(),
  171. update_by = $operator_id
  172. WHERE id = $student_id;"
  173. # Add dormitory processing only if assignments exist and are valid
  174. if [[ -n "$dorm_ids" && "$dorm_ids" != "NULL" && -n "$bed_ids" && "$bed_ids" != "NULL" ]]; then
  175. transaction_sql+="
  176. -- Process dormitory checkout for specific assignments
  177. UPDATE xg_dorm_bed_student SET
  178. status = '1',
  179. checkout_time = NOW(),
  180. update_time = NOW(),
  181. update_by = $operator_id
  182. WHERE FIND_IN_SET(id, '$dorm_ids') AND deleted = 0;
  183. -- Update bed status for specific beds
  184. UPDATE xg_dorm_bed SET
  185. bed_status = '0'
  186. WHERE FIND_IN_SET(id, '$bed_ids') AND deleted = 0;"
  187. log "INFO" "将处理宿舍退宿 - 分配ID: $dorm_ids, 床位ID: $bed_ids"
  188. else
  189. log "INFO" "无需处理宿舍退宿(无有效宿舍分配)"
  190. fi
  191. transaction_sql+="
  192. -- 8. Update change record status
  193. UPDATE xg_student_status_change SET
  194. process_status = 1,
  195. update_by = $operator_id,
  196. update_time = NOW()
  197. WHERE id = $record_id;
  198. COMMIT;
  199. "
  200. # Execute transaction (using execute_sql to allow error output for debugging)
  201. if execute_sql "$transaction_sql"; then
  202. log "INFO" "成功处理学生状态变更 - ID: $record_id, 学生: $student_no"
  203. return 0
  204. else
  205. log "ERROR" "处理学生状态变更失败 - ID: $record_id, 学生: $student_no"
  206. return 1
  207. fi
  208. }
  209. # Main function
  210. main() {
  211. log "INFO" "启动学生状态变更处理守护进程 (修复版本)"
  212. log "INFO" "处理类型: XIU_XUE(休学), TUI_XUE(退学), BI_YE(毕业)"
  213. log "INFO" "以连续模式运行,间隔10分钟"
  214. # 测试数据库连接
  215. log "INFO" "正在测试数据库连接..."
  216. if ! execute_sql "SELECT 1;" >/dev/null 2>&1; then
  217. error_exit "数据库连接失败"
  218. fi
  219. log "INFO" "数据库连接成功"
  220. # Main processing loop
  221. while true; do
  222. log "INFO" "开始新的处理周期"
  223. # 获取未处理的记录
  224. local query_sql="
  225. SELECT id, student_id, student_no, change_type, operator_id
  226. FROM xg_student_status_change
  227. WHERE process_status = 0
  228. AND change_type IN ('XIU_XUE', 'TUI_XUE', 'BI_YE')
  229. AND deleted = 0
  230. AND effective_date <= NOW()
  231. ORDER BY create_time ASC
  232. LIMIT $BATCH_SIZE;
  233. "
  234. local records
  235. records=$(execute_query "$query_sql")
  236. if [[ $? -ne 0 ]]; then
  237. log "ERROR" "获取待处理记录失败"
  238. sleep $CYCLE_INTERVAL
  239. continue
  240. fi
  241. # 检查是否有记录需要处理(过滤掉空行和MySQL警告信息)
  242. local clean_records
  243. clean_records=$(echo "$records" | grep -v "^mysql:" | grep -v "^$" | wc -l)
  244. if [[ $clean_records -eq 0 ]]; then
  245. log "INFO" "未找到未处理的记录,正在等待..."
  246. else
  247. log "INFO" "找到 $clean_records 条待处理记录"
  248. local processed_count=0
  249. local failed_count=0
  250. # Process records directly from the query result, skip header and empty lines
  251. local is_first_line=true
  252. while IFS=$'\t' read -r record_id student_id student_no change_type operator_id; do
  253. # Skip completely empty lines
  254. [[ -z "$record_id$student_id$student_no$change_type$operator_id" ]] && continue
  255. # Skip header line (first non-empty line that looks like column names)
  256. if [[ $is_first_line == true ]]; then
  257. is_first_line=false
  258. # Check if this looks like a header line
  259. if [[ "$record_id" == "id" || "$record_id" =~ ^[[:space:]]*id[[:space:]]*$ ]]; then
  260. log "INFO" "跳过标题行"
  261. continue
  262. fi
  263. fi
  264. # Validate required fields
  265. if [[ -z "$record_id" || -z "$student_id" || -z "$student_no" || -z "$change_type" ]]; then
  266. log "WARN" "跳过无效记录 - ID: '$record_id', 学生ID: '$student_id', 学号: '$student_no', 类型: '$change_type'"
  267. continue
  268. fi
  269. # Log the record being processed for debugging
  270. log "INFO" "读取到记录 - ID: $record_id, 学生ID: $student_id, 学号: $student_no, 类型: $change_type, 操作员: ${operator_id:-1}"
  271. local retry_count=0
  272. local success=false
  273. while [[ $retry_count -lt $MAX_RETRY && $success == false ]]; do
  274. if process_student_change "$record_id" "$student_id" "$student_no" "$change_type" "$operator_id"; then
  275. success=true
  276. ((processed_count++))
  277. else
  278. ((retry_count++))
  279. if [[ $retry_count -lt $MAX_RETRY ]]; then
  280. log "WARN" "处理失败,$RETRY_DELAY 秒后重试... (尝试 $retry_count/$MAX_RETRY)"
  281. sleep $RETRY_DELAY
  282. fi
  283. fi
  284. done
  285. if [[ $success == false ]]; then
  286. ((failed_count++))
  287. log "ERROR" "$MAX_RETRY 次尝试后仍处理失败 - ID: $record_id"
  288. fi
  289. done <<< "$(echo "$records" | grep -v "^mysql:" | grep -v "^$")"
  290. log "INFO" "处理周期完成 - 已处理: $processed_count, 失败: $failed_count"
  291. fi
  292. # 等待配置的间隔时间后开始下一个周期
  293. log "INFO" "等待 $CYCLE_INTERVAL 秒后开始下一个处理周期..."
  294. sleep $CYCLE_INTERVAL
  295. done
  296. }
  297. # Run main function
  298. main "$@"