
简介《软件工程实习记录》是一份面向本科软件工程专业学生、尤其是需完成实习报告或毕业实习记录的学习者整理的文档型参考资料可用于了解企业实习的组织方式、每日记录写法与软件开发全流程。压缩包内共1个doc文件约40KB按周记录实习过程涵盖可行性分析、需求分析、概要设计、详细设计、编码与项目验收等环节并穿插项目经理职责、开发岗位分工及技术栈说明。内容预览还涉及Eclipse Swing界面、HTML/CSS/JavaScript页面、Node.js与Express、MySQL增删改查、Linux与ARM开发环境等实践场景便于读者参考记录结构、补充知识点并在此基础上修改成自己的实习材料。已有2924人学习适合作为实习记录撰写与软件开发流程梳理的对照样本。1. 从一份 25 天的开发日志说起拿到《软件工程实习记录》这份文档的人多半不是想读故事而是想找一份能照着改的实习日志底稿。它的价值不在文笔而在于它是一份按天推进、真名实姓的软件工程实习记录五周时间从可行性分析讲到项目验收从 Swing 桌面工具写到 Node.js MySQL 的网页增删改查中间还夹着 Linux 交叉编译、IIS 站点权限、Tomcat 端口占用这些只有真上手才会撞见的事。里面那句“代码实现只占整个开发过程的百分之十五”是我见过对这个行业最直白的注解。它适合三类人要交实习材料的在校生、刚进项目组想理清流程的新人、想拿一个完整小项目练手的开发者。下面按模块拆开能跑的地方给命令能改的地方给位置。2. 骨架拆解五周任务线怎么对上六大开发流程2.1 五周任务线的真实分布这份记录不是按“知识点”组织的而是按周组织每周一条主线越往后越接近一个完整项目。第一周是认知周前半段听流程、分小组后半段直接上 Eclipse 写 Swing 面板第二周从静态网页切到 Node.js把 Express 和 MySQL 串起来第三周开始碰真实环境Linux、ARM 开发板、IIS、Tomcat 轮着来第四周补框架理论Struts、Spring、Hibernate 各讲一层同时开始搭“某网上书城项目”的模型第五周做完整的增删改查和部署最后一天卡在端口占用上。把它抽成一张表会更清楚这也是我拿到任何一份项目日志时做的第一件事——先看时间轴上每个阶段交付了什么而不是看它写了多少字。周次主线任务涉及技术当周交付物第一周流程认知 桌面工具Java Swing、Eclipse、HTML/CSS/JS文件查找与排序面板、静态教师信息页第二周前后端打通Node.js、Express、MySQL、JSON可增删改查的单页第三周真实运行环境企业级 Linux 9.0、ARM 交叉工具链、IIS、Tomcathello 程序上板、本地站点可访问第四周框架与建模Struts、Spring、Hibernate、JSP书城项目需求模型、信息录入功能第五周功能补全与上线JSP、Tomcat、端口配置部署完成的完整项目这张表能解释一个很多人忽略的问题为什么日志里第一周还在写 Swing第二周突然跳到 Node.js。不是作者东一榔头西一棒子而是公司的任务安排本身就是“先让你把界面和逻辑摸熟再让你理解数据从哪来、到哪去”。桌面端和 Web 端在这份记录里不是两个项目是同一件事的两个侧面——都是“输入条件、查数据、回显结果”只是承载它的壳子换了。提示如果你要拿这份记录改自己的实习日志别照抄任务顺序。先确定你所在岗位的真实主线前端、后端还是实施再按那条线重排周次否则读起来会像拼凑。2.2 六大流程在日志里的落点文档开头列了软件开发的六个步骤可行性分析、需求分析、概要设计、详细设计、编码、项目验收。很多人写实习记录时会把这段当“帽子”扣在开头后面该写啥写啥流程和正文两张皮。这份记录好在哪它后面每一周的任务其实都能对应回这六步里的某一步只是作者当时没意识到。可行性分析落在第一天的动员会上——公司介绍业务范围、说明项目类型本质上是在判断“这个需求我们接不接、用什么技术接”。需求分析的影子在第四周最明显一群人围在一起讨论书城系统要哪些功能图书信息录入怎么设计字段。概要设计和详细设计被压缩了表现出来就是“建模型”那两天先画功能结构再定页面和数据库表的对应关系。编码占了篇幅的大头这是必然的因为这是唯一能截图、能跑出结果的部分。项目验收则是第五周的部署把本地能跑的东西搬到服务器上让别人也能访问。这里有个反直觉的点值得展开日志里花最多笔墨的是编码但作者自己在第一天就写了“代码实现只占百分之十五”。这不是自相矛盾而是新手视角和项目视角的差别。新手看代码因为代码是唯一他能完全掌控的部分项目看流程因为流程决定这个代码最终会不会被验收、会不会被返工。我在带人的时候经常说你写的第一版代码能不能活下来八成不取决于你写得多漂亮而取决于需求阶段有没有人把“模糊查找”这四个字定义清楚——是匹配文件名还是匹配全路径还是匹配文件内容。所以读这份记录建议倒着读一遍先看第五周部署时出了什么问题再回头看第二周写代码时有没有为这些问题埋下伏笔。你会发现端口号写死、字符集没统一、路径硬编码这些坑在写代码那天就已经挖好了只是到部署那天才掉进去。2.3 工具与环境清单从 Eclipse 到 ARM 交叉工具链把日志里出现的工具按用途归一下类方便你复现时一次性把环境备齐。桌面端用 EclipseWeb 端用 MyEclipse这两者在当年是标配现在用 IntelliJ IDEA 或 VS Code 加插件也能替代只是 Swing 的可视化面板设计器在新 IDE 里体验差一些常见做法还是拿 Eclipse 的 WindowBuilder 拖控件。数据库选 MySQL辅助包用 node 侧的 mysql 驱动Web 容器用 Tomcat另外还碰了 IIS因为公司网站那台机器是 Windows 环境。第三周的 Linux 部分值得单独说因为它是整份记录里唯一涉及嵌入式的段落。任务是写一个 hello.c配一个 Makefile用交叉编译工具链编出能在 ARM 开发板上跑的二进制。这条链路新手容易卡在“编出来了但跑不了”原因通常是编译器选错——宿主机上的 gcc 编出来的是 x86 指令开发板认不了。常见做法是显式指定交叉编译器前缀把所有编译动作收进 Makefile避免手敲命令时漏参数。# 交叉编译 hello.c产物给 ARM 开发板用 CC arm-linux-gcc TARGET hello OBJ hello.o $(TARGET): $(OBJ) $(CC) -o $(TARGET) $(OBJ) $(OBJ): hello.c $(CC) -c hello.c clean: rm -f $(TARGET) $(OBJ)这段 Makefile 的逻辑很直白CC决定用哪个编译器改这一行就能在宿主编译和交叉编译之间切换$(TARGET): $(OBJ)说明可执行文件依赖目标文件make 会自动判断谁新谁旧只重编改过的部分clean是后悔药产物出问题时先make clean再make比对着报错猜半天快。参数上唯一要注意的是arm-linux-gcc这个名字跟你实际装的工具链前缀必须一致有的环境叫arm-none-linux-gnueabi-gcc抄之前先ls /usr/local/bin | grep gcc看一眼。编译完用file hello验证输出里带“ARM”字样才算对如果显示的是“x86-64”说明 Makefile 里的CC没生效多半是终端里还有环境变量覆盖了它。这一步验证花十秒能省掉后面把程序拷到板子上跑不起来、又回头怀疑板子坏了的半小时。3. 桌面端模块Swing 文件查找与冒泡排序的两处硬伤3.1 com.shixi.viwe 与 com.shixi.main 的分包逻辑日志里出现的包名是com.shixi.viwe这个拼写是view少写了一个字母属于典型的手快错误。我没打算替他美化反而觉得这个细节值得留着——真实项目里的包名、变量名多多少少带点这种痕迹你二次开发时第一件事往往不是读逻辑而是先把这些拼写统一掉否则后面 grep 都搜不全。分包思路本身是对的视图放view包主程序和运行逻辑放main包。这是最朴素的 MVC 雏形虽然只有 V 和入口没有独立的 Controller 和 Model。放在当年那个练手规模下够用但如果要扩展建议再拆两个包service放查找、排序这些与界面无关的逻辑model放文件条目这类数据结构。拆完之后视图只负责“拿输入、显示结果”逻辑可以单独写单元测试不用每次启动界面点按钮验证。具体到代码组织视图类的职责应该收敛到三件事摆放组件、绑定事件、把结果刷到列表上。很多新手会把扫描磁盘的循环直接写在按钮的actionPerformed里结果界面点一下卡三秒因为 Swing 的事件分发线程被阻塞了。常见做法是把耗时操作丢到SwingWorker的doInBackground里做完再回主线程更新组件。这一步在文件数量少的时候感觉不出来一旦目录深一点、文件多几千个界面假死就是必然的。3.2 模糊查找从输入框到结果回显的链路这个功能的需求一句话能说完输入框里敲文件名或者商品名的一部分点查找把匹配到的结果列出来。难点不在匹配在“从哪儿找”和“怎么找得快而不卡”。先看视图层的骨架这段是日志里描述过的面板结构我按常见写法补全// com.shixi.view.FindPanel public class FindPanel extends JPanel { private final JTextField keywordField new JTextField(20); private final JButton searchBtn new JButton(查找); private final DefaultListModelString model new DefaultListModel(); private final JListString resultList new JList(model); private final JLabel statusLabel new JLabel(就绪); public FindPanel() { setLayout(new BorderLayout(8, 8)); JPanel top new JPanel(new FlowLayout(FlowLayout.LEFT)); top.add(new JLabel(关键字)); top.add(keywordField); top.add(searchBtn); add(top, BorderLayout.NORTH); add(new JScrollPane(resultList), BorderLayout.CENTER); add(statusLabel, BorderLayout.SOUTH); searchBtn.addActionListener(e - startSearch()); } }keywordField是输入框searchBtn触发查找DefaultListModel是列表的数据源往 model 里加元素界面自动刷新不用手动调repaint。BorderLayout把界面切成上中下三块中间的列表套一层JScrollPane结果多了能滚动。这些参数没有玄学照抄能跑唯一要注意的是FlowLayout的对齐方式默认居中改成LEFT视觉上更自然。真正干活的是扫描逻辑// 递归扫描目录返回文件名包含关键字的文件 private ListFile scan(File dir, String keyword) { ListFile hits new ArrayList(); File[] files dir.listFiles(); if (files null) return hits; // 目录无权限或不存在直接返回空 for (File f : files) { if (f.isDirectory()) { hits.addAll(scan(f, keyword)); // 子目录递归下去 } else if (f.getName().toLowerCase() .contains(keyword.toLowerCase())) { hits.add(f); // 模糊匹配包含即命中 } } return hits; }listFiles()返回 null 的情况必须处理这是新手最容易漏的一行——遇到系统目录或者被占用的目录不判空直接遍历就是空指针。toLowerCase()两边都转是为了让匹配不区分大小写用户输入“report”也能查到“Report.docx”。这里的模糊匹配用的是contains属于子串匹配够用且好理解如果需要通配符比如*.txt就得换成PathMatcher复杂度上一个台阶。回显部分把File转成字符串塞进 model 就行但建议只显示文件名和大小不显示完整路径否则列表横向拉得老长。完整路径放在选中项的详情区或者双击打开时再用。3.3 冒泡排序按文件名排能跑但要知道代价日志里写得很清楚排序依赖文件名算法用冒泡排序。这个选择在教学场景下完全合理因为冒泡是最好讲清楚的排序但放到真实目录扫描的结果上就得知道它的边界在哪。// 冒泡排序按文件名升序带提前退出优化 public static void bubbleSort(File[] arr) { for (int i 0; i arr.length - 1; i) { boolean swapped false; for (int j 0; j arr.length - 1 - i; j) { if (arr[j].getName().compareToIgnoreCase(arr[j 1].getName()) 0) { File tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) break; // 本轮没交换说明已有序提前收工 } }外层循环控制趟数内层循环做相邻比较和交换arr.length - 1 - i是因为每趟结束最大的元素已经沉到末尾不用再比。swapped这个标志是性价比最高的一处优化对近乎有序的数据第一趟跑完就退出复杂度从 O(n²) 退到 O(n)。比较用compareToIgnoreCase保证大小写混排时顺序稳定。代价在哪冒泡的时间复杂度是 O(n²)一千个文件大概百万次比较用户感觉不出来一万个文件就是一亿次界面会明显卡顿。这时候正确做法不是优化冒泡是直接换Arrays.sort(arr, Comparator.comparing(File::getName, String.CASE_INSENSITIVE_ORDER))底层是归并或快排几行代码解决。所以我的建议是冒泡留着但要在注释里写明“仅适用于小数据量”别让后面接手的人以为这是深思熟虑的选择。排序和查找还有个顺序问题。日志里的做法是先查再排这是对的——先缩小结果集再排序比全盘排序后再过滤省太多。如果反过来每次输入关键字都要重排整个磁盘的文件列表体验会很差。3.4 路径写死那次翻车改成可选目录日志里第二天写完查找功能第三天就发现“只能从固定的地址进行查找”去问了老师才改好。这个坑太典型了几乎每个第一次写文件工具的人都会踩扫描的根目录直接写死在代码里比如new File(D:/data)换台机器就废。改法不复杂加一个目录选择按钮把路径变成运行时可变的参数// 选择扫描根目录替代硬编码路径 private File rootDir new File(System.getProperty(user.home)); private void chooseDir() { JFileChooser chooser new JFileChooser(rootDir); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); // 只让选目录 if (chooser.showOpenDialog(this) JFileChooser.APPROVE_OPTION) { rootDir chooser.getSelectedFile(); statusLabel.setText(当前目录 rootDir.getAbsolutePath()); } }System.getProperty(user.home)取当前用户主目录作为默认值跨平台比写D:/稳。DIRECTORIES_ONLY限制只能选目录避免用户选了个文件导致listFiles()返回 null。选中后立刻更新状态栏让用户知道当前扫的是哪这是个小细节但能省掉“为什么搜不到我桌上的文件”这类追问。真正值得记的不是这段代码而是发现问题的路径功能写完先自己点一遍换个目录再点一遍发现结果没变才意识到路径写死了。这个自测习惯比任何调试技巧都管用我到现在还保持着“每个带路径的功能至少换两个目录试”的规矩。4. Web 端链路静态表格怎么长成 Express MySQL 的增删改查4.1 第一版静态页数组渲染与重复显示的修复第一版页面最简单教师信息存在数组里用表格显示出来。这一步的目的是先把“数据长什么样、页面怎么摆”定下来不涉及网络请求。日志里第二周第一天做的就是这个结构大致是页面顶部三个输入框编号、姓名、课程名一个添加按钮下面一张表。问题出在添加功能上——点一次按钮数据追加一次但页面没刷新干净旧数据和新数据叠在一起显示。日志里的解决方法是“添加信息前先删除原来的信息”翻译成代码就是渲染前清空容器// 先清空 tbody再整体重绘避免重复追加 function renderTeachers(list) { const tbody document.querySelector(#teacherTable tbody); tbody.innerHTML ; // 关键清空旧行 list.forEach(t { const tr document.createElement(tr); tr.innerHTML td${t.id}/tdtd${t.name}/tdtd${t.course}/td; tbody.appendChild(tr); }); }innerHTML 是清空最直接的方式代价是丢掉原有 DOM 节点上的事件绑定所以如果表格行上有删除按钮得用事件委托挂在tbody上而不是逐行绑定。这个细节在静态数据阶段无所谓一旦接上数据库做增删改查就会变成“删了第一行第二行的按钮失灵”这种诡异 bug。顺带说一句用模板字符串拼 HTML 在这里没问题但如果数据来自用户输入就得防注入——td${t.name}/td里如果 name 是script页面就中招了。常见做法是用textContent赋值或者引入转义函数。这是静态页升级到真实项目时最容易漏的一环。4.2 Express 接管请求get 与 post 的分工静态页只能自己跟自己玩要连数据库就得有个服务端。日志里选了 Node.js Express思路是先跑通请求响应再接数据库。这一步的学习曲线主要在“前后端怎么约定数据格式”代码本身不长。const express require(express); const app express(); app.use(express.json()); // 解析 post 提交的 json 体 // 查询get 带查询参数返回列表 app.get(/api/teachers, async (req, res) { const keyword req.query.kw || ; // 可选关键字空串表示全部 const [rows] await pool.query( SELECT id, name, course FROM teacher WHERE name LIKE ?, [%${keyword}%] ); res.json(rows); }); // 新增post 提交完整对象 app.post(/api/teachers, async (req, res) { const { id, name, course } req.body; await pool.execute( INSERT INTO teacher(id, name, course) VALUES (?, ?, ?), [id, name, course] ); res.json({ ok: true }); }); app.listen(3000, () console.log(listening on 3000));express.json()这行不能省省了req.body就是 undefined前端 post 过去的数据全丢。get 用req.query取 URL 上的参数post 用req.body取请求体这是两者的本质分工get 适合查询参数可见、可缓存、可收藏post 适合写入参数在体里长度不受 URL 限制。SQL 里用?占位符而不是字符串拼接这是硬性要求不是风格问题。拼接写法遇到名字里带单引号就会报错更严重的是给注入留了门。pool.query和pool.execute的区别在于后者走预编译参数类型处理更稳写入场景优先用 execute。4.3 接上 MySQL辅助包、连接池与 JSON 回显日志里写“需要安装 node 对 MySQL 数据库的辅助包再将读出的数据用 json 工具进行格式转化”。这一步的坑集中在一处连上了但读出来是乱码或者读出来是 Buffer 不是字符串。const mysql require(mysql2/promise); const pool mysql.createPool({ host: 127.0.0.1, port: 3306, user: root, password: ******, database: school, charset: utf8mb4, // 连接层字符集必须和表、页面一致 connectionLimit: 10 // 连接池上限按并发量调 });mysql2/promise是带 Promise 的版本能直接await不用回调套回调。charset这一项是中文乱码的高发区数据库表是 utf8mb4连接层写 utf8某些字符就会在传输环节被截断或替换。三个地方——建库建表、连接配置、页面 meta——字符集必须统一缺一个都可能出问题。connectionLimit控制连接池大小写 10 对练手项目绰绰有余。设太大数据库端会拒绝设太小高并发时请求排队。这个值不是拍脑袋定的要看你数据库的max_connections和实际压测结果。查询结果rows直接res.json(rows)就能回给前端Express 会自动序列化。要注意的是 MySQL 里的DECIMAL、BIGINT在某些驱动版本下会返回字符串前端做数值计算前先Number()转一下否则会出现“1 1 11”这种哭笑不得的结果。4.4 增删改查的按钮链路一次点击背后发生了什么日志里第五周的核心是把四个操作补齐实现方式是“点击按钮激活不同的 js 函数由 js 函数执行不同的 SQL”。这个描述略粗实际链路是四层按钮 → 事件处理函数 → fetch 请求 → Express 路由 → SQL。中间任何一层断了表现都是“点了没反应”所以排查时要从后往前看。操作前端触发请求方式服务端 SQL查询页面加载 / 搜索框回车GET /api/teachers?kwSELECT ... WHERE name LIKE ?新增点“添加”按钮POST /api/teachersINSERT INTO teacher ...修改点行内“编辑”后保存PUT /api/teachers/:idUPDATE teacher SET ... WHERE id?删除点行内“删除”DELETE /api/teachers/:idDELETE FROM teacher WHERE id?删除按钮的绑定方式值得单独讲。如果每渲染一行就addEventListener一次行被删掉再重绘旧监听器不会自动回收次数多了内存就上去了。稳妥写法是事件委托// 事件委托只在 tbody 上绑一次动态行也能响应 document.querySelector(#teacherTable tbody) .addEventListener(click, async (e) { if (!e.target.matches(.btn-del)) return; // 只处理删除按钮 const id e.target.dataset.id; // 行上带>// check-records.js校验每篇记录字段是否齐全 const fs require(fs); const required [日期, 任务, 技术点, 问题, 解决]; fs.readdirSync(./records).forEach(file { const text fs.readFileSync(./records/${file}, utf8); const missing required.filter(k !text.includes(k)); if (missing.length) console.warn(${file} 缺字段${missing.join(、)}); });readdirSync遍历目录includes做粗粒度检查够用。真要严格可以改成解析 Markdown 的二级标题但练手阶段没必要。跑一遍缺字段的文件一目了然比人工翻快得多。第二件是把技术点从叙述里抽出来单独维护一张问题索引表。日志里那些坑——字符集、端口、驱动版本、目录权限——本质上是同一类问题的不同表现抽成表之后下次遇到“页面能开但没数据”直接按表里的排查顺序走不用重新想。症状优先怀疑验证命令 / 动作页面空白但状态 200前端请求地址写错浏览器 Network 面板看请求 URL中文变问号字符集三处不一致SHOW VARIABLES、看页面 meta启动报端口占用旧进程未退出netstat -ano 查 PID500 且日志含糊驱动版本不匹配核对数据库版本与 jar 版本本地站点访问不到目录权限 / 磁盘格式查 NTFS、加来宾账户读权限改造的时候还有个容易忽略的点日志里所有涉及具体环境的信息比如机器名、内网地址、账号二次使用时都要替换掉。这份记录里已经做了模糊处理只保留技术事实这个尺度是对的——保留“端口被占用”这件事删掉“哪台机器上的哪个端口”信息量不减风险归零。我自己的习惯是每份要交出去的技术记录写完先放两天再以“完全没参与过这个项目的人”的视角读一遍凡是读不懂、需要猜的地方就是需要补的地方。这份实习记录里有几处就是这样的提到“老师修改后成功实现”但没说改了什么提到“配置好端口和 IP”但没写具体配在哪。这些位置如果补上一两句整份材料的可复用性会上一个台阶。从那以后我每次整理项目记录都强制走一遍“字段校验 问题索引”这两步宁可多花半小时也不让文档变成只有作者自己能看懂的流水账。希望帮到你。本文还有配套的精品资源点击获取