news 2026/10/29 2:27:13

Python Flask教务系统实战:选课并发、排课冲突与绩点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python Flask教务系统实战:选课并发、排课冲突与绩点

简介:面向学习Python与数据库课程设计的学生及初级开发者,这份教务系统压缩包提供一套可参考的课程项目素材,聚焦学生信息、课程安排、成绩录入与查询等教务流程的自动化实现。资源共9个文件,以7个SQL脚本、1个Python主程序和1篇docx设计论文为主,压缩包约467KB,目录精炼,便于按数据库、源码与文档三部分查阅。已有594人学习,说明其在课程实践场景中具有一定参考价值。读者可借助SQL脚本了解学生、教师、课程、选课与成绩等数据表的建表及初始化思路,结合Python程序理解业务逻辑与数据库CRUD操作;论文则可用于对照需求分析、架构设计、技术选型、功能实现与测试等环节,适合用作课程设计、毕业项目起步或教务管理系统二次开发的参考资料。

1. 选课开放第一天最容易出问题的,不是服务器扛不住

选课开放的第一分钟,真正让教务处头疼的问题往往不是页面卡顿,而是同一个学生在周二第三、四节被排进了两门必修课,教室还隔着两栋楼。这套基于 python 的学校教务系统就是从这个具体麻烦出发的:把课程资源、教学班、教师任课、教室容量和选课记录收进同一套数据库模型,让排课、选课、成绩三条线共用一份数据,而不是靠三张 Excel 互相抄。它适合正在做毕设或课程设计、需要一套能跑通全流程的管理系统的学生,也适合要交一份教务模块方案的从业者。下面按需求、设计、实现、验收的顺序,把这套系统拆开讲清楚。

2. 需求拆解与技术选型:先划清四类角色和七张表

教务系统的难点并不在功能数量,真正难的是同一份数据要同时满足四种角色的不同视角。学生关心“我这学期还缺几个学分”,教师关心“我带的班有几个学生、成绩录到哪一步”,系部管理员关心“本系课程资源够不够、教师课时是否超标”,教务管理员关心“全校课表有没有冲突、选课数据什么时候锁定”。这四类诉求如果不在需求阶段分清楚,等到写代码时就会出现一个万能 admin 视图包打天下的局面,后期谁都不敢改。

2.1 角色与权限矩阵:先定边界再定功能

我的习惯是先把权限矩阵画成一张表,再照着表去设计路由和装饰器。权限划不清,后面每加一个功能都要回头改判断逻辑,这种反复调整很麻烦。

功能学生教师系部管理员教务管理员
课程资源查看全校只读全校只读本系读写全校读写
选课 / 退课本人操作无权限本系代选(限时段)全局调整
成绩录入只读本人任课班级录入本系审核锁定与解锁
排课无权限提交时间偏好本系排课全校发布
用户与班级管理无权限无权限本系学生全部用户

这张表的关键在于两处约束:一是“教师不能改成绩审核状态”,二是“系部管理员不能跨系查学生”。很多同学的系统在这里不够严谨,任何人手动拼一个 URL 就能改别人的成绩,演示的时候看不出问题,答辩时被追问一句“这个接口谁都能调吗”就很难回答。

2.2 技术选型:Flask + SQLAlchemy + MySQL 的取舍

这套系统选的是 Flask 而不是 Django,原因很实在:教务系统的业务逻辑集中在服务层,Django 自带的后台和 ORM 强约定反而会让学生在写“排课冲突判断”这类核心逻辑时被框架绕晕。Flask 的路由短、蓝图分区清晰,一个模块一个文件,答辩时翻代码心里有底。数据库选 MySQL,是因为教学班容量扣减、选课记录唯一性这些场景需要事务和行锁,MySQL 的 InnoDB 能直接支持;用 SQLite 单机演示没问题,但一开多线程压测就会出问题。

前端不引入脚手架,用 Jinja2 模板加一套现成的 CSS 框架就够。教务系统的页面是表格密集型,学生名单、成绩单、课表都是宽表,前端框架带来的组件化收益不大,反而会让“成绩表格里改一个分数要提交整张表单”这种细节被藏起来。另外把数据库访问统一收进 SQLAlchemy 的会话管理,禁止在视图函数里裸写cursor.execute,否则连接泄漏会在演示到一半时出现。

2.3 数据模型:七张表撑起课程资源

