1. 校园智能点餐系统项目整体设计与技术选型
1.1 为什么选择Android Studio作为开发主阵地
做校园点餐这类项目,最怕的就是选错技术栈。我见过太多同学一上来就想搞跨平台,结果环境配置卡了三天,代码没写几行,热情先耗光了。Android Studio作为官方原生开发工具,在校园项目这个场景下优势非常明显:调试链路短、文档齐全、模拟器成熟,遇到问题搜一下基本都有答案。对于课程设计或者毕业设计来说,稳定跑起来比炫技重要得多。
这个项目的核心逻辑其实不复杂:学生端浏览菜品、下单、支付(模拟)、查看订单状态;商家端管理菜品、接单、更新出餐进度。但要把这套流程做顺,涉及的技术点相当密集——UI布局、本地数据库、网络请求、多线程处理、状态同步,每一个环节都有坑。我选择用Java作为主要开发语言,虽然Kotlin现在是官方推荐,但考虑到大部分高校课程还是以Java为主,用Java写出来的代码更容易被同学理解和二次修改。
技术选型上,我做了几个关键决策。本地数据存储用SQLite配合Room持久化库,Room的好处是编译期就能检查SQL语句的正确性,避免运行时才发现表名写错这种低级错误。网络层用OkHttp加Retrofit的组合,Retrofit的注解式接口定义让代码可读性提升一个档次。图片加载用Glide,一行代码搞定缓存和圆角处理。这些库都是经过大量项目验证的,社区活跃,遇到问题容易找到解决方案。
注意:不要一上来就引入太多第三方库。我见过有同学在项目里塞了十几个依赖,结果版本冲突导致构建失败,排查了半天。建议每引入一个库都先跑一遍构建,确认没问题再继续。
1.2 系统架构的分层思路与模块划分
整个项目我采用了经典的三层架构:表现层、业务逻辑层、数据访问层。表现层负责UI展示和用户交互,Activity和Fragment都在这一层;业务逻辑层处理订单状态流转、价格计算、库存校验等核心规则;数据访问层封装了Room的DAO操作和Retrofit的网络请求。这样分层的好处是,当需求变更时,比如要增加一个“预约取餐”功能,只需要在业务层增加相应逻辑,UI层和数据层改动很小。
模块划分上,我拆成了五个主要包:ui包放所有Activity和Adapter,model包放实体类,db包放Room相关类,network包放Retrofit接口和响应模型,util包放工具类。这种划分方式在中小型项目中足够清晰,不会出现找一个类翻遍整个项目的情况。
数据库设计方面,核心表有四张:用户表、菜品表、订单表、订单详情表。订单详情表的设计是关键,它记录了每个订单中包含的菜品、数量、单价,这样即使菜品价格后续调整,历史订单的金额也不会算错。这个细节很多同学会忽略,直接把菜品信息冗余在订单表里,后期做数据统计时就会很痛苦。
1.3 开发环境搭建与项目初始化实操
安装Android Studio这件事,说简单也简单,说坑多也确实多。首先去官网下载最新稳定版,注意要选对系统版本。下载完成后安装,一路下一步就行,但有几个地方需要留意:安装路径不要有中文和空格,SDK路径同理。我见过有同学把SDK放在“D:\我的文件\Android SDK”下面,结果Gradle构建时报了一堆路径错误,排查了半天才发现是中文路径的问题。
安装完成后第一次启动,会提示下载SDK组件。这里建议至少勾选三个版本:当前最新稳定版、上一个稳定版、以及API 21(Android 5.0)。为什么要装API 21?因为校园里还有不少同学的手机是老旧机型,最低兼容版本设到21能覆盖绝大多数设备。SDK下载完成后,创建一个新的Empty Activity项目,语言选Java,最低API选21。
项目创建好后,第一件事是配置Gradle。打开build.gradle文件,在dependencies块中添加项目需要的依赖库。这里有个经验:把依赖版本号统一管理,在项目根目录的build.gradle中定义变量,子模块引用变量。这样做的好处是升级版本时只需要改一个地方,避免版本不一致导致的诡异问题。
// 项目根目录 build.gradle ext { roomVersion = '2.6.1' retrofitVersion = '2.9.0' glideVersion = '4.16.0' }然后在app模块的build.gradle中引用这些变量。配置完成后点击Sync Now,等待Gradle同步完成。如果同步失败,大概率是网络问题,可以尝试切换Gradle的仓库地址,或者手动下载对应的依赖包放到本地仓库。
提示:Gradle首次同步会下载大量文件,建议在网络稳定的环境下操作。如果公司或学校网络有限制,可以配置国内镜像仓库,速度会快很多。
2. 核心功能模块的详细拆解与实现要点
2.1 用户登录注册模块的完整实现
登录注册看起来简单,但要做好并不容易。这个模块我实现了三种登录方式:学号密码登录、手机验证码登录(模拟)、以及游客模式。学号密码登录是主要方式,密码在存储时做了MD5加密处理,虽然MD5现在安全性不够高,但对于校园项目来说足够用了,而且实现简单,不会引入额外的复杂度。
注册流程中,我加了学号格式校验。不同学校的学号格式不一样,有的是纯数字,有的包含字母。我在代码里做了一个可配置的正则表达式,部署到不同学校时只需要改这个正则就行。这个设计思路来源于实际部署时的经验——如果写死校验规则,换一个学校就得改代码重新打包,非常麻烦。
// 学号校验正则,可根据学校规则调整 private static final String STUDENT_ID_PATTERN = "^[0-9]{10}$"; public static boolean isValidStudentId(String studentId) { return Pattern.matches(STUDENT_ID_PATTERN, studentId); }登录成功后,用户信息会保存在SharedPreferences中,同时写入一个登录状态标志。这里有个细节需要注意:保存密码时不要明文存储,即使做了MD5,也建议再加一层盐值。我在项目中用了随机盐值加MD5的方式,每个用户的盐值不同,这样即使数据库泄露,攻击者也无法通过彩虹表反推出原始密码。
自动登录的实现逻辑是:App启动时检查SharedPreferences中的登录状态,如果已登录且未过期,直接跳转到主界面。过期时间我设置的是7天,这个时长可以根据实际需求调整。过期后需要重新登录,但学号会保留在输入框中,减少用户操作步骤。
2.2 菜品展示与购物车逻辑的联动设计
菜品展示模块我用了RecyclerView加GridLayoutManager,两列布局展示菜品图片和名称。每个菜品卡片上显示图片、名称、价格和月销量。图片加载用Glide,设置了占位图和错误图,避免网络慢时出现空白。这里有个优化点:Glide默认的缓存策略是ALL,即同时缓存原始图片和转换后的图片,对于菜品图片这种尺寸不大的资源,可以改成只缓存转换后的图片,节省存储空间。
购物车的实现是整个项目中最复杂的部分之一。我用了本地数据库来存储购物车数据,而不是简单的内存列表。为什么这么做?因为用户可能在选菜过程中退出App,如果购物车数据只存在内存里,退出后就丢失了,体验很差。用数据库存储后,即使App被杀掉,重新打开购物车里的菜品还在。
购物车与菜品列表的联动逻辑是这样的:点击菜品卡片上的“+”按钮,购物车中该菜品的数量加一;如果购物车中还没有这个菜品,则新增一条记录。菜品卡片上会实时显示当前已选数量,这个数量通过观察者模式从数据库查询后更新UI。具体实现上,我用了Room的LiveData,当购物车数据变化时,UI自动刷新,不需要手动调用刷新方法。
// 购物车ViewModel中观察购物车数据变化 cartViewModel.getAllCartItems().observe(this, cartItems -> { // 更新菜品列表上的数量显示 adapter.updateCartQuantities(cartItems); // 更新底部购物车栏的总价和数量 updateCartBar(cartItems); });购物车底部栏的设计参考了主流外卖App:左侧显示购物车图标和总数量,中间显示总价,右侧是“去结算”按钮。当购物车为空时,底部栏隐藏;有菜品时,底部栏从底部滑入。这个动画效果用属性动画实现,时长设置在300毫秒左右,太快了显得突兀,太慢了显得拖沓。
2.3 订单提交与状态流转的核心机制
订单提交涉及多个步骤的原子性操作:生成订单号、写入订单表、写入订单详情表、清空购物车、扣减库存(如果有)。这些操作必须全部成功或者全部失败,否则会出现订单生成了但购物车没清空,或者库存扣了但订单没生成的问题。我用Room的事务机制来保证原子性,把这些操作放在一个@Transaction注解的方法中执行。
订单号生成规则我设计成“日期+时间戳后四位+随机两位”,这样既能保证唯一性,又便于人工识别。比如“2025011514302501”表示2025年1月15日14点30分25秒的第01号订单。这个规则在实际使用中很方便,商家看到订单号就能知道下单时间,不需要再去查数据库。
订单状态流转是另一个关键点。我定义了五种状态:待支付、已支付、制作中、待取餐、已完成。状态之间的流转有严格的规则,比如待支付只能流转到已支付或已取消,已支付只能流转到制作中。这些规则在业务层做了校验,防止出现状态跳跃的异常情况。
| 当前状态 | 可流转状态 | 触发操作 |
|---|---|---|
| 待支付 | 已支付、已取消 | 用户支付/用户取消 |
| 已支付 | 制作中、已退款 | 商家接单/商家拒单 |
| 制作中 | 待取餐 | 商家出餐 |
| 待取餐 | 已完成 | 用户取餐确认 |
| 已完成 | 无 | 订单结束 |
状态流转的实现用了状态模式,每个状态对应一个类,类中定义了该状态下允许的操作。这样做的好处是新增状态时只需要增加一个类,不需要修改大量if-else逻辑。虽然对于校园项目来说可能有点过度设计,但这是一个很好的学习机会,能帮助理解设计模式的实际应用。
2.4 商家端菜品管理与订单处理功能
商家端的功能相对简单一些,但也有一些需要注意的地方。菜品管理包括增删改查,其中删除操作我做了软删除处理,即把菜品标记为已删除而不是真正从数据库中移除。为什么要这样做?因为历史订单中可能引用了这个菜品,如果直接删除,查看历史订单时就会出现菜品信息缺失的问题。软删除后,历史订单仍然能正常显示菜品名称和价格。
订单处理界面用了TabLayout加ViewPager2的组合,分为“待接单”、“制作中”、“待取餐”三个标签页。商家可以在待接单页面看到新订单,点击接单后订单自动流转到制作中页面。这个交互设计参考了实际商家的操作习惯——他们通常希望一眼看到所有待处理的订单,而不是在一个长列表中翻找。
订单提醒功能我用了本地通知实现。当有新订单时,发送一条通知,点击通知直接跳转到订单详情页。通知的渠道在Android 8.0以上需要配置NotificationChannel,这个细节很多同学会忽略,导致在较新系统上通知不显示。配置Channel的代码很简单,但如果不写,通知就静默失败了。
// 创建通知渠道(Android 8.0+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "新订单提醒", NotificationManager.IMPORTANCE_HIGH ); channel.setDescription("收到新订单时提醒商家"); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); }3. 从零到一的完整部署与调试实操记录
3.1 项目导入与Gradle构建的避坑指南
拿到源码后第一步是导入项目。打开Android Studio,选择“Open an Existing Project”,找到项目根目录下的build.gradle文件,点击打开。这里有个常见问题:如果项目使用的Gradle版本和你本地安装的版本不一致,Android Studio会提示下载对应版本。建议选择“Use Gradle from: gradle-wrapper.properties file”,让项目自己管理Gradle版本,避免版本冲突。
导入后等待Gradle同步。同步过程中可能会遇到几个典型问题。第一个是依赖下载失败,报错信息通常是“Could not resolve...”或“Connection timed out”。解决办法是在项目根目录的build.gradle中把仓库地址改成国内镜像。具体操作是在repositories块中把google()和mavenCentral()替换成对应的镜像地址。
第二个常见问题是SDK版本不匹配。如果项目编译时用的SDK版本你本地没有安装,会提示“Failed to find target with hash string”。解决办法是打开SDK Manager,安装对应版本的SDK。或者修改项目中的compileSdkVersion和targetSdkVersion为你本地已有的版本,但要注意API差异可能导致代码报错。
第三个问题是JDK版本问题。Android Studio自带JDK,但有时候项目配置的JDK版本和自带的不一致。可以在“File > Project Structure > SDK Location”中检查JDK路径,建议使用Android Studio自带的JDK,避免环境变量冲突。
提示:如果Gradle同步一直失败,可以尝试删除项目根目录下的
.gradle文件夹和build文件夹,然后重新同步。这两个文件夹中缓存了上次构建的信息,有时候缓存损坏会导致同步异常。
3.2 数据库初始化与测试数据填充
项目第一次运行时,数据库是空的,需要初始化一些测试数据。我在db包中写了一个DatabaseInitializer类,在App首次启动时检查数据库是否为空,如果为空则插入预设的测试数据。测试数据包括几个测试用户、十几个菜品分类和对应的菜品。
数据库初始化的时机很关键。我放在Application类的onCreate方法中执行,但要注意不能在主线程做耗时操作。Room的数据库操作默认不允许在主线程执行,所以初始化逻辑需要放在子线程中。我用了一个简单的ExecutorService来执行初始化任务,初始化完成后再通知UI刷新。
// Application中初始化数据库 public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { AppDatabase db = AppDatabase.getInstance(this); if (db.userDao().getUserCount() == 0) { // 插入测试数据 initTestData(db); } }); } }测试数据的菜品图片我放在了assets文件夹中,数据库里只存图片的文件名。显示时通过file:///android_asset/路径加载。这样做的好处是图片不占用数据库空间,而且替换图片时只需要替换assets中的文件,不需要改数据库。菜品分类我设计了六个:热菜、凉菜、主食、汤品、饮品、甜点,基本覆盖了校园食堂的常见品类。
3.3 模拟器与真机调试的差异处理
模拟器调试方便,但和真机有一些差异需要注意。首先是网络请求,模拟器访问本机服务时需要用10.0.2.2这个特殊IP,而不是localhost或127.0.0.1。这个坑我踩过好几次,每次都要愣一下才想起来。如果后端服务部署在局域网的其他机器上,直接用那台机器的IP就行。
其次是性能差异。模拟器的GPU渲染和真机不一样,有些在模拟器上流畅的动画在真机上可能卡顿,反之亦然。我建议在开发阶段就用真机调试,模拟器只用来做快速验证。真机调试需要开启USB调试模式,不同品牌的手机开启方式略有不同,但基本都在“设置 > 关于手机 > 连续点击版本号”这个路径下。
真机调试还有一个常见问题是Android版本差异导致的UI适配问题。比如状态栏高度、导航栏样式、权限弹窗样式,在不同版本上表现不一样。我在项目中用了EdgeToEdge的方式处理状态栏,让内容延伸到状态栏下方,同时给顶部内容加上状态栏高度的padding。这样在不同版本上都能获得一致的视觉效果。
// 处理状态栏高度 ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; });3.4 打包APK与部署到校园服务器的流程
项目开发完成后,需要打包成APK分发给同学使用。打包前要配置签名文件,在“Build > Generate Signed Bundle / APK”中创建新的签名文件。签名文件要妥善保管,后续更新APK时必须用同一个签名文件,否则用户无法覆盖安装。
打包时选择release版本,并开启代码混淆和资源压缩。混淆配置需要仔细写,Room的实体类和DAO接口不能被混淆,否则运行时会找不到对应的类。Retrofit的接口定义也不能混淆,需要在proguard-rules.pro中添加keep规则。
# Room相关类不混淆 -keep class com.example.campusorder.db.** { *; } # Retrofit接口不混淆 -keep interface com.example.campusorder.network.** { *; } # 实体类不混淆 -keep class com.example.campusorder.model.** { *; }APK打包完成后,可以部署到校园服务器上供同学下载。如果只是课程设计演示,直接把APK文件发给老师或同学安装就行。如果要真正在校园内使用,需要考虑后端服务的部署。后端我用了Spring Boot加MySQL,部署在校园网的服务器上,配置好数据库连接和端口后启动即可。前端App中的API地址需要改成服务器的IP地址,这个配置我放在了Constants类中,方便统一修改。
4. 开发过程中踩过的坑与排查技巧实录
4.1 常见编译错误与快速定位方法
开发过程中遇到最多的就是编译错误。Android Studio的报错信息有时候很隐晦,需要一些经验才能快速定位。我整理了几个高频错误和对应的排查思路。
第一个是“Cannot resolve symbol”,通常是因为缺少import或者依赖没有正确引入。排查方法是先检查import语句,如果import没问题,再检查build.gradle中是否添加了对应的依赖。有时候依赖添加了但Gradle没有同步,点击“Sync Now”即可。
第二个是“Duplicate class”,这个错误通常是因为引入了多个包含相同类的库。比如同时引入了androidx.appcompat和旧版的support-v4,两者包含相同的类。解决办法是统一使用AndroidX,在gradle.properties中添加android.useAndroidX=true和android.enableJetifier=true。
第三个是“Program type already present”,和Duplicate class类似,也是类冲突问题。排查方法是运行gradlew app:dependencies查看依赖树,找到冲突的库,用exclude排除掉重复的依赖。
| 错误类型 | 常见原因 | 排查方法 |
|---|---|---|
| Cannot resolve symbol | 缺少import或依赖 | 检查import和build.gradle |
| Duplicate class | 库冲突 | 统一使用AndroidX |
| Program type already present | 类重复 | 查看依赖树并exclude |
| Gradle sync failed | 网络或版本问题 | 切换镜像或删除缓存 |
4.2 运行时崩溃的排查思路与工具使用
运行时崩溃比编译错误更难排查,因为错误发生在运行阶段,需要看Logcat日志。我常用的排查流程是:先看Logcat中的异常堆栈,找到第一个“Caused by”后面的内容,那通常是根本原因。然后根据异常类型判断问题方向。
NullPointerException是最常见的崩溃类型,通常是因为某个对象没有初始化就使用了。排查方法是找到崩溃行,检查该行涉及的对象是否可能为null。我习惯在可能为null的地方加日志,打印对象状态,快速定位问题。
NetworkOnMainThreadException是因为在主线程做了网络请求。Android从4.0开始禁止在主线程做网络操作,所有网络请求必须放在子线程。我用OkHttp的时候,默认的execute()方法是同步的,需要在子线程调用;enqueue()方法是异步的,可以在主线程调用。建议统一用enqueue(),避免忘记切换线程。
SQLiteException通常和数据库操作有关,比如表不存在、字段名写错、数据类型不匹配。Room在编译期会检查SQL语句,所以这类错误在Room中比较少见。如果用了原生SQLite,建议开启SQLiteDatabase的setForeignKeyConstraintsEnabled,让外键约束生效,避免脏数据。
注意:Logcat中的日志量很大,建议用包名过滤,只看自己应用的日志。在Logcat搜索框中输入
package:com.example.campusorder即可过滤。
4.3 性能优化与内存泄漏的预防措施
校园项目虽然用户量不大,但性能问题还是要注意,尤其是内存泄漏。最常见的内存泄漏是Activity被静态变量或单例持有。比如把Activity的Context传给了单例类,单例的生命周期比Activity长,导致Activity无法被回收。
排查内存泄漏可以用LeakCanary,在build.gradle中添加依赖后,LeakCanary会自动检测内存泄漏并在通知栏提示。我实测下来,LeakCanary能发现大部分常见的内存泄漏问题,比如Handler、AsyncTask、监听器未注销等。
图片加载也是性能优化的重点。菜品图片如果尺寸过大,加载到内存中会占用大量空间。我在Glide中配置了override参数,把图片压缩到实际显示尺寸的1.5倍左右,既保证清晰度又节省内存。另外,列表滑动时暂停图片加载,停止滑动后再继续,这个用Glide的pauseRequests和resumeRequests实现。
// RecyclerView滑动时暂停和恢复图片加载 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { Glide.with(context).resumeRequests(); } else { Glide.with(context).pauseRequests(); } } });4.4 数据一致性问题的处理经验
订单相关的数据一致性是最容易出问题的地方。我遇到过一个bug:用户快速点击“提交订单”按钮,结果生成了两个订单。原因是按钮点击后没有立即禁用,用户在网络请求返回前又点了一次。解决办法是在点击后立即禁用按钮,等请求返回后再恢复。这个细节很小,但如果不处理,用户可能会被重复扣款。
另一个问题是购物车数量和库存的同步。如果两个用户同时下单同一个菜品,可能会出现超卖。校园项目虽然并发量不大,但逻辑上还是要处理。我在下单时加了一个库存校验,如果库存不足则提示用户。虽然数据库层面没有做严格的行锁,但对于校园场景来说,这个方案已经够用了。
订单状态更新时,我用了乐观锁的方式。在订单表中加了一个version字段,每次更新时version加一,更新时检查version是否匹配。如果匹配则更新成功,不匹配说明订单已被其他操作修改,需要重新读取。这个机制能有效防止并发更新导致的数据覆盖。
5. 项目扩展方向与二次开发建议
5.1 功能扩展的可行方向
这个项目的基础框架已经搭好了,后续可以扩展的方向很多。最直接的是增加支付功能,目前支付是模拟的,可以接入真实的支付渠道。不过校园项目一般不需要真实支付,模拟支付反而更安全,避免涉及资金问题。
另一个方向是增加推荐功能。根据用户的历史订单,推荐可能喜欢的菜品。实现方式可以很简单:统计用户点得最多的菜品分类,在首页优先展示该分类的菜品。不需要复杂的推荐算法,简单的统计就能带来不错的体验提升。
还可以增加评价功能。用户取餐后可以对菜品进行评分和评论,商家可以看到评价并回复。这个功能能增加用户粘性,也为食堂改进菜品提供参考。评价数据还可以用来做菜品排序,评分高的菜品排在前面。
5.2 代码重构与架构升级的思路
如果要把这个项目作为长期维护的项目,建议做一些重构。首先是把Java代码逐步迁移到Kotlin,Kotlin的空安全特性能减少大量NullPointerException。其次是引入依赖注入框架,比如Hilt,让对象的创建和管理更清晰。
架构上可以从简单的三层架构升级到MVVM。目前项目中已经用了ViewModel和LiveData,但还不够彻底。可以把业务逻辑进一步抽离到UseCase层,让ViewModel只负责UI相关的逻辑。这样代码的测试性会更好,也更容易维护。
网络层可以引入Repository模式,把数据来源(本地数据库和远程服务器)统一封装。这样上层不需要关心数据是从哪里来的,只需要调用Repository的方法即可。当需要增加缓存策略或者切换数据源时,只需要修改Repository的实现。
5.3 源码阅读与学习建议
拿到源码后,建议按照“先跑通再深入”的顺序学习。第一步是导入项目并成功运行,看到界面能正常显示、能下单、能查看订单。这一步的目的是建立信心,确认环境没问题。第二步是阅读核心类的代码,从MainActivity开始,顺着用户操作流程跟踪代码执行路径。第三步是尝试修改一些简单的功能,比如改改颜色、改改文字,感受代码的修改和生效过程。第四步是增加一个小功能,比如增加一个“清空购物车”的按钮,从UI到数据库完整走一遍。
阅读源码时,建议用调试模式,在关键位置打断点,单步执行看变量变化。这样比单纯看代码理解得更深。Android Studio的调试功能很强大,可以查看调用栈、变量值、内存状态,善用这些工具能大幅提升学习效率。
提示:源码中我加了详细的注释,关键逻辑都有说明。遇到看不懂的地方,先看注释,再看代码,最后再查资料。不要一上来就查资料,那样容易迷失在信息的海洋里。
5.4 部署到实际校园环境的注意事项
如果要把这个系统真正部署到校园环境使用,有几个问题需要提前考虑。首先是服务器性能,校园网的用户量可能比想象中大,尤其是中午用餐高峰期,并发请求会集中爆发。建议用Nginx做反向代理和负载均衡,后端服务至少部署两个实例。
其次是数据安全。用户密码必须加密存储,传输过程要用HTTPS。虽然校园网环境相对封闭,但基本的安全措施还是要做。数据库要定期备份,防止数据丢失。建议每天凌晨自动备份一次,保留最近七天的备份文件。
最后是用户培训。商家端的操作界面要尽量简单,最好能在一页内完成所有常用操作。我见过一些系统功能很全,但操作路径太深,商家用起来很费劲。校园食堂的商家通常不是年轻人,界面设计要考虑到他们的使用习惯,字体大一点、按钮大一点、操作步骤少一点。
这个项目从设计到实现再到部署,前后花了大概三周时间。中间踩了不少坑,但也积累了很多经验。校园点餐系统虽然不是什么高深的技术项目,但麻雀虽小五脏俱全,把每个环节都做扎实,对提升开发能力很有帮助。源码和部署文档我都整理好了,有需要的同学可以拿去参考,遇到问题也欢迎交流。