校园二手交易APP开发实战:Android+Spring Boot+MySQL全栈实现
发布时间:2026/10/1 21:06:19来源:尧图网络
简介校园二手商品交易平台APP的设计与实现.doc 是一份面向校园场景的移动应用开发技术文档适合移动开发者、高校学生及电商平台设计者阅读。它围绕二手商品交易的核心流程完整梳理了研究背景、相关文献综述包括移动互联网地理社交、商业模式、校园电子商务等、需求分析与关键技术并结合 Android 平台给出从用户登录注册、创建店铺、发布商品到我的商品的系统设计方案。资源包共1个DOC文档大小约1.75MB内容采用目录化结构组织涵盖 MVC 框架、SQLite 数据库以及总体框架设计等核心内容。阅读者可以获得配套的需求建模思路、功能模块划分、前后端交互逻辑与数据库设计参考对完成毕业设计、课程报告或校园电商类项目的预研都具有直接帮助。目前已有110人学习/下载。1. 校园二手商品交易平台APP到底在做什么一个毕设题目背后的真实工程问题校园二手商品交易平台APP听起来像是一个典型的毕业设计题目但它涉及的远不止“设计几个页面、写几个接口”那么简单。这件事的本质是把“线下跳蚤市场”搬到手机上用户注册登录、发布闲置商品、浏览搜索、私聊议价、线下交易完成整个闭环都需要客户端和服务端的配合。做这个题目最大的价值不是学会调接口而是通过一个完整的业务系统把 Android 开发、后端接口、数据库设计、状态流转、异常处理这些零散技能串成一条线。适合谁做正在找毕设方向的学生或者想练手一个全栈小项目但不想只写 CRUD 的 Android 开发者。这个标题能落地的难点不在功能多而在于每个环节都有极易翻车的细节——比如图片上传失败、订单状态错乱、真机联调不通。这篇笔记就按一个可复现的最小方案讲清楚从选型、建表、写接口到 Android 端对接以及那些让你卡两天的坑。2. 技术选型与整体架构为什么是 Android Spring Boot MySQL而不是其他组合做校园二手交易 APP第一步不是写代码而是定技术栈。这个选择决定了你后续所有的工作量和踩坑面。最常见的组合是 Android 原生客户端 Spring Boot 后端 MySQL 数据库另外还有一个必须提前定好的东西——图片存储方案。下面把选型理由、架构分层和基础表结构一次说清。2.1 客户端技术栈怎么选原生、UniApp 还是 Flutter如果你只在学校做毕设答辩原生 Android 是最稳的选择。理由很直接Android Studio 的调试工具、模拟器、文档和网上的报错解决方案最多遇到问题搜得到而 UniApp 和 Flutter 虽然跨平台但打包配置、原生插件调用、真机调试这些环节对新手来说更容易翻车。项目标题只写了“APP”没限定平台那就按 Android 原生来做用 Java 还是 Kotlin 都可以这里示例用 Java接口友好、范例多。如果你以后想上线双端换个客户端壳复用同一个后端接口就可以了后端设计时避开 Android 专属依赖即可。客户端这边的网络层用 Retrofit OkHttp图片加载用 Glide列表用 RecyclerView状态页用 SwipeRefreshLayout。这套组合是 Android 开发里最成熟的方案网上每个库都有大量踩坑帖适合作为第一个全栈项目的起点。2.2 后端与接口设计RESTful API 和统一返回格式后端选用 Spring Boot 2.x MyBatis-Plus MySQL 5.7 或 8.0 都可以。MyBatis-Plus 的代码生成器和条件构造器能省掉大量重复的 SQL 编写对课程设计级别的信息管理系统够用且直观。接口设计遵循 RESTful 风格按资源划分用户、商品、订单、消息。所有业务接口统一返回同样的 JSON 结构这样 Android 端解析时只需要写一个 BaseResponse 泛型类。统一返回格式的做法是定义一个 Result 类包含 code、message、data 三个字段。code 为 200 表示成功400 表示参数错误401 表示未登录500 表示服务器异常。Android 端拦截到非 200 的 code直接弹 Toast 提示 message而不是去解析 data 里的具体内容。这样前后端约定清晰排查问题时抓手也明确。要注意图片上传接口单独设计为 POST /api/upload返回图片的 URL 字符串。商品发布接口接收的就不是文件本体而是图片 URL 数组。这么设计的好处是商品表和图片之间是字符串字段关联不用额外建附件表上传失败时不会污染商品记录可以先传图片再提交商品信息。我一般会在上传接口加一个图片大小限制为 5MB超过则拒绝。2.3 数据库表结构设计用户表、商品表、订单表的核心字段数据库是整个系统的地基表结构设计不合理后面写接口时处处别扭。这里给出最小可运行的三张核心表用户表、商品表、订单表。字段设计上类型、长度、默认值、索引都要仔细考虑因为涉及到后续的查询效率。CREATE TABLE 语句如下。-- 用户表 CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, openid varchar(64) DEFAULT NULL COMMENT 小程序或APP登录标识, username varchar(32) NOT NULL COMMENT 用户名, password varchar(128) NOT NULL COMMENT 密码BCrypt加密, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, phone varchar(11) DEFAULT NULL COMMENT 手机号, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 商品表 CREATE TABLE product ( id int(11) NOT NULL AUTO_INCREMENT, seller_id int(11) NOT NULL COMMENT 卖家id关联user.id, title varchar(64) NOT NULL COMMENT 商品标题, description varchar(500) DEFAULT NULL COMMENT 商品描述, price decimal(10,2) NOT NULL COMMENT 价格, origin_price decimal(10,2) DEFAULT NULL COMMENT 原价, images varchar(1024) DEFAULT NULL COMMENT 图片URL逗号分隔, category tinyint(4) DEFAULT 0 COMMENT 分类1教材 2数码 3生活 4其他, status tinyint(4) DEFAULT 0 COMMENT 状态0在售 1已卖出 2下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_seller (seller_id), KEY idx_category_status (category,status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单表 CREATE TABLE trade_order ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号业务生成, product_id int(11) NOT NULL, buyer_id int(11) NOT NULL, seller_id int(11) NOT NULL, price decimal(10,2) NOT NULL COMMENT 成交快照价格, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待付款 1待确认 2已完成 3已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_buyer (buyer_id), KEY idx_seller (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;参数说明用户名做唯一索引登录时按 username 快速定位商品表把 category 和 status 建联合索引因为浏览、筛选是高频操作订单表订单号用业务规则生成比如时间戳加随机数而不是依赖数据库自增 id。密码一律用 BCrypt 加密存储不要明文入库存。图片字段直接存逗号分隔的 URL 字符串前端拿到后 split 即可——这个设计虽然不算正规但实测在课程设计里比建关联表省事得多数据量级几千条完全没压力。另外要提醒一点外键约束在这种信息管理系统里建议不要用。学校机房里的 MySQL 版本不一外键带来的级联删除和更新逻辑容易在数据初始化时出问题。业务层面的数据一致性通过 Java 代码控制而不是数据库约束。3. 从零到能跑用户注册登录、发布商品和列表搜索的完整实现架构定好后就到了最核心的开发阶段。这个章节会给出后端接口和 Android 端调用的完整代码片段覆盖三个最高频功能注册登录、发布商品、商品列表与搜索。每一段代码后面会说明关键参数和逻辑方便你改造成自己的业务。建议按先后端再前端的顺序写后端用 Postman 验证通过后再写 Android 端对接这样排查问题范围可以缩小一半。3.1 注册登录接口Token 机制与 Android 端 Session 保持登录模块最常见的需求是用户名密码注册、登录成功后后续接口需要携带凭证。这个项目用最简单可靠的 Token 方案而不是复杂的 JWT 解析库。用户登录成功后后端生成一个 UUID 字符串作为 token存到 Redis 里过期时间设置为 7 天。客户端每次请求在 Header 里带 token 字段后端用一个拦截器统一校验。这样做的好处是token 可以随时在 Redis 里删除实现“强制下线”不需要在 JWT 里维护过期时间、密钥、算法版本等元数据降低理解成本。后端登录接口示例RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; Autowired private StringRedisTemplate redisTemplate; PostMapping(/register) public Result register(RequestBody RegisterRequest req) { if (!req.getPassword().equals(req.getConfirmPassword())) { return Result.fail(400, 两次密码不一致); } // BCrypt加密入库 User user new User(); user.setUsername(req.getUsername()); user.setPassword(BCrypt.hashpw(req.getPassword(), BCrypt.gensalt())); userService.save(user); return Result.success(null); } PostMapping(/login) public Result login(RequestBody LoginRequest req) { User user userService.lambdaQuery() .eq(User::getUsername, req.getUsername()).one(); if (user null || !BCrypt.checkpw(req.getPassword(), user.getPassword())) { return Result.fail(400, 用户名或密码错误); } String token UUID.randomUUID().toString().replace(-, ); redisTemplate.opsForValue().set(token: token, String.valueOf(user.getId()), 7, TimeUnit.DAYS); return Result.success(token); } PostMapping(/logout) public Result logout(RequestHeader(token) String token) { redisTemplate.delete(token: token); return Result.success(null); } }逻辑说明注册时对密码做 BCrypt 加密数据库里永远不存明文登录成功生成随机 token 写入 Rediskey 加了 token: 前缀避免和其他业务 key 冲突。Android 端拿到 token 后存到 SharedPreferences后续每个请求都从 SharedPreferences 取出 token 放入 Header。退出登录时调用 /logout 接口把 Redis 里的 token 删掉这样旧 token 立即失效。参数说明Redis 的 key 过期时间设 7 天包含双端用户每天打开的场景。如果你担心 Redis 使用门槛也可以把 token 存到数据库表里但每次请求都查一次库性能比 Redis 差一个量级。另一个细节是拦截器里要排除 /register 和 /login 两个接口否则会误伤未登录的注册请求。3.2 商品发布图片上传的 Multipart 与 Android 端压缩技巧商品发布的核心难点不在保存文字信息而在于图片上传。Android 端拍照或从相册选的照片通常有几 MB 大小直接传会导致两个结果上传慢、后端存储压力大。常见的做法是先在客户端对图片做压缩再调用上传接口。后端上传接口接收 MultipartFile保存到本地磁盘或云存储返回访问 URL。后端上传接口示例PostMapping(/api/upload) public Result upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.fail(400, 文件为空); } if (file.getSize() 5 * 1024 * 1024) { return Result.fail(400, 图片不能超过5MB); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); if (!Arrays.asList(.jpg, .jpeg, .png, .webp).contains(ext.toLowerCase())) { return Result.fail(400, 仅支持jpg/png/webp格式); } String filename UUID.randomUUID().toString().replace(-, ) ext; String datePath new SimpleDateFormat(yyyyMMdd).format(new Date()); File dir new File(uploadDir / datePath); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(dir.getAbsolutePath() / filename)); } catch (IOException e) { return Result.fail(500, 文件保存失败); } String url /files/ datePath / filename; return Result.success(url); }这段代码里三个参数要注意uploadDir 是配置在 application.yml 里的本地保存目录比如 /data/app-images最后的 URL 是相对路径Android 端拿到的就是字符串5MB 上限和扩展名白名单是对图片的基本校验。为什么不用项目里的 static 目录直接存因为 Spring Boot 打成 jar 包后static 目录里的文件会打进 jar运行时无法写入。把上传目录放到外部绝对路径再用配置类映射成静态资源访问才是常见的稳定做法。Android 端图片压缩可以用系统自带的 ImageDecoder 或 BitmapFactory把图片缩放到最大边 1080质量压到 80%基本能控制在 300KB 以内。压缩代码不复杂但务必在子线程执行否则主线程会卡死导致 ANR。商品发布接口 Android 端调用时用 Retrofit 的 Multipart 注解标记上传接口响应体再配合 BaseResponse 泛型解析。图片 URL 收集完成后再调用 /api/product/publish 接口提交标题、描述、价格和图片字符串数组。这样设计的好处是发布流程可以受控的拆成两步方便你逐步调试。3.3 商品列表与搜索分页加载避免列表卡顿商品列表是整个 APP 最核心的页面这里的实现方式直接决定用户体验。初学者常见的做法是一次性查询所有商品返回给 Android列表用 ScrollView 嵌套。这个方案在商品数量超过 100 条时会明显卡顿内存也不断上涨。正确的做法是后端做分页Android 端用 RecyclerView 上拉加载更多。后端列表接口示例GetMapping(/api/product/list) public Result list(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) Integer category, RequestParam(required false) String keyword, RequestParam(defaultValue 0) int sort) { LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(Product::getStatus, 0); // 只显示在售 if (category ! null) { wrapper.eq(Product::getCategory, category); } if (StringUtils.hasText(keyword)) { wrapper.like(Product::getTitle, keyword); } // sort: 0默认时间倒序 1价格升序 2价格降序 if (sort 1) { wrapper.orderByAsc(Product::getPrice); } else if (sort 2) { wrapper.orderByDesc(Product::getPrice); } else { wrapper.orderByDesc(Product::getCreateTime); } PageProduct pageData productService.page(new Page(page, size), wrapper); return Result.success(pageData); }参数说明page 从 1 开始size 默认 10接口返回的数据结构里包含 total、records 等字段MyBatis-Plus 会自动帮我们算好总页数。Android 端在首次加载和下拉刷新时请求 page1滚到底部时请求 page1并把新数据追加到列表尾部。Android 端这里有一个非常容易翻车的地方页码必须与当前加载的列表数量保持一致。如果用户快速下拉刷新紧接着又上拉加载两个请求同时发送会出现重复数据或数据错乱。解决方案是加一个布尔标识 isLoading在请求开始到结束期间禁止发起新的加载请求刷新的请求也要放到串行队列里。这个细节不处理好列表就会出现“跳页”的玄学问题实际上是并发请求导致的竞态条件。4. 订单与交易流程状态机设计和 Android 端交互的追坑指南订单模块是校园二手交易平台里业务逻辑最复杂的一部分。商品从在售变成已卖出订单从创建到完成中间经历多个状态。很多人在这里把代码写成 if-else 连环套最后自己都理不清逻辑。这一章先把订单状态流转讲清楚再给出两个关键接口的写法下单、确认收货。4.1 订单状态流转设计为什么不能用两个字段代替状态机先定义订单的四种状态0 待付款、1 待确认买家已付款等待卖家确认发货或线下交付、2 已完成、3 已取消。在校园场景里交易方式基本是线下当面交易所以可以把“待确认”理解为“等待双方确认完成”。状态流转只允许以下路径0 可以流向 1 或 31 可以流向 2 或 32 和 3 是终态。不允许从 2 跳回 0也不允许从 3 跳到 1。为什么不能用两个字段比如 is_paid 和 is_done因为这样会导致非法状态的出现is_paid1 且 is_done1 可能是正常完成但 is_paid0 且 is_done1 就是脏数据逻辑上说不通。用一个 status 字段配合枚举校验能保证数据永远处于合法状态。这个思想就是状态机模型在订单、审批、工单系统里通用。下单接口中除了创建订单还要同时做两件事把商品状态改为已卖出status1并校验商品是否已经被别人下单。这两步必须放在一个事务里否则会出现 A、B 两个用户同时下单同一件商品商品状态被改成已卖出后其中一个人还能创建订单成功的脏数据。具体的实现是在 product 表更新状态的 SQL 里加上条件 status0UPDATE product SET status 1 WHERE id #{productId} AND status 0;如果这条 SQL 的受影响行数为 0说明商品已被下单或下架直接报错回滚事务。下单接口示例Transactional PostMapping(/api/order/create) public Result createOrder(RequestHeader(token) String token, RequestBody CreateOrderRequest req) { // 1. 从token获取userId Long userId getUserIdFromToken(token); // 2. 查询商品校验商品存在且在售 Product product productService.getById(req.getProductId()); if (product null || product.getStatus() ! 0) { return Result.fail(400, 商品不存在或已下架); } // 3. 生成订单号 String orderNo ORD System.currentTimeMillis() RandomUtil.randomNumbers(4); // 4. 创建订单 TradeOrder order new TradeOrder(); order.setOrderNo(orderNo); order.setProductId(product.getId()); order.setBuyerId(userId); order.setSellerId(product.getSellerId()); order.setPrice(product.getPrice()); order.setStatus(0); orderService.save(order); // 5. 用乐观锁方式将商品置为已卖出 boolean updateFlag productService.lambdaUpdate() .eq(Product::getId, product.getId()) .eq(Product::getStatus, 0) .set(Product::getStatus, 1) .update(); if (!updateFlag) { throw new RuntimeException(商品已被他人下单); } return Result.success(orderNo); }4.2 Android 端订单界面的刷新策略避免用户看到过期状态Android 端订单列表页面同样使用 RecyclerView 分页但有一个额外的问题用户从商品详情页跳到订单确认页再返回列表订单状态可能已经被其他端修改了。很多项目用 onResume 里重新请求接口来解决这个做法简单有效但要注意不要在 onResume 里每次都拉全部分页数据而是刷新当前页的数据即可。我一般会采用“下拉刷新 页面可见时自动拉第一页”的组合onResume 时拉第一页下拉刷新时也拉第一页上拉加载时拉后续页。这里要做一个防抖如果 onResume 触发时上一次请求还没结束直接 return。否则用户从一个订单详情页返回时可能连续触发两次网络请求虽然数据一样但页面会有明显的闪烁和跳变。另一个细节是订单状态按钮的显示逻辑。用户在“待付款”状态能看到“去支付”“取消订单”按钮在“待确认”状态能看到“确认收货”按钮在终态看不到任何操作按钮。这些按钮建议用后端接口返回的可操作状态字段来驱动而不是 Android 端自己根据 status 推断。原因在于业务规则发展后比如增加“申请退款”状态Android 端改逻辑的代价远高于后端返回一个 buttons 数组。4.3 模拟支付与线下交易如何不引入真实支付而完成闭环校园二手平台通常不需要接入支付宝或微信支付因为交易大多是线下当面付。但订单流程里必须有“支付”这一步否则无法体现完整的业务流程。常见做法是做一个模拟支付接口用户点击“去支付”弹窗确认后调 /api/order/pay 接口后端把订单状态从 0 改为 1不做真实扣款。这个设计在答辩时完全说得通论文里写“为降低落地成本采用模拟支付流程实际交易在线下完成”。同时这个模拟支付的接口也要遵守“同一个订单只能支付一次”的约束。后端在更新订单状态时SQL 里加条件 status0这样并发请求只有第一个能成功。5. 实战避坑与常见问题真机联调、图片路径、并发扣减和 Token 失效排查这个章节是整篇笔记里最值钱的部分全部来自实际操作中反复遇到的故障现象。每一条都按“现象 - 原因 - 解决”的结构写清楚。建议你做到这里时如果遇到了相同现象直接翻这一部分对照排查能省掉不少搜索时间。5.1 真机连不上后端10.0.2.2 与局域网 IP 的分水岭现象模拟器里页面数据加载正常换到真机后接口超时网络请求全部失败。原因Android 模拟器里访问宿主机的 localhost 要用 10.0.2.2而真机必须用电脑的局域网 IP。很多初学者在模拟器调试通过后把代码原样装到真机上访问的还是 10.0.2.2自然不通。解决把后端接口的 baseUrl 抽到一个常量类里真机调试时改成电脑的局域网 IP例如 http://192.168.1.5:8080/。额外注意两点一是手机和电脑必须在同一 WiFi 下二是 Android 9 起默认禁止明文 HTTP 流量需要在 AndroidManifest 的 application 节点加 android:usesCleartextTraffictrue。这是出现频率最高的一个坑没有之一。5.2 图片加载出来是 404Spring Boot 静态资源映射漏配现象上传文件接口返回成功浏览器直接打开图片 URL 能显示但 APP 里 Glide 加载这张图却 404。更奇怪的是有时同一个 URL 在电脑上能打开在手机上不能。原因上传目录是外部绝对路径Spring Boot 默认不映射这个路径到 HTTP 访问路由。浏览器能打开是因为电脑上可能装了其他静态文件服务或者你访问的是本机路径手机上自然是 404。解决写一个 WebMvcConfigurer 配置类把外部目录映射成 URL 前缀Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadDir /); } }注意 addResourceLocations 的路径最后必须是斜杠结尾否则匹配失败。这类路径配置类问题没什么玄学纯属配置遗漏加上映射即可。5.3 商品被人重复下单缺少乐观锁导致状态错乱现象两个用户同时打开同一件在售商品几乎同时点下单两个人都看到了“下单成功”的页面。但刷新后只有一个订单存在另一个人看到商品已下架。原因下单接口更新商品状态时没有加 status0 条件两个请求都先查询到 status0然后都执行了 update status1数据库层面没有任何约束阻止第二次更新。解决如 4.1 节所示在更新语句的条件里加上status 0利用 MyBatis-Plus 的 lambdaUpdate 的 eq 方法实现。更新条数等于 1 才提交订单否则抛业务异常回滚。这里补一个额外提示事务里必须先更新商品状态再创建订单避免两个事务同时对查询结果做判断而错乱。5.4 Token 在 APP 里莫名其妙失效Redis key 过期时间设置不当现象用户明明每天都在打开 APP但每隔几天就被强制要求重新登录。更麻烦的是这个问题只在凌晨出现白天一切正常。原因token 过期时间设置的是绝对时间比如 7 天。不管用户是否活跃到期就失效。很多项目的 token 设计应该是滑动过期用户在过期时间内有操作就刷新过期时间连续 7 天不活跃才失效。Redis 可以在每次请求的拦截器里重新设置 key 的过期时间代价是多一条逻辑但能避免用户频繁被踢下线。解决在拦截器里校验 token 存在后调用 redisTemplate.expire(token: token, 7, TimeUnit.DAYS) 刷新过期时间。同时要注意刷新过期时间也会刷新 get 操作的缓存如果后端是多实例部署需要确保所有实例连接同一个 Redis否则会偶发 token 失效问题。5.5 Android 端列表数据重复分页请求的并发条件竞态现象快速触发上拉加载时列表里出现相邻两页数据完全相同或者中间少了某条数据。原因用户滚动到底部触发加载第 2 页但此时下拉刷新请求正在处理第 1 页两个请求同时返回后加载第 2 页的线程把新数据加上了紧接着刷新线程又重置了整个列表最终数据错乱。这是客户端并发编程的典型竞态问题。解决在每个请求方法入口加一个互斥锁用 AtomicBoolean 做状态标记。请求开始时判断是否能进入不能则直接返回请求结束 finally 中重置标记。这个方法不优雅但稳定适合所有不用 RxJava 响应式编程的小项目。如果用了协程或 RxJava可以取消上一页请求但对本项目来说加锁是最容易理解的做法。6. 从毕设到上线最少可行的加固清单与验收建议如果你打算把这份代码从课程设计提升到实际可用的程度下面这几件事是性价比最高的。不需要引入复杂框架每一条都是直接在现有代码上改动就能生效。第一对所有写操作接口做权限校验。目前商品发布、订单创建这类接口只是校验了 token 存在没有校验登录用户是否被禁用。可以在 User 表加一个 status 字段0 正常、1 禁用拦截器里查出用户状态禁用状态直接返回 403。这个改动大概需要 20 行代码但能挡住占位符验证和恶意注册刷接口的问题。第二给上传的图片加一层重新压缩。后端在接收到 MultipartFile 后用 Java 的 ImageIO 读入再按最大宽高重新输出到保存目录。这样即使客户端传了一个 4MB 的大图落到磁盘时也只有几百 KB节省存储空间的同时也给 APP 加载图片减轻压力。第三把项目打成 APK 发布包前务必开启混淆和压缩资源。Android Studio 里 build.gradle 的 minifyEnabled 改成 true配合 proguard-rules.pro 里对 Retrofit、Glide、Gson 的 keep 规则APK 体积能减小 30% 左右。注意如果混淆规则没写全运行时会抛 ClassNotFoundException建议在配置后立刻用 Release 包做一轮全流程回归测试。第四验证性测试清单可以这样列注册新用户、退出再登录、发布一件带图片的商品、用另一个账号浏览并下单、原账号确认收货、再回到详情页验证商品状态已同步。这个流程覆盖了大部分核心链路跑通后再考虑异常场景比如重复下单、未登录访问接口、图片超限上传这些。用 Postman 保存一份接口自动化文档App 端每改动一次就回归一遍。关于上线如果你只是交毕设把 APK 通过微信发给答辩老师演示即可不需要发布到应用市场。如果你真的想上架需要注册开发者账号、软著申请、隐私协议说明这个过程比写代码还繁琐建议先想清楚是否有必要。以我个人的经验校园二手平台最容易出问题的不是技术而是业务规则——比如卖家重复上架同一件商品、买家故意不确认、管理员权限没有边界。这些在论文里最好能落到数据库字段或接口设计上而不是只提“后续可以考虑”。做这个项目的这段时间我最大的教训是把大量时间花在了真机联调的地址配置和环境切换上后来把所有环境相关参数统一收敛到一个 BuildConfig 字段里才彻底终结了这些问题。希望这篇笔记里提到的每一个坑都能帮你少走一段我走过的弯路祝你的项目和论文顺利收尾。本文还有配套的精品资源点击获取
网站建设高端定制企业官网