This commit is contained in:
zlwu committed 2026-06-26 15:30:24 +08:00
1 parent a88d87047d
commit c0de1b9bef
19 files changed
+17083 -202

No files matched your search

@@ -22,8 +22,33 @@ HEADER_API_KEY = "X-Jw-Cx-Key"
VIEW_NAME = "vw_jw_student_change_export"
# 视图列名(与 vw_jw_student_change_export 一致;筛选近 N 天时使用)
COL_SUBMIT_DATE = "提交时间"
# 视图列名:API 按「异动生效时间」所在自然年筛选
COL_EFFECTIVE_TIME = "异动生效时间"
# 对外返回字段(与教务对接清单一致;不含视图内部列)
EXPORT_COLUMNS = (
"提交时间",
"异动生效学期",
"异动类型",
"申请人学号",
"姓名",
"异动前所在年级",
"异动前所在校区",
"异动前所在院系",
"异动前所在专业",
"异动前所在班级",
"异动前学制",
"异动后所在校区",
"异动后所在年级",
"异动后所在班级",
"异动后所在院系",
"异动后所在专业",
"异动后学制",
"异动后班主任工号",
"异动后班主任姓名",
"新名字",
"审核状态",
)
_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
_DEFAULT_CONFIG_PATH = os.path.join(_BASE_DIR, "config.ini")
@@ -99,60 +124,37 @@ def server_bind():
}
def _api_defaults_from_config():
"""从 [api] 读取 page_size、recent_days 默认值(INI 见 config*.ini)。"""
page_size = _get_int("api", "page_size", 500)
recent_days = _get_int("api", "recent_days", 30)
if page_size < 1:
page_size = 1
if page_size > 5000:
page_size = 5000
if recent_days < 1:
recent_days = 1
if recent_days > 3660:
recent_days = 3660
return page_size, recent_days
def _default_filter_year():
"""配置文件 default_year;未配置或无效时使用当前自然年。"""
raw = _get("api", "default_year", "")
if raw:
try:
year = int(raw)
if 2000 <= year <= 2100:
return year
except ValueError:
pass
return date.today().year
def _clamp_int(value, lo, hi):
v = int(value)
if v < lo:
return lo
if v > hi:
return hi
return v
def _resolve_query_paging():
def _resolve_filter_year():
"""
合并 URL 参数与配置文件:
- page_size:Query `page_size`,未传则用 [api] page_size
- recent_days:Query `recent_days` 或 `days`(别名),未传则用 [api] recent_days
范围:page_size 1~5000,recent_days 1~3660
- year:Query `year`,未传则用 [api] default_year 或当前自然年
"""
cfg_ps, cfg_rd = _api_defaults_from_config()
raw_ps = request.args.get("page_size")
if raw_ps is not None and str(raw_ps).strip() != "":
default_year = _default_filter_year()
raw_year = request.args.get("year")
if raw_year is not None and str(raw_year).strip() != "":
try:
page_size = _clamp_int(raw_ps, 1, 5000)
year = int(raw_year)
except (ValueError, TypeError):
return None, None, "page_size 必须为整数"
return None, "year 必须为整数"
if year < 2000 or year > 2100:
return None, "year 有效范围为 2000~2100"
else:
page_size = cfg_ps
year = default_year
raw_rd = request.args.get("recent_days")
if raw_rd is None or str(raw_rd).strip() == "":
raw_rd = request.args.get("days")
if raw_rd is not None and str(raw_rd).strip() != "":
try:
recent_days = _clamp_int(raw_rd, 1, 3660)
except (ValueError, TypeError):
return None, None, "recent_days / days 必须为整数"
else:
recent_days = cfg_rd
return page_size, recent_days, None
return year, None
def _check_api_key():
@@ -178,8 +180,8 @@ def _serialize_value(value):
return value
def _row_to_dict(row):
return {k: _serialize_value(v) for k, v in row.items()}
def _row_to_export_dict(row):
return {col: _serialize_value(row.get(col)) for col in EXPORT_COLUMNS}
def _db_connect():
@@ -207,41 +209,26 @@ def list_student_changes():
if not _check_api_key():
return jsonify({"code": 401, "msg": "无效的密钥或未提供请求头 " + HEADER_API_KEY}), 401
try:
page = int(request.args.get("page", "1"))
except ValueError:
return jsonify({"code": 400, "msg": "page 必须为整数"}), 400
if page < 1:
page = 1
page_size, recent_days, param_err = _resolve_query_paging()
filter_year, param_err = _resolve_filter_year()
if param_err:
return jsonify({"code": 400, "msg": param_err}), 400
offset = (page - 1) * page_size
# 近 recent_days 天:按视图中的「提交时间」筛选(登记/提交日期)
where_recent = (
"WHERE `{col}` >= DATE_SUB(CURDATE(), INTERVAL {days} DAY)".format(
col=COL_SUBMIT_DATE, days=recent_days
select_cols = ", ".join("`%s`" % col for col in EXPORT_COLUMNS)
where_year = (
"WHERE `{col}` IS NOT NULL AND YEAR(`{col}`) = %s".format(
col=COL_EFFECTIVE_TIME
)
)
sql_count = "SELECT COUNT(*) AS cnt FROM `{view}` {where_clause}".format(
view=VIEW_NAME, where_clause=where_recent
)
sql_list = (
"SELECT * FROM `{view}` {where_clause} "
"ORDER BY `来源记录id` ASC LIMIT %s OFFSET %s"
).format(view=VIEW_NAME, where_clause=where_recent)
"SELECT {cols} FROM `{view}` {where_clause} "
"ORDER BY `来源记录id` ASC"
).format(cols=select_cols, view=VIEW_NAME, where_clause=where_year)
conn = None
try:
conn = _db_connect()
with conn.cursor() as cur:
cur.execute(sql_count)
total = int(cur.fetchone()["cnt"])
cur.execute(sql_list, (page_size, offset))
cur.execute(sql_list, (filter_year,))
rows = cur.fetchall()
except pymysql.Error as e:
return jsonify({"code": 500, "msg": "数据库查询失败", "detail": str(e)}), 500
@@ -249,17 +236,15 @@ def list_student_changes():
if conn is not None:
conn.close()
data = [_row_to_dict(r) for r in rows]
data = [_row_to_export_dict(r) for r in rows]
return jsonify(
{
"code": 200,
"msg": "success",
"data": data,
"page": page,
"page_size": page_size,
"total": total,
"filter_recent_days": recent_days,
"filter_by_column": COL_SUBMIT_DATE,
"total": len(data),
"filter_year": filter_year,
"filter_by_column": COL_EFFECTIVE_TIME,
}
)