表结构是整个系统的地基。我一般的做法是先定“教学班”这个概念——它不是课程,而是“某门课在某学期由某位老师教、在某个教室、某个时间段上课”的一次具体开班。课程资源可以复用,教学班每学期重建,选课和排课都挂在这个概念上。

-- 教学班:课程 + 教师 + 学期 + 教室 + 时间的组合,是选课和排课的最小单位 CREATE TABLE teaching_class ( id BIGINT NOT NULL AUTO_INCREMENT, course_id BIGINT NOT NULL COMMENT '关联课程资源表', teacher_id BIGINT NOT NULL, term_id BIGINT NOT NULL COMMENT '学期主键,如 2024-2025-1', classroom_id BIGINT DEFAULT NULL COMMENT '排课后回填', capacity SMALLINT NOT NULL DEFAULT 0 COMMENT '容量上限', selected_count SMALLINT NOT NULL DEFAULT 0 COMMENT '已选人数,与选课记录同事务更新', time_mask BIGINT NOT NULL DEFAULT 0 COMMENT '60 位时间片掩码,第 n 位为 1 表示占用', status TINYINT NOT NULL DEFAULT 0 COMMENT '0草稿 1已发布 2已停开', PRIMARY KEY (id), KEY idx_term_teacher (term_id, teacher_id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

这段建表语句里有两个字段值得单独说。selected_count是冗余字段,理论上可以从选课记录表count(*)算出来,但选课高峰期每秒几百次查询会把数据库压垮,用冗余字段加事务内更新是更实际的选择。time_mask存的是一个整数的位掩码,把一周的上课时间编码进一个 BIGINT,冲突判断就退化成一次按位与运算,比存“星期几 + 第几节 + 连上几节”三列再去区间比对要快得多,也不用担心“第一节到第三节”和“第二节到第四节”这种部分重叠的情况被漏判。

其余六张表分别是:course(课程资源,含课程编号、学分、学时、课程性质)、student/teacher(用户与身份)、classroom(教室与座位数)、enrollment(选课记录,带唯一索引)、score(成绩,含平时分、期末分、总评、绩点)、term(学期与选课时间窗口)。七张表的关系不复杂,难的是约束——enrollment上必须有(student_id, teaching_class_id)的唯一索引,这是防止超卖的兜底方案,后面第四章会用到。

3. 总体架构设计:三层结构与六个模块的职责边界

结构设计的目标是让“改一处不影响一片”。教务系统最典型的耦合点在于:排课改了时间,选课要跟着校验冲突;成绩改了总评,绩点和学分统计要跟着重算。如果这些逻辑散落在各个视图函数里,改一个规则要翻十个文件。所以整体按三层切开,模块按蓝图像切豆腐一样分区。

3.1 分层结构:视图层、服务层、数据访问层的分工

视图层只做三件事:取参数、调服务、渲染模板。所有带 if-else 的业务判断都下沉到服务层,比如“这个学生能不能选这门课”要检查先修课程、学分上限、时间冲突、容量余量四件事,这四件事凑在一起写在视图里,页面代码会迅速膨胀到看不下去。数据访问层用 SQLAlchemy 的模型加少量原生 SQL,凡是涉及行锁和批量更新的场景走原生 SQL,其余走 ORM。

服务层的函数签名尽量保持“输入基本类型、输出结果对象”,不要在服务层里读request。这样做的好处是单元测试好写——排课冲突判断这种纯函数,脱离 Web 环境就能跑几百组用例,答辩现场临时改个规则也不慌。

# services/enrollment_service.py from dataclasses import dataclass @dataclass class EnrollResult: ok: bool code: str # OK / FULL / TIME_CONFLICT / CREDIT_LIMIT / DUP message: str def check_enrollable(student, tc, term, enrolled_mask, enrolled_credit): """选课前置校验,全部是纯逻辑,不碰数据库会话。 enrolled_mask: 该生本学期已占用的 60 位时间掩码 enrolled_credit: 已选学分合计 """ if enrolled_mask & tc.time_mask: return EnrollResult(False, "TIME_CONFLICT", "与已选课程时间冲突") if enrolled_credit + tc.credit > term.credit_limit: return EnrollResult(False, "CREDIT_LIMIT", "超过本学期选课学分上限") if tc.selected_count >= tc.capacity: return EnrollResult(False, "FULL", "教学班已满") return EnrollResult(True, "OK", "")

这段代码把四种失败原因分别返回独立的code,前端据此给不同提示,而不是统一弹一句“选课失败”。参数里的enrolled_mask由调用方从已选课程聚合出来,term.credit_limit是学期配置项,通常设为 25 学分上下,具体数值按学校规则改配置表即可,不要硬编码在函数里。

3.2 模块划分:六个蓝图与路由前缀

按业务边界切六个蓝图:auth(登录与身份)、course(课程资源维护)、schedule(排课与课表)、enroll(选课退课)、score(成绩与绩点)、admin(用户、班级、学期)。每个蓝图挂一个路由前缀,/enroll、/score这样一眼能看出归属。蓝图之间不允许互相 import 视图函数,跨模块调用一律走服务层,这条规则写进项目的 README,后续加功能就不会把依赖搞成一团。

课程资源和排课之间有一处容易被忽略的边界:课程的“学分、学时、课程性质”属于课程资源,改动会影响历史学期的统计;而“哪个老师教、什么时间上、在哪个教室”属于教学班,每学期独立。把这两者混成一张表,等到第二个学期复制数据时就会出现“改了课程名,上学期成绩单上的课程名也变了”的问题。

3.3 时间片建模:60 个二进制位装下一周课表

把一周切分成 5 天 × 12 节 = 60 个时间片,每个片对应一个二进制位,这样一个 BIGINT 就能表示任意一种上课安排。位运算判断冲突只需一次与运算,复杂度是常数级,比循环比对区间简单得多。

SLOTS_PER_DAY = 12 DAYS = 5 def to_bitmask(day: int, start: int, length: int) -> int: """把「第 day 天、从第 start 节起连上 length 节」编码成位掩码。 day 从 1 开始(周一=1),start 从 1 开始,length 为连排节数。 例:周一第 1-2 节 -> 0b11 = 3 """ if not 1 <= day <= DAYS: raise ValueError(f"day 必须在 1~{DAYS} 之间") if start < 1 or start + length - 1 > SLOTS_PER_DAY: raise ValueError("节次超出当天范围") base = (day - 1) * SLOTS_PER_DAY + (start - 1) return ((1 << length) - 1) << base def has_conflict(mask_a: int, mask_b: int) -> bool: """两个掩码只要有一位同时为 1,就说明时间重叠""" return (mask_a & mask_b) != 0

base是把“第几天第几节”换算成从 0 开始的位序号,(1 << length) - 1生成连续length个 1,再左移base位就得到目标区间。这个写法的好处是可逆——从掩码反解出“周几第几节”只要找最低位的 1,课表页面渲染时直接用,不需要再存一份文本描述。需要注意的是length不要超过 12,跨天连排(比如周一 11-12 节接周二 1-2 节)这种安排通常不允许,真要用就得把整周看成一个连续序列,判断逻辑要重写。

4. 关键模块实现:选课、成绩、排课三段代码怎么落地

设计讲完,落到最能体现功力的三个地方。这三段代码如果只是“能跑”,答辩时很难讲出东西;把并发、规则、算法三件事各讲透一个点,整个项目的技术含量就立住了。

4.1 选课并发:行锁加唯一索引兜住超卖

选课最经典的翻车场景是容量 50 的班选进 53 个人。原因是“查余量”和“扣余量”分成了两步,两个请求同时读到 49,各自加一,结果变成 51。解决办法是把这两步放进同一个事务,并且对教学班那一行加排他锁。

START TRANSACTION; -- 锁定教学班行,其他事务在这一行上排队,读到的余量一定是准的 SELECT capacity, selected_count FROM teaching_class WHERE id = %s FOR UPDATE; -- 条件更新:即使前面判断被绕过,这里也不会把余量扣成负数 UPDATE teaching_class SET selected_count = selected_count + 1 WHERE id = %s AND selected_count < capacity; -- 唯一索引 uk_student_class 拦住重复选课,插入失败就整体回滚 INSERT INTO enrollment (student_id, teaching_class_id, term_id, status) VALUES (%s, %s, %s, 1); COMMIT;

三层保护各管一件事:FOR UPDATE保证读到的是最新余量,WHERE selected_count < capacity保证即使锁没生效也不会超卖,唯一索引保证同一学生对同一教学班不会产生两条记录。退课就是把selected_count减一、把enrollment.status置为已退,同样放在事务里。这里有个细节:退课不要物理删除记录,否则学生退课再选时唯一索引会冲突,用状态位标记更稳妥。

需要注意锁的粒度。FOR UPDATE锁的是教学班这一行,同一门课的不同教学班互不影响,并发度是可接受的。但如果把整张teaching_class表锁住,选课高峰期会直接串行化,页面转圈十几秒——这种为了省事写的LOCK TABLES属于典型的技术债务。

4.2 成绩录入与绩点:规则集中在服务层

成绩模块的坑在于绩点换算规则会变。今年 90 分以上算 4.0,明年可能改成 4.3 制;有的学校用五分制,有的用百分制转绩点。如果换算逻辑写死在模板的 Jinja 表达式里,规则一改就要全局搜一遍。

# services/score_service.py # 分数段 -> 绩点,从高到低排列,命中第一条即返回 GPA_RULES = [ (90, 4.0), (85, 3.7), (82, 3.3), (78, 3.0), (75, 2.7), (72, 2.3), (68, 2.0), (64, 1.5), (60, 1.0), (0, 0.0), ] def score_to_gpa(score: float) -> float: """百分制转绩点,score 低于 60 返回 0""" for lower, point in GPA_RULES: if score >= lower: return point return 0.0 def calc_final(usual: float, exam: float, usual_ratio: float = 0.3) -> float: """总评 = 平时分 × 比例 + 期末分 × (1 - 比例),保留一位小数""" return round(usual * usual_ratio + exam * (1 - usual_ratio), 1) def weighted_gpa(records) -> float: """records 为 [(credit, final_score), ...],按学分加权求平均绩点""" total_credit = sum(c for c, _ in records) if total_credit == 0: return 0.0 total_point = sum(c * score_to_gpa(s) for c, s in records) return round(total_point / total_credit, 3)

GPA_RULES按分数从高到低排列,用“命中第一条”代替一堆if-elif,加规则时只改列表不改逻辑。usual_ratio做成参数而不是常量,是因为不同课程平时分占比不一样,实验课可能占到 50%,体育课甚至更高,这个值应该存在教学班表里随课程走。weighted_gpa里对total_credit == 0的判空是必须的——新生第一学期还没出成绩时,除零会让整个成绩页报 500,这种错误在演示时特别扎眼。

成绩的另一个设计点是“锁定”。教师录完提交后状态变为待审核,系部审核通过后变为已锁定,锁定后教师端只读。用状态机约束,而不是靠口头约定,这样历史成绩的可信度才站得住。

4.3 排课落地:贪心分配加回溯修补

排课是整套系统里最像算法题的部分。完整的排课是 NP 问题,做毕设不需要追求最优解,用“贪心 + 局部回溯”就能出一个能用的课表:按容量从大到小处理教学班(大班可选教室少,先安排),教室按座位数从小到大挑(小教室先消耗,避免大教室被浪费),遇到冲突就跳过。

def assign_classrooms(classes, rooms, occupied): """classes: 待排教学班列表;rooms: 教室列表; occupied: {(classroom_id, term_id): time_mask},记录教室已被占用的时间片 返回 (成功分配列表, 无法分配的教学班 id 列表) """ plan, failed = [], [] # 大班优先,教室从小到大,减少大教室碎片 for tc in sorted(classes, key=lambda c: -c.capacity): for room in sorted(rooms, key=lambda r: r.seats): if room.seats < tc.capacity: continue key = (room.id, tc.term_id) used = occupied.get(key, 0) if used & tc.time_mask: # 教室在该时段已被占用,换下一个 continue occupied[key] = used | tc.time_mask plan.append({"class_id": tc.id, "room_id": room.id}) break else: failed.append(tc.id) # 所有教室都排不下 return plan, failed

sorted(classes, key=lambda c: -c.capacity)决定了处理顺序,break命中第一个能装的教室就停。for-else这个结构在这里刚好用上:循环正常走完没break,说明所有教室都不合适,进failed。真正实施时还要交叉校验教师时间冲突和班级时间冲突,把三个掩码做按位与即可,判断方式和 3.3 节完全一致。

拿到failed列表后不要直接报“排课失败”,而是进入回溯环节:尝试给这些教学班换一个时间段(教师和班级的空闲掩码取反后找连续可用的位),换成功就重新走一遍分配。这一步通常能救回七八成的失败项,剩下实在排不下的才提示人工调整。答辩时把这个“先贪心、再修补”的思路讲出来,比说“用了遗传算法”却解释不清适应度函数要可信得多。

5. 避坑与常见问题排查:五个最容易出问题的地方

代码跑通和系统可用之间隔着一段距离,下面这几处都是我在类似项目里真实遇到过或者看着别人踩过的,按“现象—原因—解决”写清楚。

5.1 选课人数超过容量,但数据库里查不出问题

现象:教学班容量 50,后台显示已选 53,学生的选课记录却是 53 条各不相同,没有重复。

原因:扣减余量的 UPDATE 和插入选课记录分在两个连接里,中间没有事务包裹,或者用了SELECT ...查余量再在 Python 里加一。并发下两个请求读到同一个旧值,各自加一,实际只加了两次却都以为从 49 变成 50。

解决:按 4.1 的写法,把SELECT ... FOR UPDATE、条件UPDATE、INSERT放进同一个事务,并确认表的存储引擎是 InnoDB——MyISAM 不支持行锁,加了FOR UPDATE也是摆设。排查时先看表的引擎,再看事务隔离级别,最后确认代码里没有提前commit。

5.2 排课冲突校验通过,课表上还是撞车

现象:系统提示排课成功,学生课表里两门课时间重叠。

原因:冲突校验只比对了“教师”维度或只比对了“班级”维度,漏了学生自主选课的组合。同一个行政班的学生不一定选同一批课,冲突要在学生层面动态算。

解决:把冲突判断分三层——教师时间、教室时间、学生已选时间。前两层在排课时静态校验,第三层放在选课环节的动态校验里,用enrolled_mask & tc.time_mask实时拦截。检查时构造一个学生,手动选两门时间重叠的课,看是否被拦住。

5.3 成绩页面加载到一半报 500,日志里是除零

现象:新生班级的成绩统计页打开报错,老生班级正常。

原因:weighted_gpa里学分总和为 0 时直接做除法,或者平时分、期末分为 NULL 时参与运算抛异常。

解决:所有聚合函数入口做零值和 None 判断,返回 0 或空列表而不是抛异常;数据库层面对分数字段设DEFAULT 0,但更稳的是服务层兜底,因为历史数据可能是NULL。这类错误最容易在演示现场出现,因为演示用的往往是还没出成绩的新学期。

5.4 中文课程名写入后变成问号

现象:录入“数据结构与算法”后数据库里是????,或者页面显示乱码。

原因:数据库、表、连接三处的字符集不一致。常见的是库是 utf8mb4,连接串没指定编码,或者建表时用了默认的 latin1。

解决:建库建表统一utf8mb4,连接串显式指定charset=utf8mb4,页面模板声明UTF-8。排查顺序是“库 → 表 → 列 → 连接 → 页面”逐层看,五个地方有一个不对就会出乱码。

5.5 权限判断写在模板里,改一个角色要动十几处

现象:给系部管理员加一个“导出本系成绩”的按钮,结果在八个模板里各改一遍,还漏了两处。

原因:用{% if user.role == 2 %}这种硬编码角色判断散落在模板里,角色和权限没有抽象成能力项。

解决:把权限收敛成服务层的can(user, "score:export")函数,模板里只调这一个函数,角色的能力映射写在配置里。这样加角色只改映射表,加功能只加一个能力项。这个重构越早做越好,等到模板超过二十个再改,就得有足够的耐心。

6. 验收与答辩:用一份可复现的清单把项目讲清楚

验收这件事,我一般不建议照着“功能列表”逐条点一遍,因为演示者知道下一步该点什么,看起来很顺,实际上一换人就卡住。更可靠的做法是准备一份可复现的验收清单,让别人按清单独立走一遍,走不通的地方就是系统真正的短板。

验收项操作步骤通过标准
并发选课用脚本对同一教学班发起超出容量的并发请求最终已选人数等于容量,无超卖
时间冲突学生已选周一 1-2 节课程,再选同一时段课程提示冲突并拒绝,掩码判断生效
权限越界用学生账号直接访问成绩录入接口返回 403 或跳转登录,不泄露数据
绩点计算录入一组已知分数,手工核算加权绩点系统结果与手工结果一致到小数点后三位
排课修补构造教室不足的场景失败项进入回溯后部分可解,剩余项有明确提示
数据恢复删除一条选课记录后重新选课不触发唯一索引冲突,状态位正确

表格里最值得花时间的是前两项。并发选课要用工具压,不能靠手点;时间冲突要构造“部分重叠”的用例,比如已选 1-2 节,再选 2-3 节,只比对开始节次的实现会漏判这一种。答辩时把这两组的测试结果直接放出来,比口头说“考虑了并发”有说服力得多。

答辩陈述的节奏建议压到五分钟以内,结构是:一句话说清系统解决什么、一张图讲数据模型、一段代码讲并发控制、一组数据讲验收结果。评审最常追问的三个问题是“选课超卖怎么防的”“排课冲突怎么判断的”“成绩被别人改了怎么办”,这三个问题正好对应第 4 章的三个小节,提前把答案写进 PPT 备注里。别在答辩时现场打开代码翻文件,工程结构图准备好,指到哪个模块讲哪个模块。

还有一个容易被忽略的点:把种子数据准备好。一套干净的演示数据应该包含两三个学期、十几个教学班、几十个学生、若干条已出成绩的记录,这样演示绩点统计和课表时不用现场编数据。我有一次演示到一半发现测试库里只有一条成绩记录,统计页面全是 0,那种尴尬到现在还记得。从那以后我每次交付前都强制走一遍“清库 → 导入种子数据 → 按验收清单完整跑一遍”,跑不通就不算完。这套流程看着笨,但省下来的都是演示当天的心跳。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 2:26:57

Flask+SQLite AI智能联系人管理:去重、拼音检索与CSV导入

简介&#xff1a;这份毕业设计资料包面向计算机相关专业学生、Python初学者及需要完整项目案例的开发者&#xff0c;主题为AI智能联系人管理系统。系统围绕联系人增删改查、按姓名电话邮箱检索分类、CSV或VCF导入导出等基础功能展开&#xff0c;并预留自然语言查询解析与用户行…

作者头像 李华
网站建设 2026/10/29 2:26:44

PELT负载跟踪:从rq到se的调度器负载均衡与调频原理

简介&#xff1a;面向Linux内核调度与性能调优方向的学习者&#xff0c;这份PDF围绕CFS调度器中的PELT&#xff08;per-entity load tracking&#xff09;机制展开&#xff0c;重点回答Linux 3.8之前以运行队列跟踪负载时无法定位负载来源、负载值波动大的问题&#xff0c;并说…

作者头像 李华
网站建设 2026/10/29 2:25:47

Python股票大数据分析实战:从爬虫到指标计算全流程

简介&#xff1a;这份资源面向具备一定Python基础、希望进入金融数据分析领域的开发者与量化爱好者&#xff0c;围绕股票大数据场景串联爬虫采集、数据清洗、统计分析与机器学习预测等环节&#xff0c;帮助读者建立从数据获取到建模应用的完整实践认知。压缩包为7z格式&#xf…

作者头像 李华
网站建设 2026/10/29 2:25:46

Python股票大数据分析实战:从数据采集到可视化全链路

简介&#xff1a;面向金融数据分析初学者与进阶者的Python股票大数据实战资料&#xff0c;围绕爬虫采集、数据清洗、可视化分析与机器学习预测构建完整学习路径&#xff0c;适合希望把Python技能落到真实行情场景的读者。压缩包为7z格式&#xff0c;整体约4.47MB&#xff0c;上…

作者头像 李华
网站建设 2026/10/29 2:24:44

Flutter双端影视APP源码:工程结构、播放器封装与二次开发避坑

简介&#xff1a;这份源码面向移动应用开发者与小型技术团队&#xff0c;提供安卓与苹果双端的在线视频聚合APP工程代码&#xff0c;可用于搭建影视类应用&#xff0c;也适合作为跨平台开发与完整业务闭环的学习样本。资源以zip压缩包分发&#xff0c;整体约42.5MB&#xff0c;…

作者头像 李华
网站建设 2026/10/29 2:24:33

基于iText的PDF批量处理:创建、电子签章、水印与文本替换实战

简介&#xff1a;围绕 iText 库处理 PDF 的完整 Java 工程示例&#xff0c;面向有一定 Java 基础、需在业务系统中实现 PDF 生成与二次编辑的开发者。内容覆盖新建文档、电子签章、斜字水印插入与文本替换四条主线&#xff0c;并配套 BouncyCastle 加密组件与亚洲字体支持&…

作者头像 李华