首页 / 资讯中心 / 文章详情

Java Web实战:Servlet+JSP+MySQL构建开心农场源码全解析

Java Web实战:Servlet+JSP+MySQL构建开心农场源码全解析 ★ FEATURED ARTICLE
简介一套Java简易版开心农场源码带“智慧北京”项目实践内容面向Java/Android初中级开发者与刚接触完整项目流程的程序员。资料以系统开源方式整理ZIP压缩包约6.56MB便于快速下载与本地部署学习。项目围绕社交类App搭建涵盖聊天模块、语音发送与取消、朋友圈、摇一摇、漂流瓶等功能点并给出产品经理、开发、UI、测试、运营等角色分工以及需求评估、任务分配、燃尽图、版本迭代流程。源码中还可学习闪屏页旋转/缩放/渐变动画、引导页设计以及通过ViewTreeObserver在onCreate中获取View宽高与margin等开发细节。已有428人浏览学习适合用来理解Android项目结构、常见业务模块拆解和移动端团队协作节奏。1. 一个能种菜、收菜、卖钱的 java 简易版开心农场源码值不值得下载这份 java简易版开心农场源码工程代号 ZHBJ是我在评审一批课程设计作品时真正跑起来玩过的一份。它能注册登录、买种子、种地、等成熟、收进仓库、卖钱升级一个小游戏的链路完整闭环。它不是几百行的教学 Demo而是一个能讲清楚 Java Web 请求到数据库每个环节的完整系统开源项目。正在找课程设计底座、想快速把作品跑起来的人这份源码值得下载。照着下文顺序操作半天内就能在浏览器里种出第一棵菜。2. 技术选型与架构Servlet JSP MySQL 的 MVC 骨架怎么撑起一座农场2.1 技术选型为什么这个年代还用 Servlet JSP先别急着嫌它老。我第一眼看到这份源码的 pom.xml 时也愣了一下JDK 8、Tomcat 8.5、Servlet 3.0、JSP JSTL、MySQL、Druid 连接池外加 Maven 打 war 包怎么看都像五年前的组合。但跑起来之后我理解了作者的意图这是一个要现场演示、要能答辩讲清楚的作品讲究的是轻、快、好讲。简易版意味着核心是业务链路不是框架版本号把花架子放在 Spring 上没意义。Servlet 3.0 让 web.xml 基本可以退役WebServlet 注解直接挂在控制器类上映射路径写在类名上方比搞一套 Spring MVC 的 HandlerMapping 直观得多。JSP 负责渲染页面配合 EL 表达式和 JSTL 标签把 Java 代码从页面里隔离出去。数据访问层就是原生 JDBC 加 Druid 连接池没有 MyBatis 的 XML 映射文件SQL 直接写在 DAO 里排查问题时一眼就能看到执行了什么语句。我一般会这样评价这种选型技术栈老不等于架构差只要分层清楚日后往 Spring Boot 迁移只是换壳。真正淘汰一个项目的从来不是版本号而是没有边界的代码。这套组合放在课程设计场景下是合理的也正因为是这套组合每一个环节都能在答辩时展开讲。2.2 目录结构五层分包一个请求经过了哪些门这份源码的工程名是 farm包名统一以 com.zhbj.farm 开头整体按经典 MVC 分包。打开工程后每个类的位置都很明确farm/ ├── pom.xml # Maven 配置打包方式 war ├── src/main/java/com/zhbj/farm/ │ ├── controller/ # Servlet 层接收请求、返回 JSON/页面 │ │ ├── UserServlet.java # 注册、登录、登出 │ │ ├── PlantServlet.java # 种菜入口 │ │ ├── HarvestServlet.java # 收菜入口 │ │ ├── FieldListServlet.java # 查询地块列表返回 JSON │ │ └── ShopServlet.java # 买种子、卖菜 │ ├── service/ # 业务层事务和规则判断都在这里 │ │ ├── UserService.java │ │ ├── FieldService.java │ │ └── WarehouseService.java │ ├── dao/ # 数据访问层原生 JDBC │ │ ├── UserDao.java │ │ ├── CropDao.java │ │ ├── FieldDao.java │ │ └── WarehouseDao.java │ ├── entity/ # 实体类对应四张表 │ └── util/ │ ├── DBUtil.java # Druid 连接池初始化 │ ├── LevelUtil.java # 经验与等级换算 │ └── Result.java # 统一 JSON 返回结构 ├── src/main/resources/ │ └── druid.properties # 数据库连接配置 └── src/main/webapp/ ├── login.jsp / register.jsp # 登录注册页 ├── farm.jsp # 游戏主页面地块渲染 ├── shop.jsp / warehouse.jsp # 商店和仓库 └── static/js/farm.js # 页面轮询、地块点击逻辑请求的路径很直白farm.jsp 里的按钮点击后走 ajax 到 controller 包下的 ServletServlet 拿参数交给 service 处理业务规则service 再调 dao 完成 SQL 操作结果以 JSON 返回给页面。整个链路没有跨层调用controller 不写 SQLdao 不知道业务规则这是这份源码最值得模仿的地方。它把请求到底经过哪些类这件事变成了一条清晰的路而不是一个黑洞。2.3 数据库设计四张表怎么闭环金币、经验和作物数据库叫 happy_farm字符集 utf8mb4。全文只有四张业务表没有冗余的配置表种子数据以初始数据的形式放在 SQL 里。建表语句如下CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(32) NOT NULL, nickname VARCHAR(20) DEFAULT , coins INT DEFAULT 200, -- 初始金币 exp INT DEFAULT 0, -- 累计经验 level INT DEFAULT 1, -- 当前等级 create_time DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_crop ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(10) NOT NULL, -- 作物名 seed_price INT NOT NULL, -- 种子价格 grow_minutes INT NOT NULL, -- 成熟所需分钟数 sell_price INT NOT NULL, -- 单个作物卖出价 exp_reward INT NOT NULL, -- 收获一次的经验 unlock_level INT DEFAULT 1 -- 解锁所需等级 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_field ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, -- 属于哪个用户 plot_index INT NOT NULL, -- 第几块地从 1 开始 status TINYINT DEFAULT 0, -- 0 空地 1 生长中 2 可收获 crop_id INT, -- 当前种的作物 plant_time BIGINT -- 种下时的时间戳毫秒 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_warehouse ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, crop_id INT NOT NULL, quantity INT DEFAULT 0, UNIQUE KEY uk_user_crop (user_id, crop_id) -- 同一作物只存一行数量累加 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有个细节值得说t_field 的 plant_time 用 BIGINT 存毫秒时间戳而不是 DATETIME。常见做法是存 DATETIME 让数据库管理时间但这个项目需要在 Java 里反复计算种下到现在过了多少分钟还要避免时区干扰存 long 可以省掉格式化开销前端拿到也能直接算倒计时。代价是看表时不够直观需要配合代码理解。四张表的闭环逻辑是t_crop 是静态配置定义每种作物的价格和生长时间t_user 是动态账户金币和经验从这里扣、往这里加t_field 是状态位记录每块地此刻种了什么、种了多久t_warehouse 是库存收获的作物先进仓库卖出时再扣。一个玩家的完整生命周期就是在这四张表之间流转。注意t_field 里没有外键约束user_id 和 crop_id 只做逻辑关联。这是课程设计里的常见取舍避免建表顺序依赖也让 SQL 更简单代价是数据完整性靠业务层保证。3. 核心模块拆解一颗种子从种下到卖出经历了哪几步3.1 地块状态机用时间戳算成长进度全程没有定时器第一次跑起来种菜时我原以为熟菜是靠后台线程每分钟扫表实现的。翻完代码发现完全不是整份源码里没有任何定时任务。设计者把成长状态做成了按需计算的模式数据库只记录种下的时间戳每次前端轮询地块列表后端现算当前处于哪个阶段。这样做最实际的好处是抗重启如果用定时器去改 statusTomcat 重启后定时器就丢了所有种下去的菜永远停在原地。而时间戳计算是幂等的不管服务器重启多少次只要当前时间减去种下时间超过生长分钟数菜就熟了。FieldService 里最核心的方法如下public ListFieldVO listFields(int userId) { ListField fields fieldDao.findByUserId(userId); ListFieldVO result new ArrayList(); for (Field field : fields) { FieldVO vo new FieldVO(field); if (field.getStatus() FIELD_GROWING) { // 1: 生长中 Crop crop cropDao.findById(field.getCropId()); long elapsed (System.currentTimeMillis() - field.getPlantTime()) / 60000L; int total crop.getGrowMinutes(); vo.setProgress(Math.min(100, (int) (elapsed * 100 / total))); if (elapsed total) { vo.setStage(3); vo.setStageText(成熟了); } else if (elapsed total * 2 / 3) { vo.setStage(2); vo.setStageText(快熟了); } else if (elapsed total / 3) { vo.setStage(1); vo.setStageText(生长中); } else { vo.setStageText(刚出苗); } } result.add(vo); } return result; }参数说明elapsed 是把毫秒差除以 60000 得到的分钟数progress 用 elapsed 乘以 100 再除以 grow_minutes语义是成熟度百分比上限截断到 100避免算出 103% 这种数据。stage 是给前端显示用的分级0 空地、3 可收获中间用两个阈值把生长切成三段让用户看到的不是一条干巴巴的进度条而是刚出苗、生长中、快熟了、成熟了四个文案。有一个边界值得注意种下去后状态立刻变成生长中但 elapsed 为 0 时显示刚出苗符合直觉。收获动作不会自动发生必须由用户点击触发这也给二次开发加好友偷菜玩法留了天然的口子。3.2 种菜与收菜Servlet 的校验顺序、参数名和返回约定种菜的链路从 farm.jsp 上的一个点击开始。页面拿到选中的地块编号和商店里选中的种子编号POST 到 /plant 接口。PlantServlet 本身很短重点在 service 里的三道校验WebServlet(/plant) public class PlantServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.setCharacterEncoding(UTF-8); HttpSession session req.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { writeJson(resp, Result.error(未登录)); return; } int plotIndex Integer.parseInt(req.getParameter(plotIndex)); int cropId Integer.parseInt(req.getParameter(cropId)); Result result new FieldService().plant(user, plotIndex, cropId); writeJson(resp, result); } private void writeJson(HttpServletResponse resp, Result result) throws IOException { resp.setContentType(application/json;charsetUTF-8); resp.getWriter().write(result.toJson()); } }对应的 service 方法public Result plant(User user, int plotIndex, int cropId) { Field field fieldDao.findByUserAndIndex(user.getId(), plotIndex); if (field.getStatus() ! FIELD_EMPTY) { // 第一道地必须是空的 return Result.error(这块地已经有作物了); } Crop crop cropDao.findById(cropId); if (crop.getUnlockLevel() user.getLevel()) { // 第二道等级解锁 return Result.error(等级不够先收菜升级); } if (user.getCoins() crop.getSeedPrice()) { // 第三道金币校验 return Result.error(金币不足去仓库卖点菜); } userDao.updateCoins(user.getId(), -crop.getSeedPrice()); // 先扣款 fieldDao.plant(user.getId(), plotIndex, cropId, System.currentTimeMillis()); // 再种地 return Result.success(); }参数说明plotIndex 是地块序号而不是 t_field 主键 id因为前端只关心第几块地主键在整个生命周期里被复用清地后下一次种植还是同一行。cropId 来自商店列表前端拿到种子列表后把 id 和价格存在页面数据里。三道校验的顺序是刻意安排的先查地、再查等级、最后查钱避免钱扣了才告诉你等级不够这种让玩家恼火的体验。Result 统一为 {code:0, msg:...}code 为 0 成功非 0 是业务错误前端根据 code 决定弹提示还是刷新界面。收菜接口 /harvest 的校验逻辑反过来它要处理归属权和成熟度public Result harvest(User user, int fieldId) { Field field fieldDao.findById(fieldId); if (field.getUserId() ! user.getId()) { // 归属校验 return Result.error(这不是你的地); } if (field.getStatus() FIELD_EMPTY) { return Result.error(这块地是空的); } Crop crop cropDao.findById(field.getCropId()); long elapsed (System.currentTimeMillis() - field.getPlantTime()) / 60000L; if (elapsed crop.getGrowMinutes()) { // 成熟度校验 return Result.error(还没熟再等等); } warehouseDao.add(user.getId(), crop.getId(), 1); // 进仓库 userDao.updateCoins(user.getId(), crop.getSellPrice()); // 到账金币 userDao.updateExp(user.getId(), crop.getExpReward()); // 增加经验 fieldDao.clear(fieldId); // 清地恢复空地 return Result.success(); }这里我一般会补一句收获必经的四次写入仓库、金币、经验、清地在业务上是一个整体。如果 updateCoins 抛异常仓库里已经多了一个作物而地没清掉玩家就能反复收。原版代码在单机演示时问题不大但一旦多人同时操作这个隐患就会被放大第五章有对应的完整排查和事务改造方案。3.3 经济系统经验曲线、等级解锁和作物定价表整个经济系统由两个公式驱动经验换算等级、等级解锁作物。LevelUtil 提供的换算非常朴素public static int getLevel(int exp) { int level 1; int need 20; // 1 级升 2 级需要 20 经验 while (exp need) { exp - need; level; need level * 20; // 每级所需经验 等级 * 20 } return level; }参数说明这个曲线意味着升级越来越贵2 级升 3 级要 40 经验5 级升 6 级要 100 经验。白萝卜每次收获 5 经验、西瓜 26玩家必须持续种高等级作物才能维持升级速度这正好把种低级菜刷经验的成本抬高了。解锁条件存在 t_crop.unlock_level买种子时校验一次收菜时不再重复校验。初始数据里五种植物的定价参数如下这是一张可以直接抄进报告里的对照表作物种子价生长分钟卖出价单次经验解锁等级白萝卜20103651胡萝卜30206282土豆403592123番茄6050135184西瓜8070205265从表里能看出来所有作物利润率都在 70% 以上白萝卜更是 10 分钟回本 80%典型的新手期爽、后期被迫换高级作物节奏。经验值和价格成正比所以不存在某种作物绝对最优玩家真正的选择依据是我打算多久回来收一次。这给后续扩展季节系统、随机虫害留了玩法空间。另一个值得留意的问题是种菜扣款后 session 里的 coins 并不会立即更新要等下一次查询用户信息时刷新否则页面上金币数会滞后。这个细节在第五章有对应的排查记录。4. 运行指南四十分钟内把它跑起来并种出第一棵菜4.1 环境准备JDK、MySQL、Tomcat 的版本匹配这份源码是 Maven 工程打包方式 war运行环境是传统 Java Web 三件套。先做版本匹配这是新手最容易在第一步卡住的地方组件版本说明JDK1.8代码里没有用到高于 8 的语法Maven3.6用于导入依赖和打包Tomcat8.5 或 9.08.5 配 JDK8 最稳MySQL5.7 或 8.08.0 需要注意驱动类名驱动是个隐形的坑MySQL 5.7 时代大家写 com.mysql.jdbc.DriverMySQL 8.0 之后驱动类改成 com.mysql.cj.jdbc.Driver如果本机装的是 8.0 而配置里还写着老驱动类名启动时直接抛 ClassNotFoundException。建议统一用 8.0 驱动同时兼容 5.7 服务端。4.2 建库、导数据和连接池配置第一步建库。用命令行或者可视化工具都行关键是字符集必须指定CREATE DATABASE happy_farm DEFAULT CHARACTER SET utf8mb4; USE happy_farm; SOURCE /你的路径/farm.sql;farm.sql 里除了建表语句还有种子初始数据和默认地块数据。每个新用户注册时会自动插入 6 块空地记录这块逻辑在 UserService 里不需要手动处理。导完数据后修改 src/main/resources/druid.properties 里的连接信息driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/happy_farm?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8 usernameroot password123456 initialSize5 maxActive20url 里的三个参数一个都不能省useUnicodetrue 和 characterEncodingUTF-8 保证中文不乱码serverTimezoneGMT%2B8 是显式指定东八区时区否则 MySQL 8 驱动在连接时可能因时区不确定直接报错。password 改成你数据库自己的密码端口不是 3306 的话记得在 url 里同步改。DBUtil 在类加载时读这个文件并初始化 Druid 数据源之后的 DAO 都通过 DBUtil.getConnection() 拿连接。这份源码的 DAO 方法里用 try-with-resources 管理连接省去了手动关闭的麻烦也是我比较推荐的一种写 JDBC 的方式。4.3 导入 IDEA、配 Tomcat、注册账号跑通完整流程导入这一步没有玄学IDEA 里 File - Open选中 pom.xml 按 Maven 工程导入等依赖下载完。如果等待时间异常长检查 Maven 镜像配置这里不展开。接着配置 TomcatRun - Edit Configurations - 左上角加号 - Tomcat Server - Local在 Deployment 标签页添加 Artifact选名字带 exploded 的 war 包Application context 填 /farm。最容易翻车的就在这context 填错后面所有接口都会 404。启动后访问 http://localhost:8080/farm/login.jsp看到登录页就算环境通了。验证流程我建议按这个顺序走每一步都有明确的观察点注册一个账号。密码在表里是明文答辩时说明这是简易版处理即可。登录后进 farm.jsp看到 6 块空地顶部显示 200 金币、等级 1。商店买一个白萝卜种子回农场点第一块地选种子地块变成生长状态。为了快速验证收菜把 t_crop 里白萝卜的 grow_minutes 临时改成 1等 60 秒让页面轮询到成熟了。点击成熟地块收菜提示收获成功。仓库页能看到白萝卜 1金币从 200 经历扣 20 种子钱、加 36 卖价后变成 216。提示把 grow_minutes 改成 1 只是临时测试手段验证完记得改回 10。正式演示前把服务器和本机时间校准避免收菜接口和前端显示判断不一致。整个流程最快半小时。跑通之后再去碰二次开发心里对数据流向就很有底了。5. 避坑记录新手最容易翻车的五个瞬间5.1 登录后所有操作都 404Servlet 映射和 context path 对不上现象页面能打开注册登录都成功但一点种菜按钮浏览器控制台出现 POST /plant 404或者 /farm/plant 404。原因两种可能。一是 Application context 没配对IDEA 里默认可能是 /war 包名而前端 ajax 请求写的是 /farm/plant把 context 设成 /farm 就对上了。二是 Servlet 注解路径写错源码里 WebServlet(/plant)部署后的完整路径是 context /plant请求里不要再拼一层 farm。解决先打开 Network 面板看请求的完整 URL和实际部署路径对比。我一般会在 farm.js 顶部定义 var ctx /farm所有 ajax 请求都用 ctx 拼接以后改 context 只动这一处。5.2 中文乱码页面正常但数据库里全是问号现象注册时填的中文昵称管理工具里看是 ????收菜提示文案也变成乱码。原因三层必须同时是 UTF-8漏一层就乱。第一层是 JSP 的 pageEncoding第二层是 Servlet 在 getParameter 之前要 setCharacterEncoding(UTF-8)第三层是数据库连接 url 里的 characterEncodingUTF-8。JSP 只声明 pageEncoding 而不设置请求编码的话POST 请求必乱GET 还要看 Tomcat 的 URIEncoding 配置。解决最省心的做法是加一个 CharacterEncodingFilter在 web.xml 里注册过滤器把 request 和 response 的编码统一成 UTF-8。源码里各个 Servlet 自己设置了编码单独跑没问题但以后新增接口时容易漏所以还是建议补上过滤器。5.3 菜一直显示刚出苗时间戳被当成秒来用了现象把 grow_minutes 改成 1 后等了好几分钟进度条还是 0%文案一直是刚出苗刷新页面也没变化。原因时间单位问题。plant_time 存的应该是 System.currentTimeMillis()单位是毫秒计算 elapsed 时除以 60000 得分钟。如果某次改动把它改成存秒或者建表时用了 DATETIME 再从 Java 端转换一旦混用elapsed 会偏小 1000 倍10 分钟成熟的作物要等一万分钟。解决统一单位。用 SQL 初始化地块时如果 plant_time 是从 NOW() 转的Java 端要用 Date.getTime() 拿毫秒如果存的是秒数就在计算处除以 1000 换算。我排查这个问题的口诀是先看 plant_time 是 13 位还是 10 位数字13 位是毫秒10 位是秒。5.4 收菜提示还没熟但页面明明显示成熟了现象页面上已经出现成熟了点击收菜却提示还没熟再等等过几分钟又能收成功。原因前端进度条和后端成熟判断用的不是同一个时间基准。farm.js 每 30 秒轮询一次拿到的是上一次的计算结果等用户点击时已经过了几秒甚至几十秒如果 grow_minutes 设置得很短比如测试时的 1 分钟这个误差就会被放大。另一类情况是服务器时区被改成零区和本地差 8 小时导致时间戳换算偏移。解决这属于边界情况页面显示的是 30 秒前的快照不完全是 bug。我会在点击收菜时先禁用按钮收到还没熟后延迟 5 秒自动重试一次而不是让用户干等。如果是时区问题检查服务器时区设置把东八区对齐即可。5.5 收菜后金币和仓库数据对不上事务没包住现象连续快速收获多块地后仓库里的作物数量比实际收的少或者金币总额差一点偶尔还会出现一块地收了两次。原因这正是第三章提到的隐患。harvest 方法里 warehouseDao.add、userDao.updateCoins、userDao.updateExp、fieldDao.clear 是四次独立操作每条 SQL 自动提交。第三次抛异常前面两次已经提交玩家白得作物地还没清掉还能再收一次。解决service 里用同一个 Connection 包事务。常见做法是Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); warehouseDao.add(conn, user.getId(), crop.getId(), 1); userDao.updateCoins(conn, user.getId(), crop.getSellPrice()); userDao.updateExp(conn, user.getId(), crop.getExpReward()); fieldDao.clear(conn, fieldId); conn.commit(); } catch (Exception e) { conn.rollback(); return Result.error(收货失败 e.getMessage()); } finally { conn.setAutoCommit(true); conn.close(); }参数说明改造后四个 DAO 方法都要接收 Connection 作为第一个参数这打破了 DAO 内部自己拿连接的写法是 Java Web 项目里做事务最朴素也最好讲的一种方式。改完再去连续收获十块地做回归测试数据不会再抖。6. 二次开发从单机种菜到好友互动的改造路线6.1 加一个好友采摘入口原版只支持自己收菜二开最先值得做的是好友偷菜。设计上先建 t_friend 表和 t_steal_log 表再在 harvest 校验之前插一步判断当前用户能不能摘别人的地。逻辑顺着第三章的成熟度判断往下写public Result steal(User visitor, int fieldId) { Field field fieldDao.findById(fieldId); if (field.getUserId() visitor.getId()) { return Result.error(这是自己的菜直接收); } Crop crop cropDao.findById(field.getCropId()); long elapsed (System.currentTimeMillis() - field.getPlantTime()) / 60000L; if (elapsed crop.getGrowMinutes()) { return Result.error(还没熟偷不了); } warehouseDao.add(visitor.getId(), crop.getId(), 1); // 偷得一个 return Result.success(); }验证方法是注册两个账号互为好友A 种菜B 去 A 的农场点击成熟地块B 的仓库里能看到作物。把 grow_minutes 改成 1 的话十分钟内能测完整个链路。6.2 加新作物只需要两步新增作物不用改 Java 代码往 t_crop 插一行、往 static 目录加一张图片即可因为商店页和种植逻辑都是遍历 t_crop 渲染的。改的时候注意 unlock_level 别设太高否则玩家一直看不到新作物。我习惯把新作物加在列表末尾给一个略高于老作物的利润率先用等级 1 的账号验证可见性再调数值。6.3 如果想往 Spring Boot 迁Controller 层可以平移成 RestControllerService 和 DAO 原样搬JSP 换成 Thymeleaf 或干脆静态页加 ajax事务问题顺手用 Transactional 解决比手写 Connection 优雅。但 JSP 里的 EL 表达式洗成 Vue 模板会花不少时间别想一次性全改完先让登录注册跑通再迁移农场主页面每一步都能验证再继续。从那以后我每次拿到一份 Java Web 课程设计源码第一件事不是急着配环境跑起来而是先翻建表语句把状态机怎么流转、金币怎么流入流出画在纸上确认这两点再动手。这个习惯帮我少改了很多冤枉代码也让我在答辩被追问时总能接得上话。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站