暑假 Java 后端实习总结:两个月里我做了什么,又学到了什么
本文记录我的一次 Java 后端实习经历。出于一些考虑,文中的产品名称、业务数据、表名和代码都做了一些简化。
前言
暑假,我在一家武汉小规模的互联网公司实习了两个月,岗位是 Java 后端开发实习生。
这次实习所在的团队并不大,技术部前后端+测试一共9个人,项目也不是什么"日活千万、每秒几万请求"的大型系统,就是一个有真实用户、已经运行了一段时间的社区产品,每日活跃用户大概1w。
不过,对当时的我来说,它依然比学校里做过的项目复杂很多。
学校项目一般是自己从头开始搭,数据库表、接口和代码结构都比较清楚。公司项目完全不一样,代码量比较大,有不少历史逻辑,同一个字段可能在用户端、运营后台和定时任务里都被修改。
有时候需求看上去只需要改几行代码,真正开始做之后,才发现它还会影响缓存、统计、通知、审核记录和旧数据。
实习期间,我参与了帖子、评论、角色、头像、内容审核和群聊机器人等模块的开发,也处理了一些测试环境和线上环境的问题。
这篇文章不会按照工作日报逐条罗列,而是挑几个我印象比较深的需求和问题,聊一聊自己当时是怎么做的,以及后来有哪些新的理解。
一、项目大概是什么样的
我参与的是一个二次元为主的兴趣社区项目。
用户可以使用不同的虚拟角色发帖、评论、加入群聊,也可以申请新的角色和上传角色头像。运营人员则通过管理后台审核帖子、评论、角色和头像,同时管理群聊机器人。
项目主要使用:
- Java 8
- Spring Boot
- MongoDB
- MySQL
- Redis
- Redisson
- MyBatis-Plus
- CompletableFuture
- JUnit 5
项目整体上是一个 Maven 多模块单体。
帖子、评论、角色、头像和群组等数据主要放在 MongoDB 中。
系统配置、机器人配置和部分统计记录放在 MySQL 中。
Redis 主要用于缓存、登录状态、频率限制和分布式锁。
这些技术我之前在学校项目里多多少少接触过一些,但那时候基本只是"会用"。真正进入一个已经运行的项目后,我才慢慢理解为什么要这样用,以及使用之后还会带来哪些问题。
二、刚接手项目时,我有点不知道从哪里开始
实习第一周比较直接的感受就是:代码太多了,我主要看的部分有admin的前后端代码,以及app的后端代码。
在自己学习做的项目里,一个请求通常就是:
text
Controller
↓
Service
↓
Mapper
↓
MySQL
但公司项目中,一个帖子接口可能同时涉及:
- 帖子本身;
- 发布角色;
- 星球信息;
- 话题;
- 评论数;
- 点赞状态;
- 好友关系;
- 审核状态;
- 缓存;
- 消息通知;
- 成就系统。
一开始我接到需求后,会直接搜索接口名称,然后从 Controller 往下看。
后来发现,只看接口调用链还不够。因为有些状态不只在这个接口里修改,运营后台、定时任务和异步线程也可能修改同一条数据。
后面我逐渐形成了一个比较固定的习惯:
- 先找到接口入口;
- 看主要 Service;
- 找到对应实体和数据库字段;
- 全局搜索关键字段的所有读写位置;
- 查看相关代码的历史提交;
- 列出这个需求可能影响的其他模块;
- 最后再开始修改。
这个过程看起来有点慢,但是借用ai去做效率也是不低的,而且很大程度上降低了ai乱改动代码的风险,比改完之后不断补 Bug 要好一些。
三、第一次真正注意到"异步结果可能已经过期"
实习期间,我参与比较多的是 AI 内容审核。
帖子、评论、角色和头像提交后,系统会调用外部 AI 服务进行审核。因为 AI 接口响应比较慢,所以部分审核任务是异步执行的。
流程大概是:
text
用户提交内容
↓
后端先保存数据
↓
状态设置为"审核中"
↓
把 AI 审核任务放进线程池
↓
接口先返回给用户
↓
AI 返回结果后再更新数据库
一开始我觉得这个流程比较简单,无非就是调用接口、解析 JSON,然后把结果写回数据库。
后来测试时出现了一个问题:运营人员已经在后台完成人工审核,但过了一会儿,状态又被 AI 修改了。
3.1 问题是怎么产生的
可能的执行顺序是:
text
用户提交申请
↓
AI 开始审核
↓
运营人员先人工审核通过
↓
AI 结果稍后返回
↓
AI 把状态重新修改
原来的逻辑大概相当于:
java
AuditRecord record = findById(recordId);
record.setAiAuditResult(aiResult);
record.setStatus(targetStatus);
update(record);
只要这条数据还存在,AI 就会继续更新,并不知道人工已经处理过了。
我最开始想到的办法也是先查一下状态:
java
if (record.getStatus() == AUDITING) {
updateAiResult(record);
}
但后来继续分析发现,这样其实还是不安全。
因为"查询"和"更新"是两步操作。
可能在线程查询到"审核中"之后,人工审核刚好完成,接着 AI 线程仍然会继续更新。
3.2 把判断条件放进数据库更新里
最后采用的思路是:不要先查询再判断,而是在更新数据库时直接带上条件。
简化后的写法类似:
java
Query query = new Query();
query.addCriteria(
Criteria.where("_id").is(recordId)
.and("status").is(AUDITING)
.and("audit_time").is(null)
);
Update update = new Update()
.set("ai_audit_status", aiResult)
.set("ai_audit_reason", aiReason)
.set("status", targetStatus);
long updatedCount = updateFirst(query, update);
只有状态仍然是"审核中",并且人工审核时间还是空时,AI 才允许更新。
如果更新数量是 0,就说明数据已经被其他流程处理了,这个 AI 结果已经过期,直接忽略即可。
这也是我第一次在真实业务里比较直观地理解"条件更新"的作用。
以前学习数据库时,知道单条文档更新是原子的,但没有真正想过它可以用来解决这种并发状态问题。
3.3 用户重新提交也会产生旧结果覆盖
处理完人工审核覆盖后,又发现了另一个类似的问题。
如果用户修改角色资料并重新提交,就可能同时存在两次 AI 请求:
text
第一次提交
↓
AI 请求 A 开始
用户修改后再次提交
↓
AI 请求 B 开始
请求 A 比请求 B 更晚返回
↓
第一次结果覆盖第二次提交
仅仅判断当前状态是不是"审核中"已经不够了,因为第二次提交后,状态同样是审核中。
所以每次提交时,还会生成一个新的审核请求 ID:
java
String requestId = generateRequestId();
并保存到当前申请中:
java
Update update = new Update()
.set("ai_audit_request_id", requestId)
.unset("ai_audit_status")
.unset("ai_audit_reason");
AI 回写时,除了匹配业务 ID 和审核状态,还要匹配请求 ID:
java
Criteria.where("_id").is(recordId)
.and("status").is(AUDITING)
.and("ai_audit_request_id").is(requestId);
如果用户已经重新提交,数据库中的请求 ID 就会发生变化,旧 AI 请求自然无法更新新版本的数据。
我后来把它理解成一个比较简单的业务版本号。
3.4 AI 服务异常时怎么办
外部 AI 服务并不总是稳定,实际开发中遇到过不少需要兼容的情况:
- 请求超时;
- 返回状态码异常;
- 返回体为空;
- 缺少约定字段;
- 返回内容不是合法 JSON;
- JSON 外面带着 Markdown 代码块;
- 原本约定返回英文,实际返回了中文;
- 返回了系统没有定义的状态。
刚开始写这类代码时,我容易把注意力放在正常响应上。但真正联调后会发现,异常响应的处理量可能并不少。
审核系统有一个比较重要的原则:
AI 调用失败时不能直接当作审核通过。
否则一旦第三方服务异常,所有内容都有可能绕过审核。
项目中不同内容的处理方式不完全一样,有的会继续等待人工审核,有的会进入限制可见状态。但总体思路都是:记录异常原因,保留业务 ID,不默认公开。
3.5 为什么还需要审核历史
角色申请可以被用户多次修改。
如果每次都只覆盖当前记录,那么最后只能看到最新内容,不知道前几次提交了什么,也不知道为什么被拒绝。
因此,审核完成后还需要保存一份快照,包括:
- 申请 ID;
- 第几次提交;
- 审核结果;
- 审核人;
- 拒绝原因;
- 审核时间;
- 当时的角色名称、简介和头像等信息。
保存时可以使用"申请 ID + 提交次数"作为业务唯一条件。
同一个提交版本重复保存时更新原记录,不会不断产生重复历史。
当然,这种方案保留的是每个提交版本的最终结果。如果以后要求保留运营人员的每一次点击操作,还需要单独设计操作流水。
3.6 这部分给我的最大收获
以前提到异步,我想到的主要是"提高响应速度"。
做完这部分后,我才发现异步最麻烦的不是怎么创建线程,而是:
任务执行完成时,原来的业务条件是否还成立?
后面再看到异步代码时,我会主动多想几个问题:
- 结果可能乱序吗?
- 同一个任务可能执行多次吗?
- 用户可能已经提交新版本吗?
- 人工可能已经修改状态吗?
- 失败之后需要重试吗?
- 旧结果回来后如何识别?
这些都是学校项目里比较少遇到的。
四、原以为只是"计个数"的发帖频率限制
另一个让我印象比较深的需求是发帖频率限制。
需求本身不难理解:用户不能在短时间内连续发太多帖子,每天也要有一个发布上限。
但开始设计后,需要确定的问题还挺多:
- 按账号、角色还是 IP 限制?
- 普通帖和投票帖是否一起计算?
- 编辑帖子算不算发帖?
- 参数校验失败算不算一次?
- 发布失败后要不要计数?
- 多个服务实例同时收到请求怎么办?
- 修改配置后什么时候生效?
- Redis 异常后是放行还是拒绝?
这些问题如果没有提前确认,最后很容易出现后端认为正确、产品认为不对的情况。
4.1 为什么按照账号限流
这个产品允许同一个账号使用多个角色。
如果按照角色 ID 限制,用户切换角色后就可以继续发帖,相当于绕过限制。
如果只按照 IP,又可能误伤同一个网络环境下的多个用户。
所以最终使用登录账号的 userId 作为主要限流维度。
4.2 短时间窗口为什么使用 ZSet
短时间限制使用 Redis ZSet。
score 保存发帖时间戳,member 使用"时间戳 + 随机值"。
每次检查时:
- 删除时间窗口外的记录;
- 查询当前窗口内还有多少条;
- 判断是否达到限制;
- 发布成功后再记录本次时间。
简化后的代码类似:
java
long now = System.currentTimeMillis();
redis.opsForZSet().removeRangeByScore(
key,
0,
now - windowMillis
);
Long count = redis.opsForZSet().zCard(key);
if (count != null && count >= limit) {
throw new RuntimeException("发帖频率过快");
}
发布成功后再添加记录:
java
String member = now + ":" + UUID.randomUUID();
redis.opsForZSet().add(key, member, now);
redis.expire(key, Duration.ofSeconds(windowSeconds));
member 后面拼随机值,是为了避免同一毫秒出现多个请求时互相覆盖。
4.3 为什么还需要分布式锁
虽然 Redis 的单个命令是原子的,但完整流程不是一个命令:
text
检查冷却状态
↓
删除过期记录
↓
查询当前数量
↓
发布帖子
↓
记录次数
如果两个请求同时执行,可能都查询到还没有超过限制,然后一起发布成功。
因此项目中按照 userId 使用 Redisson 分布式锁。同一个账号的并发发帖请求需要依次处理,不同账号之间不受影响。
java
RLock lock = redissonClient.getLock(
"POST:FREQUENCY:LOCK:" + userId
);
boolean locked = lock.tryLock(
waitSeconds,
TimeUnit.SECONDS
);
锁需要在 finally 中释放,并且释放前要判断是不是当前线程持有。
4.4 为什么发布成功后才增加次数
如果一进入接口就增加次数,可能发生:
text
用户提交请求
↓
计数加一
↓
参数校验失败
↓
帖子没有发布
这样用户没有成功发帖,却消耗了一次额度。
所以限流流程是:
java
checkLimit();
Post post = publish();
recordSuccessfulPublish();
return post;
只有真正发布成功后才计数。
不过,这个方案也不是完全没有问题。
如果帖子已经写入 MongoDB,但应用在写 Redis 前发生异常,就可能少计算一次。
对于普通社区发帖,这种小概率误差可以暂时接受。如果是支付或者强风控业务,就需要使用更加严格的事务事件或额度预占方案。
这也是我在实习中慢慢学到的一点:很多技术方案不是"绝对正确",而是看当前业务能接受什么程度的误差。
4.5 Redis 异常时该不该放行
这个问题当时也让我思考了很久。
如果 Redis 异常时所有请求都拒绝,那么限流组件本身会影响正常发帖。
如果 Redis 异常时直接放行,又可能在故障期间失去限流保护。
当前业务不是支付和资金相关业务,限流主要是防止灌帖,所以更倾向于记录异常后降级放行,优先保证主流程可用。
如果换成登录防爆破、支付或高风险操作,策略可能完全不同。
4.6 动态配置和本地缓存
限流参数保存在 MySQL 系统配置表中。
如果每次发帖都查询一次数据库,会产生没有必要的压力,所以代码中增加了几秒钟的本地缓存。
java
private volatile LimitConfig cachedConfig;
private volatile long configExpireAt;
缓存过期后,再使用同步块刷新。
这里用到的技术并不复杂,但是真正在项目里遇到后,我才对 volatile 的可见性和双重检查有了更具体的理解。
以前背概念时比较抽象,现在能够对应到"多个请求线程同时读取动态配置"这个实际场景。
4.7 这个方案还可以怎么改
现在回头看,当前方案还有一些不足:
- 一次检查包含多次 Redis 操作;
- 分布式锁会覆盖整个发帖过程;
- 短窗口和每日计数不是同一个原子操作;
- Redis 计数失败后没有补偿;
- 缺少限流命中次数和降级次数监控。
如果继续优化,可以用 Lua 脚本合并部分 Redis 操作,减少网络交互。
但因为帖子必须发布成功后才能确认计数,所以即使用 Lua,也仍然需要考虑数据库写入和 Redis 计数之间的一致性。
五、一个"小需求"牵出来的评论数问题
还有一个需求表面上非常简单:某些无意义评论需要隐藏,但发布者自己仍然能够看到。
一开始我以为只要给评论加一个"隐藏"状态,然后查询时过滤就可以了。
真正改起来才发现,它会影响很多地方:
- 评论列表;
- 评论内容展示;
- 帖子评论数;
- 回复数量;
- 帖子热度;
- 删除逻辑;
- 成就进度;
- 后台审核。
5.1 "本人可见"到底指谁
这个社区允许同一个账号使用多个角色。
因此,"隐藏评论只有本人可见"最后确认的含义是:
只有发布这条评论的具体角色可以看到。
同一个账号切换成另一个角色后,也不能看到这条隐藏评论。
查询条件大概是:
java
Criteria visibility = new Criteria().orOperator(
Criteria.where("status").is(ONLINE),
Criteria.where("status").is(HIDDEN)
.and("author_role_id").is(currentRoleId)
);
5.2 为什么评论数会对不上
假设一个帖子有:
text
10 条公开评论
1 条角色 A 发布的隐藏评论
那么:
text
角色 A 应该看到 11 条
角色 B 应该看到 10 条
未登录用户应该看到 10 条
如果隐藏评论创建后,直接把帖子中的公共 replyCount 加一,其他用户就会看到评论数是 11,但点进去只有 10 条。
所以隐藏评论不能进入所有用户共享的公开评论数。
返回帖子数据时,再根据当前角色补充其自己的隐藏评论数。
5.3 避免逐个帖子查询
帖子列表一次可能返回很多条数据。
如果每个帖子都单独查询一次当前角色的隐藏评论,就会产生 N+1 查询。
最后采用 MongoDB 聚合,一次传入这一页所有帖子 ID,按帖子 ID 分组统计当前角色可见的评论数,再将结果填充回帖子列表。
这个需求让我比较直观地感受到,接口中的一个数字也需要和实际数据展示保持同一口径。
5.4 删除逻辑也不能直接复用
隐藏评论创建时没有增加公开评论数,也没有增加一些公开评论相关的成就进度。
因此删除隐藏评论时,也不能照搬普通评论删除逻辑。
否则可能把帖子评论数错误减一,或者把用户的成就进度错误回退。
我以前写业务代码时,容易把"删除"理解成修改删除状态。后来发现,删除前必须先知道这条数据创建时产生过哪些副作用,删除时才能决定需要撤销哪些内容。
六、机器人管理中的跨数据库问题
运营后台需要配置群聊机器人,一个机器人还可以加入多个群。
机器人配置放在 MySQL 中,群成员关系放在 MongoDB 中。
MySQL 中采用"一机器人一群一行"的方式保存,后台查询时再按照用户 ID 和角色 ID 聚合成一个机器人。
6.1 已经在群里的用户为什么不能成为机器人
有一次遇到的问题是:某个角色本来已经在群里,运营再把它设置成机器人时,保存失败。
排查后发现,新增机器人时会固定调用一次加群逻辑。
角色已经是群成员,再次加群就会触发"重复入群"的校验,导致后面的机器人配置没有保存。
原来的流程相当于:
text
新增机器人
↓
执行加群
↓
保存机器人配置
修改后变成:
text
查询是否已经是群成员
↓
不是群成员:先加群
已经是群成员:跳过加群
↓
保存机器人配置
这个问题的根本原因是:
text
"已经在群里"
和
"已经被配置成机器人"
实际上是两个不同的状态,不能认为已经在群里就不需要继续后面的操作。
6.2 先查询再插入也不是绝对安全
虽然增加前置查询可以解决大部分重复操作,但两个请求同时执行时,仍然可能都查询到不存在,然后一起插入。
所以关键业务还需要数据库唯一索引兜底,例如:
text
user_id + role_id + group_id
以前我经常认为"代码里判断过了就不会重复",实习后才慢慢意识到,应用层判断和数据库约束解决的是不同层次的问题。
6.3 @Transactional 解决不了所有事务问题
机器人配置在 MySQL,群成员在 MongoDB。
即使方法上添加了 @Transactional,也不能自动让两个数据库一起提交、一起回滚。
可能出现:
text
MongoDB 加群成功
↓
MySQL 保存机器人配置失败
↓
两个数据库状态不一致
实习期间的方案主要是通过前置检查、幂等和异常日志降低问题概率,并没有实现严格的跨库强一致。
如果以后要继续完善,可以增加:
- 带状态的任务表;
- 失败重试;
- 补偿逻辑;
- 定时对账;
- Outbox;
- 事务消息。
这部分我目前也只是有了基本认识,还没有真正独立设计过完整的分布式事务方案。
七、关于机器人数据看板
除了机器人配置,我还参与了机器人触发记录和数据看板。
看板需要统计:
- 今日触发次数;
- 昨日触发次数;
- 活跃用户数;
- 不同触发类型;
- 按小时或按天的趋势;
- 关键词分布;
- 用户高频问题;
- 新手群高频问题。
其中有几个细节以前很容易忽略。
7.1 时间范围使用左闭右开
时间查询使用:
sql
created_at >= start_time
AND created_at < end_time
例如统计 8 月 1 日:
text
[2026-08-01 00:00:00, 2026-08-02 00:00:00)
这样 8 月 2 日零点的数据只会进入第二天,不会被两天重复统计。
7.2 昨日数量为 0 时不能直接算增长率
增长率一般是:
text
(今日数量 - 昨日数量) / 昨日数量
如果昨日为 0,就会除以 0。
项目中可以返回空值,让前端展示"暂无对比",而不是随便返回 0%。
这个问题很小,但如果没有处理,接口就可能在没有历史数据时直接异常。
7.3 实时统计并不适合所有数据量
当前数据规模下,可以直接从触发记录中按照时间和类型进行 GROUP BY。
这样实现简单,结果也比较及时。
但数据量增大后,按时间分组、关键词分组和 COUNT(DISTINCT user_id) 都可能变慢。
如果以后数据量增加,可以改成按小时预聚合:
text
原始记录
↓
小时统计任务
↓
小时统计表
↓
日报、周报从统计表查询
实习期间我没有真正接触特别大的数据量,所以这方面更多是根据当前实现做的思考,不能说自己有大数据系统经验。
八、测试和问题排查给我的帮助
实习期间,我使用 JUnit 5 和 Mockito 给部分核心逻辑补充了单元测试。
例如发帖限流会测试:
- 限流关闭时是否直接放行;
- 达到阈值后是否进入冷却;
- 冷却期间是否阻止发布;
- 发布失败后是否增加计数;
- 跨北京时间零点时如何重置。
评论隐藏会测试:
- 发布角色能否看到原文;
- 其他角色是否看不到;
- 隐藏评论是否影响公共评论数;
- 删除隐藏评论时是否错误扣减计数。
AI 审核会测试:
- 再次提交时是否清理旧结果;
- 请求 ID 是否更新;
- AI 状态映射是否正确;
- 审核快照是否保存;
- 同一提交版本是否产生重复历史。
我以前写测试比较容易只覆盖"正常返回成功"的情况。实习后发现,真正值得固定下来的往往是边界规则。
8.1 我常用的问题排查过程
遇到测试或线上问题后,我一般会先确认:
- 用户 ID;
- 业务对象 ID;
- 发生时间;
- 操作入口;
- 预期结果和实际结果。
然后根据这些信息检索日志,再查询 MongoDB 或 MySQL 中的数据状态。
大致流程是:
text
确认问题现象
↓
根据业务 ID 搜索日志
↓
找到请求和异步任务
↓
查询数据库当前状态
↓
根据时间还原状态变化顺序
↓
搜索所有可能修改该字段的代码
↓
构造复现场景
↓
修复并验证
我印象比较深的就是前面提到的 AI 覆盖人工审核问题。
单看日志时,每一步似乎都成功了;把 AI 请求时间、人工审核时间和数据库更新时间放在一起,才发现问题出在执行顺序上。
8.2 日志不能只写"操作失败"
以前我可能会写:
java
log.error("AI审核失败");
这种日志真正出问题时帮助不大。
后来会尽量带上:
- 业务类型;
- 业务 ID;
- 请求 ID;
- 当前状态;
- 目标状态;
- 异常堆栈。
例如:
java
log.error(
"AI审核失败, bizType={}, bizId={}, requestId={}",
bizType,
bizId,
requestId,
exception
);
尤其是异步任务,如果没有业务 ID,很难和最初的用户请求对应起来。
九、这次实习中做得不够好的地方
回头看这两个月,肯定也有不少做得不够好的地方。
1. 有时过于关注当前需求
刚开始接需求时,我经常只关注产品明确写出来的主流程,没有主动想到旧数据、重复请求和异步结果。
后面出现问题之后,才逐渐养成全局搜索状态字段和检查其他写入口的习惯。
2. 对监控和性能了解得不够
我参与了日志排查和数据库查询,但没有完整负责过服务监控、容量评估和性能压测。
所以我可以分析某个 SQL 或聚合在数据量增大后可能出现的问题,但不能把自己包装成有高并发系统经验。
3. 跨库一致性只处理了具体问题
机器人配置中使用了 MongoDB 和 MySQL,我主要处理的是重复加群、状态检查和幂等问题,并没有实现完整的跨库事务方案。
这部分也是我后续想继续学习的内容。
4. 对历史代码的理解有时不够完整
项目中部分审核逻辑经过多次调整,新旧实现同时存在。刚开始修改时,如果只看当前方法,很容易忽略其他模块中的旧逻辑。
后来我会更多地查看 Git 历史和全局引用,但这方面仍然需要继续积累经验。
十、两个月实习后,我对后端开发的理解
实习前,我觉得后端开发主要是:
text
设计数据库表
写接口
实现业务逻辑
返回数据
实习后,我觉得还要加上很多内容:
text
并发时会不会出错
重复执行是否安全
异步结果是否过期
第三方失败后怎么处理
缓存和数据库是否一致
旧版本是否还能使用
统计口径是否统一
出了问题能不能快速找到原因
一个接口正常返回,并不代表一个需求已经真正完成。
例如:
- AI 审核接口调用成功,不代表回写状态一定正确;
- 隐藏评论保存成功,不代表评论数一定正确;
- 机器人加群成功,不代表 MySQL 配置一定成功;
- Redis 限流生效,不代表数据库和计数绝对一致;
- 操作日志保存成功,也不代表日志语义一定准确。
这些问题听起来比较琐碎,却是我这次实习中接触最多的内容。
结语
这次实习所在的公司规模不大,项目也没有特别夸张的并发量,其实可以说几乎没有什么并发问题。
但对一个第一次正式参与真实项目的大三学生来说,这两个月还是让我学到了很多学校项目中接触不到的东西。特别是git相关以及不同环境部署,Linux服务器相关的,查询日志排查错误,以及使用宝塔配置定时任务等。
我现在仍然有很多不了解的地方,也还没有真正经历过大型系统的设计和优化。因为公司人员少,项目需求多,公司老师也很少指导我相关技术以及业务代码的规范,小公司更看中能不能解决这个需求,而对于实习生的培养并没有特别上心,但至少经过这次实习,我对后端开发以及职场有了更基本的认识,也知道了自己接下来应该朝什么方向去走。