ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

五周软件工程实习记录拆解:项目骨架与避坑清单

五周软件工程实习记录拆解:项目骨架与避坑清单

简介:这份资源是一份本科软件工程实习记录文档,面向需要撰写实习报告、实习日志的在校生以及刚进入软件开发岗位的初学者,可用于参考格式、内容组织与知识点梳理,也可直接在其基础上修改。压缩包内共1个doc文件,约40KB,按周次记录实习过程,内容从公司动员、分组分工到具体功能开发依次展开。文档结合具体日期记录了文件查找与排序功能实现、Swing界面设计、HTML与CSS页面布局、JavaScript动态交互、Node.js项目搭建、Express请求响应、MySQL数据导入与增删改查等实践环节,并穿插说明软件开发六大流程、项目经理职责、软件开发员与架构师等岗位分工以及前后端技术栈,使记录不止于流水账。目前已有2924人学习下载,适合作为毕业实习材料模板与开发流程入门参考使用。

1. 从一份五周实习记录里,能拆出多少可复用的东西

很多人拿到「软件工程实习记录」这类文档,第一反应是当成交作业的素材,扫两眼就丢进回收站。我最初也这么干过,直到自己带新人时被一个问题问住:一个只会写单文件 CRUD 的学生,进项目组第一个月到底会卡在哪?翻回这份五周的记录,答案其实写得很清楚——卡在环境、编码、端口、数据库连接这些「不是业务逻辑」的地方。这份资源是一份本科软件工程实习的逐日记录,从 Eclipse 里用 Swing 画查找面板,到 Express 接 MySQL 做增删改查,再到 Redhat 下交叉编译、IIS 权限、Tomcat 部署,五周时间把一条从桌面端到 Web 端、从开发到发布的链路走了一遍。它最适合两类人:正在准备实习记录、需要一份有真实技术细节的参照模板的同学;以及刚入行、想看看「一个功能从需求到上线到底要过几道坎」的初级开发。

2. 技术栈与目录结构:五周任务的模块化还原

这份记录的价值不在文笔,在于它无意中保留了一条完整的工程演进路径。第一周在 Eclipse 里做 Swing 桌面程序,第二周转到 Node.js + Express + MySQL 的 Web 单页,第三周补 Linux 与服务器运维,第四、五周用 JSP + Tomcat 做「网上书城」这类经典 JavaWeb 项目。三个阶段的技术栈差异很大,但如果按模块重新组织,会发现它其实是一份很标准的「从单体桌面应用到三层 Web 应用」的迁移样本。我在复现这份记录时,习惯把它拆成四个可独立运行的模块目录,每个目录对应记录里的一段任务,这样无论是要写实习报告还是真的要跑代码,都能一处一处对上。

2.1 四个模块与技术选型对照

记录里出现的工具有 Eclipse、Swing、Node.js、Express、MySQL、JSP、Tomcat、MyEclipse、Redhat Linux 9.0、IIS、SQL Server 2000。这些工具放在今天看有些年头,但选型逻辑依然成立:桌面端用 Swing 是因为要快速做出带输入框和按钮的本地面板;Web 端用 Express 是因为它把路由和请求处理压到十几行代码就能跑通;数据库选 MySQL 是因为安装门槛低、和 Node 的驱动生态成熟;最后部署用 Tomcat 是因为 JSP 项目天然跑在 Servlet 容器里。我在还原时把目录按下面的方式组织,每个模块都能单独启动,互不干扰。

software-practice/ ├── 01-swing-finder/ # 第一周:文件查找与排序(Swing 桌面) │ └── src/ │ ├── com/shixi/viwe/ # 视图层,记录里包名拼作 viwe,保留原样 │ └── com/shixi/main/ # 主程序入口 ├── 02-express-crud/ # 第二周:Express + MySQL 单页增删改查 │ ├── app.js │ ├── routes/ │ ├── public/ │ └── package.json ├── 03-linux-hello/ # 第三周:Linux 下的编译与 Makefile │ ├── hello.c │ └── Makefile └── 04-jsp-bookstore/ # 第四、五周:JSP 网上书城 ├── src/com/shop/control/ # 控制层 └── WebContent/ # JSP 页面与 web.xml

