ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

仓库管理系统课设:前后台分离架构与REST接口设计实战

仓库管理系统课设:前后台分离架构与REST接口设计实战 简介这是一套基于 Android Studio 开发的前后台分离仓库管理系统完整源码面向移动应用开发初学者、课程设计学生及需要 Android 实战练手项目的开发者。项目以角色权限为核心划分超级管理员、商品管理员与出入库人员三类身份覆盖注册登录、用户管理、商品增删查、入库出库等典型业务场景能帮助读者理解多角色权限控制与前后台数据交互的完整实现思路。压缩包共 529 个文件约 15.47MB包含 18 个 java 源文件、35 个 xml 布局与配置、123 个 json 数据文件、16 张 png 及 5 张 jpg 界面素材另有 dex、class、jar、gradle 等构建与依赖文件结构清晰、注释详尽。项目涉及 ListView 列表、SQLite 数据库增删改查、下拉框与 Intent 传值等核心知识点界面达十多个UI 完成度较高。目前已有 3355 人学习适合作为满分课设参考或 Android 入门进阶的实战范例。1. 仓库管理系统课设为什么前后台分离比堆UI更值得先做做过 Android 课设的人都清楚一个尴尬现实UI 做得再花哨答辩老师一句「数据从哪来」就能把整个项目问穿。仓库管理系统这个题目尤其典型——入库、出库、库存查询、预警四个模块背后是同一套数据在流动如果全部塞进 Activity 里用 SQLite 硬扛改一个字段就要动五六个文件。前后台分离的思路是Android 端只负责界面渲染和用户交互所有业务逻辑和数据持久化交给后台服务两端通过 HTTP 接口通信。这样做的好处不是「显得高级」而是当你需要把库存预警规则从「低于10件」改成「低于安全库存」时只改后台一个方法前端一行不动。这篇文章面向的是正在做课设、想把项目做得能讲清楚架构的 Android 初学者也适合已经写完但说不清前后端边界的同学。接下来我会按「接口怎么定 → 前端怎么搭 → 后台怎么建 → 联调怎么排错」的顺序把一套可复现的方案拆开讲。2. 接口先行仓库管理系统的 REST 接口设计与数据契约2.1 为什么先定接口再写代码前后台分离最容易翻车的地方不是技术难度而是两端对「一个入库单长什么样」的理解不一致。前端以为quantity是整数后台返回了字符串前端按{code, data, msg}解析后台直接返回了裸数组。这类问题在联调阶段暴露出来改起来牵一发动全身。我的习惯是动手写第一行 Kotlin 之前先用一张表把接口定死包括路径、方法、请求体字段、响应体字段、字段类型、是否必填。这张表就是两端的「合同」后面谁改谁负责同步。仓库管理系统的核心接口不多通常六到八个就能覆盖课设需求接口路径方法请求参数响应字段说明/api/goods/listGETpage, size, keywordid, name, spec, quantity, safeStock分页查询商品/api/goods/addPOSTname, spec, quantity, safeStockcode, msg新增商品/api/stock/inPOSTgoodsId, quantity, operatorcode, msg入库/api/stock/outPOSTgoodsId, quantity, operatorcode, msg出库/api/stock/recordsGETgoodsId, startTime, endTimelist[]出入库记录/api/warning/listGET无list[]库存预警列表这张表里有两个设计决策值得说清楚。第一所有写操作统一返回{code, msg}code0表示成功非零表示业务失败比如出库数量超过库存HTTP 状态码始终是 200。这样做的好处是前端只需要判断code不用同时处理 HTTP 异常和业务异常两套逻辑。第二查询接口统一返回分页结构{code, data: {list, total}, msg}即使当前数据量很小也预留分页字段避免后期数据变多时改接口格式。2.2 用 Postman 或 Apifox 先跑通接口再写前端接口定完之后不要急着打开 Android Studio。先用 Postman 或者 Apifox 把每个接口手动调一遍确认后台返回的 JSON 结构和表格里写的一致。这一步花二十分钟能省掉后面至少两小时的联调扯皮。具体做法是在 Postman 里建一个 Collection把六个接口全部录入每个接口保存一个示例响应。这个 Collection 可以直接导出成 JSON 分享给前端同学如果是团队课设也可以作为自己写 Retrofit 接口定义时的参照。调接口时重点看三件事字段名大小写是否和约定一致、数值类型是否匹配quantity是Int还是String、空数据时返回的是null还是空数组。这三点是后面解析翻车的高发区。确认无误后把示例响应复制到一个.json文件里放在 Android 项目的assets目录下写 Retrofit 的 Mock 拦截器时可以直接用这样即使后台还没部署到服务器前端也能先跑起来。3. Android 端用 Retrofit RecyclerView 搭出能用的仓库管理界面3.1 Gradle 依赖与 Retrofit 接口定义Android 端的技术选型我一般固定为Retrofit 做网络请求Gson 做 JSON 解析RecyclerView 做列表展示ViewModel LiveData 做数据持有。这套组合在课设场景下足够稳定资料也多遇到问题容易搜到答案。先在app/build.gradle里加依赖dependencies { implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation androidx.recyclerview:recyclerview:1.3.2 implementation androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2 implementation androidx.lifecycle:lifecycle-livedata-ktx:2.6.2 }版本号不用照抄用 Android Studio 提示的稳定版即可。加完依赖后 Sync 一次如果报Duplicate class错误多半是某个库被间接引入了两次用./gradlew app:dependencies查看依赖树找到重复的用exclude排除。接下来定义接口。假设后台部署在本机模拟器访问本机用10.0.2.2真机用电脑局域网 IP// ApiService.kt interface ApiService { GET(api/goods/list) suspend fun getGoodsList( Query(page) page: Int 1, Query(size) size: Int 20, Query(keyword) keyword: String? null ): ApiResponsePageDataGoods POST(api/stock/in) suspend fun stockIn(Body body: StockRequest): ApiResponseUnit POST(api/stock/out) suspend fun stockOut(Body body: StockRequest): ApiResponseUnit } // ApiResponse.kt 统一响应壳 data class ApiResponseT( val code: Int, val msg: String, val data: T? ) data class PageDataT( val list: ListT, val total: Int ) data class Goods( val id: Int, val name: String, val spec: String, val quantity: Int, val safeStock: Int ) data class StockRequest( val goodsId: Int, val quantity: Int, val operator: String )这里的关键点是ApiResponseT这个泛型壳。后台所有接口都返回{code, msg, data}三层结构用泛型可以把data的类型交给调用方决定。suspend关键字让接口可以直接在协程里调用不用再写enqueue回调。Query用于 GET 参数Body用于 POST 的 JSON 体Retrofit 会自动用 Gson 序列化。3.2 RecyclerView 适配器与列表页实现仓库管理系统的主界面通常是一个商品列表每行显示名称、规格、库存数量库存低于安全库存时数量标红。适配器写法class GoodsAdapter(private val onItemClick: (Goods) - Unit) : RecyclerView.AdapterGoodsAdapter.VH() { private val items mutableListOfGoods() fun submitList(newList: ListGoods) { items.clear() items.addAll(newList) notifyDataSetChanged() } class VH(view: View) : RecyclerView.ViewHolder(view) { val tvName: TextView view.findViewById(R.id.tv_name) val tvSpec: TextView view.findViewById(R.id.tv_spec) val tvQty: TextView view.findViewById(R.id.tv_qty) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val view LayoutInflater.from(parent.context) .inflate(R.layout.item_goods, parent, false) return VH(view) } override fun onBindViewHolder(holder: VH, position: Int) { val goods items[position] holder.tvName.text goods.name holder.tvSpec.text goods.spec holder.tvQty.text goods.quantity.toString() // 低于安全库存标红 val color if (goods.quantity goods.safeStock) { ContextCompat.getColor(holder.itemView.context, R.color.red_warning) } else { ContextCompat.getColor(holder.itemView.context, R.color.text_normal) } holder.tvQty.setTextColor(color) holder.itemView.setOnClickListener { onItemClick(goods) } } override fun getItemCount() items.size }submitList方法里用notifyDataSetChanged是课设场景下的简化做法数据量小的时候没问题。如果列表超过两百条出现滑动卡顿换成DiffUtil做增量更新只刷新变化的行。库存标红的逻辑放在onBindViewHolder里每次绑定都重新判断保证出库后列表刷新时颜色跟着变。3.3 ViewModel 里发起请求与状态管理Activity 里不直接调 Retrofit而是通过 ViewModel 持有数据这样旋转屏幕时数据不会丢class GoodsViewModel : ViewModel() { private val _goods MutableLiveDataListGoods() val goods: LiveDataListGoods _goods private val _error MutableLiveDataString() val error: LiveDataString _error fun loadGoods(keyword: String? null) { viewModelScope.launch { try { val resp RetrofitClient.api.getGoodsList(keyword keyword) if (resp.code 0) { _goods.value resp.data?.list ?: emptyList() } else { _error.value resp.msg } } catch (e: Exception) { _error.value 网络异常${e.message} } } } }viewModelScope.launch确保协程在 ViewModel 销毁时自动取消不会造成内存泄漏。try-catch捕获网络异常把错误信息通过 LiveData 传给 Activity 弹 Toast。注意resp.data?.list用了安全调用因为后台在查询无结果时data可能为null直接.list会崩。Activity 里观察 LiveDataviewModel.goods.observe(this) { list - adapter.submitList(list) } viewModel.error.observe(this) { msg - Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() } viewModel.loadGoods()这套结构跑通后入库和出库页面只需要复用同样的模式一个表单、一个提交按钮、一个 ViewModel 方法。区别只是调用的接口不同。4. 后台服务用 Spring Boot MySQL 实现库存业务逻辑4.1 数据库表设计与库存扣减的原子性后台选 Spring Boot 是因为它和 Android 端的 JSON 交互最顺注解一贴就能跑。数据库用 MySQL建两张核心表CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, spec VARCHAR(100), quantity INT NOT NULL DEFAULT 0, safe_stock INT NOT NULL DEFAULT 10 ); CREATE TABLE stock_record ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, type TINYINT NOT NULL COMMENT 1入库 2出库, quantity INT NOT NULL, operator VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (goods_id) REFERENCES goods(id) );出库操作有一个必须注意的点扣减库存和写入记录必须在同一个事务里否则可能出现库存扣了但记录没写或者记录写了库存没扣。Service 层方法加TransactionalService public class StockService { Autowired private GoodsMapper goodsMapper; Autowired private StockRecordMapper recordMapper; Transactional public void stockOut(int goodsId, int quantity, String operator) { Goods goods goodsMapper.selectById(goodsId); if (goods null) { throw new BizException(商品不存在); } if (goods.getQuantity() quantity) { throw new BizException(库存不足当前库存 goods.getQuantity()); } // 扣减库存 goodsMapper.updateQuantity(goodsId, -quantity); // 写入出库记录 StockRecord record new StockRecord(); record.setGoodsId(goodsId); record.setType(2); record.setQuantity(quantity); record.setOperator(operator); recordMapper.insert(record); } }Transactional保证两个写操作要么都成功要么都回滚。库存不足时抛自定义异常BizException由全局异常处理器捕获后返回{code: 1, msg: 库存不足...}前端拿到code ! 0就弹提示。这里不要用synchronized去锁方法课设并发量低数据库事务足够加锁反而容易死锁。4.2 统一响应封装与跨域配置后台所有 Controller 返回统一格式用一个Result类包一层public class ResultT { private int code; private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 0; r.msg success; r.data data; return r; } public static T ResultT fail(String msg) { ResultT r new Result(); r.code 1; r.msg msg; return r; } // getter/setter 省略 }Controller 里直接return Result.ok(goodsService.list())。全局异常处理器用RestControllerAdvice捕获BizException并返回Result.fail(e.getMessage())这样前端永远只需要解析一种结构。跨域配置在课设阶段容易被忽略。如果 Android 端用模拟器访问本机后台不涉及跨域但如果用浏览器调试接口或者后台部署到另一台机器就需要加 CORSConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*); } }allowedOrigins(*)在课设场景下够用生产环境要改成具体域名。4.3 库存预警的查询实现预警列表的 SQL 很简单就是查quantity safe_stock的商品SELECT * FROM goods WHERE quantity safe_stock ORDER BY (safe_stock - quantity) DESC;按差值降序排列缺口最大的排最前面。MyBatis 里写一个对应的selectWarningList方法即可。这个接口不需要分页因为预警商品通常不多。前端在首页顶部放一个「预警」入口点进去展示这个列表每条显示商品名和当前库存/安全库存的对比。5. 联调避坑六个让课设翻车的典型问题与排查路径5.1 模拟器访问本机后台返回连接超时现象Postman 里接口正常Android 模拟器里请求一直转圈最后超时。原因模拟器里的localhost指向模拟器自身不是宿主机。解决把 BaseUrl 里的localhost或127.0.0.1改成10.0.2.2。真机调试则用电脑的局域网 IPipconfig查看并确保手机和电脑在同一 WiFi 下。如果还不行检查电脑防火墙是否拦截了 8080 端口。5.2 Gson 解析报 IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY现象接口返回的 JSON 是数组但 Retrofit 定义的是对象类型。原因后台某个接口没有用Result包装直接返回了List。解决统一后台所有接口的返回格式或者在 Retrofit 里把返回类型改成ApiResponseListGoods。我一般选前者因为统一格式对前端更友好。5.3 出库后列表数量没变手动下拉才更新现象出库成功提示弹了但列表里的库存数字还是旧的。原因出库操作完成后没有重新调用loadGoods()。解决在出库成功的回调里调一次viewModel.loadGoods()或者在onResume里刷新。更优雅的做法是用ActivityResultLauncher出库页面关闭时返回一个needRefresh标志列表页收到后刷新。5.4 库存扣成负数现象并发测试时或者快速连点出库按钮出现库存为负。原因查询库存和扣减库存之间有间隙两个请求同时读到相同库存。解决在 SQL 的UPDATE语句里加条件AND quantity #{quantity}根据受影响行数判断是否成功UPDATE goods SET quantity quantity - #{quantity} WHERE id #{id} AND quantity #{quantity}Mapper 返回intService 里判断if (affected 0) throw new BizException(库存不足)。这样即使并发也不会扣成负数。5.5 Android Studio 报 Duplicate class 或资源重复现象Sync 或 Build 时报Duplicate class或Duplicate resources。原因依赖冲突或res目录下有同名文件比如两个ic_launcher.png在不同mipmap文件夹。解决依赖冲突用./gradlew app:dependencies查看树找到重复的用exclude group: xxx排除资源重复检查res下各文件夹是否有同名文件删掉多余的。5.6 后台返回中文乱码现象前端收到的msg字段中文显示为问号或乱码。原因后台响应头Content-Type没有指定 UTF-8。解决在application.properties里加server.servlet.encoding.charsetUTF-8和server.servlet.encoding.forcetrue。如果是 Tomcat 部署检查server.xml的Connector标签是否加了URIEncodingUTF-8。6. 让课设经得起追问接口文档、异常兜底与演示脚本答辩时老师最爱问的三个问题是「你这个数据存哪」「如果两个人同时出库怎么办」「断网了会怎样」。前两个在前面已经解决了第三个需要在前端做兜底。我的习惯是在 ViewModel 的catch块里区分异常类型SocketTimeoutException提示「连接超时请检查网络」ConnectException提示「无法连接服务器」其他异常统一提示「操作失败请重试」。这样演示时即使后台没启动前端也不会直接崩而是给出可读的提示。另一个让课设加分的小技巧是准备一份接口文档。不需要多正式用 Markdown 写一个表格列出每个接口的路径、方法、参数、返回示例放在项目根目录的docs/api.md里。答辩时如果老师问「前后台怎么约定的」直接打开这个文件比口头解释有说服力得多。文档里的返回示例直接从 Postman 里复制真实响应不要手写手写容易和实际不一致。演示脚本我一般按这个顺序走先展示商品列表证明查询通再新增一个商品证明写入通然后对这个商品做一次入库和一次出库证明业务逻辑通最后把它的库存改到低于安全库存刷新看预警列表证明预警通。整个流程控制在三分钟内每一步都有明确的「输入→输出」对应关系。演示前把数据库重置到初始状态避免上次演示的脏数据干扰。最后说一个我踩过的坑不要在演示当天才把后台部署到服务器。课设环境里用本机跑后台 模拟器跑前端是最稳的组合服务器部署涉及端口、防火墙、域名解析一堆变量任何一个出问题都会耽误演示。如果必须远程演示提前一天部署好并完整走一遍流程把每一步的截图存下来万一现场网络出问题可以切到截图讲解。希望帮到你。本文还有配套的精品资源点击获取
返回列表