ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Kotlin外卖App课设拆解:三层架构与数据库设计实战

Kotlin外卖App课设拆解:三层架构与数据库设计实战 简介这是一套面向高校学生与Android开发初学者的完整课程设计项目采用Kotlin语言开发涵盖外卖应用的客户端、服务端与数据库三层架构适合作为大三课程设计参考或移动开发入门实战案例。压缩包共270个文件约150MB包含52个kt源码、42个xml布局、23个java文件、35个so库及5个apk安装包另附sql建表脚本、gradle构建配置、docx设计报告与gif演示图覆盖界面设计、业务逻辑与数据持久化全流程。目前已有148人学习下载。项目主干版本结构清晰可直接运行体验点餐、订单与用户认证等核心功能帮助读者理解Kotlin与Java互操作、Android界面交互及服务端接口设计是课程答辩与技能提升的实用参考。1. 从一份大三课设拆起Kotlin 外卖 App 的三层架构到底长什么样很多人拿到「Kotlin 写的 Android 外卖应用带数据库、客户端、服务端」这类压缩包第一反应是解压、双击app-release.apk装上看看界面然后就没有然后了。我这次拆的这份jleme课设包价值不在那个能装到手机上的成品 APK而在于它把移动开发里最容易讲糊的三层——客户端、服务端、数据库——用一套能跑通的代码串了起来。它适合两类人正在做大三课程设计、需要一份结构完整参考的同学以及想从「只会写 Activity」过渡到「理解一个 App 数据怎么从界面流到数据库再流回来」的 Android 初学者。包里除了 APK还有gradlew.bat、index.css、饥了么外卖报告.docx和一张wel_jleme.gif启动图说明它是一份带文档、带构建脚本、带演示素材的完整交付物而不是一段孤零零的源码。下面我按「它是什么 → 怎么跑起来 → 坑在哪」的顺序把这份资源拆到能照着复现。2. 客户端、服务端、数据库三层各自负责什么为什么这么分2.1 三层架构在课设里的真实分工外卖类应用的本质是「一个用户看到菜单、下单、商家/后台处理订单」的闭环。这份课设把它拆成三层不是照搬教科书而是因为这三层的数据形态完全不同。客户端Android 端负责的是展示和交互它拿到的是已经组装好的 JSON 或对象不需要知道订单在数据库里是怎么存的服务端负责业务规则比如「下单时校验库存、生成订单号、计算总价」它是唯一能直接碰数据库的角色数据库负责持久化用户表、菜品表、订单表、订单明细表都落在这里。这么分的好处是客户端换 UI 不用动服务端服务端改业务逻辑不用重装 App数据库换表结构只影响服务端。课设里常见的错误是把数据库操作直接写在 Activity 里用SQLiteOpenHelper在客户端本地建表结果「服务端」变成了一个空壳。这份包既然标题明确写了「包含数据库客户端服务端」说明它是按真正的 C/S 结构组织的客户端通过接口拿数据而不是自己查本地库。2.2 从压缩包文件看项目组成先看包里能直接看到的文件判断项目形态文件类型作用app-release.apk/jleme.apk/jleme-v1.0.apk安装包已编译的客户端成品用于快速验证界面gradlew.bat构建脚本Windows 下用 Gradle 编译项目的入口index.css样式文件服务端若带 Web 管理页这是它的样式饥了么外卖报告.docx文档课程设计报告含需求、设计、测试wel_jleme.gif素材启动页动画资源gradlew.bat的存在说明客户端是标准 Android Gradle 工程不是 Eclipse 时代的老结构。index.css是个关键信号——纯 Android 客户端项目不会带 CSS它大概率对应服务端的一个后台管理页面比如商家查看订单的 Web 端。这意味着这份课设的服务端可能不是纯 REST 接口而是「接口 简易 Web 后台」的混合形态。2.3 环境准备与工程导入要复现先备好环境。Android 端需要 Android Studio建议 2022 以后的版本对 Kotlin 支持更稳JDK 用 11 或 17Gradle 版本跟着工程里的 wrapper 走不要手动改。服务端如果是 Java/Kotlin 写的同样用 JDK 11如果带 Web 页面可能还需要一个 Servlet 容器或 Spring Boot 内嵌容器。导入步骤解压jleme.zip找到含settings.gradle或build.gradle的目录那才是工程根目录不要直接在压缩包外层用 Android Studio 打开。用 Android Studio 的Open选择工程根目录等待 Gradle Sync。第一次 Sync 会下载依赖网络不稳时容易卡在Downloading。Sync 成功后检查local.properties里的sdk.dir是否指向本机 SDK没有就手动补上。服务端单独用 IDEA 打开确认它的启动类和端口配置。# Windows 下用工程自带的 wrapper 编译避免本机 Gradle 版本不一致 gradlew.bat assembleDebug # 如果只想装到已连接的设备上 gradlew.bat installDebugassembleDebug生成的是调试包installDebug会直接推到 adb 连接的设备。用 wrapper 而不是本机gradle命令是因为 wrapper 锁定了工程验证过的 Gradle 版本能避开「本机版本太新导致插件不兼容」这类玄学问题。提示如果 Sync 报Could not find com.android.tools.build:gradle多半是仓库地址被改过检查build.gradle里的repositories是否包含google()和mavenCentral()。3. 数据库设计与服务端接口订单数据怎么从界面落到表里3.1 核心表结构与字段设计外卖系统的数据库设计有固定套路核心就四张表用户、菜品、订单、订单明细。下面是我按这类课设常见做法整理的表结构字段名可按报告里的实际定义调整但关系不能乱。-- 用户表存登录信息和角色 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 实际项目应存哈希课设常存明文 phone VARCHAR(20), role TINYINT DEFAULT 0 -- 0 普通用户1 商家/管理员 ); -- 菜品表菜单数据 CREATE TABLE dish ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, category VARCHAR(50), image_url VARCHAR(255) ); -- 订单表一笔订单的主记录 CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, -- 0 待处理 1 已完成 2 已取消 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) ); -- 订单明细一笔订单包含哪些菜品、各几份 CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, quantity INT NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (dish_id) REFERENCES dish(id) );orders和order_item拆开是必须的因为一笔订单有多个菜品如果把菜品塞进订单表的一个字段里查询和统计都会变成字符串拼接的灾难。status用整型而不是字符串是为了索引效率和状态机判断方便。total_price冗余存在订单表里是为了避免每次查订单都去order_item里求和这是典型的读多写少场景下的取舍。3.2 服务端接口与客户端调用服务端对外暴露的接口通常按资源划分客户端用 Retrofit 或 OkHttp 调用。下面是一个下单接口的服务端伪代码和客户端调用示例。// 服务端下单接口的核心逻辑Kotlin 任意 Web 框架 fun createOrder(userId: Int, items: ListOrderItemReq): OrderResp { // 1. 校验库存 for (item in items) { val dish dishDao.findById(item.dishId) ?: throw BizException(菜品不存在) if (dish.stock item.quantity) { throw BizException(库存不足: ${dish.name}) } } // 2. 计算总价 var total BigDecimal.ZERO for (item in items) { val dish dishDao.findById(item.dishId)!! total total.add(dish.price.multiply(BigDecimal(item.quantity))) } // 3. 写订单主表 明细表扣库存应在同一事务里 val orderId orderDao.insert(userId, total) for (item in items) { orderItemDao.insert(orderId, item.dishId, item.quantity) dishDao.reduceStock(item.dishId, item.quantity) } return OrderResp(orderId, total, status 0) }这段逻辑的关键在「校验—计算—写入」的顺序以及第 3 步必须放在一个数据库事务里。如果扣库存和写订单分开提交一旦中间失败就会出现「库存扣了但订单没生成」的对不上账。课设里经常忽略事务答辩时被问到就是硬伤。// 客户端Retrofit 接口定义与调用 interface ApiService { POST(order/create) suspend fun createOrder(Body req: CreateOrderReq): ApiResponseOrderResp } // 在 ViewModel 里调用 viewModelScope.launch { try { val resp api.createOrder(req) if (resp.code 0) { _uiState.value UiState.Success(resp.data) } else { _uiState.value UiState.Error(resp.msg) } } catch (e: Exception) { _uiState.value UiState.Error(网络异常: ${e.message}) } }客户端用suspend函数配合viewModelScope是为了不阻塞主线程。ApiResponse统一包一层code/msg/data是服务端接口的常见约定客户端只认code 0为成功。这里要注意createOrder是写操作不能重试网络超时后不能盲目重发否则可能生成重复订单正确做法是服务端用订单号做幂等。3.3 客户端到服务端的地址配置课设最容易翻车的地方是地址。模拟器访问本机服务端localhost指向的是模拟器自己不是你的电脑。// 常见做法把 baseUrl 抽到 BuildConfig 或常量里 object ApiConfig { // Android 模拟器访问宿主机用 10.0.2.2 const val BASE_URL http://10.0.2.2:8080/ // 真机调试时改成电脑的局域网 IP如 http://192.168.1.100:8080/ }10.0.2.2是 Android 模拟器映射到宿主机的固定地址真机则必须用电脑在局域网里的实际 IP且手机和电脑要在同一网段。另外 Android 9 以后默认禁止明文 HTTP需要在AndroidManifest.xml的application标签加android:usesCleartextTraffictrue否则请求直接失败日志里只有一句含糊的Cleartext HTTP traffic not permitted。4. 避坑与排查这份课设跑不起来时先看这几条4.1 装上 APK 能看界面但登录/下单全失败现象APK 能正常打开点登录转圈后提示网络错误或直接闪退。 原因成品 APK 里打包的BASE_URL指向的是原作者当时的服务端地址那个地址在你这里根本不存在或者服务端压根没启动。 解决不要指望成品 APK 能连上你的服务端。正确路径是导入源码把BASE_URL改成你自己的地址重新编译安装。服务端要先启动确认http://你的IP:端口/在浏览器里能访问到接口返回再让客户端去连。4.2 Gradle Sync 卡住或报依赖找不到现象Android Studio 打开工程后一直Gradle: Resolve dependencies或者报某个androidx库找不到。 原因工程里的仓库配置可能只有jcenter()而 jcenter 早已停服或者依赖版本太老新版本 Android Studio 不再默认支持。 解决把build.gradle里的repositories改成google()mavenCentral()删掉jcenter()。如果某个依赖版本确实拉不到去 Maven 仓库查一个相近的可用版本替换改完点Sync Now。这一步没有后悔药只能一个个依赖试。4.3 数据库连不上或表不存在现象服务端启动报Unknown database jleme或Table jleme.user doesnt exist。 原因只导入了代码没有执行建库建表的 SQL或者数据库连接配置里的库名、账号密码和本机不一致。 解决先在 MySQL 里CREATE DATABASE jleme再执行项目里的.sql脚本建表。然后检查服务端的数据库配置文件常见是application.properties或db.properties把url、username、password改成你本机的。MySQL 8 的连接串还要带serverTimezoneAsia/Shanghai否则时间字段会报错。4.4 图片加载不出来现象菜品列表文字正常但图片全是占位图或空白。 原因image_url存的是原作者的图床地址或本地路径你的环境访问不到或者客户端没申请网络权限。 解决检查AndroidManifest.xml是否有uses-permission android:nameandroid.permission.INTERNET/。图片地址如果是外链换成你自己的图床或本地服务端静态资源路径。用 Glide 加载时打开日志能看到具体是 404 还是超时。4.5 中文乱码现象接口返回的中文在客户端显示成问号或方块。 原因服务端响应头没指定 UTF-8或数据库表和连接字符集不是utf8mb4。 解决数据库建库时用CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci服务端响应设置Content-Type: application/json;charsetUTF-8。客户端 Retrofit 默认按 UTF-8 解析一般不用改。5. 进阶把课设改成能写进简历的项目以及我的验证习惯课设能跑通只是及格线要让这份jleme真正有价值得做几处改造。第一把明文密码换成 BCrypt 哈希登录接口加一个简单的 Token 机制哪怕只是 UUID 存内存也比裸传密码强。第二给下单接口加幂等键客户端每次下单带一个唯一requestId服务端用它去重这样网络重试不会生成两笔订单。第三把total_price的计算从服务端挪到数据库事务里用SELECT ... FOR UPDATE锁住菜品行避免并发下单超卖。验证改造是否生效我一般会走一遍固定流程验证项操作预期结果库存扣减库存设 1两个客户端同时下单同一菜品只有一个成功另一个提示库存不足幂等同一requestId连发两次下单请求只生成一笔订单密码安全查数据库 user 表密码字段是哈希值不是明文异常回滚下单时故意让明细插入失败订单主表和库存都不变这套流程走下来项目就从「能演示」变成了「能讲清楚边界」。我自己的习惯是每次改完服务端逻辑先不碰客户端直接用 Postman 或 curl 打接口确认返回符合预期再去调 UI。因为客户端的问题往往只是表象接口对了UI 的问题才好定位。# 用 curl 直接验证下单接口绕开客户端 curl -X POST http://localhost:8080/order/create \ -H Content-Type: application/json \ -d {userId:1,items:[{dishId:1,quantity:2}],requestId:test-001}这条命令的好处是把「客户端—网络—服务端—数据库」四段链路缩短成「curl—服务端—数据库」一旦返回不对问题一定在服务端或数据库不用怀疑客户端。从那以后我每次调接口都强制先走一遍 curl确认服务端没问题再动 Android 端省下的排查时间比想象中多。希望这份拆解能帮你把这份课设真正跑起来、改下去。本文还有配套的精品资源点击获取
返回列表