ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

学生成绩管理系统数据库设计:表结构、SQL优化与并发控制

学生成绩管理系统数据库设计:表结构、SQL优化与并发控制 简介一份面向广东海洋大学数据库课程设计的完整报告文档以学生成绩管理系统为实践项目适合计算机科学与技术及相关专业学生参考。文档从数据库设计全流程出发覆盖需求分析、概念结构设计、逻辑结构设计、物理结构设计、系统实现及自我评价帮助读者理解课程设计报告的标准撰写框架解决设计文档不知从何下手的困扰。资料共1个PDF文件压缩包大小仅1.23MB内容包含功能模块图、E-R图、关系模式转换、数据库建表语句、登录界面与管理主界面设计说明涵盖学生信息管理、课程管理、成绩录入查询与统计分析等核心模块并附完整目录和代码附录便于对照学习。资源采用C#结合SQL Server 2005开发对希望掌握ADO.NET数据库程序设计、SQL语句书写与C#调用方法的读者尤为实用。已有529人学习浏览适合用于课程设计前调研、报告结构参考或自查补漏无论是初步接触数据库原理还是准备答辩汇报都能从中获取清晰的设计思路与实现细节。1. 项目概述与核心需求解析1.1 这个课程设计到底在做什么数据库课程设计里“学生成绩管理系统”属于出现频率最高的题目没有之一。原因很简单它的业务边界足够清晰涉及的关系模型足够典型又能在数据库设计、SQL编写、应用开发三个层次全面考察学生的基本功。我当时拿到这个题目的时候第一反应是“这不就是增删改查吗”但真正动手做下去才发现能把一张成绩表背后的事务一致性、统计汇总、权限控制做实做深远比表面上看起来复杂得多。从业务场景看这个系统需要覆盖三类用户学生查询自己的成绩、查看学分绩点、教师录入成绩、修改成绩、导出成绩单、教务管理员管理学生信息、教师信息、课程信息、审核成绩的最终合规性。这三类角色的数据权限天然不同所以你需要在数据库层面就设计好访问边界而不是等到写业务代码时才去“打补丁”。从我多年的经验来看很多人在这个题目上翻车不是不会写SQL而是把数据库设计当成了“建几张表”的体力活。成绩管理系统最大的陷阱在于你很容易把业务规则散落在代码里而不是沉淀到数据库约束里。比如“同一门课一个学生只能有一条成绩记录”这个规则如果你只是在前端做判断并发插入时就会出现重复数据。正确的做法是在表结构里用联合唯一约束把它固化下来。1.2 适合谁来参考这份设计这份设计文档面向的核心人群有三类正在准备数据库课程设计的在校生尤其是有MySQL基础、但没系统接触过数据库建模的、需要批量管理成绩数据的教师或教务人员想用工具化思维替代手工Excel表格的、以及刚入行想补齐数据库基础的数据开发初学者。另外我要特别说明的是虽然题目挂的是“广东海洋大学”的课程设计但成绩管理系统的库表设计和实现思路是完全通用的不管是普通高校、职业院校还是培训机构只要业务是“学生-课程-教师-成绩”四元关系这套设计就能直接复用。我在写这套系统的时候刻意没有绑定任何特定的教学班底或教务平台所有表结构都是基于典型高校教务场景抽象出来的换到任何环境都能落地。2. 数据库整体设计与表结构拆解2.1 物理模型与技术选型数据库选型上最稳妥的选择是MySQL 8.0。为什么不用Oracle或SQL Server一方面课程设计的环境要求通常是轻量部署MySQL的安装、迁移成本最低社区资料也最齐全另一方面MySQL 8.0的窗口函数ROW_NUMBER、RANK等在计算成绩排名时非常好用比5.7版本用变量模拟窗口函数的方式清爽得多。如果你所在的环境只能用SQL Server核心表结构和SQL语法做少量适配也能跑通但后面的分析函数就需要改成对应的语法了。字符集方面我强烈建议统一使用utf8mb4而不是utf8。原因是utf8在MySQL里是“残缺版”最多存3字节的字符遇到生僻字或者某些特殊符号比如学生姓名里的生僻汉字会直接报错或丢数据。utf8mb4是真正的4字节UTF-8编码兼容性最好。排序规则选utf8mb4_unicode_ci还是utf8mb4_general_ci都可以课程设计场景里两者差别不大我自己用的是utf8mb4_unicode_ci它在多语言排序上更严谨。存储引擎选InnoDB这个基本是定论。MyISAM虽然查询速度快一点但不支持事务、不支持外键而成绩管理系统里“录入后必须确保原子性”是硬需求。想象一个场景教师在录入整个班的成绩时突然断电如果使用MyISAM可能前20条记录写入了、后10条没写入数据直接处于“半完成”状态InnoDB配合事务可以保证要么全部提交、要么全部回滚。2.2 核心表结构设计在成绩管理系统里基础表是student、teacher、course核心表是score外围还有user登录账号、class行政班级、semester学期等辅助表。我的建表设计如下CREATE TABLE student ( student_id VARCHAR(20) NOT NULL COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender ENUM(男, 女) DEFAULT NULL COMMENT 性别, class_id INT DEFAULT NULL COMMENT 班级ID, enrollment_date DATE DEFAULT NULL COMMENT 入学日期, PRIMARY KEY (student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT学生信息表; CREATE TABLE course ( course_id INT NOT NULL AUTO_INCREMENT COMMENT 课程ID, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(3,1) NOT NULL COMMENT 学分, teacher_id VARCHAR(20) DEFAULT NULL COMMENT 授课教师ID, course_hours INT DEFAULT NULL COMMENT 学时, PRIMARY KEY (course_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT课程信息表; CREATE TABLE score ( score_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 成绩记录ID, student_id VARCHAR(20) NOT NULL COMMENT 学号, course_id INT NOT NULL COMMENT 课程ID, semester VARCHAR(20) NOT NULL COMMENT 学期如2023-2024-1, usual_grade DECIMAL(5,2) DEFAULT NULL COMMENT 平时成绩, exam_grade DECIMAL(5,2) DEFAULT NULL COMMENT 期末成绩, final_grade DECIMAL(5,2) DEFAULT NULL COMMENT 综合成绩, grade_point DECIMAL(3,2) DEFAULT NULL COMMENT 学分绩点, status TINYINT DEFAULT 0 COMMENT 审核状态0未提交1待审核2已通过, PRIMARY KEY (score_id), UNIQUE KEY uk_student_course_semester (student_id, course_id, semester), KEY idx_course_id (course_id), KEY idx_semester (semester) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT成绩表;这个设计的核心思想有三个第一成绩表必须保留平时成绩和期末成绩的原始值同时计算综合成绩。为什么因为课程设计答辩时老师很可能会问“成绩隐私和公平性如何保证”“如果平时成绩录入错误怎么溯源”。如果你只存一个最终成绩一旦出现异议根本没有回溯的依据。我当时设计的时候还加了一列audit_log的扩展思路——即把每次修改操作的旧值和新值记录到日志表里这个虽然没有在物理表中体现但对支撑“成绩一旦审核通过后必须走申请才能修改”的业务流程非常关键。第二联合唯一约束是不可省略的。UNIQUE KEY uk_student_course_semester (student_id, course_id, semester)保证了同一学生在同一学期的同一门课程只能有一条成绩记录。这是我在上文提到的“业务规则下沉到数据库层”的典型实现。如果不加这个约束当两个并发请求同时为该学生插入成绩时数据库中会出现两条互相矛盾的记录。课程设计的评分标准里这一点往往是“良好”和“优秀”的分水岭。第三用预留字段而不是直接写死状态。status字段虽然只有0/1/2三个值但使用TINYINT而不是直接存字符串是因为它在后续扩展时可以无缝加状态比如3代表“归档”。2.3 为什么需要用户表与角色权限分离权限设计是课程设计里最容易被忽略、又被评审查得最凶的环节。我见过太多设计直接把角色字段塞进学生表比如加一列role默认是学生这在小系统里能跑但一旦要考虑“一个教师同时兼任班主任”或“学生代表协助教务管理”时单一角色字段就彻底卡死了。更好的做法是抽象出一张user表只负责身份认证用户名密码然后用user_role中间表关联到role表。虽然课程设计为了避免过度设计可以砍掉中间表但我至少会保留一个面向业务的建议。实际建表时我采用的是简化的方案user表里有role字段ENUM(admin,teacher,student)因为课程设计的时间有限这个粒度已经够用。密码存储是另一个考察点。绝对不要用明文密码。哪怕你做的只是课程设计也应该体现“安全设计意识”。使用BCrypt对密码进行哈希存储这是当前业界的主流做法。代码层面可以用Spring Security自带的BCryptPasswordEncoder如果你用PHP可以用password_hash()函数。为了便于演示初始化密码可以用123456的哈希值而不是纯文本插入数据库。3. 核心SQL实现与排名统计3.1 成绩综合计算的存储过程设计成绩数据录入以后需要将平时成绩和期末成绩按权重折算成综合成绩再依据综合成绩映射成学分绩点。这个计算逻辑放在哪里我的习惯是用存储过程或者数据库函数实现而不是在Java/Python的业务代码里做。原因有两个第一数据库里一旦计算规则调整比如平时分权重从40%改成30%只需改数据库层的函数而不需要重新发布应用第二课程设计评分标准中数据库对象存储过程、函数、触发器的使用往往是加分项。下面是综合成绩与绩点计算的示例代码DELIMITER // CREATE FUNCTION calc_final_grade(usual DECIMAL(5,2), exam DECIMAL(5,2)) RETURNS DECIMAL(5,2) DETERMINISTIC BEGIN DECLARE final_grade DECIMAL(5,2); SET final_grade usual * 0.4 exam * 0.6; RETURN final_grade; END// CREATE FUNCTION calc_grade_point(grade DECIMAL(5,2)) RETURNS DECIMAL(3,2) DETERMINISTIC BEGIN DECLARE gp DECIMAL(3,2); IF grade 90 THEN SET gp 4.0; ELSEIF grade 85 THEN SET gp 3.7; ELSEIF grade 82 THEN SET gp 3.3; ELSEIF grade 78 THEN SET gp 3.0; ELSEIF grade 75 THEN SET gp 2.7; ELSEIF grade 72 THEN SET gp 2.3; ELSEIF grade 68 THEN SET gp 2.0; ELSEIF grade 64 THEN SET gp 1.5; ELSEIF grade 60 THEN SET gp 1.0; ELSE SET gp 0.0; END IF; RETURN gp; END// DELIMITER ;使用这两个函数后更新成绩就可以直接调用UPDATE score SET final_grade calc_final_grade(usual_grade, exam_grade), grade_point calc_grade_point(calc_final_grade(usual_grade, exam_grade)) WHERE score_id ?;这里有一个值得优化的点如果每次手动执行UPDATE那么每次成绩变化都需要触发一次重新计算。我在实际操作中会在score表上加一个BEFORE INSERT和BEFORE UPDATE触发器统一计算final_grade和grade_point这样应用层代码就不需要关心计算公式了也避免不同模块实现的计算口径不一致。触发器方案的唯一顾虑是批量操作时的性能但成绩管理系统的写入量远够不到瓶颈放心用。3.2 总学分绩点与专业排名的窗口函数写法计算学生总学分绩点GPA是成绩管理系统的高频功能。一般公式是GPA Σ(课程绩点 × 课程学分) / Σ(课程学分)。用SQL实现如下SELECT s.student_id, s.name, SUM(sc.grade_point * sc.credit) / SUM(sc.credit) AS gpa FROM student s LEFT JOIN ( SELECT sc.student_id, sc.grade_point, c.credit FROM score sc JOIN course c ON sc.course_id c.course_id WHERE sc.status 2 -- 只统计审核通过的成绩 ) sc ON s.student_id sc.student_id GROUP BY s.student_id, s.name ORDER BY gpa DESC;这里的LEFT JOIN刻意保留了没有成绩记录的学生GPA为NULL方便教务发现漏录、漏考的情况。而WHERE sc.status 2这个条件更是关键——如果不过滤审核状态那么教师保存草稿但未提交的成绩就会被计入GPA这显然是错误的。专业排名同样是高频需求MySQL 8.0的窗口函数可以一行搞定SELECT student_id, gpa, RANK() OVER (ORDER BY gpa DESC) AS rank_no FROM ( SELECT student_id, SUM(grade_point * credit) / SUM(credit) AS gpa FROM score sc JOIN course c ON sc.course_id c.course_id WHERE status 2 GROUP BY student_id ) t;RANK()和DENSE_RANK()的区别要特别注意如果有两个学生并列第一RANK()会生成1,1,3而DENSE_RANK()生成1,1,2。如果教务处希望“并列后不占后续名次位”就用RANK如果希望“紧凑排列”就用DENSE_RANK。这个细节如果你能在文档里主动提出来会很加分。3.3 成绩统计分析视图与报表除了排名还需要统计每个班级各门课的及格率、平均分、最高分、最低分等。一次课程设计中这张视图基本能应付80%的教务统计需求CREATE VIEW v_course_score_stats AS SELECT c.course_id, c.course_name, sc.semester, COUNT(sc.student_id) AS total_students, SUM(CASE WHEN sc.final_grade 60 THEN 1 ELSE 0 END) AS pass_students, ROUND(AVG(sc.final_grade), 2) AS avg_grade, MAX(sc.final_grade) AS max_grade, MIN(sc.final_grade) AS min_grade, ROUND((SUM(CASE WHEN sc.final_grade 60 THEN 1 ELSE 0 END) / COUNT(sc.student_id)) * 100, 2) AS pass_rate FROM course c LEFT JOIN score sc ON c.course_id sc.course_id AND sc.status 2 GROUP BY c.course_id, c.course_name, sc.semester;视图的好处在于应用层只需要SELECT * FROM v_course_score_stats就可以拿到一张统计报表代码里不需要再写冗长的聚合SQL。实际答辩时你可以现场跑一次这个视图展示“成绩分布饼图”的数据来源评委会非常直观地感受到数据库层的设计能力。4. 登录鉴权和操作流水设计4.1 会话管理和账号对应的三种权限边界学生成绩管理系统一定要有登录功能而且不同角色登录后的操作面必须不一样。很多课程设计做到这一步就变成了“前端菜单不同”而已后端接口所有人都能调。这在数据库设计层面怎么体现答案是每个业务表的增删改查都绑定到登录人身份上。我的做法是数据库连接层做一层基本鉴权过滤再配合应用层的拦截器进行二次校验。具体的权限矩阵设计如下角色学生教师教务管理员查询自己成绩允许仅自己负责的课程全部新增/修改成绩禁止仅授课课程且未审核全部含审核操作查看排名统计允许仅自己自己课程范围内全部管理学生/课程信息禁止禁止允许这张表直接映射到了auth的逻辑代码里。从数据库设计的角度讲核心表score的status字段承担了“审核流”的关键控制教师新增的成绩默认进入待审核状态管理员审核通过后status置为2。之后任何对该成绩的修改都必须走“申请-审批”流程而不是直接UPDATE。这个设计是我整个项目里最有价值的部分它避免了很多成绩管理系统的脏数据问题。4.2 操作日志表的结构设计凡是涉及成绩变化、审核动作的相关操作都应该留下日志痕迹。我在项目里做了一个非常轻量的操作日志表CREATE TABLE operation_log ( log_id BIGINT NOT NULL AUTO_INCREMENT, user_id VARCHAR(20) NOT NULL COMMENT 操作用户ID, action_type VARCHAR(20) NOT NULL COMMENT 操作类型INSERT/UPDATE/DELETE/AUDIT, table_name VARCHAR(50) NOT NULL COMMENT 操作表名, record_id BIGINT DEFAULT NULL COMMENT 操作记录ID, old_value TEXT COMMENT 操作前数据JSON, new_value TEXT COMMENT 操作后数据JSON, op_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (log_id), KEY idx_user_time (user_id, op_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT操作日志表;这张表的妙处在于old_value和new_value直接存JSON字符串可以无需再建一张子表就能完整记录变化前后状态。性能上如果数据量上百万再考虑拆表课程设计场景里完全够用。其实这个表也是我后期答辩时最有底气的加分项。因为评委随机抽查一位同学的成绩修改历史时我可以现场演示“这科成绩老师在1月4日做过一次调整调整前85分调整后88分审核人是谁、权限在哪个角色下都有完整记录。”5. 索引与事务并发问题处理5.1 索引设计的关键取舍成绩表的查询场景以“按学生查”“按课程查”“按学期查”为主所以索引设计集中在三个方向(student_id, semester)联合索引快速定位某学生某学期的全部成绩用于生成个人成绩单。(course_id, semester)联合索引快速定位某门课某学期的全部成绩用于教师录入与统计。status单列索引很多统计只查审核过的数据带上status可以大幅降低扫描量。需要注意的坑是反范式冗余字段更适合查询但不宜过度加索引。我在调试过程中发现三个联合索引加上两个单列索引已经能让千万级数据量的查询稳定在毫秒级别。再加更多的索引不但不会提速反而会因为写入时的索引维护拖慢速度。课程设计报告的索引设计部分如果能写清楚“为什么建这个索引、能覆盖哪些查询”比罗列十来个索引更显功力。5.2 成绩录入时的锁与事务高并发场景下成绩写入会遇到一个经典问题两个老师同时给同一批学生录入成绩或者同一个老师开着两个页面重复提交。这个问题的核心在于数据库事务隔离级别和锁机制。我在录入接口上使用了SELECT ... FOR UPDATE对score表的待处理记录进行行级锁控制事务隔离级别设为READ COMMITTED。这样做的效果是当教师A正在提交某学生某学期某课程的成绩时教师B对该学生的同一门课程提交会被锁阻塞直到A的事务提交完成。如果A先把成绩插入后回滚B的操作也能正常执行不会产生数据错乱。紧接着要处理的是重复提交拦截。除了数据库层的联合唯一约束外我还在接口层做了防重令牌幂等键。每次打开成绩录入页面时生成一个request_id同一批数据用同一个request_id后端收到重复的request_id直接返回“已提交请勿重复操作”。这个设计虽然偏应用层但能从根源上减少死锁产生的概率。5.3 常见死锁场景与排查成绩管理系统里最常见的死锁是两个事务以不同顺序更新同一张表。比如事务A先更新student表再更新score表事务B先更新score表再更新student表两边互相持有对方的锁就会造成死锁。解决办法是统一加锁顺序所有业务操作都先锁student再锁score。排查死锁的经典手段是用SHOW ENGINE INNODB STATUS查看最近一次死锁的具体事务和资源占用信息。从输出中找LATEST DETECTED DEADLOCK部分里面会明确告诉你等待的锁和持有的锁分别在哪行。我曾遇到一个非常隐蔽的问题是“同一事务中先执行了全表扫描的UPDATE又执行了索引条件的UPDATE”因为扫描范围不同锁定的记录范围交叉导致了死锁。最后通过把全表UPDATE改成走主键的等值UPDATE解决了。这一类排查经验如果写进课程设计报告的“问题与解决方案”章节绝对能拉开差距。6. 常见问题排查与技术难点实操记录6.1 中文乱码与字符集问题这类问题在课程设计初期出现的频率极高。表现是插入中文后查询出来变成???或一堆繁体乱码。排查思路很固定先检查数据库连接串是否指定了字符集。以JDBC为例连接串里必须显式加上characterEncodingutf8否则会使用驱动默认的字符集去传输中文字符。再从三个层面逐一检查字符集# 查看数据库服务端字符集 SHOW VARIABLES LIKE character_set_server; # 查看当前数据库字符集 SHOW CREATE DATABASE your_db; # 查看表字符集 SHOW CREATE TABLE student;这三处的任何一处是latin1都会导致乱码。另外一个隐蔽坑是ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4和ALTER TABLE ... DEFAULT CHARACTER SET utf8mb4的区别前者会同时对已有数据做字符集转换后者只改默认、对已存在的列无效。用错了表看起来改了字符集、但列还是旧的就容易出现“改完还乱码”的怪事。6.2 忘记数据库密码或MySQL无法启动这两个问题其实是校内实训室电脑上最常出现的。忘密码的场景如果MySQL 8.0的root密码在安装后从未改过可以尝试mysql -u root直接回车登录因为某些默认安装环境允许空密码。如果不行就要走skip-grant-tables模式重置密码。具体步骤是在配置文件中增加skip-grant-tables重启MySQL服务后不用密码直接登录然后用ALTER USER rootlocalhost IDENTIFIED BY new_password;重置密码最后把配置项去掉、重启服务。这里有一个大坑8.0版本中只靠UPDATE mysql.user SET authentication_string WHERE Userroot;是不够的必须用ALTER USER命令因为8.0的认证插件是caching_sha2_password手动清空密码字段反而会触发初始化错误。如果MySQL无法启动先看端口占用默认3306容易被其他程序吃掉再看错误日志Linux下面通常是/var/log/mysql/error.logWindows下在数据目录或安装目录的data文件夹里。绝大多数“启动失败”最后都指向my.cnf/my.ini里的配置写错比如datadir路径末尾多了一个斜杠或者basedir路径包含中文。6.3 成绩统计结果与手工Excel算对不上这是一个让无数人抓狂的“疑难杂症”。明明SQL看起来没有错但统计出来和教务手工用Excel算的结果对不上。我用过一次很典型的案例来说明某个班的平均分SQL结果比Excel高了0.5分。排查后发现成绩表里有一条final_grade NULL的记录——那位同学缺考成绩未录入。手工Excel的做法大概率是“忽略空白单元格”但SQL里AVG(final_grade)会自动忽略NULL这没问题可如果代码里用了IFNULL(final_grade, 0)就会把缺考者当0分计入平均拉低整体成绩。这种“缺失值处理口径不一致”的问题在课程设计中极具代表性。解决方案是在统计报表前先明确定义好缺失值的语义——缺考是记0分、还是单独标记、还是排除出分母。这个语义必须由教务方确认然后统一在SQL里实现最怕的是每次手工处理时临时变更口径。6.4 备份策略与导出报表实践数据库备份虽然不直接属于“成绩管理”功能但课程设计文档里写到这一项基本稳妥加分。最简单的备份命令# 逻辑备份某张表 mysqldump -u root -p your_db score score_backup.sql # 恢复 mysql -u root -p your_db score_backup.sql如果想让备份更自动化可以写一个Linux下的shell脚本配合crontab任务做每日备份保留最近7天的备份文件。Windows环境可以用计划任务mysqldump命令实现。备份文件建议按日期命名比如score_backup_20250201.sql便于归档。7. 给新手的四个实操建议这一节本来不在我的设计范围里但最近被好几个学弟学妹问到同样的问题就额外写一下。如果你也是刚开始做数据库课程设计可以参考这几条经验第一先画好ER图和数据流图再动手建表。ER图不是形式化作业它是你建表前的思考工具箱。实体之间的关系是1对多还是多对多直接决定了中间表的设计。学生和课程就是典型的多对多关系必须通过成绩表作为关联表来桥接。第二把所有SQL语句都记录到项目文档里。答辩时老师往往不看完整的项目报告而是直接问“你实现某功能用的哪条SQL”。如果你现场写SQL熟练度不够提前准备一个“SQL速查表”会非常有帮助。第三数据量不要只填三五条演示数据。尽量生成100名学生、30门课程、满学期的成绩数据。否则很多性能问题和SQL逻辑错误根本测不出来。我自己就曾遇到过——小数据量下分组统计完全正常换成真实数据量后才发现漏了GROUP BY的一个字段导致部分班级统计有误。第四做权限设计和操作日志表别嫌麻烦。这两个环节是你从“学生作品”跨到“企业级系统”的关键证据。哪怕实现很简陋只要有这个意识、留下了可扩展的接口评委在打分时就会把你和只会写增删改查的区分开来。这是一套我在实际课程设计中踩了不少坑之后沉淀下来的方案和细节。你可以直接拿来当参考骨架也可以把表字段改成自己学校的业务需求再扩展。数据库设计从来不是一锤子买卖它一定会在使用过程中不断暴露出问题然后被不断修正。真正能让你成长的正是这个“设计-实现-发现问题-重构”的循环过程。本文还有配套的精品资源点击获取
返回列表