这个结构不是记录里原样给出的,而是我按任务边界重新划分的结果。记录中第一周和第四周都提到「新建 com.shixi.viwe 包」,包名拼写有误,我在还原时保留了原样,因为这类细节恰恰是实习记录的真实感来源,改成 view 反而丢掉了现场痕迹。每个模块对应一周左右的工时,读者可以按需取用:只想补实习记录的同学,直接看每个模块的业务描述和技术点;想真的跑起来的同学,从 02 和 04 入手,这两个模块的依赖最少、反馈最快。

2.2 模块之间的依赖关系与复用边界

四个模块表面独立,实际有三处隐性依赖值得先说清楚。第一处是数据库:02 模块用 MySQL 存学生信息,04 模块用 SQL Server 2000 存图书信息,记录里第三周还提到「jar 包和数据库版本不匹配导致 500 错误」,说明这两套数据源在实习环境里是并存的,还原时不要图省事统一成一个库,否则复现不出那个经典的连接失败场景。第二处是字符集:04 模块反复出现中文乱码,从录入到回显都有,根因是页面编码和数据库编码不一致,这在 02 模块里同样存在,只是记录里没写出来。第三处是端口:Tomcat 默认 8080 被占用后改成 89,这个细节在本地多服务并行时非常常见。

复用边界也要提前划好。01 模块的查找与排序逻辑是纯 Java SE,不依赖任何 Web 容器,可以单独抽成一个工具类给别的项目用;02 模块的 Express 路由写法可以迁移到任何 Node 项目,但它的静态数组回显方式只是过渡态,别照抄进生产代码;03 模块的 Makefile 是交叉编译的最小示例,换开发板时只需要改编译器前缀;04 模块的 control 层结构是典型的「一个功能一个类」,适合教学,不适合中大型项目。把这条边界想明白,后面读代码时就不会把教学代码当成工程范本。

3. 核心模块拆解:从文件查找到数据库增删改查的完整链路

这一段是整份记录里技术含量最密集的部分。第一周的 Swing 查找和排序,第二周的 Express 接口与 MySQL 读写,第四周的 JSP 控制层,三处代码覆盖了「本地程序 → 前后端通信 → 服务端渲染」三种形态。我在复现时把每处的关键代码都摘了出来,并在每段后面写清楚它在整个链路里承担什么角色、哪些参数是需要按环境改的、跑不通时先看哪里。需要提醒的是,记录里给出的代码是片段式的,下面这些是我按它的描述补全后的可运行版本,业务字段和表结构以记录里提到的编号、姓名、课程名、图书信息为准。

3.1 Swing 文件查找:模糊匹配与结果回显

记录里 8 月 28 日的任务是在输入框输入文件名或商品名,支持模糊查找并把结果回显到页面。实现上分视图和主程序两个包,视图负责面板布局,主程序负责扫描目录。模糊匹配我用的是文件名包含判断,没上正则,因为实习场景下用户输入的就是片段,包含判断足够且不会因为特殊字符炸掉。

// com/shixi/main/FileFinder.java public class FileFinder { // root 是查找根目录,keyword 是用户输入的关键词 public List<File> search(String root, String keyword) { List<File> result = new ArrayList<>(); File dir = new File(root); if (!dir.exists() || !dir.isDirectory()) { return result; // 目录不存在直接返回空,避免 NPE } File[] files = dir.listFiles(); if (files == null) return result; for (File f : files) { if (f.isDirectory()) { // 递归子目录,这是记录里"只能从固定地址查找"被老师修正的关键点 result.addAll(search(f.getAbsolutePath(), keyword)); } else if (f.getName().contains(keyword)) { result.add(f); } } return result; } }

