mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7
1662 字
5 分钟
成绩管理系统大作业
2026-07-01

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 + JavaScriptGoogle Material You (M3) 暗色主题,单页面应用
图表ECharts数据可视化(趋势图、分布图、柱状图)
后端Python FlaskWeb 服务框架 + 前端文件服务
算法C 语言 (DLL)38 个导出函数,Python ctypes 调用,失败自动回退 Python 实现
数据库MySQL (PyMySQL)关系型数据库,连接池复用连接
AI硅基流动 SiliconFlowOpenAI 兼容格式,多模型选择;可选 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.dll

5.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_cvmalloc/freeqsort、数学运算
多科目(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 os
import sys
from 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_DIR
else:
# 开发模式
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 pymysql
from 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 None

6.2 通过 ctypes 调用 C 算法(含 Python 回退)#

import ctypes
# 加载 C 算法 DLL
c_lib = None
try:
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 0

6.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/userPOST / POST / GET登录 / 登出 / 当前用户
用户/api/users /api/users/<id>GET / POST / PUT / DELETE用户 CRUD(非 root 需审核)
审核/api/approval-requests /api/approval-requests/<id>/reviewGET / PUT审核列表 / 审批
组织/api/hierarchy /api/studentsGET组织层级 / 学员列表
课程/api/courses /api/courses/<id>GET / POST / PUT / DELETE课程 CRUD
成绩/api/scores /api/scores/<id> /api/scores/examsGET / POST / PUT / DELETE成绩 CRUD、考试名列表
座位/api/seat-arrangement/arrange /api/seat-arrangement/<course> /api/seat-arrangement/<course>/swap /api/seat-arrangement/<course>/exportPOST / GET / PUT / GET排座 / 查询 / 交换 / 导出 CSV
统计/api/stats/dashboard /api/analyze/course/<id> /api/analyze/student/<id> /api/analyze/aiGET / POST仪表盘 / 课程分析 / 学员分析 / AI 分析
活动/api/activities /api/signups /api/comments /api/likesGET / POST / PUT / DELETE活动 / 报名 / 留言 / 点赞

七、运行说明#

7.1 环境准备#

  1. Python 3.8+:加入 PATH
  2. MySQL 8.0:启动服务,创建数据库
    CREATE DATABASE bigwork DEFAULT CHARACTER SET utf8mb4;
  3. 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.3
Flask-CORS==4.0.0
python-dotenv==1.0.0
PyMySQL==1.1.0

另外 app.py 用到 dbutils(连接池)、requests(AI 调用)、cryptography,启动脚本 start.bat 会自动 pip install 这些。

7.3 配置环境变量#

复制 backend/.env.examplebackend/.env,填写:

MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=你的密码
MYSQL_DATABASE=bigwork
# AI(可选)
AI_PROVIDER=siliconflow
SILICONFLOW_API_KEY=你的Key
SILICONFLOW_MODEL=Qwen/Q2.5-7B-Instruct

7.4 初始化数据库#

mysql -u root -p bigwork < database/init.sql
# 或使用脚本:
cd backend
python init_mysql_db.py

7.5 启动项目#

方式一:一键启动(推荐)

双击根目录 start.bat,脚本自动执行 4 步检测:

  1. 检查 Python 环境
  2. 安装 / 检查依赖
  3. 检查 MySQL 数据库连接
  4. 启动 Flask 服务并打开浏览器

任何一步失败都会提示是否重试。

方式二:手动启动

cd backend
python check_db.py # 检查数据库
python app.py # 启动服务

服务启动后访问 http://127.0.0.1:5000

7.6 打包为 Windows 可执行文件#

cd build
build.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 算法模块中集中练习了课程要求的核心知识点:

  • 变量与数据类型intdoublechar
  • 数组:存储成绩序列、二维座位表
  • 循环语句for 遍历计算
  • 条件判断if-else 成绩分级
  • 函数:模块化 38 个算法函数
  • 结构体SeatStudentSeatPrioritySeatResult
  • 指针:Python ctypes 调用时的数据传递(double*int*
  • 动态内存分配malloc / freecalc_median 中复制数组)
  • 动态链接库__declspec(dllexport) 编译为 DLL
  • 数学库sqrtmath.h
  • 排序算法:冒泡排序、选择排序、qsort
  • 二维数组:座位编排(MAX_SEAT_ROWS × MAX_SEAT_COLS
  • 全局变量g_seat_resultg_students
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

成绩管理系统大作业
https://blog.radarweb.top/posts/c/成绩管理系统大作业/
作者
Sherry
发布于
2026-07-01
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录