尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

仿闲鱼二手交易平台源码实战:从Spring Boot后端到安卓端部署

发布时间:2026/9/26 13:23:50

资讯中心
01
ARTICLE

仿闲鱼二手交易平台源码实战:从Spring Boot后端到安卓端部署

仿闲鱼二手交易平台源码实战:从Spring Boot后端到安卓端部署
简介面向计算机相关专业学生与安卓开发初学者的Java仿闲鱼二手交易平台项目包含完整的安卓客户端与后端打包代码实现邮箱注册登录、商品发布与购买、列表浏览、聊天沟通及消息列表等核心电商交易功能可直接用于毕业设计、课程设计或项目阶段演示。压缩包内共174个文件以Java源码、XML布局与配置、Gradle构建脚本、PNG图片素材及APK安装包为主另含README说明文档整体仅4.02MB结构清楚便于定位代码与资源。已有505人学习下载适合中等基础者对照实践或二次开发。项目代码经测试运行成功答辩评审平均分达96分可私聊远程教学读者可用其掌握安卓端与后端联调流程也可基于现有模块扩展支付、订单管理等新功能是入门移动电商开发的实用参考。1. 仿闲鱼安卓端加后端这套源码到底能拿来干什么临近毕设或者准备 Java 面试的人多半都见过这类标题仿闲鱼的二手交易平台安卓端、后端、源代码、打包全给齐。看着东西挺全真拿到手却往往不知道先看哪块——直接跑安卓端大概率连不上接口先看后端又看不懂业务上下文。这套项目本质上是一个典型的前后端分离实战案例安卓端负责商品流、发布、下单、个人中心后端用 Java 技术栈提供 RESTful 接口和订单流转逻辑最后各自打包成 APK 和 JAR 部署。它适合有 Java 基础、想用完整项目补简历或者应付课程设计的人。一个反直觉的结论是复现这种项目第一步不是打开 Android Studio 跑模拟器而是先把数据模型和接口约定啃明白否则后面前后端联调时全是翻车。2. 先拆数据模型商品、订单、用户三张表怎么撑起一个交易闭环2.1 接口约定先定死RESTful 路由与统一响应体前后端分离项目实战里最怕的不是业务复杂而是两边各写各的。安卓端连不上、解析不了后端数据八成不是代码坏了是接口约定没对齐。拿到这套仿闲鱼源码我一般会先建一张接口清单把路由、方法、入参、出参列出来再去看实现。核心接口不会超过八个用户注册登录、商品列表、商品详情、发布商品、下单、订单列表、更新订单状态。路由设计要遵循资源语义动词尽量交给 HTTP Method 表达。功能方法路径入参出参注册POST/api/user/register手机号、昵称、密码用户 ID登录POST/api/user/login手机号、密码token商品列表GET/api/product/listpage、size、keyword商品分页商品详情GET/api/product/{id}—商品完整信息发布商品POST/api/product表单商品信息 图片商品 ID下单POST/api/orderproductId订单号我的订单GET/api/order/liststatus订单列表更新订单状态PUT/api/order/{id}/statusstatus成功/失败统一响应体是另一个容易被新手忽略的约定。前后端分离项目最忌讳后端一会儿返回 JSON 对象、一会儿返回裸字符串。规范做法是包一层 Result 结构code 为 0 表示成功非 0 表示业务错误message 给人类可读的提示data 放业务数据。安卓端用 Gson 反序列化时只认这一层壳解析逻辑才能统一。很多源码项目里这一层是缺失的接口直接裸返回 Map联调阶段安卓端代码会写得非常痛苦。2.2 建表 DDL为什么 order 表不能只存买家 ID数据模型决定了这个项目能走多远。二手交易平台的核心是商品和订单的状态流转表设计必须把状态字段、金额单位、时间字段这三件事想清楚。用户表最简单但也别乱建。手机号要做唯一索引密码存哈希不存明文信用分给默认值——闲鱼这类平台的信用体系在课程设计里可以用一个 int 字段糊弄过去但字段得有。CREATE TABLE user ( user_id bigint NOT NULL AUTO_INCREMENT COMMENT 用户ID, nickname varchar(32) NOT NULL COMMENT 昵称, phone varchar(20) DEFAULT NULL COMMENT 手机号, password_hash varchar(128) NOT NULL COMMENT 密码哈希, credit_score int NOT NULL DEFAULT 100 COMMENT 信用分, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (user_id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;商品表和订单表才是重头戏。商品表里的 status 字段我建议用 tinyint 而不是字符串四个状态0 待上架、1 在售、2 已下架、3 已售出。整数比字符串省空间索引效率也高更重要的是后端写状态机判断时switch(status)比equals(ON_SALE)干净得多。CREATE TABLE product ( product_id bigint NOT NULL AUTO_INCREMENT, seller_id bigint NOT NULL COMMENT 卖家ID, title varchar(64) NOT NULL COMMENT 标题, description text COMMENT 描述, price_cents bigint NOT NULL COMMENT 价格单位分, original_price_cents bigint DEFAULT 0 COMMENT 原价单位分, status tinyint NOT NULL DEFAULT 1 COMMENT 0上架中 1在售 2下架 3已售出, image_urls varchar(1024) DEFAULT NULL COMMENT 图片URL逗号分隔, view_count int NOT NULL DEFAULT 0 COMMENT 浏览次数, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (product_id), KEY idx_seller_status (seller_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;价格字段用price_cents而不是pricedouble这是支付和交易系统里的通用惯例。浮点数存储金额会有精度误差0.1 加 0.2 这种问题在 double 里是玄学级别的坑用分为单位的 bigint下单时把元转成分展示时再除以 100后端逻辑里永远不碰浮点运算。订单表命名要注意 order 是 MySQL 的保留字直接建order表会爆语法错误正规做法是建orders表。CREATE TABLE orders ( order_id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 业务订单号, product_id bigint NOT NULL COMMENT 商品ID, buyer_id bigint NOT NULL COMMENT 买家ID, seller_id bigint NOT NULL COMMENT 卖家ID冗余, status tinyint NOT NULL DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, price_cents bigint NOT NULL COMMENT 成交快照价, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, paid_at datetime DEFAULT NULL, received_at datetime DEFAULT NULL COMMENT 确认收货时间, PRIMARY KEY (order_id), UNIQUE KEY uk_order_no (order_no), KEY idx_product (product_id), KEY idx_buyer (buyer_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;订单表里冗余了seller_id这违反教科书上的三范式但符合交易系统的实际需要——查“我卖出的”和查“我买到的”是两个最高频的查询如果不冗余卖家 ID每次都要 join 商品表再 join 用户表没必要。另一个关键设计是price_cents存的是快照价不是实时读商品表价格。买家下单后卖家改价订单也不能跟着变快照价是交易凭证。物理外键在交易系统里不建议建。外键约束会影响插入性能分库分表后外键直接失效业界主流做法是逻辑外键加索引靠业务代码保证一致性。这套源码如果是教学向的可能建了物理外键拿到手建议去掉保留 KEY 索引就够了。2.3 订单状态机从“在售”到“已收货”的九种可能订单状态是整个项目的业务核心也是面试时最容易被追问的点。先看状态定义0 待付款、1 待发货、2 待收货、3 已完成、4 已取消。看似五个状态实际流转路径比想象的多。正常路径是买家下单订单进入待付款付款后状态变成待发货卖家发货后变成待收货买家确认收货订单完成。但分支也很多待付款阶段买家取消订单直接进已取消卖家在商品被下单后手动下架已产生的订单不能跟着取消得让买家感知到交易仍在进行。public enum OrderStatus { WAIT_PAY(0, 待付款), WAIT_SHIP(1, 待发货), WAIT_RECEIVE(2, 待收货), COMPLETED(3, 已完成), CANCELED(4, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public static boolean canTransit(int from, int to) { // 0-1 支付0-4 买家取消 // 1-2 发货1-4 卖家取消 // 2-3 确认收货 if (from WAIT_PAY.code (to WAIT_SHIP.code || to CANCELED.code)) { return true; } if (from WAIT_SHIP.code (to WAIT_RECEIVE.code || to CANCELED.code)) { return true; } return from WAIT_RECEIVE.code to COMPLETED.code; } }状态机写成枚举的好处是业务代码里不会出现魔法数字。下单接口里判断商品 status 是否为 1在售订单接口里判断订单 status 能否从当前值跳到目标值全部走canTransit非法流转直接抛业务异常。安卓端那边我的发布页和订单详情页要根据状态渲染不同按钮文案前后端共用同一套状态定义联调时才算对得上。商品表的状态和订单表的状态是两套状态机中间通过“下单时把商品状态从在售改成已售出”这个动作联动。这个联动一旦没做事务控制就会出现商品已经下单了但列表里还在卖这也是后面并发问题要单独聊的。3. 后端实现Spring Boot 控制器、鉴权和状态流转3.1 项目骨架与依赖spring-boot-starter-web 之外的三个必加项后端骨架用 Spring Boot 是这套项目的主流方案没有争议。比起直接用 RuoYi 这种成熟脚手架改我更建议自己从空工程搭因为面试时被问“你这个项目架构怎么来的”答不上来反而扣分。自建骨架的依赖控制在六个以内就行。核心依赖是 spring-boot-starter-web这是必须的。数据访问层用 MyBatis-Plus比原生 MyBatis 省掉大量 XML 映射数据库驱动用 com.mysql:mysql-connector-j注意新版坐标已经不带 .j 后缀的旧包名了再用 lombok 消掉 getter/setter 模板代码。鉴权不建议直接上 Spring Security课程设计和简历项目用拦截器加 JWT 就够了轻量且能讲清楚原理。dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependencyapplication.yml 里三个配置决定了这个项目能不能跑起来数据库连接、MyBatis-Plus 的日志和驼峰映射、文件上传大小限制。图片上传是二手交易平台的刚需Spring Boot 默认单文件 1MB发布商品带三张图必炸必须调大。spring: datasource: url: jdbc:mysql://localhost:3306/flea_market?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 30MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplURL 参数里characterEncodingutf8mb4和serverTimezoneAsia/Shanghai缺一不可。前者保证中文商品标题不乱码后者保证时间字段不差八小时。map-underscore-to-camel-case让数据库的seller_id自动映射到实体的sellerId否则查出来全是 null这是新手最容易卡住的地方。IDEA 里启动这个项目的顺序一般是先启动 MySQL再启动 Spring Boot 后端最后才是安卓模拟器。后端启动后先去浏览器访问/api/product/list能返回 JSON 再碰安卓端。3.2 发布商品接口MultipartFile 上传与图片路径回显发布商品是二手交易平台第一个核心接口。它的特点是既传 JSON 又传文件不能直接用 RequestBody 一把接要用表单格式。PostMapping(/api/product) public ResultLong publish(RequestHeader(token) String token, RequestPart(product) ProductReq req, RequestPart(value images, required false) ListMultipartFile images) { Long sellerId userService.getUserIdFromToken(token); if (sellerId null) { return Result.error(401, 登录已过期); } ListString urls new ArrayList(); if (images ! null !images.isEmpty()) { for (MultipartFile file : images) { String url fileService.saveImage(file); urls.add(url); } } Product product new Product(); product.setSellerId(sellerId); product.setTitle(req.getTitle()); product.setDescription(req.getDescription()); product.setPriceCents(req.getPriceCents()); product.setStatus(1); product.setImageUrls(String.join(,, urls)); productService.save(product); return Result.ok(product.getProductId()); }这里有几个关键点。RequestPart(product)接的是表单里名为 product 的 JSON 字符串安卓端用 OkHttp 的 MultipartBody 提交时part 的 name 必须对应上否则后端直接报 Required part product is not present。RequestHeader(token)从请求头拿登录凭证体现的是前后端分离项目里 token 走 header 不走参数的约定。图片保存路径是另一个坑。fileService.saveImage 内部不能把图片存到项目的 resources 目录因为后端打包成 JAR 后 resources 是只读的。常规做法是配置一个外部磁盘路径比如 /data/flea-market/images然后把该目录映射成静态资源生成 URL 存进数据库的 image_urls 字段。Configuration public class WebConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceHandler(file: uploadDir /); } }文件存储路径不要写在代码里放进 application.yml 的file.upload-dir配置项打包部署时改成服务器实际路径就行。这样本地开发、服务器部署、安卓真机联调三套环境不用改代码只改配置。3.3 下单接口乐观锁与状态校验的顺序不能反下单是面试里最常被拷打的接口。表面逻辑简单查商品、判断在售、插入订单、改商品状态。但并发下单时库存超卖问题马上暴露。仿闲鱼这种 C2C 平台商品数量是 1超卖的表现是同一件商品被两个人同时下单成功订单只该有一个。秒杀系统的乐观锁方案在这里同样适用核心是更新时带条件判断影响行数。Transactional public Long createOrder(Long buyerId, Long productId) { Product product productService.getById(productId); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品不存在或已下架); } // 乐观锁只有 status 还是 1 时才能更新成已售出 boolean updated productService.lambdaUpdate() .eq(Product::getProductId, productId) .eq(Product::getStatus, 1) .set(Product::getStatus, 3) .update(); if (!updated) { throw new BusinessException(手慢了商品已被拍下); } Order order new Order(); order.setOrderNo(generateOrderNo()); order.setProductId(productId); order.setBuyerId(buyerId); order.setSellerId(product.getSellerId()); order.setStatus(0); order.setPriceCents(product.getPriceCents()); orderService.save(order); return order.getId(); }注意顺序先查一遍商品拿到卖家 ID 和价格快照再用带 status 条件的 UPDATE 抢占商品。UPDATE 影响行数为 0 说明商品状态已经不是 1直接抛异常。这里不能反过来先插入订单再更新商品否则订单表里可能出现孤儿订单。查和改之间有窗口期纯靠 synchronized 锁 JVM 实例没用一是多实例部署锁不住二是锁范围太粗会把不同商品的下单也串行化。带条件的 UPDATE 是数据库层面保证原子性的做法单机多实例都适用。Transactional必须加在 createOrder 上。商品状态更新和订单插入要在一个事务里任何一个失败整体回滚。面试追问事务失效场景时要能答出来同类内部调用不走代理导致 Transactional 失效、异常被 try-catch 吞掉导致回滚不触发、非 public 方法不生效。这些都是实际项目里踩过的坑。业务订单号 generateOrderNo 也别用数据库自增 ID下单后回显给用户、客服查单、对账都要用业务号。规则可以是时间戳加随机数加用户 ID 后四位保证唯一性靠数据库的唯一索引兜底。4. 安卓端实现Retrofit 封装、列表到详情再到下单的页面闭环4.1 Gradle 配置与 Retrofit 封装BaseUrl 是打包时最常改的地方安卓端的技术选型很清楚网络层用 Retrofit 加 OkHttp图片加载用 GlideJSON 解析用 Gson页面用 RecyclerView 撑起商品流。这套组合在主流源码项目里出现频率最高教程多、踩坑记录也全对复现项目的人来说最稳妥。build.gradle 里的依赖要把版本对齐Retrofit 2.9.0 配 OkHttp 4.x 是稳定组合。converter-gson 必须加否则 Retrofit 不会自动把 JSON 转成 Bean很多人漏了这一个依赖导致接口返回后解析直接崩溃。implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation com.squareup.okhttp3:okhttp:4.10.0 implementation com.squareup.okhttp3:logging-interceptor:4.10.0 implementation com.github.bumptech.glide:glide:4.15.1封装 Retrofit 单例有个关键参数BaseUrl。本地开发时安卓模拟器访问宿主机要用http://10.0.2.2:8080/不能用 localhost模拟器里的 localhost 指向模拟器自己。真机联调要改成电脑在局域网里的 IP比如http://192.168.1.5:8080/此时手机和电脑必须连同一个 Wi-Fi。这个 BaseUrl 也是后面打包时最常改的地方。很多源码项目把 BaseUrl 写死在代码里本地跑通了打包给测试装测试的手机连不上开发机网络请求全部失败。public class ApiClient { private static final String BASE_URL http://10.0.2.2:8080/; private static ApiService apiService; public static ApiService getApiService() { if (apiService null) { OkHttpClient client new OkHttpClient.Builder() .addInterceptor(new AuthInterceptor()) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .build(); Retrofit retrofit new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); apiService retrofit.create(ApiService.class); } return apiService; } }AuthInterceptor 是登录态的统一注入点每个请求自动附带 token安卓端不用在每个接口调用处手动塞 token。超时时间参数也有讲究10 秒连接超时、15 秒读取超时后端保存图片时耗时较长读取超时设得太短会在上传接口上反复体验超时。注意安卓 9 以后默认禁止明文 HTTP 流量。后端是 http:// 而不是 https:// 的话不配置直接请求报 Cleartext HTTP traffic not permitted。AndroidManifest.xml 的 application 标签里加android:usesCleartextTraffictrue这是复现阶段必踩的坑。4.2 首页商品流RecyclerView 与 Glide 的缓存目录问题首页是二手交易平台的门面逻辑不复杂分页拉商品列表、RecyclerView 渲染卡片、下拉刷新、上拉加载更多。但实现上有三个细节决定体验和稳定性。适配器里绑定数据时Glide 加载网络图片默认会用应用缓存目录这本身没问题。但如果后端返回的是http://192.168.1.5:8080/images/xxx.jpg这种局域网地址换网络环境后缓存里的图片还在但原地址变了图片加载会失败。解决方案是在 Glide 的 RequestOptions 里配置.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)或者维护一个缓存清理入口。二手交易平台商品图片变化不频繁调试阶段直接跳过缓存最省心。列表项的商品状态决定按钮展示形态。适配器里要根据 product.status 切换操作按钮而不是所有商品一律显示“立即购买”。public void bind(Product product) { holder.title.setText(product.getTitle()); holder.price.setText(formatPrice(product.getPriceCents())); Glide.with(holder.itemView.getContext()) .load(product.getFirstImageUrl()) .placeholder(R.drawable.ic_placeholder) .into(holder.image); switch (product.getStatus()) { case 1: holder.btnBuy.setVisibility(View.VISIBLE); holder.btnBuy.setText(立即购买); holder.tvStatus.setVisibility(View.GONE); break; case 3: holder.btnBuy.setVisibility(View.GONE); holder.tvStatus.setVisibility(View.VISIBLE); holder.tvStatus.setText(已卖出); break; default: holder.btnBuy.setVisibility(View.GONE); holder.tvStatus.setText(已下架); } }这里 status 的取值必须跟后端枚举定义完全一致。后端如果改成字符串状态安卓端这边全部要跟着改所以前面强调前后端共用同一套状态定义。分页加载用标准做法列表滚动到底部时自动请求下一页pageNum 加一拼接数据后 notifyDataSetChanged。注意用 loadMore 标志位防止重复请求这个标志位没做好快速滑动时同一页数据会重复插入列表商品卡片出现两遍是列表页最常见的 bug。4.3 我的发布页状态不同按钮文案与可用性要跟着变个人中心里的“我的发布”页是检验对状态机理解的地方。同样是商品列表首页展示的是一律可买但我的发布页展示的是我卖出的东西每个商品的操作按钮完全不同在售商品可以下架已下架商品可以重新上架已卖出商品只能查看订单。这个页面调用的接口是/api/product/list?sellerId当前用户ID后端按卖家维度查询。按钮点击事件要把操作封装成接口调用下架是修改商品状态重新上架也是修改商品状态两个操作其实调用同一个状态更新接口只是传的目标状态不同。POST(/api/product/{id}/status) CallResultVoid updateStatus(Path(id) long productId, Query(status) int status);安卓端要做的是根据当前状态决定下拉按钮的选项。列表里看到“已下架”点进去应该是“重新上架”看到“在售”按钮文案变成“下架”。这些逻辑不复杂但不写清楚的话用户会困惑为什么我的商品按钮全是灰色的。订单列表页同理。卖家视角看到的订单是“待发货”时要显示“发货”按钮“待收货”时显示“提醒买家确认”买家视角则完全不同“待付款”显示“去付款”“待收货”显示“确认收货”。两个角色在一套订单数据上靠 status 字段渲染不同视图这种交互逻辑是二手交易平台区别于普通电商列表页的地方也是项目展示时的加分项。5. 避坑从数据库乱码到机型适配二手交易 App 最常见的五个翻车点5.1 图片上传到服务器后安卓端加载 404现象发布商品时图片上传成功接口返回的商品图片 URL 在电脑浏览器里能打开但安卓端 Glide 加载显示裂图。原因后端保存图片返回的 URL 路径和静态资源映射不一致。最常见的是保存到D:/upload/xxx.jpg返回的 URL 却是/upload/xxx.jpg但 Spring Boot 没有把磁盘目录映射成 URL 路径Tomcat 找不到对应资源。另一个隐蔽原因是项目里用了classpath:static作映射开发环境能访问打成 JAR 包后路径变化导致 404。解决统一用外部磁盘目录存图WebMvcConfigurer 里显式映射虚拟路径和物理路径的对应关系。addResourceHandler(/images/**).addResourceLocations(file: uploadDir /)最后的斜杠不能丢丢了也会 404。线上排查时直接看后端日志里打印的保存路径和最终拼接的 URL对不上就是这个问题。5.2 并发下单库存超卖明明加了 synchronized 还是超了现象用 Jmeter 模拟 50 个并发请求同时下单同一件商品数据库里出现两笔有效订单商品状态也是已售出。原因synchronized 锁的是 JVM 内部对象单实例部署时锁得住但项目一旦部署多实例或者上了负载均衡每个实例各有一把锁请求分散到不同实例后锁完全失效。更本质的问题是查状态和改状态之间隔着网络往返和业务代码时间窗口里第二个请求读到旧状态判断商品还在售于是也走了下单逻辑。解决把并发控制下沉到数据库用带条件的 UPDATE 作为抢占动作。UPDATE product SET status3 WHERE product_id? AND status1数据库行锁保证同一时刻只有一个请求能成功更新影响行数为 0 则说明被抢了。这条 SQL 才是真正的兜底Java 层的各种锁都只是辅助。订单表里还可以给 product_id 加唯一约束做最后防线但注意业务上要允许同一商品在一段时间内重复下单被取消的情况唯一索引不一定适合所有场景。5.3 debug 签名能跑、release 包登录就报 SSL 错误现象Android Studio 里 Run 按钮跑应用一切正常打正式包装到手机登录接口直接报 SSL 或握手失败。原因debug 包默认使用 Android Studio 自动生成的 debug 签名release 包用自己的 keystore两个签名不一样服务端如果做了签名校验就会拒。更常见的是后端接口是自签名 HTTPS 证书OkHttp 默认校验证书链debug 时可能通过过配置跳过了校验release 包没配。解决确认两端用的签名统一。后端如果是测试环境自签名证书OkHttpClient 的 SSL 校验不要全局关闭只针对测试域名做 TrustManager 白名单处理。正式环境必须换正规证书把证书配进系统信任链。另外一个隐蔽根源是混淆开关——release 包开了 minifyEnabledRetrofit 的实体类字段被混淆改名Gson 反序列化拿不到数据表现也是登录后拿不到用户信息容易和 SSL 问题混在一起。实体类和 API 接口相关的类要加混淆 keep 规则。5.4 安卓 11 以上访问图片路径崩溃现象安卓 10 以下手机正常安卓 11 以上一打开发布页选择图片就闪退Logcat 报 SecurityException。原因targetSdk 29 以上强制分区存储应用不能随便访问公共目录下的文件路径。很多旧源码还在用Environment.getExternalStorageDirectory()拿绝对路径在分区存储策略下直接抛权限异常。加上现在的手机品牌对文件访问权限管控更严格华为小米的定制 ROM 还会额外弹授权框。解决targetSdk 保持在 30 以下同时配 requestLegacyExternalStorage 是临时方案新项目别这么干直接适配分区存储。选择图片用系统 Photo Picker 或者 MediaStore 的 Uri 去取拿到的是 content:// 开头的 Uri 而不是文件路径上传时用 ContentResolver 打开流写入临时文件再转 MultipartFile。图片显示同理Glide 可以直接加载 content:// Uri不需要转成绝对路径。5.5 时间字段差 8 小时JSON 序列化把 LocalDateTime 当 UTC 用现象安卓端看到的下单时间比实际晚 8 个小时后端数据库里查出来时间是对的接口返回后就不对。原因Spring Boot 默认的 Jackson 序列化 LocalDateTime 时没有带时区信息安卓端 Gson 解析后按本地时区展示。数据库连接串里配了 serverTimezoneAsia/Shanghai但 JSON 输出这一层又丢了一次时区。同一个系统里数据库、后端、安卓三个环节各差八小时排查起来很折磨人。解决后端全局配置 ObjectMapper 的时区而不是靠局部的 JsonFormat 注解到处打补丁。spring.jackson.time-zoneGMT8和spring.jackson.date-formatyyyy-MM-dd HH:mm:ss写进 application.yml。数据库连接串的 serverTimezone、后端 Jackson 的 time-zone、安卓端 SimpleDateFormat 默认时区三处保持一致时间问题一次根治。另外字段类型统一用 LocalDateTime别混着用 Date 和 LocalDateTime否则前后端联调时格式对不齐。6. 打包与验证从源码到可安装 APK 和可部署 JAR 的完整链路6.1 安卓端打包签名文件、版本号与 buildTypes 配置安卓端打包的核心是签名。用 keytool 生成一个正式的 keystore签名信息写进 build.gradle 的 signingConfigs别再用 debug 签名交付。keytool -genkeypair -v -keystore flea-release.jks \ -keyalg RSA -keysize 2048 -validity 10950 -alias fleabuild.gradle 里引用签名文件versionCode 每次发版手动加一versionName 按语义化版本写。Release 包的 buildType 里开启 minifyEnabled 的话记得带上上一章说的实体类混淆 keep 规则。打包命令在项目根目录执行./gradlew assembleRelease产物在 app/build/outputs/apk/release/ 下。6.2 后端打包跳过测试、指定 Profile 与外部配置分离后端用 Maven 打包最省心的命令是跳过测试否则单测里连不上数据库直接中断构建。打包完成后用 Profile 区分环境启动配置留在外部不跟着 JAR 走。mvn clean package -DskipTests java -jar target/flea-market-0.0.1.jar \ --spring.profiles.activeprod \ --spring.config.additional-location/etc/flea-market/config/--spring.config.additional-location指定外部配置文件目录这样数据库密码、上传路径都不用打进 JAR换机器部署只改外部配置。不少人直接在 IDEA 里点 Maven 面板的 package结果因为编码问题或测试类报错构建失败命令行加 -DskipTests 能绕开大部分坑。6.3 验证清单装到真机后按这个顺序点一遍打包完成后拿一台真机而不是模拟器按这个顺序验收注册新账号、发布一件带图商品、退出登录、换个账号登录并搜索到这件商品、下单、卖家账号发货、买家账号确认收货、确认商品列表状态变成已卖出。这套流程在安装全新 APK 后走一遍能覆盖登录鉴权、文件上传、商品流转、订单状态机全部核心链路。我现在的习惯是拿到任何一套交易类源码先不急着跑界面把 application.yml 里的数据库和上传路径配置改成本机环境后端起来后拿 Postman 把商品列表和下单接口过一遍确认后端输出符合预期再打开安卓端。顺序对了这套仿闲鱼的源码从跑通到打包交付基本半天时间。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。