这段代码的核心是递归。记录里 8 月 29 日明确写了「我做出的查找功能只能从固定的地址进行查找」,老师修改后才支持任意目录,改法就是把根目录从硬编码变成方法参数,并在遍历时对子目录递归调用。参数root建议由视图层的目录选择器传入,不要写死成D:/data这类路径,否则换台机器就跑不了。keyword为空时应当在校验层拦掉,否则contains("")恒为 true,会把整个磁盘的文件都列出来,这是新手最容易翻车的一处。结果回显交给 Swing 的JList或JTable,用DefaultListModel装File对象,重写toString只显示文件名,避免路径过长撑爆面板。

3.2 冒泡排序与排序字段的取舍

8 月 30 日的任务是按文件名排序,记录里指定用冒泡排序。教学场景下用冒泡没问题,但有两个参数要先定清楚:升序还是降序、比较时是否忽略大小写。我按文件名升序、忽略大小写来实现,比较用compareToIgnoreCase,这样Report.txt和report.txt会排在一起,符合用户直觉。

// 对查找结果按文件名排序,list 来自上一节的 search 结果 public void sortByName(List<File> list) { int n = list.size(); for (int i = 0; i < n - 1; i++) { boolean swapped = false; for (int j = 0; j < n - 1 - i; j++) { String a = list.get(j).getName(); String b = list.get(j + 1).getName(); if (a.compareToIgnoreCase(b) > 0) { Collections.swap(list, j, j + 1); swapped = true; } } if (!swapped) break; // 提前退出,已经有序就不用再扫 } }

swapped标志位是冒泡排序里最容易被省略又最有用的优化,某一轮没有发生交换说明剩余部分已经有序,直接跳出。时间复杂度仍是 O(n²),文件量上万时会明显卡顿,实际项目里我会换成list.sort(Comparator.comparing(File::getName, String.CASE_INSENSITIVE_ORDER)),但写实习记录时保留冒泡反而更贴合当时的水平。排序必须作用在查找结果上而不是全盘文件,顺序是先查后排,这一点在记录里没有明说,但页面逻辑决定了它只能是这个顺序。

3.3 Express 接口:从静态数组到 MySQL 查询

9 月 5 日的任务是实现页面数据的响应与请求,先用静态数组回显学生信息,9 月 6 日再接 MySQL。我在还原时把这个演进过程保留成两个路由,方便对照。第一个路由直接返回内存数组,第二个路由查库,接口路径保持一致,前端只需要改请求地址就能切换。

// app.js const express = require('express'); const mysql = require('mysql'); const app = express(); const pool = mysql.createPool({ host: '127.0.0.1', user: 'root', password: 'your_password', // 按本地环境改 database: 'practice', charset: 'utf8mb4', // 中文乱码的第一道防线 connectionLimit: 10 }); // 过渡态:静态数组回显,对应记录里 9 月 5 日的做法 app.get('/api/students/static', (req, res) => { res.json([ { id: 1, name: '张三', course: '软件工程' }, { id: 2, name: '李四', course: '数据库原理' } ]); }); // 正式态:查库回显,对应 9 月 6 日之后的实现 app.get('/api/students', (req, res) => { pool.query('SELECT id, name, course FROM student ORDER BY id', (err, rows) => { if (err) { console.error('查询失败', err); return res.status(500).json({ error: '数据加载失败' }); } res.json(rows); // mysql 驱动返回的就是对象数组,可直接序列化 }); }); app.listen(3000, () => console.log('listening on 3000'));

charset: 'utf8mb4'这一行是整个文件里最关键的位置。记录里 9 月 21 日写「中文信息不能成功录入数据库」,解决方式就是在连接代码处限定字符集,这里的utf8mb4比utf8更彻底,能兼容四字节字符。createPool而不是createConnection,是因为单页应用会频繁请求,连接池能避免反复握手。err一定要返回 500 并打日志,记录里 9 月 13 日的 500 错误就是「数据加载失败」被吞掉后排查了半天,日志写清楚能省很多事。前端用fetch('/api/students')拿到数组后渲染表格,这一步和 9 月 7 日的增删改查按钮挂钩,每个按钮对应一个POST或DELETE路由,SQL 语句在服务端拼装,不要把 SQL 写进前端 JS。

