简介:这份 Java 简易版开心农场源码包面向正在学习 Java 与 Android 开发的初中级学习者,同时以智慧北京项目为背景,梳理了产品经理、程序员、UI 设计师、测试工程师等角色的分工。项目说明还覆盖了需求分析、需求评估、任务分配、测试、上线和版本迭代的完整流程,适合借此了解移动端团队协作的推进方式。随附内容具体到聊天模块的语音收发、上划取消、时间限制、发图片/视频、朋友圈、摇一摇、漂流瓶等功能点,并结合闪屏页的旋转、缩放、渐变动画与在 Activity#onCreate() 中通过全局布局监听器获取 view 宽高和 margin 的常见技巧,帮助读者建立从原型到实现的项目认知。资源包为 zip 压缩包,大小约 6.56MB,已有 428 人学习下载;适合需要参考开源项目源码和开发流程文档的初学者进一步实践,也可作为高校软件工程课程的项目案例参考。
1. 先说结论:一份能当课设交差、也能当 Swing 练手项目的开心农场源码
上礼拜帮一个同学看课设,他在网上翻到一份 java 简易版开心农场源码,包名带着 ZHBJ:智慧北京的题目标号,说是某高校信息技术实践活动的题目。这个项目本质是个系统开源的桌面农场游戏:Swing 画的田地界面,MySQL 存用户、作物和好友关系,你注册账号后能种菜、浇水、收获,还能跑到好友的地里偷果实。同学一直担心课设过不了,结果验收那天直接开 MySQL、跑主类、登录、种菜、偷菜一条龙演示完,老师问了两句就放行了。想找课设参考的、想学 Swing 写桌面程序的、以及想拆一份分层清晰 Java 小项目源码的,都值得花一个晚上把它跑起来。
2. 技术选型与整体架构:Swing + MySQL,一个干净的分层小项目
先说说这份源码选型上的取舍。它没有上 Spring Boot,没有用 Maven 管理一堆依赖,界面也没有用 JavaFX,就是用 JDK 自带的 Swing 加 JDBC 直连 MySQL。初看会觉得简陋,拆完会发现这样的组合恰恰是课设场景最稳的选择:实验室的机器只要能跑 JDK,就能把界面撑起来;数据库只要装了 MySQL,建库脚本一导就完事。对想读源码的人来说,没有框架包装,业务逻辑全裸在代码里,反而更容易看懂每一行在干什么。
2.1 技术栈选型:为什么是 Swing 而不是 JavaFX
Swing 是 JDK 自带的 GUI 组件库,编译完直接能跑,不需要额外装运行时。JavaFX 虽然界面更现代,但在大部分高校机房的老机器上经常缺 JavaFX 运行时组件,光配置模块路径就得折腾半天,课设答辩现场翻车的概率大增。这份源码选 Swing,说明作者优先考虑的是"在任何一台装了 JDK 的电脑上都能跑起来",而不是界面的华丽程度。
数据层用的是 JDBC + MySQL,没有引入 MyBatis 或者 Hibernate。对一个地块操作、用户登录、偷菜扣钱这种量级的业务来说,ORM 框架带来的配置成本远大于收益。自己写 SQL 反而能把每个操作对应到哪张表、改哪几个字段看得一清二楚。源码里唯一引入的外部依赖只有 MySQL 官方 JDBC 驱动 jar,这也是整个项目跑起来之前需要手动添加的一个东西,后面的避坑章节我会专门提到。
2.2 源码包目录:从启动入口到 DAO 的分层结构
解压源码包之后,目录长这样:
farm/ ├── src/ │ ├── com/zhbj/farm/ │ │ ├── Main.java # 启动入口,负责拉起登录窗口 │ │ ├── view/ # Swing 界面层 │ │ │ ├── LoginFrame.java # 登录注册窗口 │ │ │ ├── FarmFrame.java # 农场主界面,包含田地网格 │ │ │ ├── ShopDialog.java # 商店选种对话框 │ │ │ └── FriendPanel.java # 好友列表面板 │ │ ├── controller/ # 业务控制层,处理按钮事件 │ │ │ ├── UserController.java │ │ │ └── PlotController.java │ │ ├── model/ # 实体类:User、Plant、Plot │ │ │ ├── User.java │ │ │ ├── Plant.java │ │ │ └── Plot.java │ │ ├── dao/ # JDBC 数据访问层 │ │ │ ├── UserDAO.java │ │ │ ├── PlantDAO.java │ │ │ ├── PlotDAO.java │ │ │ └── FriendDAO.java │ │ └── util/ │ │ ├── DBUtil.java # 统一管理数据库连接 │ │ └── ImageLoader.java # 从 resources 加载作物图标 │ └── resources/ │ ├── db/farm.sql # 建库建表脚本 │ └── images/ # 各种作物的图标文件 └── README.md # 原作者的运行说明这个结构基本是照着 MVC 的思想砍出来的:view 只负责画界面和接收鼠标事件,controller 做业务判断,dao 只拼 SQL 查数据,model 就是单纯的字段载体。虽然没有 Spring 的注解和自动装配,但每个类的职责边界很清楚。数据在层与层之间的流转顺序是:界面按钮事件触发 controller 的方法,controller 调 dao,dao 从 DBUtil 拿连接执行 SQL,结果封装成 model 对象逐层返回,最后界面用 model 的数据刷新显示。
顺着这个调用链去读源码,你会发现真正不好写的部分不在界面,而在"地块状态怎么变化、金币怎么保证不错"这两件事上,这也是第 3 章要展开的核心内容。
2.3 数据库设计:五张核心表撑起农场全部玩法
打开 resources/db/farm.sql,一共建了五张表。我把每张表的职责和关键字段整理成了表:
| 表名 | 职责 | 关键字段 |
|---|---|---|
| user | 用户账号与金币 | user_id, username, password, coin |
| plant | 作物配置 | plant_id, name, seed_cost, grow_time, sell_price, icon |
| plot | 地块状态 | plot_id, user_id, plant_id, status, plant_time |
| friend | 好友关系 | id, user_id, friend_id |
| log | 操作流水 | log_id, user_id, detail, create_time |
user 表不用多说,存账号密码和金币余额。plant 表是作物字典,游戏里能种什么、种子多少钱、多久成熟、卖多少钱,全在这张表里配置。plot 表是最核心的一张表,它记录了每一块地的归属、种了什么作物、当前状态和种植时间。friend 表是一张简单的自关联表,记录谁和谁是好友。log 表是操作流水,偷菜、收获、购买种子都会往里插一条记录,方便排查"金币怎么对不上"这类问题。
plot 表的设计很关键,我单独把建表脚本贴出来:
CREATE TABLE plot ( plot_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, plant_id INT DEFAULT NULL, status TINYINT DEFAULT 0 COMMENT '0空地 1生长中 2成熟', plant_time DATETIME DEFAULT NULL, FOREIGN KEY (user_id) REFERENCES user(user_id) );status 字段只有 0、1、2 三个值,分别对应空地、生长中、成熟,这就是整个农场玩法的状态机基础。plant_time 记录种子种下的时刻,用当前时间减去 plant_time 再和 plant 表的 grow_time 比较,就能算出这块地还剩多久成熟。原版开心农场很多状态存在内存里,重启就丢,这份源码把地块状态落到了 MySQL,重启之后地块还在原地,光是这一点作为课设答辩就是一个能拿出来的改进点。
3. 核心模块拆解与启动:从登录到偷菜的完整链路
这一章按读源码的顺序,把从登录到偷菜几条关键链路拆开讲。每一步都贴出核心代码并说明参数含义,最后给出一份可以直接照抄的启动命令。
3.1 登录与主界面装载
程序入口是 Main.java,它只干一件事:new 出登录窗口然后 setVisible。真正做登录判断的是 UserController,代码长这样:
// UserController.java public boolean login(String name, String pwd) { User user = userDAO.login(name, pwd); if (user != null) { // 登录成功,把当前用户对象传给农场主界面 new FarmFrame(user).setVisible(true); return true; } JOptionPane.showMessageDialog(null, "账号或密码不对"); return false; }这段代码是典型的 Swing 事件回调加一层薄薄的 controller。LoginFrame 的按钮事件只负责从文本框取值,然后调 login 方法。这里有个细节:界面层取密码时用的是 getPassword() 而不是 getText(),返回的是 char[] 而不是 String,这是 Swing 的安全习惯,防止密码字符串常驻内存被 dump 出来。DAO 的 login 方法内部执行的是"SELECT * FROM user WHERE username=? AND password=?",查询成功就 new 一个 User 对象返回,查不到就返回 null。
登录成功后,FarmFrame 接收 User 对象,这个对象会在整个会话里被反复使用。比如扣金币、涨金币都要先改内存里的 user 对象,再同步调 userDAO.updateCoin 落库。这种"内存先改,再落库"的模式在这份源码里贯穿始终,后面讲种菜时你会再次看到。
3.2 植物生长状态机:定时线程驱动成熟
农场玩法的核心是地块状态机。前面建表脚本里 status 只有三个值:0 空地、1 生长中、2 成熟。状态流转是:0 -> 1(种菜)-> 2(成熟),2 之后要么收获回到 0,要么被偷后直接回到 0。驱动 1 到 2 转变的不是用户操作,而是一个定时任务。
FarmFrame 里有一段定时器代码,每 5 秒扫一次当前用户所有地块:
Timer timer = new Timer(5000, e -> { for (Plot p : plotList) { if (p.getStatus() == 1) { // growTime 单位是分钟,换算成毫秒 long growMs = p.getGrowTime() * 60L * 1000L; if (System.currentTimeMillis() - p.getPlantTime().getTime() >= growMs) { p.setStatus((byte) 2); // 内存里先改成成熟 plotDAO.updateStatus(p); // 落库,防止重启丢状态 } } } refreshGrid(); // 重绘田地网格 }); timer.start();把成熟判断的算法拆开看:plant_time 是种下时刻,grow_time 是作物配置里的成熟分钟数,两者比较算出是否到时间。growMs 的计算用了 60L * 1000L 两个 Long 型常量,避免 int 溢出,这个细节在课设代码里很少见,值得学。轮询间隔设的是 5000 毫秒,也就是成熟时间最多有 5 秒的误差,课设演示完全够用。如果你做二次开发想精确到秒,可以把 Timer 的间隔和 grow_time 的单位都改成秒。
这种轮询的做法在生产环境会被批评浪费资源,但对单机课设来说是最简单的方案:不用引入 Quartz,不用跑消息队列,一个 Swing Timer 就完成了从生长到成熟的全部推进逻辑。要读懂这份源码,先抓住这个定时器,后面所有界面刷新都建立在它之上。
3.3 地块操作:种菜、收获的代码路径
种菜的逻辑在 PlotController 里,入口是商店对话框点选某一种作物。核心方法如下:
// PlotController.java public void seedPlot(Plot plot, Plant plant) { // 第一步:校验金币够不够 if (user.getCoin() < plant.getSeedCost()) { JOptionPane.showMessageDialog(frame, "金币不够"); return; } // 第二步:扣金币,改地块状态为生长中 user.setCoin(user.getCoin() - plant.getSeedCost()); plot.setPlantId(plant.getPlantId()); plot.setStatus((byte) 1); plot.setPlantTime(new Date()); // 第三步:分别落库 userDAO.updateCoin(user); plotDAO.save(plot); refreshGrid(); }这个方法的执行顺序值得细看:先校验,再改内存,最后落库。校验放在最前面是为了避免无效的数据库操作;内存先改是为了让界面可以立即用 User 对象的新金币数去刷新显示,不用重新查库;落库放在最后保证异常发生时内存和数据库尽量一致。seedCost、growTime、sellPrice 这三个字段全部来自 plant 表的配置,也就是说新增作物不用改 Java 代码,只要往 plant 表插数据就行,这一点到第 5 章二次开发时还会用到。
收获的逻辑和种菜对称——检查 status 是否为 2,是就把金币加上 plant.sellPrice,再把地块 status 改回 0,plant_id 清空。这套"校验 -> 改状态 -> 落库 -> 刷新界面"的模板在收获、铲地、浇水里反复出现,读会一个就等于读会了四个功能。
3.4 偷菜逻辑:事务与先查后改的必要性
偷菜是这份源码里最有技术含量的一段,也是拆的时候最惊喜的部分。很多课设版本的偷菜就是简单地"改个状态加个钱",但这份源码里偷菜走的是事务:
// PlotController.java public boolean stealPlot(User thief, Plot plot) { Connection conn = null; try { conn = DBUtil.getConnection(); // 开启事务,后面三条 SQL 要么全成功要么全回滚 conn.setAutoCommit(false); // 偷之前再查一次,防止这块地刚被主人收了 Plot fresh = plotDAO.findById(conn, plot.getPlotId()); if (fresh.getStatus() != 2) { conn.rollback(); return false; } plotDAO.updateStatus(conn, plot.getPlotId(), (byte) 0); // 偷完变空地 userDAO.addCoin(conn, thief.getUserId(), 50); // 小偷加 50 userDAO.subCoin(conn, fresh.getOwnerId(), 50); // 主人扣 50 conn.commit(); return true; } catch (Exception ex) { if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} } return false; } finally { DBUtil.closeQuietly(conn); } }这段代码有三个要点。第一,setAutoCommit(false) 把自动提交关掉,之后的 updateStatus、addCoin、subCoin 三条 SQL 变成一个原子操作,任何一条失败都能整体回滚,避免出现"地空了但金币没扣"这种数据不一致。第二,DAO 方法全部接收 Connection 参数,而不是自己内部再开新连接——这是事务能否生效的关键,如果每个 DAO 自己拿连接,事务就失效了。第三,偷之前先重新查一次地块状态,防止两个人同时偷同一块地的并发问题,MySQL 里 updateStatus 更新的是同一行,天然有行锁,加上这个"先查再做"的防御,基本堵住了重复偷的路。
偷菜罚金 50 金币是写死在代码里的,严格说应该做成可配置参数放在 plant 表或者单独的配置表里,这是这份源码留给二次开发的一个优化点。有些改过这份源码的同学把这里改成随机罚金或者按作物比例扣钱,都是从这个方法入手的。
3.5 三步启动:建库、改连接、运行主类
读到这里你已经知道核心逻辑了,现在把它跑起来。假设你的电脑已经装好了 JDK 8 和 MySQL,整个过程三步:
# 第一步:导入建库脚本,会自动创建 farm 库和五张表 mysql -u root -p < src/resources/db/farm.sql # 第二步:打开 DBUtil.java,把连接串和密码改成你本机的 # jdbc:mysql://localhost:3306/farm?useSSL=false&serverTimezone=Asia/Shanghai # 第三步:编译并运行(Windows 用分号分隔 classpath,Linux 用冒号) javac -encoding UTF-8 -cp .;lib/mysql-connector-java.jar com/zhbj/farm/Main.java java -Dfile.encoding=UTF-8 -cp .;lib/mysql-connector-java.jar com.zhbj.farm.Main连接串里的 serverTimezone=Asia/Shanghai 是给 MySQL 8 用的,旧版 MySQL 5 不加也能跑,加了也没坏处。useSSL=false 是关掉安全连接提示,本地开发必须加,不然控制台会刷一堆警告。如果你用的是 IDEA,第三步可以直接略过,右键 Main.java 点 Run 就行,前提是把 mysql-connector-java.jar 通过 Project Structure 加到 Module 依赖里。
跑起来之后建议注册两个账号,互相加好友,这样演示偷菜时不需要另找一台机器。演示时主号种菜,小号去偷,再把小号的金币转给主号看余额变化,五分钟就能把完整功能链走一遍。
4. 避坑记录:首次运行农场源码的五个高频问题
这一章写的是陪跑这份源码时踩过的坑,每一条都是"现象 -> 原因 -> 解决"的格式。这些问题大多和代码本身无关,而是环境或配置的细节,但它们能让一个能跑的项目死活起不来,最离谱的一次花了一晚上才发现是密码没改对。这类问题经常被当玄学,其实原因都很具体。
4.1 环境与连接类问题
现象一:一运行就报 ClassNotFoundException: com.mysql.jdbc.Driver。
原因:项目里没有引入 MySQL JDBC 驱动 jar。源码包只给了 src 目录,lib 目录需要自己建,容易漏。
解决:从 MySQL 官网或 Maven 仓库下载 mysql-connector-java 对应版本的 jar,拷到项目根目录的 lib 文件夹里,然后在 IDE 里右键 jar -> Add as Library。如果用命令行编译,记得把 jar 加进 -cp 参数,也就是 3.5 节里写的那条 javac 命令。
现象二:报错 Access denied for user 'root'@'localhost'。
原因:DBUtil.java 里写死的数据库账号密码和你本机 MySQL 实际的不一致。源码原作者默认用的是 root / 123456,很多同学的 MySQL 装的是别的密码。
解决:打开 DBUtil.java,把 password 常量改成你本机的密码,重新编译就行。顺带说一句,把密码硬编码在 Java 类里是课设的常规操作,但如果你想把这份源码改成自己长期用的工具,建议把连接串抽到 properties 配置文件里,DBUtil 启动时读取,这样换机器不用重新编译。
现象三:报错 The server time zone value '中国标准时间' is unrecognized。
原因:MySQL 8 的时区参数和 MySQL 5 不一样,连接串里没带 serverTimezone 就会报这个。这是 MySQL 8 升级带来的老问题。
解决:连接串改成 jdbc:mysql://localhost:3306/farm?useSSL=false&serverTimezone=Asia/Shanghai,改完重启程序。如果你连的 MySQL 是 5.x,这个报错基本不会出现。
4.2 运行与逻辑类问题
现象四:界面和数据库里的中文全部变成问号。
原因:源码文件是 UTF-8 编码,但 Windows 命令行默认用 GBK 编码去读 Java 文件,javac 编译时没指定编码,中文就乱了。
解决:用 IDEA 的话,把 File Encoding 和 Project Encoding 都设成 UTF-8;用命令行编译的话,编译时加 -encoding UTF-8,运行时加 -Dfile.encoding=UTF-8。我在 3.5 节的启动命令里已经把这俩参数写进去了,直接抄就行。
现象五:偷菜之后金币数额对不上,或者两个人同时偷一块地,两个人都偷成功了。
原因:是"先查状态再改状态"之间存在时间差,两个请求同时读到 status=2,都执行了 update,导致同一块地被偷两次。另一个常见原因是改动代码时把事务去掉了,三条 SQL 变成独立提交,其中一条失败就出现数据不一致。
解决:把偷菜流程保持在第 3.4 节那个事务版本里,别拆开。如果要彻底防并发,可以在 plot 表加一个 version 字段做乐观锁,更新时带上 WHERE version=?,受影响行数为 0 就说明已经被别人改过了,直接返回失败。这是面试和答辩都爱问的并发控制问题,把这段讲清楚非常加分。
提示:如果你改了 DBUtil 里的密码后发现还是报错,先确认你改的是不是被编译过的那个类。IDEA 里改了源码记得 Build 一次,命令行改完要重新 javac,这种"改了没生效"的坑,十个里有八个是用错了 class 文件。
5. 二次开发:从加一种新作物到新增一块地
这份源码最友好的一点是,加新作物不用改 Java 代码,只需要做三件事:往 plant 表插一条数据、准备一张作物图标、重启项目。插数据的 SQL 长这样:
INSERT INTO plant (name, seed_cost, grow_time, sell_price, icon) VALUES ('番茄', 80, 20, 150, 'tomato.png');参数含义按顺序是:作物名、种子价格、成熟分钟数、卖出价格、图标文件名。插入之后把 tomato.png 放进 resources/images 目录,商店面板是每次打开时动态查询 plant 表的,重启项目新作物就会出现在商店列表里。想验证的话,注册新账号买种子种下,把 grow_time 临时改成 1 分钟,等它成熟再收获,确认金币到账、地块状态变回空地,整条链路就算通了。
加新地块稍微动点代码。FarmFrame 里田地网格用的是 GridLayout,行列数是两个常量,把行数从默认的 3 改成 4,地块总数就多了三分之一。注意改完要处理 plotDAO 里按用户查地块的 SQL,如果它写死了返回条数,记得去掉 LIMIT 限制。我的习惯是加完新功能先看日志,再完整跑一遍"种 -> 熟 -> 收 -> 偷"四个动作,确认没有任何异常才交货。
那次陪同学改完番茄作物之后,我落下一个习惯:动数据库脚本之前,先看一眼对应的 DAO 查询字段对不对得上,改完代码强制重启一次完整流程再收工。这次拆这份源码最大的体感是,小而全的项目反而最能看清 Swing 和 JDBC 的配合方式,希望帮到你。
本文还有配套的精品资源,点击获取