ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于Spring Boot+安卓原生开发的二手书城App实战与国产系统适配指南

2026/9/9 20:16:15 拓冰建站 浏览量
基于Spring Boot+安卓原生开发的二手书城App实战与国产系统适配指南 这套二手书城App是我前段时间从零做完的一个完整项目后端用Spring Boot客户端是安卓原生开发中间还专门花了不少精力做了国产系统统信UOS、麒麟这类上的适配。整个项目已经跑通普通安卓手机和部分国产设备上都能正常安装使用。文章适合正在做校园二手交易、图书漂流、闲置回收类应用的开发者也适合想了解Spring Boot 安卓这套经典组合怎么落地、国产系统适配从哪下手的朋友。后台管理、客户端、接口设计、数据库表结构、打包部署这些环节我都会讲到不光是丢代码还会说清楚每个关键决策背后的原因。尤其是一些容易踩坑的地方比如Spring Boot版本选择、文件上传兼容性、国产系统上APK安装失败这些在官方文档里通常找不到现成答案。1. 项目整体设计与技术选型1.1 需求拆解二手书城到底要做哪些事做项目最怕上来就写代码需求不清楚写到一半必然返工。我的做法是先列功能清单分清楚哪些是MVP必须的、哪些可以后续迭代。用户端核心功能我一开始就定了六块注册登录、图书浏览、搜索筛选、图书详情、发布图书、下单购买。图书浏览包括首页推荐、分类列表、最新上架搜索筛选要支持关键词、分类、价格区间和成色状态发布图书需要拍照、填书名、作者、价格、描述、成色下单购买则要处理订单状态流转。订单这块是最容易扯皮的我简化了流程用户下单后生成待确认订单卖家看到订单后确认成交买家确认收货后完成。砍掉了在线支付和站内聊天因为这两个功能涉及资质和第三方服务MVP阶段先不做。如果做成二手交易平台再考虑接入支付和IM。管理员功能我没有单独做Web后台而是在安卓端加了一个管理员角色入口通过用户表的role字段区分。管理员可以下架违规图书、查看全部订单、管理用户状态。这样避免了前后端两套系统的工作量对课程设计、个人项目这种规模来说非常合适。1.2 技术栈选择Spring Boot 安卓原生这套组合怎么定的后端用Spring Boot几乎是共识生态成熟、上手快、部署简单。安卓端我选择了原生Java开发没有用Flutter、uni-app这类跨平台方案。原因很简单原生应用在系统权限调用、相机相册操作、后续对接国产系统兼容层时更顺手而且Java语言在现网排障时大家都会团队协作没有门槛。这里重点说下为什么不选跨平台方案。二手书城要调用相机拍照、访问相册选图、保存图片到本地这些能力在各家系统上行为差异很大尤其到了国产系统的安卓兼容环境里跨平台框架生成的应用可能会出现权限申请不弹窗、图片选择器打不开等奇怪问题。原生代码至少能直接操控系统API出问题可以准确定位。数据持久化我选了MySQL 8ORM用MyBatis-Plus。RESTful接口返回JSON统一格式。登录鉴权用JWT无状态、安卓端好保存。图片存储直接放服务器本地目录通过Nginx静默映射访问没有引入OSS因为个人项目没有太多云资源开销。1.3 Spring Boot版本选择为什么停在2.x这可能是很多人忽略但最关键的一个决策。我在项目初期就定下来用Spring Boot 2.7.18而不是最新的3.x版本。原因有三点。第一3.x强制要求JDK17而国内大量开发和生产环境还在用JDK8我的部署服务器上跑的就是JDK8用3.x就得换环境成本太高。第二3.x从javax.servlet迁移到jakarta.servlet很多老版本的第三方库不兼容比如文件上传、验证框架、数据库驱动都要跟着升级。第三团队里其他人对2.x更熟出了问题能快速排查没必要为了尝鲜增加风险。Spring Boot 2.7.18是2.x最后一个小版本官方维护到2023年底社区资料丰富踩坑案例都能搜到解决方案。对于中小型项目来说稳定压倒一切。我身边就有人图新鲜用了Spring Boot 3.2结果MyBatis-Plus插件版本对不上折腾了两天才搞定这是完全没必要的。2. 数据库设计与后端核心实现2.1 表结构设计用户、图书、订单、收藏数据库设计是核心基础字段设计不好后面接口写得再漂亮也是空中楼阁。我用了5张核心表user用户、book图书、book_image图书图片、orders订单、favorite收藏。分类信息用枚举字段存刚开始不需要单独建表后面分类多了再拆。用户表字段包括id、username、passwordBCrypt加密、nickname、avatar、phone、role、status、create_time。图书表字段包括id、user_id卖家ID、title、author、publisher、isbn、price、original_price、condition成色1全新 2九成新 3八成新 4七成新及以下、description、status1在售 2已下单 3已售出 4下架、category、view_count、create_time。订单表字段包括id、book_id、buyer_id、seller_id、price、status1待确认 2待收货 3已完成 4已取消、create_time、confirm_time、complete_time。收藏表简单user_id和book_id联合唯一索引即可。这里有个设计细节price字段在book表和orders表中都出现了但订单中的price是下单那一刻的成交价快照不是关联查询book表中的当前价格。因为卖家随时可以改价如果订单表只存book_id买家看到的订单价格可能会变这是交易系统的基本要求。我曾经在另一个项目里偷懒没做快照结果用户投诉价格对不上教训很深刻。建表SQL核心部分大概长这样需要注意字符集用utf8mb4否则存emoji或生僻字会乱码CREATE TABLE book ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 卖家ID, title varchar(100) NOT NULL COMMENT 书名, author varchar(50) DEFAULT NULL, publisher varchar(100) DEFAULT NULL, isbn varchar(20) DEFAULT NULL, price decimal(10,2) NOT NULL COMMENT 售价, original_price decimal(10,2) DEFAULT NULL, condition tinyint(4) DEFAULT NULL COMMENT 1全新 2九成新 3八成新 4七成新及以下, description text, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1在售 2已下单 3已售出 4下架, category varchar(20) DEFAULT NULL, view_count int(11) NOT NULL DEFAULT 0, create_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_status (status), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书信息表;2.2 Spring Boot分层结构与后端骨架搭建后端代码我严格按Controller、Service、Mapper三层来拆。Controller只做参数接收和结果封装不写业务逻辑Service负责业务处理Mapper用MyBatis-Plus封装CRUD操作。工程结构大概长这样src/main/java/com/bookcity ├── config // 配置类CORS、拦截器、MyBatis-Plus分页插件 ├── controller // 接口层 ├── service // 业务逻辑层 │ └── impl ├── mapper // 数据访问层 ├── entity // 实体类 ├── common // 公共类统一返回体、异常处理、JWT工具 └── BookCityApplication.javaapplication.yml里几个关键配置需要注意。数据库连接池我用了HikariCPSpring Boot默认最大连接数调到20连接超时设30秒避免周末高峰期连接被打爆。上传文件的临时存储目录要提前建好不能用相对路径否则打成JAR包放到服务器上会出错。MyBatis-Plus分页插件必须在config里显式配置否则分页查询会查全表内存溢出都有可能Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setMaxLimit(50L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } }2.3 核心接口设计发布、搜索、下单接口设计重点说三个发布图书、搜索图书、下单。这三个接口覆盖了二手书城最核心的业务闭环也是安卓端对接最频繁的接口。发布图书接口我设计成POST /api/book/publish请求参数用multipart/form-data包含图书信息字段和一个文件列表。这里有个细节图片文件放在图书主表中作为主图其他图片存book_image表。图书封面展示只需要一张主图所以我在book表里冗余了cover_image字段避免每次列表页都去子表查询图片。搜索图书接口是GET /api/book/list支持keyword、category、condition、minPrice、maxPrice、pageNum、pageSize参数。MyBatis-Plus的LambdaQueryWrapper用起来很舒服但要注意like查询默认会走全表扫数据量大之后得加全文索引或改Elasticsearch个人项目阶段不用过度设计。下单接口是POST /api/order/create这个接口我加了事务控制。逻辑是检查图书状态是否为在售是的话锁定图书把status改成已下单然后创建订单。如果创建订单失败要把图书状态回滚到在售不然书就莫名其妙没了。用Transactional注解处理Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateRequest request) { Book book bookMapper.selectById(request.getBookId()); if (book null || book.getStatus() ! BookStatus.ON_SALE.getCode()) { throw new BusinessException(图书不存在或已下架); } // 锁定图书 Book update new Book(); update.setId(book.getId()); update.setStatus(BookStatus.ORDERED.getCode()); bookMapper.updateById(update); // 创建订单 Order order new Order(); order.setBookId(book.getId()); order.setBuyerId(request.getBuyerId()); order.setSellerId(book.getUserId()); order.setPrice(book.getPrice()); order.setStatus(OrderStatus.PENDING_CONFIRM.getCode()); orderMapper.insert(order); return order.getId(); }2.4 登录鉴权JWT怎么融入安卓端登录鉴权我用了JWT无状态、跨端方便。用户登录成功后后端生成一个token包含用户ID和过期时间用HMAC-SHA256签名。安卓端拿到token后存在SharedPreferences里之后所有请求在请求头拼上Authorization: Bearer {token}。后端用一个拦截器统一拦截除了登录注册接口之外其他接口都要求token合法。代码结构上我定义了AuthInterceptor在preHandle里从请求头取token解析成功就把userId放到request attribute里Controller再用RequestAttribute取。这里有几个值得说的坑。第一token过期时间不能太长我设了7天。因为这是一款校园场景的应用学期结束用户就很少登录了过期时间太长反而会让人忘记账号密码。第二要处理token过期后安卓端自动跳回登录页的逻辑安卓端网络层统一判断返回码为401时清除本地token并跳转。第三密码用BCrypt加密不要用MD5MD5撞库太容易了。3. 安卓客户端开发从页面到接口3.1 客户端架构与网络层搭建安卓端我用了MVVM模式配合ViewModel、LiveData、Retrofit这三个核心组件。很多人一上来就整项目用协程、Flow但对于个人项目来说LiveData已经足够关键是架构清晰。网络层封装上我定义了一个BaseResponse 作为统一返回体包含code、message、data三个字段。后端所有接口都返回这个结构安卓端在解析时先判断codecode0表示成功其他code直接弹出错误提示。这样做的最大好处是安卓端不需要为每个接口单独做错误处理统一在网络层处理一遍。Retrofit的配置也有讲究。baseUrl不能乱写必须是域名或IP加端口且以/结尾。比如http://192.168.1.100:8080/最后这个斜杠漏了所有接口都会404。日志拦截器在调试时开Level.BODY上线后关掉避免日志信息泄露用户隐私。3.2 核心页面实现逻辑首页我用的Fragment加RecyclerView顶部一个分类横向滑动的TabLayout下面图书列表用GridLayoutManager双列瀑布流卡片。卡片上显示封面缩略图、书名、价格、成色标签。这里有几个视觉细节价格要突出显示我用红色加粗成色标签用不同颜色背景如绿色代表九成新、橙色代表七成新。图书详情页顶部是ViewPager2轮播图支持手势滑动查看多张图片。下面依次是价格、书名、作者、出版社、成色、描述。底部固定两个按钮收藏和立即购买。点击立即购买弹出确认弹窗显示订单信息确认后调用下单接口。发布图书页我用了ScrollView套多个EditText和图片选择GridView。拍照或从相册选择最多9张图第一张作为封面。这个页面的状态管理比较麻烦因为用户填到一半可能切出去我用了ViewModel保存表单数据在onCleared前把数据写回SavedStateHandle保证进程被回收后还能恢复。安卓端有一个容易踩坑的地方EditText输入手机号、ISBN这些数字时要设置inputType为number或text。否则软键盘弹出的是全键盘用户体验很差。另外价格输入框我用的是EditText加TextWatcher实时过滤非法字符只允许保留两位小数。3.3 图片加载与文件上传图片加载我选Glide原因很简单队列机制和缓存策略做得最好List快速滚动时不会卡顿。图片URL是服务器地址加相对路径比如http://192.168.1.100:8080/images/book/1.jpgGlide会自动处理加载和缓存。但要注意Glide默认缓存的是原图如果服务器返回400或404Glide会认为加载失败不缓存导致同一个URL反复请求流量浪费很严重。解决方法是启用OkHttp集成模块设置合理的缓存策略。图片上传走Retrofit的Multipart请求。安卓端先用MediaStore获取图片Uri再通过ContentResolver读取文件流包装成MultipartBody.Part上传。这里最大的坑是有些手机拍照后图片Uri指向的是content://协议不是file://直接new File会报错必须用ContentResolver读取输入流传给后端。我上传图片时还会做一次压缩因为相机拍出来的图片动不动就5MB以上不压缩的话网络慢的用户上传一张图要等半天。我封装了一个压缩工具类判断图片尺寸超过1080p就等比缩放到1080质量压缩到80%图片一般能压到300KB以内肉眼几乎看不出区别。3.4 与后端联调统一返回体与异常处理联调是整个项目里最耗时的环节。我的做法是后端接口写好一个就立刻联调一个不积压。安卓端在BaseResponse解析时把网络异常、服务器异常、业务异常分开处理。网络异常超时、连接失败提示网络异常请检查网络服务器异常HTTP 500提示服务器繁忙请稍后再试业务异常code非0直接显示后端返回的message。要做到这一点后端必须统一异常处理。我在Spring Boot里加了RestControllerAdvice全局异常处理业务异常、参数校验异常、未知异常分别返回不同的code。然后在安卓端对应判断这样联调时两边配合非常顺畅。联调时最常用的调试工具是Postman和Charles查接口问题很快。我经验是如果某个接口返回404首先检查URL是否拼写正确返回500看后端日志返回401看token是否过期返回405检查请求方法是GET还是POST这个错误频率非常高因为安卓端封装请求时Method写错了。4. 国产系统适配让App在UOS和麒麟上跑起来4.1 国产系统与安卓应用兼容的环境差异这里说的国产系统主要是统信UOS、麒麟系统这类基于Linux内核的PC操作系统。它们有些自带安卓兼容环境可以直接安装APK运行有些则运行在国产手机终端上。这两种场景都意味着你不能假设所有用户都拿标准安卓手机用你的App必须考虑在国产系统环境下的兼容性。以统信UOS为例它的安卓兼容环境本质上是一个容器化的运行时通过系统级引导把APK映射到Linux上运行。由于容器环境封装的系统版本、硬件能力各不相同同一个APK在不同国产设备上的表现可能完全不同。还有一个容易被忽略的差异很多国产系统默认禁止安装来源不明的APK或者安装时会弹风险提示。用户安装你的App时必须手动点击仍然安装这个前提条件要提前告诉用户。我当时测试时第一次安装直接被系统拦截后来才知道要到设置里打开允许安装未知来源应用。4.2 安卓App在国产系统上的适配要点适配国产系统我总结了一套实际可用的检查清单。第一targetSdkVersion不要拉太高。我用了targetSdk 30对应Android 11。targetSdk太高在兼容层版本较低的设备上可能直接安装不上太低又会触发系统对旧版应用的限制比如文件读取权限弹窗。第二多分辨率适配。国产系统的运行设备五花八门有平板、一体机、传统PC屏幕从1280x720到1920x1080都有。我统一用了dp单位关键页面做百分比适配避免出现布局溢出。第三存储路径。兼容环境下的应用私有目录可能是模拟出来的直接用getFilesDir()、getCacheDir()通常没问题但直接操作绝对路径比如/storage/emulated/0/有些设备上就访问不到。我统一改用MediaStore或应用专属目录不做传统的独立目录操作。权限申请也值得单独说明。在国产系统的兼容环境里运行时权限弹窗可能被系统自动拒绝一次或者权限弹窗根本弹不出来。我的做法是关键权限如相机、相册在进入功能前主动检查一次被拒绝跳转到系统设置页。同时在代码里做了双重校验系统权限状态和App业务层的标记状态避免用户开了权限但App不认。4.3 在国产系统上做开发与测试开发环境这一块国产系统上也能跑Android Studio。统信UOS官方有Linux版Android Studio安装包装上之后只要能正常识别JDK开发编译基本没问题。不过要注意Android Gradle Plugin版本不要用太新的因为Gradle下载依赖要从Google Maven拉国内网络环境经常拉不下来。我配置了阿里云镜像仓库编译速度快很多。我在国产系统上测试的流程是先把APK拷贝到设备上通过文件管理器双击安装如果安装失败就通过adb install命令安装可以看到具体的错误日志。adb命令在国产系统上一样能用连接方式跟普通安卓设备相同打开开发者模式开启USB调试。在一体机上跑过一次APK首页图片加载特别慢当时怀疑是设备硬件性能问题。后来发现是兼容环境的网络代理设置问题系统把无线代理强制开启了请求走了代理导致慢。这个问题在UOS上比较常见解决方法是在系统网络设置里关掉代理或者在App里禁用系统代理。4.4 适配中遇到的实际问题我在适配过程中遇到的几个典型问题写出来给同行提个醒。有台设备安装APK时提示应用未签名或者安装包损坏但APK在其他手机上是好的。排查后发现是设备对APK的签名算法要求比较高V1签名不支持必须用V2或V3签名。我在Gradle里配置了signingConfig同时勾选v1SigningEnabled和v2SigningEnabled问题解决。还有一个问题是图标显示反了。在有些国产系统的大图标模式下App图标会被拉伸变形。这是因为我没有为不同分辨率提供适配图标。后来我把mipmap各密度目录下的图标都补充了对应尺寸才恢复正常。另外一个比较隐蔽的问题是兼容层环境里的应用在某些设备上无法唤起系统相机。原因可能是应用申请CAMERA权限时被系统静默拒绝或者摄像头驱动在兼容层里没有映射。我的处理方案是在发布图书页如果检测到无法唤起相机就提示用户从相册选择同时在后端做兼容接受单纯的相册图片上传。5. 常见问题与排查技巧实录5.1 Spring Boot后端问题速查这一节分享几个实际项目里反复遇到的问题。数据库连接报错commysqlcjexceptionsCJCommunicationsException排查下来通常有三个原因数据库服务没启动、连接地址写错、账号密码不对。最坑的是连接地址写成localhost但MySQL监听的是127.0.0.1的IPv4而JDBC驱动默认解析到IPv6 ::1就会连不上。解决方法是直接写127.0.0.1:3306。CORS跨域问题安卓端用Retrofit不存在跨域但如果是Web管理端访问就会出现跨域。我在配置类里全局开启CORS允许所有来源、所有请求头、所有请求方法并配置了allowedOriginPatterns(*)否则带凭证的请求会被忽略。Spring Boot项目JAR包在服务器上启动不了检查了端口占用、JDK版本、内存不足。最隐蔽的问题是JAR包里的配置文件用了相对路径导致上传的图片保存到临时目录服务重启丢失。我的解决方法是在上传目录配置上用绝对路径并且启动脚本里先创建目录增加启动命令-Dspring.config.additional-location指定外部配置。这个坑在Spring Boot项目上线时非常常见因为本地IDE运行时工作目录是项目目录JAR包运行时工作目录变成当前执行目录两个场景的路径解析完全不同。5.2 安卓客户端问题速查安卓端最容易出的问题集中在网络请求、图片加载和真机调试这三块。网络请求404我遇到过一次后端接口路径是/api/book/list安卓端Retrofit注解写的也是api/book/list看着一样实际后端路径带/api前缀Retrofit注解少写了斜杠导致路径变成api/book/list拼上baseUrl。404调试时先看Retrofit日志BaseResponse解析失败基本是JSON字段名不一致导致的。我用Gson解析时如果后端字段是under_score命名而安卓实体是camelCase忘了加SerializedName注解就会解析失败返回null。图片加载失败Glide加载不出来先看URL浏览器能否访问再用adb shell查看日志。我遇到过一种情况后端返回的图片URL是相对路径安卓端需要拼上baseUrl但拼接时多了一个斜杠导致URL变成/images//book/1.jpg服务器返回404。后来我在工具类里统一处理URL拼接去重斜杠这个问题再也不出现。真机调试USB连不上手机上开启开发者模式并且开启USB调试但Windows电脑上连接后adb devices看不到。解决方法重装厂商USB驱动换数据线很多数据线只能充电不能传输在adb授权弹窗上勾选始终允许。在国产系统设备上调试时还要检查系统是否限制了USB调试端口。5.3 国产系统运行问题速查这里列一个我在国产系统适配过程中整理的速查表非常适合遇到类似问题的人直接对号入座。现象可能原因处理方案安装APK提示未签名/损坏设备要求V2签名APK只有V1签名Gradle开启v1v2签名重新打包启动后立即闪退targetSdkVersion过高、兼容层版本低降低targetSdkVersion到30或更低权限请求不弹窗兼容层静默拒绝运行时权限跳转系统设置手动授权App内二次校验图片加载慢/加载异常系统代理开启、网络被代理关闭系统代理禁用代理检测图标变形/模糊缺少对应分辨率图标补齐mipmap各密度目录图标无法唤起相机摄像头驱动未映射到容器提示用户从相册选择图片替代软件商店提示不兼容应用market标签不匹配修改AndroidManifest中market分配置国产系统适配的总体思路是把真实设备上可能出现的现象当成一个黑盒通过日志和逐步修改来缩小问题范围。不要指望一次就能在全部设备上完美运行先保证主力设备正常再扩展兼容。6. 性能优化、打包与部署6.1 后端接口的分页与缓存优化二手书城这类应用图书列表是查询量最大的接口不做优化数据库很容易成为瓶颈。MyBatis-Plus分页插件实测可以解决大部分问题但页数大的时候性能会下降。我除了分页还加了两个优化手段。第一给图书表的category、status、create_time建联合索引。查询条件通常是category和status过滤然后按create_time排序这个联合索引可以显著提升查询速度。我在数据量到10万条时测过一次加了索引的查询从2秒降到40毫秒效果立竿见影。第二用Redis缓存首页推荐图书列表。首页推荐逻辑是最近一个月上架、浏览量高、在售状态的图书按浏览量倒序取20条。因为这个数据对实时性要求不高我设置缓存时间为10分钟。查询时先查Redis缓存不存在才查MySQL并回填。实现方式是用Spring Cache注解Cacheable简洁并且不侵入业务代码。分页查询还有一个细节当前端传入pageNum超过总页数时不能直接查数据库返回空列表。我封装了分页结果对象包含total、pages、current、records四个字段前端可以判断当前页是否大于总页数自动回退到最后一页。6.2 安卓APK瘦身与内存优化安卓APK初始打包出来有几十MB主要原因是没有做资源压缩和混淆。我做了两步优化APK从48MB瘦到了21MB。第一步Gradle配置开启minifyEnabled和shrinkResources启用R8混淆和资源压缩。这样代码中未引用的类、方法、资源和图片都会被移除。第二步启用resConfigs只保留中文和默认英文资源这样支持库里的多语言资源不会全部打包进去。第三步对于图片资源没有用矢量图的一律使用WebP格式比PNG小很多。内存优化方面图书列表页最容易出现OOM。我在RecyclerView上用了Glide的into(view)方法配合图片规格监听加载时统一指定目标宽度避免大图原图加载。同时RecyclerView的item布局里图片控件设置了fixed比例避免加载不同尺寸图片时反复测量导致卡顿。我还处理了列表滚动时的图片加载策略停止滚动时才加载图片快速滚动时只显示占位图。Glide自带skipMemoryCache(true)和diskCacheStrategy(DATA)相关配置实际效果是滚动跟手不会出现卡顿。6.3 部署上线与后续扩展部署环节相对简单我买了台Linux服务器把Spring Boot打成JAR包用systemd配置成服务开机自启监控日志。Nginx反向代理到8080端口客户端统一访问域名下的/api路径。同时Nginx做了静态资源映射图书图片放在/data/book_images目录通过/images路径访问。本来只想做个校内二手书城后来在真实使用中发现光有图书交易还不够。我在小结里把自己的思考沉淀下来不写太多展望就说两个我自己已经验证的方案。第一个是图书详情页增加相似图书推荐。实现起来很简单根据当前图书的分类和价格区间在缓存中查同分类下其他图书排除当前这本取6条。这个功能对用户价值很高实测点击转化率比首页推荐高很多。第二个是订单状态通知。虽然没有接入推送服务但我用了一个轮询方案订单详情页前台定时每30秒刷新一次订单状态。因为二手书城的订单流转是低频场景用户不会一直盯着订单轮询的开销完全可以接受。如果后续接入消息推送再替换成即时通知。项目做到这里我自己最大的体会是不要把技术选型和版本决策当成理所当然的默认选项每个选择都要知道为什么。Spring Boot 2.7.18、安卓原生、targetSdk 30这些看似保守的选择背后都是对实际环境的认真评估。国产系统适配更是这样提前接触真机、提前暴露问题远比上线后被动修复要舒服得多。最后分享一个小技巧发布到国产系统应用商店前可以用adb shell pm list packages确认应用签名和包名是否冲突很多安装失败都是包名冲突导致的。