3.4 JSP 控制层:一个功能一个类的组织方式

9 月 25 日的任务是图书商城的删除和编辑修改,做法是先建两个 JSP 页面和对应的 control 类,首页上加按钮、设超链接跳转。这种「一个功能一个类」的结构很朴素,但把请求分发和业务处理分开了。下面是删除功能的控制类写法。

// com/shop/control/BookDeleteServlet.java public class BookDeleteServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String idStr = req.getParameter("id"); if (idStr == null || !idStr.matches("\\d+")) { resp.sendRedirect("list.jsp?msg=参数错误"); return; } int id = Integer.parseInt(idStr); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement( "DELETE FROM book WHERE id = ?")) { ps.setInt(1, id); ps.executeUpdate(); } catch (SQLException e) { log("删除图书失败 id=" + id, e); resp.sendRedirect("list.jsp?msg=删除失败"); return; } resp.sendRedirect("list.jsp"); // 重定向回列表页,刷新即生效 } }

PreparedStatement加占位符是必须的,记录里没提防注入,但用字符串拼 SQL 在删除场景下极其危险。id做了数字校验,避免id=1 or 1=1这类参数直接把整表删掉。删除后重定向而不是转发,是为了避免用户刷新页面时重复提交,这是「重定向后 GET」的常规做法。记录里 9 月 26 日提到用 JS 函数打开新窗口做编辑,比超链接体验好,原因也在这里:编辑完关窗刷新列表,不需要整页跳转。这里有一份踩坑血泪史值得提前说:JSP 页面的pageEncoding、响应的contentType、数据库的连接字符集,三处编码必须统一,任意一处不一致,回显时就是一片问号。

4. 把项目跑起来:三个环境的启动顺序与验证点

这份记录横跨桌面、Web、服务器三种环境,直接一把梭很容易在某个环节卡死又不知道卡在哪。我建议按「先本地 Web、再桌面、最后服务器」的顺序推进,每一步都有明确的验证点,通过了再往下走。下面给出的命令和配置都是最小可运行版本,路径和端口按自己机器改,记录里提到的软件版本较老,新版本安装时选项名称可能略有不同,但逻辑一致。

4.1 Node + MySQL 模块的启动与连通性检查

先装 Node 和 MySQL,然后在02-express-crud目录下初始化项目。记录里 9 月 4 日提到用npm init建项目,这里补上依赖安装和建表语句。

cd 02-express-crud npm init -y # 跳过交互,生成 package.json npm install express mysql # 记录里用的就是这两个包 mysql -u root -p < schema.sql # 先建库建表,再启动服务 node app.js # 看到 listening on 3000 即启动成功

建表语句要跟着写,否则查库接口会直接 500。

CREATE DATABASE practice DEFAULT CHARACTER SET utf8mb4; USE practice; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, course VARCHAR(100) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO student (name, course) VALUES ('张三', '软件工程'), ('李四', '数据库原理');

验证分三步:浏览器访问http://localhost:3000/api/students/static能看到两条 JSON,说明路由通;访问/api/students看到同样的两条,说明查库通;往表里插一条中文数据再访问,能看到中文而不是问号,说明字符集通。三步任何一步失败,先看 Node 控制台的报错,再看 MySQL 的SHOW VARIABLES LIKE 'character%',不要急着改代码。记录里 9 月 20 日数据库装不上,原因是安装时没选「开发者」选项,这类问题看安装向导的选项说明就能避开。

4.2 JSP 项目在 Tomcat 下的部署与端口调整

04-jsp-bookstore是标准的 JavaWeb 项目,需要 JDK、Tomcat 和 MyEclipse 或 IDEA。记录里 9 月 27 日用 MyEclipse 关联 Tomcat,9 月 28 日把项目移到webapps目录下启动,结果端口被占用。步骤我按现代工具重写,逻辑不变。

