1. 为什么2025年了,纯Servlet商城依然是毕业设计的常青树
先别急着质疑——我知道你心里在想什么:都什么年代了,毕业设计还在用Servlet?Spring Boot都出到3.x了,微服务都快被淘汰了,谁还写web.xml?
但现实情况恰恰相反。五年内我带过的、见过的、评审过的计算机专业毕设项目里,Servlet相关的选题从来就没断过。原因其实很实在:大多数本科院校的Java Web课程,讲的就是Servlet + JSP + MySQL这条线。你课堂上学的、期末实验做的、教材上写的,全是这套东西。到了做毕设的时候,选题要求和课程体系要对齐,技术栈跨度太大反而容易出问题。
再有一个现实考量:毕设的核心不是炫技,而是让老师看到"你理解了这个系统的完整生命周期"——需求分析、数据库设计、编码实现、测试部署。Servlet和JSP这套技术路线,把HTTP请求、会话管理、数据库连接、前后端交互这些Java Web底层的东西全部暴露在明面上,没有任何框架帮你封装。老师一问"你Filter是怎么配的""Session是怎么维持的",你能原原本本答上来,这个优势是直接写Spring Boot的人不具备的。
所以这篇内容如果被推到你这儿,大概率你也正在纠结选题,或者手里已经攥着一个"servlet网上水果商城系统"的项目不知道该从哪儿下手。那我直接把话说透:这个选题的价值,不在于技术有多新,而在于它是一个典型的、完整的、能落地的B2C电商系统,麻雀虽小五脏俱全。把它的每一个模块掰开揉碎搞明白,你收获的不止是一个毕设,而是对整个Java Web开发路径的完整认知。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 一个"能过答辩"的水果商城,功能上到底要有什么
系统功能设计是答辩老师第一眼看的东西。别小看这一步,我见过太多人上来就写代码,最后功能零零散散,连基本的购物流程都没走通。网上水果商城系统,核心是"商城"两个字,那商城的完整链路就不能缺。
2.1 前台用户端:顾客能干什么
前台面向的是普通消费者,最核心的诉求就是**"看到水果——挑水果——下单买水果"**这一整条链路。按照这个逻辑,前台功能分成几块:
- 用户注册与登录:这个不用多解释,商城必须要有的基础能力。要注意的点是,用户密码不能明文存储,至少做一层MD5加盐处理。答辩的时候老师如果问"你这样存密码安全吗",你直接甩出"我做了加盐哈希处理",水平立马不一样。
- 水果商品浏览:列表展示和分类筛选。注意,这里不是简单把所有商品堆到页面上,而是要有分类维度——按水果种类分(苹果类、柑橘类、热带水果类),按销量排序,按价格区间筛选。搜索功能同样需要,别整那种模糊匹配所有字段的粗暴实现,至少按商品名称搜,再做分页展示。
- 商品详情:点进单个水果,要有大图、价格、库存、销量、商品描述。库存这个点容易被忽略,但实际做的时候一定要体现——如果库存为0,你就不能让人家加购物车。
- 购物车管理:加购、改数量、删除、清空、勾选结算。购物车的实现方案后面会细说,但功能上先保证这五个动作全的。
- 订单提交与支付模拟:用户在购物车勾选商品后结算,生成订单。这里不需要接真实支付,但要有"模拟支付"的流程——比如生成订单后状态是"待支付",点击支付后变"已支付"。这个状态流转一定要清晰。
- 个人中心:查看我的订单列表、订单状态(待发货、已发货、已完成),以及修改个人信息、修改密码。
- 购物评价:收到货之后可以对商品进行评价。这个一般毕设很少做,但做了就是加分项,因为它体现了电商系统的互动闭环。
2.2 后台管理端:管理员要管什么
后台是给系统运营人员用的,别做一堆花里胡哨的统计图表,核心就是把数据管起来:
- 管理员登录:和普通用户登录分开,单独一张管理员表,权限独立。
- 水果商品管理:商品的增删改查。上架新水果、修改价格、调整库存、删除下架。
- 分类管理:商品分类的维护,新增分类、修改名字、删除分类。
- 订单管理:查看所有用户订单,按状态筛选。核心操作是"发货"——把订单状态从"已付款"改成"已发货"。再细一点可以做"查看订单详情",看这个订单里有哪些商品、多少钱、收货人是谁。
- 用户管理:查看注册用户列表,禁用/启用账号。别小看这个功能,有用户体系就必然需要账号管理能力。
2.3 功能边界的建议:别给自己挖坑
很多同学做毕设恨不得把所有功能都往上报,什么优惠券系统、积分体系、多人拼团、直播带货,全列上去。听我一句劝:毕设的功能复杂度,一定要和你的时间和能力匹配。
功能太多,意味着表结构更复杂、代码量成倍增长、联调成本指数级上升。而且答辩的时候,老师会挑你最薄弱的功能深挖——你做了个优惠券,但优惠券叠加规则没想清楚,被问住反而丢分。
比较稳妥的功能矩阵是:前台做全购物主链路(注册登录→浏览搜索→购物车→下单支付→订单管理→评价),后台做全数据管理链路(商品→分类→订单→用户)。 这套功能做完,系统完整性已经很高了,老师挑不出明显的短板。
3. 数据库设计:五张核心表撑起整个商城
数据库设计是毕设的基础工程,也是老师稳拿的第一张图纸。网上水果商城的表不用刻意设计得很花哨,但一定要满足基本范式,并且表和表之间的关联关系要严谨。
3.1 核心数据表清单
我按实际开发习惯给你列出最核心的几张表,你可以照着这个思路去建模:
| 表名 | 功能 | 关键字段 |
|---|---|---|
t_user |
前台用户表 | id(主键自增)、username、password(加盐密文)、nickname、phone、address、avatar、create_time、status |
t_admin |
后台管理员表 | id、username、password、real_name、create_time |
t_category |
水果分类表 | id、name、sort_order、create_time |
t_fruit |
水果商品表 | id、category_id(关联分类)、name、image、price、stock、sales、description、status(上架/下架)、create_time |
t_cart_item |
购物车表 | id、user_id(关联用户)、fruit_id(关联商品)、quantity、checked、create_time |
t_order |
订单主表 | id、order_no(订单编号)、user_id、total_amount、status(待支付/已付款/已发货/已完成/已取消)、receiver_name、receiver_phone、receiver_address、create_time、pay_time |
t_order_item |
订单明细表 | id、order_id(关联主表)、fruit_id、fruit_name(冗余快照)、price(快照)、quantity |
3.2 设计逻辑说明:为什么要冗余这些字段
这里我要特意讲两个容易被忽略但答辩高频问到的设计细节。
第一个,t_order_item里为什么要有fruit_name和price这个冗余字段? 因为订单一旦生成,商品信息后续可能发生变化——水果价格上调了、名称改了、甚至商品下架了。如果你的订单明细全部依赖实时关联查询t_fruit表,那用户查看历史订单时看到的价格可能是当前的、已经变过的价格。这样订单数据就不具备"历史快照"的意义。把fruit_name和price在生成订单的那一刻冗余进明细表,才能真正保存"用户当时以什么价格买了什么东西"。这个细节做到位,答辩老师一眼就能看出来你理解"订单不可变"的设计原则。
第二个,为什么订单主表要单独一个order_no订单编号字段,而不是直接用自增主键id? 两个原因。一是自增id会泄露业务数据量——单号是10086,说明前面有10085个订单,这在真实商业场景里是不可接受的。二是订单编号需要具备一定的格式和可读性,比如"SF202506153001"。毕设里你可以用时间戳加随机数拼接生成,既简单又能说明白设计意图。
3.3 表关联关系
- 用户和购物车:一对多。一个用户有多条购物车记录。
- 商品和分类:多对一。一个分类下多个商品。
- 订单主表和明细表:一对多。一个订单包含多条明细。
- 用户和订单:一对多。注意商品表通过外键关联分类表,订单明细通过外键关联订单主表和商品表,但订单表本身不直接关联商品,而是通过明细表间接关联。
外键约束在毕设里建议建立。有些同学为了省事,在物理表上不建外键,全靠代码逻辑保证,理由是"外键影响性能"。但毕设阶段,物理外键能帮你和数据库都守住数据的完整性,还是在表结构里加上。
4. 核心代码链路:用户从登录到下单,Servlet在中间干了什么
说完了设计和数据库,到了写代码的环节。这个项目最核心的代码链路,就是一条从登录到下单的主线流程。把它彻底吃透,整个项目你就通了。
4.1 Servlet的开发模式:一个请求对应一个Servlet
纯Servlet项目的开发模式非常直白:每个页面请求或操作,对应一个或多个Servlet。比如:
UserLoginServlet——处理用户登录请求FruitListServlet——查询商品列表并跳转到展示页CartAddServlet——处理加购请求OrderCreateServlet——提交订单AdminOrderListServlet——后台订单列表
每一个Servlet只需要继承HttpServlet,重写doGet或doPost方法,然后在web.xml里配置映射路径就行。有人喜欢用@WebServlet注解,这个看个人习惯,但毕设的话建议用web.xml方式——理由后面专门讲。
一个典型的FruitListServlet核心代码长这样:
java复制@WebServlet("/fruit/list")
public class FruitListServlet extends HttpServlet {
private FruitService fruitService = new FruitService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
// 1. 获取请求参数(当前页、搜索关键字、分类id)
String currentPageStr = req.getParameter("currentPage");
String keyword = req.getParameter("keyword");
String categoryIdStr = req.getParameter("categoryId");
int currentPage = 1;
int pageSize = 8;
if (currentPageStr != null && !"".equals(currentPageStr)) {
currentPage = Integer.parseInt(currentPageStr);
}
// 2. 调用Service查询分页数据
PageResult<Fruit> pageResult = fruitService.queryPage(
currentPage, pageSize, keyword, categoryIdStr);
// 3. 将结果存入request域,转发到JSP页面展示
req.setAttribute("pageResult", pageResult);
req.setAttribute("keyword", keyword);
req.setAttribute("categoryId", categoryIdStr);
req.getRequestDispatcher("/fruit_list.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
}
这段代码体现了Servlet的标准流程:接参数 → 调服务 → 存域 → 转发/重定向。你把这个套路套到所有功能模块上,整个项目就会像搭积木一样顺畅地搭起来。
4.2 三层架构:别把所有代码怼进Servlet
很多同学第一次做Servlet项目会把所有业务逻辑直接写在Servlet的doGet/doPost里,几十个if-else堆在一起,数据库操作到处重复。代码能跑,但一被问"你这个项目分层了吗"就哑火了。
建议在动手前先想好分层,这是Java Web开发的基本功:
- Controller层(Servlet):只负责接收请求参数、调用Service、向前端传数据、控制页面跳转。
- Service层:负责业务逻辑,比如用户登录时要校验密码、下单时要扣除库存、生成订单号。
- Dao层(数据访问层):用JDBC封装数据库操作,SQL语句集中在这一层。
比如下单这个动作,Service层的方法大概是这样:
java复制public boolean createOrder(Order order, List<CartItem> cartItems) {
// 开启事务
Connection conn = DbUtil.getConnection();
try {
conn.setAutoCommit(false);
// 1. 生成订单编号
String orderNo = generateOrderNo();
order.setOrderNo(orderNo);
// 2. 插入订单主表,获取订单id
int orderId = orderDao.insert(conn, order);
// 3. 遍历购物车项,逐条插入订单明细表
for (CartItem item : cartItems) {
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(orderId);
orderItem.setFruitId(item.getFruitId());
orderItem.setFruitName(item.getFruitName());
orderItem.setPrice(item.getPrice());
orderItem.setQuantity(item.getQuantity());
orderItemDao.insert(conn, orderItem);
// 4. 扣减库存
fruitDao.reduceStock(conn, item.getFruitId(), item.getQuantity());
}
// 5. 清空购物车
cartDao.clearCart(conn, order.getUserId());
// 提交事务
conn.commit();
return true;
} catch (Exception e) {
// 出错回滚
conn.rollback();
throw new RuntimeException("下单失败", e);
} finally {
DbUtil.close(conn);
}
}
这个问题我要单独强调一下:下单、减库存、清购物车这三个动作必须放在同一个事务里。 如果下单成功但库存没减,或者购物车没清,数据就全乱套了。很多毕设系统表面上功能齐全,但这个事务问题没处理好,一测试就暴露。
4.3 登录状态如何维持:Session的方方面面
Session是Servlet项目的核心机制,也是必考环节。用户登录成功后,你在HttpSession里存一个用户对象:
java复制User user = userService.login(username, password);
if (user != null) {
HttpSession session = req.getSession();
session.setAttribute("loginUser", user);
resp.sendRedirect("index");
} else {
req.setAttribute("errorMsg", "用户名或密码错误");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
这里有两个细节值得注意:
第一,存Session而不是用Cookie存用户名密码。 这点一定要清楚,Session是服务端会话机制,用户信息保存在服务器内存里,客户端只拿到一个sessionId,安全性和可控制性都更好。Session中的loginUser可以在任何页面通过req.getSession().getAttribute("loginUser")取到,判断用户是否登录、显示昵称头像。
第二,登录拦截要用Filter实现。 很多Servlet项目做登录权限控制,在每个Servlet里手动判断session是否为空,这样代码极其冗余。正确的做法是写一个LoginFilter,实现javax.servlet.Filter接口,对需要登录的路径(比如/cart/*、/order/*、/user/*)进行拦截:
java复制public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 放行已登录用户
if (req.getSession().getAttribute("loginUser") != null) {
chain.doFilter(request, response);
} else {
// 未登录,重定向到登录页,并带上原路径方便登录后跳回
resp.sendRedirect(req.getContextPath() + "/login.jsp?redirect=" + req.getRequestURI());
}
}
}
4.4 购物车实现:Session还是数据库
购物车是个经典设计题。实现方案有几种,毕设里最合理的是:未登录时可以用Session购物车,登录后显示数据库中的持久化购物车。
更简单的做法是:只要你登录了,购物车数据就存数据库(t_cart_item表),这样用户换个设备购物车还在。每次加购操作,调用CartItemDao去update或者insert一条记录,数值增减通过SQL完成。有同学会担心频繁操作数据库性能问题,其实毕设场景的数据量,这个担心完全多余。
加购的核心代码逻辑:
java复制public void addToCart(Integer userId, Integer fruitId, Integer quantity) {
// 1. 先查该用户购物车里有没有这个商品
CartItem existing = cartItemDao.findByUserIdAndFruitId(userId, fruitId);
if (existing != null) {
// 2. 已有则更新数量
int newQty = existing.getQuantity() + quantity;
cartItemDao.updateQuantity(existing.getId(), newQty);
} else {
// 3. 没有则新插入
CartItem cartItem = new CartItem();
cartItem.setUserId(userId);
cartItem.setFruitId(fruitId);
cartItem.setQuantity(quantity);
cartItemDao.insert(cartItem);
}
}
购物车页面上,每条记录还要通过联表查询把商品名称、图片、单价、小计(单价乘数量)拼出来,最后一并算出购物车总金额供用户参考。
5. 别忽略的细节:网页上的JSP与前端渲染
整个系统功能逻辑跑通之后,到了展示层。这时候你会发现,真正影响用户使用体验的,反而是那些看起来"不起眼"的前端页面细节。
5.1 JSP的职责:只做显示,别写混乱的业务逻辑
JSP页面在Servlet项目里的角色是视图层,用来渲染数据。很多同学的JSP里塞了大量<% Java代码 %>,页面乱到没法维护。建议的做法是:在页面中使用${}表达式(EL表达式)和JSTL标签来遍历数据。
比如商品列表页的核心渲染片段:
jsp复制<c:forEach items="${pageResult.list}" var="fruit">
<div class="fruit-card">
<a href="fruit/detail?id=${fruit.id}">
<img src="${fruit.image}" alt="${fruit.name}">
</a>
<h3>${fruit.name}</h3>
<p class="price">¥${fruit.price}</p>
<button onclick="addToCart(${fruit.id})">加入购物车</button>
</div>
</c:forEach>
前三行就是EL表达式,第三行是JSTL的forEach循环。用这套方式让JSP保持干净,代码可读性也更高。如果页面要显示"当前登录用户是谁",直接在JSP里用${sessionScope.loginUser.nickname}取,比在Servlet里往request里放一份再传给JSP要优雅得多。
5.2 前端交互:可以用一点基础的Ajax
从整体功能出发,页面之间的跳转用普通表单提交和超链接就行。但有几个操作建议用一点简单的Ajax,会让系统体验明显上一个档次,答辩演示时也更亮眼:
- 加购不跳转:点击"加入购物车"后,页面原地不动,顶端弹出"已加入购物车"的提示。
- 购物车改数量:点加号减号时,小计金额和总金额无刷新更新。
- 搜索联想:这个可以不做,但有时间可以加分。
前端交互不建议引入前端框架,别把精力花在搭建复杂的前端工程上。原生JavaScript或jQuery就够用。写一个简单的Ajax调用长这样:
javascript复制function addToCart(fruitId) {
$.ajax({
url: "cart/add",
type: "POST",
data: { "fruitId": fruitId, "quantity": 1 },
dataType: "json",
success: function (result) {
if (result.code === 200) {
alert("已加入购物车!");
// 可选:更新右上角购物车数量徽标
refreshCartCount();
} else {
alert(result.msg);
}
}
});
}
6. 避坑指南:Servlet项目最常见的三个老问题
每个做过Servlet项目的同学,几乎没有不踩下面这几个坑的。我把高频的问题和排查思路写出来,给你一个直接的参考。
6.1 Post请求乱码问题:setCharacterEncoding的顺序
中文乱码是Servlet项目第一号问题。根源在于:HTTP请求传输时默认的编码是ISO-8859-1,而你的页面和数据是UTF-8。解决方法是,在Servlet的最开头调用:
java复制req.setCharacterEncoding("UTF-8");
但这里有个极容易被忽略的坑:这个设置必须在第一次读取请求参数之前调用,否则无效。 如果你先调用req.getParameter("username")再设置编码,参数已经按默认编码解析过了,后面设置就晚了。
从Servlet 4.0开始,还有一种方式是使用CharacterEncodingFilter这种过滤器统一处理编码。这个思路非常实用,在web.xml里配置一个编码过滤器,所有请求都先过一遍,就不会有遗漏了:
xml复制<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
6.2 路径问题:/到底代表什么
JSP页面里的超链接、表单提交路径、web.xml里的url-pattern,到处都在用路径。一个常用的判断标准是:
web.xml里的<url-pattern>,写/fruit/list,这个斜杠代表的是"项目根路径 + 应用上下文路径"。- JSP里写的跳转地址,如果以
/开头,默认不加项目名是找不到的,验证方法是部署后观察浏览器地址栏。 - Servlet里
resp.sendRedirect("cart/list")是相对路径,如果当前请求在/fruit/detail下,它会跳转到/fruit/cart/list,容易带错上下文。
最省心、也最保险的方案是:所有重定向都用绝对路径前缀:
java复制resp.sendRedirect(req.getContextPath() + "/index");
而在JSP页面超链接中,推荐用${pageContext.request.contextPath}动态拼接项目上下文路径:
jsp复制<a href="${pageContext.request.contextPath}/user/orders">我的订单</a>
6.3 数据库连接:用完就关,别一直开着
JDBC连接如果不关闭,MySQL连接池几分钟就满了,Tomcat直接报Too many connections错误。很多同学报这个错的第一反应是改数据库最大连接数,其实是代码里连接管理没做好。
建议的最稳写法是:工具类DbUtil统一提供获取连接和关闭连接的方法,在每个Dao方法中执行完就关,放在finally块里保证一定执行:
java复制public Fruit findById(Integer id) {
String sql = "select * from t_fruit where id = ?";
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DbUtil.getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
rs = ps.executeQuery();
if (rs.next()) {
Fruit fruit = new Fruit();
fruit.setId(rs.getInt("id"));
fruit.setName(rs.getString("name"));
// ... 其他字段
return fruit;
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
DbUtil.close(rs, ps, conn);
}
return null;
}
7. 部署与演示:Tomcat版本与JDK版本的选择
到这一步,系统代码基本写完了,准备打包部署和答辩演示。这个阶段也有几个容易卡壳的环节,提前说清楚可以少踩坑。
7.1 Servlet API版本兼容表
Servlet项目依赖Servlet API和Java版本,不同的Tomcat版本支持的API版本不同。这里的核心逻辑是:你的代码编译用的Servlet版本不能高于Tomcat支持的版本,否则部署后API缺失、类找不到。
| Tomcat版本 | 对应Servlet版本 | JDK要求 |
|---|---|---|
| Tomcat 8.5 | Servlet 3.1 | JDK 8+ |
| Tomcat 9.x | Servlet 4.0 | JDK 8+ |
| Tomcat 10.0 | Servlet 5.0(Jakarta命名空间) | JDK 8+ |
如果做毕设,Tomcat 8.5 配 JDK 8 是最省心的组合,因为网上能找到的教学资料基本都是这个体系,后续遇到问题也容易搜到答案。如果你用的是Tomcat 10,那javax.servlet要全部换成jakarta.servlet,这个坑够很多人喝一壶的。
7.2 打包与部署流程
项目在IDEA里配置好之后再打包。用Maven构建的项目:
code复制mvn clean package
打完包后,把生成的war包(比如fruit_shop.war)复制到Tomcat的webapps目录下,再在Tomcat的bin目录里执行startup.bat或startup.sh启动。
如果是把项目直接拷贝到Tomcat的webapps目录,需要注意war包的名称决定了访问路径。比如war包叫fruit_shop.war,那访问地址是http://localhost:8080/fruit_shop/。这个细节不处理好,答辩时演示就出洋相了。
7.3 答辩演示时的准备建议
答辩演示和技术研发是两码事。演示的核心是流程顺畅,不是代码炫技。我建议按这样一套顺序走:
- 先启动MySQL,再启动Tomcat,等服务器完全起来。
- 打开首页,从用户注册开始演示——注册一个账号,然后登录。
- 浏览商品列表,点进某个水果详情。
- 加入购物车,演示购物车改数量,再去结算。
- 提交订单,模拟支付,查看订单状态从"待付款"到"已付款"。
- 退出用户,用管理员账号登录后台,查看订单、发货。
- 最后演示商品管理、分类管理等管理功能。
整个演示流程控制在15分钟以内,每个环节之间做好衔接。如果网络不好,所有素材图片尽量用本地资源,不要引用外网链接,避免加载不出来。
提示:答辩不是app demo,老师更关注的是你会不会做、懂不懂为什么这么做,不需要演示每个边角功能。留几个功能给老师提问机会,比全部演示完效果更好。
7.4 扩展方向:答辩怎么加分
如果你的时间还够,可以在核心功能基础上做一两个扩展点,答辩的时候主动展示:
- Excel导出订单报表:用POI把订单列表导出到Excel。
- 可视化统计:后台首页展示简单的柱状图/饼图,展示每日订单量、热销商品排行。
- 验证码登录:登录页加入图片验证码,防止暴力破解。
- 图片上传:后台管理里上传水果图片,而不是写死URL。
这几个方向都不需要额外引入重量级技术,但展示时能明显提升系统的完整度和工程化水平。特别是图片上传,基本上所有电商类系统都需要,老师看到你在后台能够自己上传商品图,就会觉得你不是在"仅仅跑通一个demo"。
我个人更推荐做可视化统计和图片上传两个点,一个是数据分析能力,一个是资产管理能力,两个方向在教学评价里都很讨喜,而且工作量适中,不会影响主流程的稳定性。
最后再分享一个我自己的做法:代码写完后,把你的数据库建表SQL脚本、初始化数据脚本整理好放在项目根目录下的sql文件夹里,再写一个简短的README文档,记录部署步骤和演示账号。这样不单答辩方便,项目传到GitHub或者发给别人,也会显得非常专业。这个小习惯我自己坚持了很多年,你值得养成。
