news 2026/10/6 2:57:31

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
仓库管理系统课设:前后台分离架构与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},code=0表示成功,非零表示业务失败(比如出库数量超过库存),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 ): ApiResponse<PageData<Goods>> @POST("api/stock/in") suspend fun stockIn(@Body body: StockRequest): ApiResponse<Unit> @POST("api/stock/out") suspend fun stockOut(@Body body: StockRequest): ApiResponse<Unit> } // ApiResponse.kt 统一响应壳 data class ApiResponse<T>( val code: Int, val msg: String, val data: T? ) data class PageData<T>( val list: List<T>, 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 )

这里的关键点是ApiResponse<T>这个泛型壳。后台所有接口都返回{code, msg, data}三层结构,用泛型可以把data的类型交给调用方决定。suspend关键字让接口可以直接在协程里调用,不用再写enqueue回调。@Query用于 GET 参数,@Body用于 POST 的 JSON 体,Retrofit 会自动用 Gson 序列化。

3.2 RecyclerView 适配器与列表页实现

仓库管理系统的主界面通常是一个商品列表,每行显示名称、规格、库存数量,库存低于安全库存时数量标红。适配器写法:

class GoodsAdapter(private val onItemClick: (Goods) -> Unit) : RecyclerView.Adapter<GoodsAdapter.VH>() { private val items = mutableListOf<Goods>() fun submitList(newList: List<Goods>) { 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 = MutableLiveData<List<Goods>>() val goods: LiveData<List<Goods>> = _goods private val _error = MutableLiveData<String>() val error: LiveData<String> = _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 里观察 LiveData:

viewModel.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 层方法加@Transactional:

@Service 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 Result<T> { private int code; private String msg; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.code = 0; r.msg = "success"; r.data = data; return r; } public static <T> Result<T> fail(String msg) { Result<T> 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 端用模拟器访问本机后台,不涉及跨域;但如果用浏览器调试接口,或者后台部署到另一台机器,就需要加 CORS:

@Configuration 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。真机调试则用电脑的局域网 IP(ipconfig查看),并确保手机和电脑在同一 WiFi 下。如果还不行,检查电脑防火墙是否拦截了 8080 端口。

5.2 Gson 解析报 IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY

现象:接口返回的 JSON 是数组,但 Retrofit 定义的是对象类型。原因:后台某个接口没有用Result包装,直接返回了List。解决:统一后台所有接口的返回格式,或者在 Retrofit 里把返回类型改成ApiResponse<List<Goods>>。我一般选前者,因为统一格式对前端更友好。

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 返回int,Service 里判断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.charset=UTF-8和server.servlet.encoding.force=true。如果是 Tomcat 部署,检查server.xml的Connector标签是否加了URIEncoding="UTF-8"。

6. 让课设经得起追问:接口文档、异常兜底与演示脚本

答辩时老师最爱问的三个问题是:「你这个数据存哪」「如果两个人同时出库怎么办」「断网了会怎样」。前两个在前面已经解决了,第三个需要在前端做兜底。我的习惯是在 ViewModel 的catch块里区分异常类型:SocketTimeoutException提示「连接超时,请检查网络」,ConnectException提示「无法连接服务器」,其他异常统一提示「操作失败,请重试」。这样演示时即使后台没启动,前端也不会直接崩,而是给出可读的提示。

另一个让课设加分的小技巧是准备一份接口文档。不需要多正式,用 Markdown 写一个表格,列出每个接口的路径、方法、参数、返回示例,放在项目根目录的docs/api.md里。答辩时如果老师问「前后台怎么约定的」,直接打开这个文件,比口头解释有说服力得多。文档里的返回示例直接从 Postman 里复制真实响应,不要手写,手写容易和实际不一致。

演示脚本我一般按这个顺序走:先展示商品列表(证明查询通),再新增一个商品(证明写入通),然后对这个商品做一次入库和一次出库(证明业务逻辑通),最后把它的库存改到低于安全库存,刷新看预警列表(证明预警通)。整个流程控制在三分钟内,每一步都有明确的「输入→输出」对应关系。演示前把数据库重置到初始状态,避免上次演示的脏数据干扰。

最后说一个我踩过的坑:不要在演示当天才把后台部署到服务器。课设环境里用本机跑后台 + 模拟器跑前端是最稳的组合,服务器部署涉及端口、防火墙、域名解析一堆变量,任何一个出问题都会耽误演示。如果必须远程演示,提前一天部署好并完整走一遍流程,把每一步的截图存下来,万一现场网络出问题可以切到截图讲解。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 2:57:26

Discuz!前端重构实战:克米模板3.5响应式与微信登录优化指南

简介&#xff1a;本资源为Discuz!&#xff08;DZ&#xff09;论坛专用的克米模板3.5版本完整部署包&#xff0c;面向中小社区站长、PHP开发者及前端定制人员&#xff0c;解决传统DZ论坛界面陈旧、交互单一、移动端适配弱等实际运营痛点。压缩包共1755个文件&#xff0c;涵盖818…

作者头像 李华
网站建设 2026/10/6 2:56:50

64位Windows SSDT Hook过PatchGuard实战:从定位到稳定验证

简介&#xff1a;面向Windows内核研发与逆向工程人员&#xff0c;这份源码包聚焦64位系统下绕过Process Guard&#xff08;PG&#xff09;后修改SSDT实现系统服务Hook的技术&#xff0c;核心解决内核安全机制限制下无法直接Hook的问题。资源基于“二次挑战方式”演示了分步绕过…

作者头像 李华
网站建设 2026/10/6 2:56:49

SSM+微信小程序校园水电费管理系统实战部署指南

简介&#xff1a;本资源是一套完整的基于微信小程序的校园水电费管理系统的毕业设计实现方案&#xff0c;面向计算机专业本科生、Java后端开发者及小程序学习者&#xff0c;解决高校后勤场景中水电费用线上化申报、查询与统计的实际需求。压缩包共1076个文件&#xff0c;涵盖86…

作者头像 李华
网站建设 2026/10/6 2:55:59

轮胎磨损与缺陷检测:YOLOv8n轻量改造实战指南

简介&#xff1a;本资源是一套面向本科毕业设计与计算机视觉初学者的轮胎缺陷检测实战项目&#xff0c;聚焦工业质检场景中的磨损识别与表面缺陷定位问题&#xff0c;提供从数据预处理、模型训练到实时检测的完整Python实现方案。压缩包共34个文件&#xff0c;含25个核心Python…

作者头像 李华
网站建设 2026/10/6 2:55:40

微信小程序物业管理系统源码实战:从环境搭建到接口对接的完整指南

简介&#xff1a;这份资源是面向高校计算机相关专业学生的小程序毕业设计完整项目包&#xff0c;主题为小区物业管理系统&#xff0c;适合正在准备毕业设计或课程设计、需要一套可运行前后端案例的开发者参考。项目功能划分清晰&#xff1a;业主端涵盖报修信息管理、缴欠费信息…

作者头像 李华
网站建设 2026/10/6 2:55:21

Discuz城市门户2022商业版模板:安装、分类信息配置与二次开发

简介&#xff1a;城市门户2022商业版是一套基于Discuz的整站模板&#xff0c;采用1200px黄色宽屏布局&#xff0c;面向地方门户站长和中小社区运营者&#xff0c;可用于快速搭建集资讯、论坛、分类信息、同城商家服务于一体的综合平台。资源包共1204个文件&#xff0c;以gif、p…

作者头像 李华