C 语言程序设计课程设计作品。基于 C 语言算法(DLL)+ Python Flask 后端 + MySQL + 硅基流动 AI + Material You 前端 的全栈学员成绩管理系统,核心算法用 C 语言实现并编译为动态链接库,由 Python 通过
ctypes调用,调用失败时自动回退到 Python 实现。
一、项目概述
本项目是一个真实可用的 Web 成绩管理系统,覆盖成绩录入、统计分析、AI 智能分析、座位编排、活动管理、留言讨论、用户审核等完整业务流程,满足 C 语言课程设计对”基础语法 + 算法实现”的要求,同时通过 Python Web 后端将 C 算法包装为在线服务。
系统架构
┌──────────────────────────────────────────────────────────────────┐│ 前端(HTML5/CSS3/JS) ││ Material You (M3) 暗色主题 + ECharts图表 │└───────────────────────────────┬──────────────────────────────────┘ │ HTTP REST API┌───────────────────────────────▼───────────────────────────────────┐│ Python Flask 后端 ││ 用户认证 成绩管理 活动管理 留言管理 座位编排 AI智能分析 │└───────────┬───────────────┬───────────────┬──────────────────────┘ │ │ │ ↓ ↓ ↓┌───────────────────┐ ┌─────────────┐ ┌──────────────┐│ C语言算法 (DLL) │ │ 硅基流动 AI │ │ MySQL ││ • 基础统计 (6) │ │ OpenAI兼容 │ │ 13个数据表 ││ • 成绩分析 (5) │ │ 多模型选择 │ │ 组织架构 ││ • GPA计算 (2) │ │ 智能分析 │ │ 成绩管理 ││ • 排序查找 (5) │ └─────────────┘ │ 活动留言 ││ • 趋势分析 (4) │ ctypes调用 │ 座位编排 ││ • 高级统计 (5) │ 失败自动回退 └──────────────┘│ • 座位编排 (6) │ Python实现│ 合计38个函数 │└───────────────────┘二、技术栈
| 层级 | 技术 | 说明 |
|---|---|---|
| 前端 | HTML5 + CSS3 + JavaScript | Google Material You (M3) 暗色主题,单页面应用 |
| 图表 | ECharts | 数据可视化(趋势图、分布图、柱状图) |
| 后端 | Python Flask | Web 服务框架 + 前端文件服务 |
| 算法 | C 语言 (DLL) | 38 个导出函数,Python ctypes 调用,失败自动回退 Python 实现 |
| 数据库 | MySQL (PyMySQL) | 关系型数据库,连接池复用连接 |
| AI | 硅基流动 SiliconFlow | OpenAI 兼容格式,多模型选择;可选 Dify 多轮对话 |
| 安全 | SHA256 + 随机盐值 | 密码加密存储,明文密码自动升级 |
| 配置 | python-dotenv + .env | 环境变量管理敏感配置 |
| 打包 | PyInstaller | 一键打包为 Windows exe(含 _MEIPASS 资源路径处理) |
三、目录结构
bigwork/├── backend/ # Python Flask 后端│ ├── app.py # 主应用(API + 前端服务 + C算法调用)约 5300 行│ ├── config.py # 配置文件(支持开发模式 / PyInstaller 打包模式)│ ├── check_db.py # 数据库检查脚本(启动前验证)│ ├── init_mysql_db.py # 数据库初始化脚本│ ├── migrate.py # 数据库迁移工具(export/import/reset)│ ├── requirements.txt # Python 依赖列表│ ├── .env / .env.example # 环境变量(API Key、数据库密码,不入库)│ └── backup/ # 数据库备份目录│├── c_algorithm/ # C 语言算法模块│ ├── score_analysis.c # 算法源码(38 个导出函数)│ └── score_analysis.dll # 编译生成的 DLL│├── database/ # 数据库脚本│ ├── init.sql # MySQL 初始化(13 张表)│ └── hierarchy_tables.sql # 层级结构表(学校→大班→教学班→教学组)│├── frontend/ # 前端文件│ ├── index.html # 主页面│ ├── css/ # 样式(M3 暗色主题)│ ├── js/ # main.js、api.js、echarts.min.js│ └── fonts/ # NotoSansSC、Roboto、Material Symbols 字体│├── build/ # PyInstaller 打包配置与产物│ ├── bigwork.spec # 打包规格文件│ ├── build.bat # 打包脚本│ └── dist/ScoreManagementSystem/ # 打包输出(exe + _internal)│├── start.bat # Windows 启动脚本(4 步检测 + 出错重试)├── README.md # 项目说明├── 项目规划文档.md # 开发规划└── 提示词记录.md # AI 提示词记录四、数据库设计(13 张表)
| 表名 | 说明 |
|---|---|
users | 用户表(role_level: 1=root, 2=学校管理员, 3=大班管理员, 4=教学班管理员, 5=教学组管理员, 6=学员) |
courses | 课程表 |
scores | 成绩表(含 exam_name 考试名称字段) |
activities / signups | 活动表 / 报名表 |
comments / likes | 留言表 / 点赞表 |
schools / teaching_classes / teaching_groups / group_units | 四级组织架构 |
seat_arrangements | 座位编排表 |
approval_requests | 审核请求表 |
权限模型采用 6 级 role_level:root 直辖,非 root 添加/编辑用户需上级审核,修改下级密码直接执行。
五、C 语言算法核心代码
C 算法源码 c_algorithm/score_analysis.c 通过 __declspec(dllexport) 导出 38 个函数,编译命令:
cl /LD score_analysis.c /Fe:score_analysis.dll5.1 基础统计与成绩分析
#include <stdio.h>#include <math.h>#include <stdlib.h>
#define MAX_STUDENTS 100#define MAX_COURSES 10
/* 求和:遍历数组累加 */double __declspec(dllexport) get_sum(double arr[], int n) { double sum = 0; for (int i = 0; i < n; i++) { sum = sum + arr[i]; } return sum;}
/* 平均分:sum / n,注意 n=0 时返回 0 避免除零 */double __declspec(dllexport) calc_average(double arr[], int n) { if (n == 0) return 0; double sum = get_sum(arr, n); return sum / n;}
/* 方差:Σ(xi - avg)² / n */double __declspec(dllexport) calc_variance(double arr[], int n) { if (n == 0) return 0; double avg = calc_average(arr, n); double sum_squared_diff = 0; for (int i = 0; i < n; i++) { double diff = arr[i] - avg; sum_squared_diff = sum_squared_diff + diff * diff; } return sum_squared_diff / n;}
/* 标准差:方差开根号 */double __declspec(dllexport) calc_stddev(double arr[], int n) { double variance = calc_variance(arr, n); return sqrt(variance);}
/* 及格率:score >= 60 的人数占比 × 100 */double __declspec(dllexport) calc_pass_rate(double arr[], int n) { if (n == 0) return 0; int pass_count = 0; for (int i = 0; i < n; i++) { if (arr[i] >= 60) { pass_count = pass_count + 1; } } return (double)pass_count / n * 100;}
/* 优秀率:score >= 90 的人数占比 × 100 */double __declspec(dllexport) calc_excellent_rate(double arr[], int n) { if (n == 0) return 0; int excellent_count = 0; for (int i = 0; i < n; i++) { if (arr[i] >= 90) { excellent_count = excellent_count + 1; } } return (double)excellent_count / n * 100;}
/* 成绩分段统计:结果写入 level[5] level[0]=不及格(<60), level[1]=及格(60-69), level[2]=中等(70-79), level[3]=良好(80-89), level[4]=优秀(>=90) */void __declspec(dllexport) calc_level_distribution(double arr[], int n, int level[]) { for (int i = 0; i < 5; i++) { level[i] = 0; } for (int i = 0; i < n; i++) { if (arr[i] < 60) level[0] = level[0] + 1; else if (arr[i] < 70) level[1] = level[1] + 1; else if (arr[i] < 80) level[2] = level[2] + 1; else if (arr[i] < 90) level[3] = level[3] + 1; else level[4] = level[4] + 1; }}5.2 GPA 计算与排序算法
/* 分数转绩点:90→4.0, 80→3.0, 70→2.0, 60→1.0, 否则 0 */double __declspec(dllexport) score_to_point(double score) { if (score >= 90) return 4.0; if (score >= 80) return 3.0; if (score >= 70) return 2.0; if (score >= 60) return 1.0; return 0.0;}
/* GPA = Σ(绩点 × 学分) / Σ(学分) */double __declspec(dllexport) calc_gpa(double scores[], double credits[], int n) { double total_points = 0; double total_credits = 0; for (int i = 0; i < n; i++) { double point = score_to_point(scores[i]); total_points = total_points + point * credits[i]; total_credits = total_credits + credits[i]; } if (total_credits == 0) return 0; return total_points / total_credits;}
/* 冒泡排序(降序):相邻元素比较,小的后移 */void __declspec(dllexport) bubble_sort_desc(double arr[], int n) { for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (arr[j] < arr[j + 1]) { double temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } }}
/* 选择排序(降序):每轮选最大值放到已排序区末尾 */void __declspec(dllexport) selection_sort_desc(double arr[], int n) { for (int i = 0; i < n - 1; i++) { int max_index = i; for (int j = i + 1; j < n; j++) { if (arr[j] > arr[max_index]) { max_index = j; } } double temp = arr[i]; arr[i] = arr[max_index]; arr[max_index] = temp; }}5.3 高级统计:中位数 / 众数 / 相关系数
/** * 计算中位数 * 先用 malloc 复制数组避免修改原数据,再选择排序后取中间值 * 奇数个取中间,偶数个取中间两个的平均 */double __declspec(dllexport) calc_median(double arr[], int n) { if (n == 0) return 0; /* 复制数组,避免修改原数据 */ double *tmp = (double *)malloc(n * sizeof(double)); for (int i = 0; i < n; i++) { tmp[i] = arr[i]; } /* 选择排序升序 */ for (int i = 0; i < n - 1; i++) { int min_idx = i; for (int j = i + 1; j < n; j++) { if (tmp[j] < tmp[min_idx]) { min_idx = j; } } double t = tmp[i]; tmp[i] = tmp[min_idx]; tmp[min_idx] = t; } double result; if (n % 2 == 1) { result = tmp[n / 2]; } else { result = (tmp[n / 2 - 1] + tmp[n / 2]) / 2.0; } free(tmp); /* 别忘了释放! */ return result;}
/** * 计算众数(出现次数最多的值) * 若多个值频次相同,返回最小的那个 */double __declspec(dllexport) calc_mode(double arr[], int n) { if (n == 0) return 0; double mode_val = arr[0]; int max_count = 1; for (int i = 0; i < n; i++) { int count = 1; for (int j = i + 1; j < n; j++) { if (arr[j] == arr[i]) { count++; } } if (count > max_count || (count == max_count && arr[i] < mode_val)) { max_count = count; mode_val = arr[i]; } } return mode_val;}
/** * 皮尔逊相关系数:衡量两组数据(如两次考试)的线性相关程度 * 返回 [-1, 1],1=完全正相关,-1=完全负相关,0=无线性相关 * 公式:cov(x,y) / (σx × σy) */double __declspec(dllexport) calc_correlation(double x[], double y[], int n) { if (n < 2) return 0; double sum_x = 0, sum_y = 0; for (int i = 0; i < n; i++) { sum_x += x[i]; sum_y += y[i]; } double mean_x = sum_x / n; double mean_y = sum_y / n; double cov = 0, var_x = 0, var_y = 0; for (int i = 0; i < n; i++) { double dx = x[i] - mean_x; double dy = y[i] - mean_y; cov += dx * dy; var_x += dx * dx; var_y += dy * dy; } if (var_x == 0 || var_y == 0) return 0; return cov / (sqrt(var_x) * sqrt(var_y));}5.4 智能座位编排算法
座位编排是本项目最具算法含量的部分。采用「前排中间优先 + 优劣交错 + 避免双差邻座」三段式策略,使用结构体、二维数组、qsort 等多种 C 语言特性。
#define MAX_SEAT_ROWS 30#define MAX_SEAT_COLS 30#define MAX_SEAT_STUDENTS 500
/* 学生综合信息结构 */typedef struct { int student_id; /* 学号 */ double composite; /* 综合评分(此处即成绩均分) */} SeatStudent;
/* 座位优先级结构:priority 越小越优先分配 */typedef struct { double priority; int row; int col;} SeatPriority;
/* 全局变量:座位结果与学生数组(DLL 跨函数共享) */static SeatResult g_seat_result;static SeatStudent g_students[MAX_SEAT_STUDENTS];
/** * 智能排座算法 * 排座策略: * 1. 按成绩升序排序,差生优先安排好座位 * 2. 交错排列:偶数位放低分组,奇数位放高分组 * 如 60分、80分、55分、75分... 避免 60 和 55 挨着 * 3. 计算座位优先级:行号 + 列偏移/列数*0.5 * 行号越小越靠前,列越靠中间偏移越小,前排中间 priority 最小 * 4. 验证邻座搭配:发现两个不及格(<60)相邻时, * 在附近找一个及格学生交换,并校验交换后不产生新的双差邻座 */int __declspec(dllexport) arrange_seats( int student_ids[], double composites[], int heights[], int visions[], int n, int rows, int cols){ if (n <= 0 || rows <= 0 || cols <= 0 || n > MAX_SEAT_STUDENTS || rows > MAX_SEAT_ROWS || cols > MAX_SEAT_COLS) return -1;
/* 初始化座位图为 -1(空位) */ for (int r = 0; r < rows; r++) for (int c = 0; c < cols; c++) g_seat_result.seat_map[r][c] = -1;
/* 填充学生数据 */ for (int i = 0; i < n; i++) { g_students[i].student_id = student_ids[i]; g_students[i].composite = composites[i]; }
/* 按成绩升序排序(低分在前),差生优先安排好座位 */ for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (g_students[j].composite > g_students[j+1].composite) { SeatStudent tmp = g_students[j]; g_students[j] = g_students[j+1]; g_students[j+1] = tmp; } } }
/* 交错排列:偶数位放低分组(0..mid-1),奇数位放高分组(mid..n-1) */ int mid = n / 2; int interleaved[MAX_SEAT_STUDENTS]; int weak_idx = 0, strong_idx = mid; int count = 0; for (int i = 0; i < n; i++) { if (i % 2 == 0) { if (weak_idx < mid) interleaved[count++] = weak_idx++; else interleaved[count++] = strong_idx++; } else { if (strong_idx < n) interleaved[count++] = strong_idx++; else interleaved[count++] = weak_idx++; } }
/* 计算所有座位的优先级(前排中间最小) */ SeatPriority seat_order[MAX_SEAT_ROWS * MAX_SEAT_COLS]; double center_col = (cols - 1) / 2.0; int seat_count = 0; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { seat_order[seat_count].priority = r + fabs(c - center_col) / (cols * 2.0); seat_order[seat_count].row = r; seat_order[seat_count].col = c; seat_count++; } } /* 按优先级升序排序:小值优先 = 前排中间优先 */ qsort(seat_order, seat_count, sizeof(SeatPriority), cmp_seat_priority);
/* 按座位优先级分配交错排列后的学生 */ for (int i = 0; i < seat_count && i < count; i++) { int r = seat_order[i].row; int c = seat_order[i].col; g_seat_result.seat_map[r][c] = interleaved[i]; }
/* 修正阶段:确保没有两个不及格(<60)的学生上下左右相邻 若发现,则找一个及格学生交换位置,并校验不产生新的双差邻座 */ int changed = 1; int max_rounds = n * 2; /* 防止无限循环 */ while (changed && max_rounds-- > 0) { changed = 0; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { int idx1 = g_seat_result.seat_map[r][c]; if (idx1 < 0 || idx1 >= n) continue; if (g_students[idx1].composite >= 60.0) continue;
/* 检查右邻座和下邻座是否也是不及格,若是则尝试交换 */ /* ...(完整代码见源文件,使用四方向偏移数组 dr/dc 校验 safe) */ } } }
return 0;}
/* 比较函数:座位优先级升序(供 qsort 使用) */static int cmp_seat_priority(const void *a, const void *b) { double diff = ((SeatPriority*)a)->priority - ((SeatPriority*)b)->priority; if (diff > 0) return 1; if (diff < 0) return -1; return 0;}
/** * 计算座位均衡度(0-100,越高越均衡) * 算法:计算每行平均综合评分,再求各行均分的标准差 * 标准差越小越均衡,映射为 balance = 100 - stddev × 5 */double __declspec(dllexport) calc_seat_balance() { if (g_seat_result.student_count == 0) return 0; double row_avgs[MAX_SEAT_ROWS]; int valid_rows = 0; for (int r = 0; r < g_seat_result.rows; r++) { double sum = 0; int count = 0; for (int c = 0; c < g_seat_result.cols; c++) { int idx = g_seat_result.seat_map[r][c]; if (idx >= 0 && idx < g_seat_result.student_count) { sum += g_students[idx].composite; count++; } } if (count > 0) row_avgs[valid_rows++] = sum / count; } if (valid_rows < 2) return 100.0;
double total_avg = 0; for (int i = 0; i < valid_rows; i++) total_avg += row_avgs[i]; total_avg /= valid_rows;
double variance = 0; for (int i = 0; i < valid_rows; i++) { double diff = row_avgs[i] - total_avg; variance += diff * diff; } variance /= valid_rows; double stddev = sqrt(variance);
double balance = 100.0 - stddev * 5.0; if (balance < 0) balance = 0; if (balance > 100) balance = 100; return balance;}5.5 38 个导出函数分类总览
| 分类 | 函数 | C 语言知识点 |
|---|---|---|
| 基础统计(6) | get_sum / calc_average / calc_variance / calc_stddev / find_max / find_min | 数组、循环、sqrt |
| 成绩分析(5) | calc_total / calc_pass_rate / calc_excellent_rate / calc_level_distribution / warning_report | 计数器、百分比、多重 if-else |
| GPA(2) | score_to_point / calc_gpa | 条件映射、加权求和 |
| 排序(2) | bubble_sort_desc / selection_sort_desc | 二重循环、元素交换 |
| 查找(3) | find_by_id / find_max_subject / find_min_subject | 循环、条件判断 |
| 趋势分析(4) | calc_avg_change / count_improved / count_declined / count_change_distribution | 差值计算、分类统计 |
| 高级统计(5) | calc_median / calc_mode / calc_correlation / calc_range / calc_cv | malloc/free、qsort、数学运算 |
| 多科目(3) | count_warning_courses / find_best_course / find_worst_course | 最值比较 |
| 排名分类(2) | calc_rank / classify_change | 排序计数、分类 |
| 座位编排(6) | calc_seat_composite / arrange_seats / get_seat_student / swap_seats / calc_seat_balance / get_seat_composite | 结构体、二维数组、qsort、全局变量 |
六、Python 后端 API 代码
6.1 应用初始化与数据库连接池
backend/config.py 同时支持开发模式与 PyInstaller 打包模式,通过 getattr(sys, 'frozen', False) 判断运行环境。
import osimport sysfrom dotenv import load_dotenv
# 路径处理:支持 PyInstaller 打包后的路径if getattr(sys, 'frozen', False): # 打包后:_MEIPASS 是 PyInstaller 解包临时目录 MEIPASS = sys._MEIPASS BASE_DIR = os.path.dirname(sys.executable) # exe 所在目录 INTERNAL_DIR = os.path.join(BASE_DIR, '_internal') # 资源目录 PROJECT_ROOT = INTERNAL_DIRelse: # 开发模式 MEIPASS = None BASE_DIR = os.path.dirname(os.path.abspath(__file__)) INTERNAL_DIR = BASE_DIR PROJECT_ROOT = os.path.dirname(BASE_DIR)
# 加载 .env:打包模式优先从 exe 目录加载,方便用户配置if getattr(sys, 'frozen', False): env_path = os.path.join(BASE_DIR, '.env') if not os.path.exists(env_path): env_path = os.path.join(INTERNAL_DIR, 'backend', '.env')else: env_path = os.path.join(PROJECT_ROOT, 'backend', '.env')if os.path.exists(env_path): load_dotenv(env_path, override=True)
# MySQL 配置(从环境变量读取)MYSQL_HOST = os.environ.get('MYSQL_HOST', 'localhost')MYSQL_PORT = int(os.environ.get('MYSQL_PORT', '3306'))MYSQL_USER = os.environ.get('MYSQL_USER', 'root')MYSQL_PASSWORD = os.environ.get('MYSQL_PASSWORD', '')MYSQL_DATABASE = os.environ.get('MYSQL_DATABASE', 'bigwork')
# C 算法 DLL 路径if MEIPASS: C_ALGORITHM_DLL = os.path.join(MEIPASS, 'c_algorithm', 'score_analysis.dll')else: C_ALGORITHM_DLL = os.path.join(PROJECT_ROOT, 'c_algorithm', 'score_analysis.dll')
# 大模型配置SILICONFLOW_API_KEY = os.environ.get('SILICONFLOW_API_KEY', '')SILICONFLOW_API_URL = os.environ.get('SILICONFLOW_API_URL', 'https://api.siliconflow.cn/v1/chat/completions')SILICONFLOW_MODEL = os.environ.get('SILICONFLOW_MODEL', 'Qwen/Q2.5-7B-Instruct')
def get_frontend_path(): """获取前端文件目录路径(供 app.py 调用)""" if MEIPASS: return os.path.join(MEIPASS, 'frontend') return os.path.join(PROJECT_ROOT, 'frontend')app.py 使用 dbutils.PooledDB 维护连接池,避免每次请求新建连接:
import pymysqlfrom dbutils.pooled_db import PooledDB
_db_pool = None
def _get_pool(): global _db_pool if _db_pool is None: _db_pool = PooledDB( creator=pymysql, maxconnections=10, # 最大连接数 mincached=2, # 初始空闲连接 maxcached=5, # 最大空闲连接 blocking=True, host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER, password=MYSQL_PASSWORD, database=MYSQL_DATABASE, charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor # 返回字典形式结果 ) return _db_pool
def get_db_connection(): """从连接池获取数据库连接""" try: return _get_pool().connection() except Exception as e: print(f"数据库连接失败: {e}") return None6.2 通过 ctypes 调用 C 算法(含 Python 回退)
import ctypes
# 加载 C 算法 DLLc_lib = Nonetry: dll_path = C_ALGORITHM_DLL if os.path.exists(dll_path): c_lib = ctypes.CDLL(dll_path) print(f"C算法模块加载成功: {dll_path}") else: print(f"C算法DLL未找到: {dll_path},将使用Python回退实现")except Exception as e: print(f"C算法模块加载失败: {e},将使用Python回退实现")
def c_calc_average(scores): """调用C算法计算平均分,DLL不可用时回退到Python实现""" if c_lib: n = len(scores) arr = (ctypes.c_double * n)(*scores) # Python list → C double[] return c_lib.calc_average(arr, n) return sum(scores) / len(scores) if scores else 0
def c_calc_level_distribution(scores): """调用C算法计算分段分布 注意:C函数通过 int level[] 出参返回,需用 ctypes 创建数组接收""" if c_lib: n = len(scores) arr = (ctypes.c_double * n)(*scores) level = (ctypes.c_int * 5)() # 出参:5 段计数 c_lib.calc_level_distribution(arr, n, level) return [level[i] for i in range(5)] # Python 回退实现 dist = [0, 0, 0, 0, 0] for s in scores: if s < 60: dist[0] += 1 elif s < 70: dist[1] += 1 elif s < 80: dist[2] += 1 elif s < 90: dist[3] += 1 else: dist[4] += 1 return dist
def c_calc_gpa(scores, credits): """GPA 计算需要同时传入成绩数组和学分数组""" if c_lib and len(scores) == len(credits): n = len(scores) s_arr = (ctypes.c_double * n)(*scores) c_arr = (ctypes.c_double * n)(*credits) return c_lib.calc_gpa(s_arr, c_arr, n) if not scores or not credits: return 0 total_points = sum( (4.0 if s >= 90 else 3.0 if s >= 80 else 2.0 if s >= 70 else 1.0 if s >= 60 else 0.0) * c for s, c in zip(scores, credits) ) total_credits = sum(credits) return total_points / total_credits if total_credits else 06.3 密码哈希与登录接口
密码采用 SHA256 + 随机盐值,明文密码登录后自动升级为哈希:
import hashlib, os
def hash_password(password, salt=None): """密码哈希 - SHA256 + 随机盐值,格式:salt$hash""" if salt is None: salt = os.urandom(16).hex() hashed = hashlib.sha256((salt + password).encode()).hexdigest() return f"{salt}${hashed}"
def verify_password(password, stored_hash): """验证密码 - 支持哈希验证和明文兼容(旧数据验证后自动升级)""" if '$' in stored_hash: salt, hashed = stored_hash.split('$', 1) check_hash = hashlib.sha256((salt + password).encode()).hexdigest() return check_hash == hashed else: # 明文密码(兼容旧数据) return stored_hash == password
@app.route('/api/login', methods=['POST'])def login(): """用户登录""" try: data = request.get_json() username = data.get('username') password = data.get('password') if not username or not password: return error_response('请提供用户名和密码')
conn = get_db_connection() if not conn: return error_response('数据库连接失败', 500) try: with conn.cursor() as cursor: cursor.execute('SELECT * FROM users WHERE username = %s', (username,)) user = cursor.fetchone() if not user: return error_response('用户名不存在') if not verify_password(password, user['password']): return error_response('密码错误')
# 明文密码自动升级为哈希 if '$' not in user['password']: cursor.execute( 'UPDATE users SET password = %s WHERE id = %s', (hash_password(password), user['id']) ) conn.commit()
# 设置会话 session['user_id'] = user['id'] session['username'] = user['username'] session['role_level'] = user['role_level'] if user['role_level'] is not None else 6 session['user'] = { 'id': user['id'], 'username': user['username'], 'name': user['name'], 'role_level': user['role_level'], 'school_id': user.get('school_id'), 'class_id': user.get('class_id'), 'small_class_id': user.get('small_class_id'), 'group_unit_id': user.get('group_unit_id'), } return success_response({ 'id': user['id'], 'username': user['username'], 'name': user['name'], 'role_level': user['role_level'], }, '登录成功') finally: conn.close() except Exception as e: return error_response(f'登录失败: {str(e)}', 500)6.4 课程成绩分析接口(调用 C 算法)
@app.route('/api/analyze/course/<int:course_id>', methods=['GET'])def analyze_course(course_id): """使用C语言算法分析指定课程的成绩(只分析下级学生)""" subordinate_ids, err = get_subordinate_student_ids() if err: return err
conn = get_db_connection() if not conn: return error_response('数据库连接失败', 500) try: with conn.cursor() as cursor: cursor.execute('SELECT * FROM courses WHERE id = %s', (course_id,)) course = cursor.fetchone() if not course: return error_response('课程不存在')
# 仅查询当前用户下级学生的成绩 placeholders = ','.join(['%s'] * len(subordinate_ids)) cursor.execute(f''' SELECT s.score, u.name as student_name, u.id as student_id FROM scores s JOIN users u ON s.student_id = u.id WHERE s.course_id = %s AND s.student_id IN ({placeholders}) ORDER BY s.score DESC ''', [course_id] + subordinate_ids) score_records = cursor.fetchall()
if not score_records: return success_response({'course': course['name'], 'message': '该课程暂无成绩数据'})
scores = [float(r['score']) for r in score_records]
# 调用 C 算法进行统计分析 avg = c_calc_average(scores) variance = c_calc_variance(scores) stddev = c_calc_stddev(scores) max_score = c_find_max(scores) min_score = c_find_min(scores) median = c_calc_median(scores) mode_score = c_calc_mode(scores) pass_rate = c_calc_pass_rate(scores) excellent_rate = c_calc_excellent_rate(scores) level_dist = c_calc_level_distribution(scores) sorted_scores = c_bubble_sort_desc(scores) warning_count = c_warning_report(scores, 60) score_range = c_calc_range(scores) cv = c_calc_cv(scores)
return success_response({ 'course': course['name'], 'course_id': course_id, 'student_count': len(scores), 'statistics': { 'average': round(avg, 2), 'variance': round(variance, 2), 'stddev': round(stddev, 2), 'max': max_score, 'min': min_score, 'median': round(median, 2), 'mode': mode_score, 'range': round(score_range, 2), 'cv': round(cv, 2), 'pass_rate': round(pass_rate, 2), 'excellent_rate': round(excellent_rate, 2), 'warning_count': warning_count }, 'level_distribution': { 'fail': level_dist[0], 'pass': level_dist[1], 'medium': level_dist[2], 'good': level_dist[3], 'excellent': level_dist[4] }, 'sorted_scores': sorted_scores, 'c_algorithm_loaded': c_lib is not None }) finally: conn.close()6.5 AI 智能分析接口(硅基流动)
@app.route('/api/analyze/ai', methods=['POST'])def ai_analyze(): """使用大模型对成绩数据进行智能分析,支持多轮对话""" data = request.get_json() query = data.get('query', '') context = data.get('context', '') provider = data.get('provider', AI_PROVIDER) sf_model = data.get('siliconflow_model', '') or SILICONFLOW_MODEL conversation_id = data.get('conversation_id', '') # Dify 多轮对话 ID history = data.get('history', []) # 硅基流动对话历史
if not query: return error_response('请提供分析问题')
conn = get_db_connection() if not conn: return error_response('数据库连接失败', 500) try: with conn.cursor() as cursor: # 课程统计摘要 cursor.execute(''' SELECT c.name as course_name, COUNT(*) as student_count, ROUND(AVG(s.score), 1) as avg_score, ROUND(MAX(s.score), 1) as max_score, ROUND(MIN(s.score), 1) as min_score, ROUND(SUM(CASE WHEN s.score >= 60 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as pass_rate, ROUND(SUM(CASE WHEN s.score >= 90 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as excellent_rate FROM scores s JOIN courses c ON s.course_id = c.id GROUP BY c.id, c.name ORDER BY c.name ''') course_stats = cursor.fetchall() # ...学员排名、成绩分布查询省略 finally: conn.close()
# 构建详细的分析上下文喂给大模型 course_details = '\n'.join([ f" - {c['course_name']}: 平均分{c['avg_score']}, 最高{c['max_score']}, " f"最低{c['min_score']}, 及格率{c['pass_rate']}%, 优秀率{c['excellent_rate']}%, " f"共{c['student_count']}人" for c in course_stats ]) data_context = f"""成绩数据详细摘要:
【课程统计】{course_details}
{context}"""
# 前端传入的 Key 优先于服务器配置 sf_key = data.get('siliconflow_key', '') or SILICONFLOW_API_KEY dify_key = data.get('dify_key', '') or DIFY_API_KEY
if provider == 'dify' and dify_key: return _call_dify(query, data_context, dify_key, conversation_id) elif provider == 'siliconflow' and sf_key: return _call_siliconflow(query, data_context, sf_key, sf_model, history) else: return success_response({ 'answer': f'基于当前数据分析:\n\n{data_context}\n\n提示:请在设置中输入 API Key 后可获取AI智能分析。', 'source': 'local', 'data_context': data_context })6.6 智能排座接口
@app.route('/api/seat-arrangement/arrange', methods=['POST'])def arrange_seats_api(): """执行排座算法 - 基于成绩的智能排座(仅管理下级学生)""" student_ids, err = get_subordinate_student_ids() if err: return err try: data = request.get_json() course_name = data.get('course_name') rows = data.get('rows', 15) cols = data.get('cols', 11) if not course_name: return error_response('请提供课程名称')
conn = get_db_connection() if not conn: return error_response('数据库连接失败', 500) try: with conn.cursor() as cursor: if not student_ids: return error_response('您管理的范围内没有学生')
# 按课程查询每个学生的平均分 placeholders = ','.join(['%s'] * len(student_ids)) cursor.execute(f''' SELECT u.id, u.name, AVG(s.score) as avg_score FROM users u LEFT JOIN scores s ON u.id = s.student_id LEFT JOIN courses c ON s.course_id = c.id AND c.name = %s WHERE u.id IN ({placeholders}) GROUP BY u.id, u.name ORDER BY u.id ''', [course_name] + student_ids) students = cursor.fetchall() if not students: return error_response('未找到学生数据')
n = len(students) # 学生数超过座位数时自动增加行数 if n > rows * cols: rows = math.ceil(n / cols)
# 调用排座算法(Python 实现,与 score_analysis.c 逻辑一致) seats = _arrange_seats_c(students, rows, cols) balance = _calc_balance_python(seats)
# 存入数据库(UPSERT) seats_json = json.dumps(seats, ensure_ascii=False) cursor.execute(''' INSERT INTO seat_arrangements (course_name, rows_count, cols_count, seats, balance_score) VALUES (%s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE rows_count = VALUES(rows_count), cols_count = VALUES(cols_count), seats = VALUES(seats), balance_score = VALUES(balance_score), updated_at = CURRENT_TIMESTAMP ''', (course_name, rows, cols, seats_json, balance)) conn.commit()
# 扁平化座位数据方便前端使用 flat_seats = [] for r_idx, row_data in enumerate(seats): for c_idx, seat in enumerate(row_data): if seat and isinstance(seat, dict): flat_seats.append(seat)
return success_response({ 'rows': rows, 'cols': cols, 'seats': flat_seats, 'balance': round(balance, 4) }, '排座完成') finally: conn.close() except Exception as e: return error_response(f'排座失败: {str(e)}', 500)6.7 主要 API 接口清单
| 模块 | 接口 | 方法 | 说明 |
|---|---|---|---|
| 认证 | /api/login /api/logout /api/user | POST / POST / GET | 登录 / 登出 / 当前用户 |
| 用户 | /api/users /api/users/<id> | GET / POST / PUT / DELETE | 用户 CRUD(非 root 需审核) |
| 审核 | /api/approval-requests /api/approval-requests/<id>/review | GET / PUT | 审核列表 / 审批 |
| 组织 | /api/hierarchy /api/students | GET | 组织层级 / 学员列表 |
| 课程 | /api/courses /api/courses/<id> | GET / POST / PUT / DELETE | 课程 CRUD |
| 成绩 | /api/scores /api/scores/<id> /api/scores/exams | GET / POST / PUT / DELETE | 成绩 CRUD、考试名列表 |
| 座位 | /api/seat-arrangement/arrange /api/seat-arrangement/<course> /api/seat-arrangement/<course>/swap /api/seat-arrangement/<course>/export | POST / GET / PUT / GET | 排座 / 查询 / 交换 / 导出 CSV |
| 统计 | /api/stats/dashboard /api/analyze/course/<id> /api/analyze/student/<id> /api/analyze/ai | GET / POST | 仪表盘 / 课程分析 / 学员分析 / AI 分析 |
| 活动 | /api/activities /api/signups /api/comments /api/likes | GET / POST / PUT / DELETE | 活动 / 报名 / 留言 / 点赞 |
七、运行说明
7.1 环境准备
- Python 3.8+:加入 PATH
- MySQL 8.0:启动服务,创建数据库
CREATE DATABASE bigwork DEFAULT CHARACTER SET utf8mb4;
- C 编译器(可选):若需重新编译 C 算法,使用 MSVC
仓库已附带编译好的cl /LD score_analysis.c /Fe:score_analysis.dll
score_analysis.dll,无需重新编译。
7.2 安装依赖
pip install -r backend/requirements.txt依赖清单(requirements.txt):
Flask==2.3.3Flask-CORS==4.0.0python-dotenv==1.0.0PyMySQL==1.1.0另外
app.py用到dbutils(连接池)、requests(AI 调用)、cryptography,启动脚本start.bat会自动pip install这些。
7.3 配置环境变量
复制 backend/.env.example 为 backend/.env,填写:
MYSQL_HOST=localhostMYSQL_PORT=3306MYSQL_USER=rootMYSQL_PASSWORD=你的密码MYSQL_DATABASE=bigwork
# AI(可选)AI_PROVIDER=siliconflowSILICONFLOW_API_KEY=你的KeySILICONFLOW_MODEL=Qwen/Q2.5-7B-Instruct7.4 初始化数据库
mysql -u root -p bigwork < database/init.sql# 或使用脚本:cd backendpython init_mysql_db.py7.5 启动项目
方式一:一键启动(推荐)
双击根目录 start.bat,脚本自动执行 4 步检测:
- 检查 Python 环境
- 安装 / 检查依赖
- 检查 MySQL 数据库连接
- 启动 Flask 服务并打开浏览器
任何一步失败都会提示是否重试。
方式二:手动启动
cd backendpython check_db.py # 检查数据库python app.py # 启动服务服务启动后访问 http://127.0.0.1:5000。
7.6 打包为 Windows 可执行文件
cd buildbuild.bat使用 PyInstaller 打包为单 exe(dist/ScoreManagementSystem/ScoreManagementSystem.exe),打包配置见 build/bigwork.spec,会将 frontend/、c_algorithm/、backend/.env 等资源一并打入 _internal/。
7.7 测试账号
root 管理员: root / root123学校管理员: school_admin / 123456大班管理员: class_admin / 123456教学班管理员: group_admin / 123456教学组管理员: unit_admin / 123456学员: 2025001~2025150 / 123456八、C 语言知识点应用总结
本项目在 C 算法模块中集中练习了课程要求的核心知识点:
- 变量与数据类型:
int、double、char - 数组:存储成绩序列、二维座位表
- 循环语句:
for遍历计算 - 条件判断:
if-else成绩分级 - 函数:模块化 38 个算法函数
- 结构体:
SeatStudent、SeatPriority、SeatResult - 指针:Python
ctypes调用时的数据传递(double*、int*) - 动态内存分配:
malloc/free(calc_median中复制数组) - 动态链接库:
__declspec(dllexport)编译为 DLL - 数学库:
sqrt(math.h) - 排序算法:冒泡排序、选择排序、
qsort - 二维数组:座位编排(
MAX_SEAT_ROWS × MAX_SEAT_COLS) - 全局变量:
g_seat_result、g_students
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时












