资讯动态

Java Web学生信息管理系统:Spring Boot+MyBatis实战设计与开发指南

发布时间:2026/8/28 2:32:28 来源:尧图企业网站定制
简介学生信息管理系统是经典的Web应用开发项目它涵盖了从数据库设计到前后端交互的完整流程是理解企业级应用开发的绝佳实践。其核心原理在于通过MVC分层架构将业务逻辑、数据访问和用户界面分离实现高内聚低耦合。在技术层面Java Web技术栈凭借其稳定性和成熟的生态成为构建此类系统的首选。通过整合Spring Boot、MyBatis和MySQL开发者可以高效地实现用户权限管理、数据CRUD操作及报表生成等核心功能。这种组合不仅提升了开发效率其清晰的代码结构也便于维护和扩展。在实际应用场景中此类系统广泛服务于高校教务管理和企业人力资源领域对处理关联数据、实现复杂查询和保证数据一致性有较高要求。本文以学生信息管理系统为例深入剖析了使用Spring Boot和MyBatis进行数据库设计、权限控制及分页查询等关键模块的实现方案并分享了事务管理和性能优化等工程实践中的宝贵经验。1. 项目概述与核心价值最近几年无论是高校的信息化建设还是计算机相关专业的毕业设计学生信息管理系统都是一个经久不衰的热门选题。它不像电商、社交平台那样概念新颖但胜在需求明确、业务逻辑经典是检验一个开发者从需求分析到编码实现全链路能力的绝佳“试金石”。我当年毕业设计做的就是这个后来在带团队和面试新人时也无数次接触过基于Java Web技术栈实现的类似系统。今天我就以一个过来人和面试官的双重身份为你深度拆解一个完整的“基于Java Web的学生信息管理系统”应该如何设计与实现并分享那些教科书和开源代码里不会告诉你的“坑”与技巧。这个系统的核心价值在于它麻雀虽小五脏俱全。你需要处理用户管理员、教师、学生的权限管理学生、班级、课程、成绩等核心实体实现信息的增删改查CRUD并往往需要导出报表。整个过程你会直面MVC分层架构、数据库设计、前后端交互、会话管理、文件操作等Web开发的核心议题。选择Java Web意味着你大概率会接触到Servlet/JSP、Spring MVC、Spring Boot、MyBatis/Hibernate这些主流或经典的技术组合。通过亲手实现它你不仅能拿到毕业设计的学分更能扎实地掌握一套在企业级开发中依然广泛使用的技术体系这份经历远比一个炫酷但浮于表面的“玩具项目”有价值得多。2. 系统整体设计与架构选型2.1 需求分析与功能模块设计在动手写一行代码之前我们必须把业务逻辑理清楚。一个基础的学生信息管理系统通常包含以下核心模块用户认证与权限管理模块这是系统的门户。需要区分系统管理员、教师、学生三类角色。管理员拥有全部权限教师可以管理所授课程的学生成绩、查看学生信息学生只能查看和修改自己的基本信息、查询成绩。这里的设计直接关系到后续数据安全和操作逻辑。学生信息管理模块核心中的核心。包括学生学号、姓名、性别、出生日期、所属班级、联系方式、入学时间等字段的增删改查。这里要特别注意学号的唯一性校验和批量导入/导出功能。班级信息管理模块管理班级名称、所属专业、入学年份、辅导员等信息。学生信息与之关联构成了简单的层级关系。课程与成绩管理模块管理课程信息课程号、名称、学分、任课教师。成绩管理是难点它关联学生、课程、教师涉及成绩的录入、修改、查询和统计如平均分、排名。需要处理一个学生对应多门课程一门课程对应多个学生的多对多关系。数据报表与导出模块将学生列表、成绩单等信息导出为Excel或PDF格式这是非常实用的功能也是毕业设计中的亮点。设计思路我建议采用“自底向上”和“面向对象”的思想。先设计数据库表因为表结构是对业务最稳定的抽象。然后针对每个实体如Student、Course创建对应的JavaBean或POJO再围绕这些对象设计Service业务逻辑层和Controller控制层。界面则负责展示和收集数据。这就是经典的MVCModel-View-Controller模式它能有效分离关注点让代码结构清晰易于维护。2.2 技术栈选型与理由技术选型没有绝对的对错但有更适合场景和学习目的的方案。以下是几种常见组合及其考量方案A经典教学组合Servlet JSP JDBC MySQL优点最原始、最纯粹能让你深刻理解Web请求/响应生命周期、SQL编写、连接池等底层原理。很多高校教学仍以此为主。缺点开发效率低JSP中容易混杂大量Java代码脚本片段维护困难不适合大型项目。适用希望夯实基础、理解底层机制的同学。方案B主流企业级入门Spring Boot Spring MVC MyBatis Thymeleaf / Vue.js MySQL优点当前企业开发中最主流的轻量级组合。Spring Boot简化了配置Spring MVC提供了优雅的MVC支持MyBatis灵活且易于优化SQL前端模板Thymeleaf或前后端分离Vue.js可自由选择。生态丰富资料极多。缺点需要学习的框架较多入门有一定门槛。适用希望技术栈贴近当前企业需求为求职增加砝码的同学。这也是我最推荐的毕业设计选择。方案C全栈ORM方案Spring Boot Spring Data JPA Thymeleaf MySQL优点使用JPAHibernate实现几乎可以不写SQL通过操作对象来完成数据库交互开发速度最快。缺点对复杂查询的优化和控制力不如MyBatis性能调优需要更深厚的Hibernate功底。适用追求快速开发、业务逻辑不特别复杂的项目。我的选择与理由对于本次拆解我将以方案BSpring Boot MyBatis作为基础并采用前后端不严格分离的模式使用Thymeleaf模板引擎这样既能展示后端逻辑也能兼顾页面的动态渲染更符合大多数毕业设计的呈现要求。数据库选用MySQL 8.0。注意不要盲目追求最新最炫的技术。毕业设计的核心是展示你对一个完整系统的把控能力技术的稳定性、完整性和你理解的深度比“新”更重要。用Spring Boot 2.7 MyBatis 3.5完全足够且非常稳妥。3. 核心细节解析与实操要点3.1 数据库设计表结构与关系建模数据库设计是系统的基石设计不好后期编码会处处掣肘。我们至少需要以下核心表用户表 (sys_user)存储所有登录用户信息。CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL COMMENT 用户名/学号/工号, password VARCHAR(255) NOT NULL COMMENT 加密后的密码, real_name VARCHAR(50) NOT NULL COMMENT 真实姓名, user_type TINYINT NOT NULL COMMENT 用户类型0-管理员1-教师2-学生, email VARCHAR(100), status TINYINT DEFAULT 1 COMMENT 状态0-禁用1-启用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT系统用户表;要点username通常用学号或工号需唯一。password务必使用BCrypt等强哈希算法加密存储绝对禁止明文密码user_type字段用于权限判断。学生表 (student_info)扩展存储学生特有信息。CREATE TABLE student_info ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) UNIQUE NOT NULL COMMENT 学号, user_id INT UNIQUE NOT NULL COMMENT 关联用户ID, gender CHAR(1) COMMENT 性别M-男F-女, birthdate DATE, class_id INT COMMENT 所属班级ID, phone VARCHAR(20), admission_date DATE COMMENT 入学日期, FOREIGN KEY (user_id) REFERENCES sys_user(id), FOREIGN KEY (class_id) REFERENCES class_info(id) ) COMMENT学生信息表;要点通过user_id与sys_user表关联实现用户基础信息和学生扩展信息的分离。student_id学号业务唯一。班级表 (class_info)和课程表 (course)设计相对简单此处省略。关键是**成绩表 (score)**的设计CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL COMMENT 学生ID关联student_info.id, course_id INT NOT NULL COMMENT 课程ID, teacher_id INT COMMENT 录入成绩的教师ID关联sys_user.id, usual_score DECIMAL(5,2) COMMENT 平时成绩, final_score DECIMAL(5,2) COMMENT 期末成绩, total_score DECIMAL(5,2) GENERATED ALWAYS AS (usual_score * 0.3 final_score * 0.7) STORED COMMENT 总评成绩生成列, academic_year VARCHAR(9) NOT NULL COMMENT 学年如2023-2024, semester TINYINT NOT NULL COMMENT 学期1-第一学期2-第二学期, UNIQUE KEY uk_student_course (student_id, course_id, academic_year, semester), FOREIGN KEY (student_id) REFERENCES student_info(id), FOREIGN KEY (course_id) REFERENCES course(id), FOREIGN KEY (teacher_id) REFERENCES sys_user(id) ) COMMENT成绩表;核心技巧使用复合唯一约束uk_student_course防止同一个学生在同一学年学期下同一门课程被重复录入成绩。利用MySQL 5.7的生成列GENERATED COLUMN自动计算总评成绩如平时30%期末70%。这比在Java代码中计算更可靠、高效也保证了数据一致性。academic_year和semester字段对于成绩查询统计至关重要。3.2 权限控制拦截器与会话管理权限控制是后台管理系统的安全防线。一个简单的基于URL拦截的方案如下定义权限注解创建一个自定义注解如RequireRoles用来标记Controller方法需要的角色。Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequireRoles { int[] value() default {}; // 允许的角色类型数组如 {0, 1} 表示管理员和教师 }实现拦截器编写一个Spring MVC拦截器在请求到达Controller前进行校验。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 检查是否为HandlerMethod if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod (HandlerMethod) handler; // 2. 获取方法和类上的注解 RequireRoles methodAnnotation handlerMethod.getMethodAnnotation(RequireRoles.class); RequireRoles classAnnotation handlerMethod.getBeanType().getAnnotation(RequireRoles.class); RequireRoles annotation methodAnnotation ! null ? methodAnnotation : classAnnotation; // 3. 如果不需要权限直接放行 if (annotation null) { return true; } // 4. 从Session中获取当前登录用户 HttpSession session request.getSession(); User currentUser (User) session.getAttribute(currentUser); if (currentUser null) { // 未登录跳转到登录页 response.sendRedirect(request.getContextPath() /login); return false; } // 5. 检查用户角色是否在允许的范围内 int[] allowedRoles annotation.value(); if (Arrays.stream(allowedRoles).noneMatch(role - role currentUser.getUserType())) { // 权限不足返回错误页面或JSON response.sendError(HttpStatus.FORBIDDEN.value(), 权限不足); return false; } return true; } }实操心得用户登录成功后将其关键信息如id, username, userType封装成一个轻量级的User对象切勿存储整个实体对象或密码存入Session。拦截器每次从Session取出判断。注册拦截器并应用注解Configuration public class WebConfig implements WebMvcConfigurer { Autowired private AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/**) // 拦截所有路径 .excludePathPatterns(/login, /logout, /static/**); // 排除登录、静态资源等 } }在Controller上使用注解Controller RequestMapping(/admin/student) RequireRoles(0) // 仅管理员可访问此控制器下所有方法 public class StudentAdminController { GetMapping(/list) public String listStudents(Model model) { ... } PostMapping(/delete) ResponseBody RequireRoles(0) // 显式声明虽然类上已有这里更清晰 public Result deleteStudent(RequestParam Integer id) { ... } }踩坑提醒拦截路径配置要小心务必排除登录接口、静态资源CSS, JS, 图片、以及错误页面否则会导致重定向循环或资源无法加载。另外对于RESTful API权限不足时返回标准的HTTP状态码如403和JSON消息比跳转页面更合适。4. 关键功能实现与代码剖析4.1 学生信息分页查询与条件过滤这是后台管理最常见的功能。我们将使用MyBatis的动态SQL和PageHelper插件来实现。引入PageHelper依赖dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version最新版本/version /dependencyService层实现Service public class StudentService { Autowired private StudentMapper studentMapper; public PageInfoStudentVO getStudentsByPage(Integer pageNum, Integer pageSize, String studentId, String name, Integer classId) { // 使用PageHelper.startPage方法后第一个MyBatis查询会自动进行物理分页 PageHelper.startPage(pageNum, pageSize); // 执行查询此时查询语句会被自动修改加入LIMIT子句 ListStudentVO list studentMapper.selectByCondition(studentId, name, classId); // 用PageInfo包装结果包含总记录数、总页数、当前页等丰富信息 return new PageInfo(list); } }Mapper XML中的动态SQL!-- StudentMapper.xml -- select idselectByCondition resultTypecom.yourproject.vo.StudentVO SELECT s.id, s.student_id, s.gender, s.birthdate, s.phone, u.real_name, c.class_name FROM student_info s JOIN sys_user u ON s.user_id u.id LEFT JOIN class_info c ON s.class_id c.id where u.status 1 if teststudentId ! null and studentId ! AND s.student_id LIKE CONCAT(%, #{studentId}, %) /if if testname ! null and name ! AND u.real_name LIKE CONCAT(%, #{name}, %) /if if testclassId ! null AND s.class_id #{classId} /if /where ORDER BY s.id DESC /select要点where标签会智能处理AND关键字避免SQL语法错误。LIKE查询使用CONCAT是为了兼容不同数据库MySQL可以直接用%#{name}%但其他数据库可能不行。Controller层接收参数并返回GetMapping(/list) public String list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String studentId, RequestParam(required false) String name, Model model) { PageInfoStudentVO pageInfo studentService.getStudentsByPage(pageNum, pageSize, studentId, name, null); model.addAttribute(pageInfo, pageInfo); // 将查询条件回显到页面 model.addAttribute(queryStudentId, studentId); model.addAttribute(queryName, name); return admin/student_list; // 返回Thymeleaf模板视图 }4.2 成绩录入与批量处理成绩录入通常由教师操作涉及批量处理。前端可以通过表格提交多条成绩数据。前端表单设计使用动态表格每行是一个学生的成绩输入框。!-- 简化示例使用Thymeleaf语法 -- form action/teacher/score/batchSave methodpost input typehidden namecourseId th:value${course.id}/ input typehidden nameacademicYear value2023-2024/ input typehidden namesemester value1/ table tr th:eachstudent : ${studentList} td th:text${student.realName}/td td th:text${student.studentId}/td tdinput typenumber step0.1 namescores[${student.id}].usualScore//td tdinput typenumber step0.1 namescores[${student.id}].finalScore//td /tr /table button typesubmit批量保存/button /form后端接收与业务逻辑PostMapping(/batchSave) ResponseBody public Result batchSaveScores(RequestParam Integer courseId, RequestParam String academicYear, RequestParam Integer semester, RequestParam MapString, String scores) { // 接收Mapkey为studentIdvalue为成绩JSON字符串或对象 // 1. 数据校验非空、范围等 // 2. 获取当前登录教师ID Integer teacherId getCurrentTeacherId(); ListScore scoreList new ArrayList(); for (Map.EntryString, String entry : scores.entrySet()) { Integer studentId Integer.parseInt(entry.getKey()); Score score parseScoreFromString(entry.getValue()); // 解析成绩 score.setStudentId(studentId); score.setCourseId(courseId); score.setTeacherId(teacherId); score.setAcademicYear(academicYear); score.setSemester(semester); scoreList.add(score); } // 3. 调用Service进行批量保存或更新 scoreService.batchUpsert(scoreList); // Upsert: 存在则更新不存在则插入 return Result.success(成绩保存成功); }核心技巧这里使用batchUpsert而不是简单的insert是因为教师可能分多次录入或修改成绩。实现batchUpsert可以在MyBatis中使用foreach标签结合ON DUPLICATE KEY UPDATEMySQL或MERGE INTOOracle语句效率远高于逐条判断后执行insert或update。MyBatis批量Upsert示例insert idbatchUpsert INSERT INTO score (student_id, course_id, teacher_id, usual_score, final_score, academic_year, semester) VALUES foreach collectionlist itemitem separator, (#{item.studentId}, #{item.courseId}, #{item.teacherId}, #{item.usualScore}, #{item.finalScore}, #{item.academicYear}, #{item.semester}) /foreach ON DUPLICATE KEY UPDATE usual_score VALUES(usual_score), final_score VALUES(final_score), teacher_id VALUES(teacher_id) /insert要点ON DUPLICATE KEY UPDATE依赖于表上的唯一约束我们之前建立的uk_student_course。如果违反唯一约束即重复记录则执行更新操作。4.3 数据导出Excel报表生成使用Apache POI库生成Excel是Java Web中的常见需求。我们以导出学生列表为例。Service层生成Excel数据Service public class ExportService { public void exportStudentsToExcel(ListStudentVO studentList, HttpServletResponse response) throws IOException { // 1. 创建工作簿和工作表 Workbook workbook new XSSFWorkbook(); // .xlsx格式 Sheet sheet workbook.createSheet(学生信息); // 2. 创建标题行 Row headerRow sheet.createRow(0); String[] headers {学号, 姓名, 性别, 班级, 手机号, 入学日期}; for (int i 0; i headers.length; i) { Cell cell headerRow.createCell(i); cell.setCellValue(headers[i]); // 可以设置标题样式字体、背景色等 } // 3. 填充数据行 int rowNum 1; for (StudentVO student : studentList) { Row row sheet.createRow(rowNum); row.createCell(0).setCellValue(student.getStudentId()); row.createCell(1).setCellValue(student.getRealName()); row.createCell(2).setCellValue(M.equals(student.getGender()) ? 男 : 女); row.createCell(3).setCellValue(student.getClassName()); row.createCell(4).setCellValue(student.getPhone()); // 日期格式化 if (student.getAdmissionDate() ! null) { Cell dateCell row.createCell(5); dateCell.setCellValue(student.getAdmissionDate()); CellStyle dateStyle workbook.createCellStyle(); dateStyle.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat(yyyy-mm-dd)); dateCell.setCellStyle(dateStyle); } } // 4. 自动调整列宽按内容 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } // 5. 设置HTTP响应头触发浏览器下载 String fileName 学生列表_ System.currentTimeMillis() .xlsx; // 处理文件名中文乱码 fileName URLEncoder.encode(fileName, UTF-8).replaceAll(\\, %20); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filename*UTF-8 fileName); // 6. 输出流写入工作簿 ServletOutputStream out response.getOutputStream(); workbook.write(out); workbook.close(); out.flush(); } }Controller层调用GetMapping(/export) public void exportStudents(RequestParam(required false) String name, HttpServletResponse response) throws IOException { // 查询需要导出的数据这里简单起见查询所有 ListStudentVO list studentService.getAllStudentsForExport(name); exportService.exportStudentsToExcel(list, response); // 注意该方法返回void因为响应已由Service直接写入HttpServletResponse }重要提示导出数据量较大时直接查询所有数据到内存再生成Excel可能导致OOM内存溢出。务必分页查询或使用流式查询。例如可以使用MyBatis的Cursor进行流式读取或者分批次查询比如每次1000条并写入Excel的多个Sheet这是一个高级优化点在论文中可以着重阐述。5. 常见问题、调试技巧与项目部署5.1 开发环境常见问题排查数据库连接失败现象Communications link failure或Access denied for user。排查检查application.yml或application.properties中的数据库URL、用户名、密码。确认MySQL服务是否启动net start mysql。检查连接地址中的serverTimezone参数建议设置为Asia/Shanghai或UTC。如果是本地数据库检查是否允许远程连接毕业设计通常本地即可。MyBatis映射错误现象Invalid bound statement (not found)。排查Mapper接口的全限定名是否与XML中的namespace完全一致Mapper接口的方法名是否与XML中select的id一致Mapper XML文件是否被正确扫描检查Spring Boot配置mybatis.mapper-locations。使用Maven时确认XML文件是否在src/main/resources对应目录下且编译后是否被打包到target/classes。页面显示乱码现象中文变成问号???。排查数据库层面确保数据库、表、字段的字符集为utf8mb4。连接层面在JDBC URL中添加参数?characterEncodingutf8。服务端层面在Spring Boot配置中设置server.servlet.encoding.charsetUTF-8和forcetrue。前端层面确保HTML/JSP/Thymeleaf模板文件本身保存为UTF-8格式并在head中添加meta charsetUTF-8。事务不回滚现象方法抛出异常但数据库数据仍然被修改。排查是否在Service类或方法上添加了Transactional注解默认情况下Transactional只对RuntimeException和Error回滚。如果抛出的是Exception需要指定Transactional(rollbackFor Exception.class)。确保调用事务方法的是代理对象即通过Spring容器注入的Service而不是类内部的this.xxx()调用。5.2 项目打包与部署实战开发完成后你需要将项目打包并部署到服务器或本地演示。使用Spring Boot Maven插件打包build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration executabletrue/executable !-- 在Linux下可作为服务运行 -- /configuration /plugin /plugins /build在项目根目录执行命令mvn clean package -DskipTests。打包后会在target目录生成一个可执行的JAR文件如student-manage-0.0.1-SNAPSHOT.jar它内嵌了Tomcat服务器。部署到Linux服务器以CentOS为例将JAR包上传至服务器例如/home/app/目录。在服务器上安装Java运行环境JRE 8或11yum install java-11-openjdk。最简单的运行方式nohup java -jar student-manage-0.0.1-SNAPSHOT.jar --server.port8080 app.log 21 。这会在后台运行日志输出到app.log。更优方案配置为系统服务。创建服务文件/etc/systemd/system/student.service[Unit] DescriptionStudent Management System Afternetwork.target [Service] Typesimple Userappuser WorkingDirectory/home/app ExecStart/usr/bin/java -jar /home/app/student-manage-0.0.1-SNAPSHOT.jar Restarton-failure RestartSec10 [Install] WantedBymulti-user.target然后执行sudo systemctl daemon-reload sudo systemctl start student sudo systemctl enable student # 开机自启前端资源访问问题如果部署后CSS/JS/图片加载404检查Spring Boot中静态资源默认在classpath:/static/,classpath:/public/等目录。确保你的前端资源放在src/main/resources/static/下。如果使用了模板引擎如Thymeleaf页面跳转正常但静态资源丢失可能是Nginx等反向代理配置问题需要正确配置静态资源路径。5.3 论文撰写与源码整理要点毕业设计除了系统论文和源码也是重中之重。论文结构建议摘要浓缩精华讲清楚做了什么、用了什么技术、实现了什么功能、有什么特色。绪论项目背景、意义、国内外研究现状可以找几篇相关系统论文简述。相关技术介绍简要介绍Spring Boot、MyBatis、MySQL、Thymeleaf等切忌大段抄写官方文档结合你的系统说明为什么选它。系统分析可行性分析、需求分析功能模块图、用例图。系统设计这是核心。包括总体架构设计画个架构图、数据库设计ER图、详细的表结构说明、关键模块的详细设计可以画类图、时序图。系统实现配合核心代码截图讲解关键功能如登录验证、分页查询、成绩录入、数据导出是如何实现的。代码截图要清晰有注释。系统测试设计测试用例功能测试、性能测试附上测试结果截图如登录成功/失败、查询结果、导出文件。总结与展望总结完成的工作指出系统的不足如界面不够美观、未做缓存优化等并提出未来可改进的方向。源码整理删除所有无关文件如target/目录、.idea/、.vscode/、本地配置文件等。提供一个清晰的README.md写明项目名称、技术栈、如何导入Eclipse/IDEA、如何配置数据库、如何运行。确保数据库脚本schema.sql和data.sql干净、完整放在项目根目录或docs文件夹下。代码风格要统一关键方法要有注释。这是你的门面整洁的代码能给答辩老师留下好印象。从零开始实现这样一个系统你会遇到无数个小问题每一个问题的解决都是经验的积累。我的建议是先搭好框架Spring Initializr生成项目然后从数据库建表开始一个模块一个模块地攻克先完成后完善。遇到报错仔细阅读日志大部分问题都能通过搜索引擎找到答案。记住跑通整个流程比追求某个细节的完美更重要。当你看到浏览器里呈现出自己做的管理系统并能流畅地操作时那种成就感就是学习路上最好的回报。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价