# 1. 编译打包,产物是一个 war 或整个目录 # 2. 部署到 Tomcat 的 webapps 下 cp -r 04-jsp-bookstore $TOMCAT_HOME/webapps/bookstore # 3. 改端口,记录里最后用的是 89 # 编辑 $TOMCAT_HOME/conf/server.xml,找到 Connector 标签 # <Connector port="89" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

验证点按顺序来:启动startup.sh或startup.bat后,控制台最后一行应出现Server startup in xxx ms;浏览器访问http://localhost:89/bookstore/index.jsp能出页面;进列表页能看到数据库里的图书;点删除按钮能删掉并回到列表。端口被占用的排查命令是netstat -ano | findstr :8080(Windows)或lsof -i:8080(Linux),找到 PID 后决定是杀掉还是换端口。记录里直接换成 89 就能跑,说明冲突源是另一个服务,不是自身配置错误,这个判断顺序很重要。

4.3 Linux 环境下的编译与开发板验证

第三周的03-linux-hello是交叉编译的最小闭环。记录里提到 Redhat Linux 9.0、ARM 开发库、Makefile、下载到开发板运行,还原时关键是三样东西:交叉编译器前缀、目标架构、烧录方式。

# Makefile:换开发板时只需要改 CC 这一行 CC = arm-linux-gcc TARGET = hello OBJS = hello.o $(TARGET): $(OBJS) $(CC) -o $(TARGET) $(OBJS) hello.o: hello.c $(CC) -c hello.c clean: rm -f $(TARGET) $(OBJS)

make之后用file hello检查产物架构,输出里应出现ARM字样,如果显示x86-64说明CC没生效,多半是环境变量里的gcc优先级更高。把产物通过串口或网络传到开发板执行,能看到hello world就说明工具链通了。这个环节最容易翻车的是开发板挂载和权限,不是编译本身,所以先验证编译产物、再验证传输通道、最后验证运行,一层层排除。

5. 避坑与排查:五周里反复出现的那几类问题

这份记录最值钱的地方,是它把当年踩过的坑一条条写进了日期里。我把这些坑归成五类,每条按「现象、原因、解决」写清楚,因为这些问题在今天的新项目里一模一样会复现,年份变了,坑没变。读这部分时建议对照前面章节的代码看,很多报错的根因就藏在那几行参数里。

5.1 中文乱码:编码三处必须对齐

现象是页面回显中文变成问号或方块,或者录入成功但查出来是乱码。原因是三处编码各自为政:JSP 的pageEncoding、响应的contentType、数据库连接的字符集。记录里 9 月 21 日和 9 月 26 日两次遇到,第一次在连接代码加限定字符集解决,第二次把数据库和页面都改成 gbk 解决。我的做法是统一成 utf8mb4,JSP 顶部写<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>,连接串带charset=utf8mb4,建库建表也指定 utf8mb4,三处一致就不会再出现。注意 gbk 能解决部分场景,但遇到生僻字和 emoji 会再次翻车,新项目别用它。

5.2 数据库连接失败:版本与驱动不匹配

现象是 JavaWeb 项目启动后页面 500,日志里写「数据加载失败」,改 Tomcat 配置怎么改都没用。原因是 JDBC 驱动 jar 包版本和数据库版本不匹配,记录里 9 月 13 日就是这么查出来的。解决方式是把驱动换成与数据库大版本对应的版本,MySQL 5.x 用 5.1.x 驱动,MySQL 8.x 用 8.x 驱动,连接 URL 也要跟着加时区和 SSL 参数。排查顺序是:先确认数据库本身能连上,再确认驱动 jar 在WEB-INF/lib下且没有重复版本,最后才怀疑代码。很多人一上来就改配置文件,方向就错了。

5.3 端口占用:启动失败先看日志

现象是 Tomcat 或 Node 启动时报Address already in use,或者启动看起来成功但访问不通。原因是端口被别的进程占了,记录里 9 月 28 日就是 8080 被占,换成 89 后正常。解决方式是用netstat或lsof找到占用进程,能杀就杀,不能杀就换端口,换端口后记得同步改前端请求地址和反向代理配置,否则前端还在打旧端口。这里有个玄学现象:明明改了口,浏览器还是访问旧地址,多半是缓存或者上一次的进程没退干净,ps里搜一下同名进程,确认只有一个在跑。

5.4 IIS 本地站点无法访问:权限与磁盘格式

现象是在 IIS 里配好默认网站主目录,浏览器输入地址却打不开。原因是本地虚拟访问需要给 Internet 来宾账户配读取权限,记录里 9 月 12 日就是查资料后发现要设来宾账户权限,且只有 NTFS 格式的磁盘才能在资源管理器里看到安全选项卡。解决方式是确认磁盘是 NTFS,给IUSR账户加读取权限,需要后台管理的目录再加修改权限。这条坑在今天用 Nginx 或 Tomcat 的人可能遇不到,但只要还在 Windows 上做本地站点,逻辑是一样的:先看服务是否在跑,再看目录权限,最后看防火墙。

5.5 重复提交与重复显示:先删后加不是最优解

现象是点添加按钮后同一条数据在页面上出现两次,刷新后更多。原因有两种:前端每次添加都往数组里 push 而没有清空,或者表单重复提交导致后端插了两条。记录里 9 月 3 日的做法是「添加信息前先删除原来的信息」,这能解决显示重复,但治标不治本,数据其实已经重复入库了。正确做法是前端每次先清空容器再渲染,后端在表单提交后重定向,或者给表单加一次性 token 防重。排查时打开浏览器网络面板看请求次数,一次请求说明是渲染问题,两次说明是提交问题,分清楚了再改。

6. 二次开发:把这份记录改成能用的项目骨架

这份记录本质上是一份过程文档,不是成品代码库,所以「二次开发」的重点不是加功能,而是把它变成一套能反复使用的脚手架。我做过一次改造,把四个模块抽成三个可复用的基础件:一个 Swing 的文件检索工具类、一个 Express + MySQL 的 CRUD 模板、一个 JSP 控制层的基类。改造过程中最值得说的是参数化,原始记录里大量路径、端口、账号是硬编码的,抽出来之后换环境只需要改一个配置文件。比如数据库连接那段,我把它挪到config/db.js,用环境变量覆盖默认值。

// config/db.js module.exports = { host: process.env.DB_HOST || '127.0.0.1', user: process.env.DB_USER || 'root', password: process.env.DB_PASS || '', database: process.env.DB_NAME || 'practice', charset: 'utf8mb4', connectionLimit: 10 };

这样本地开发、测试环境、演示环境可以共用一套代码,只换.env。Swing 那边同理,把查找根目录和排序字段做成构造参数,视图层通过下拉框选择。JSP 的 control 层可以抽一个BaseServlet处理公共的编码设置、参数校验和异常跳转,每个具体功能只写 SQL 和业务判断,代码量能砍掉一半。验证改造是否成功的方法很直接:把项目拷到另一台机器上,不改任何一行代码,只配环境变量,能跑通就算过。我一般会额外检查三个点:中文能不能正常存取、删除操作有没有防注入、端口冲突时日志里能不能一眼看出原因。

还有一个技巧值得单独说:给每个模块加一个README.md,把启动命令、依赖版本、已知问题写进去,尤其是「数据库安装时要选开发者选项」这种一句话能省半天时间的提示。原始的实习记录是按日期流水写的,信息密度高但检索困难,按模块重组后再配索引,它的价值会从「交作业的材料」变成「新人上手手册」。从那以后我每次整理实践记录,都强制走一遍「按模块拆目录、按环境抽配置、按坑位写排查」这三步,不然过两个月自己都看不懂当时为什么那么改。希望这份拆解能帮到你,把手里那份看起来只是流水账的记录,变成真正能跑起来、能改得动的东西。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进