ARTICLE DETAIL

资讯详情

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

微信小程序 + SSM 图书管理系统全栈源码解析与联调实战

微信小程序 + SSM 图书管理系统全栈源码解析与联调实战 简介这套资源是一套前后端完整分离的图书管理系统源码包后台基于SSMSpringSpringMVCMyBatis框架前台基于微信小程序可实现书籍信息录入、全览、删除、修改单一或全部属性、按条件查询筛选并已线上发布适合Web后端与小程序前端学习者参考与二次开发。压缩包共304个文件约19.26MB核心包含47个jar依赖、21个Java源文件、25个class编译文件、16个XML配置、14个JS逻辑、9个WXML页面和10个WXSS样式并附SQL数据库脚本与项目配置目录结构清晰便于按模块定位代码。目前已有2772人学习下载。除直接部署运行外还可从中完整学习SSM与微信小程序联调、API接口对接及JSON数据交互的实现思路既能作为课程设计或毕业设计的参考也能扩展成个人书架、图书借阅登记等应用。1. 图书管理系统上线前值得先看这份小程序 SSM 全栈源码做图书管理这类 CRUD 项目最怕的不是功能复杂而是前后端“各写各的”小程序端调不通接口、后端返回格式对不上、数据库字段命名混乱。这份基于微信小程序的图书管理系统后台用 SSMSpring SpringMVC MyBatis搭建前台是微信小程序原生工程覆盖图书信息的录入、全览、删除、单属性修改、全部属性修改以及按属性查询筛选。它不是一个只能跑通 Demo 的玩具项目而是已经做过线上发布、能当个人书籍记录工具使用的完整工程。适合正在做毕业设计、想快速搭一套小程序后台联调原型、或者想拆一套 SSM 标准分层代码的开发者。整份压缩包包含小程序源码、后台源码和数据库文件拿到手按顺序配置就能跑。2. 小程序端页面与交互图书列表、录入表单、筛选查询的实现方式2.1 小程序工程结构页面、组件与工具层的分工解压后的小程序端是一个完整的微信小程序工程目录结构遵循微信官方推荐的风格。核心代码在pages/下按功能模块拆分为多个页面比如图书列表页、图书详情页、录入/编辑页。每个页面由.wxml、.wxss、.js、.json四个文件组成这是微信小程序的固定约定。我习惯先看app.js和app.json因为前者定义了全局变量和启动逻辑后者决定了页面注册顺序和窗口样式。在这套源码里app.js中通常会维护一个全局的apiBaseUrl所有请求都基于这个根路径拼接。app.json里要注意pages数组的第一个元素是首页如果你想调整启动页面改这里就行。// app.js 片段全局请求根路径配置 App({ globalData: { // 实际部署时改成你的后端 IP 或域名 apiBaseUrl: http://localhost:8080/booksys, userInfo: null }, onLaunch: function () { // 启动时检查登录态实际项目中可在这里做 token 校验 console.log(小程序启动); } });这段代码的逻辑很直接apiBaseUrl被挂在globalData下之后所有页面通过getApp().globalData.apiBaseUrl读取后端地址。要注意的是微信开发者工具中“不校验合法域名”的开关必须打开否则本地调试时http://localhost会被拦截。如果你需要切换环境测试服/正式服改这一个变量即可不需要逐个页面修改请求地址。2.2 图书列表页从 wx.request 到页面渲染的完整链路图书列表页是整个系统的主入口它的数据流是页面加载时调用wx.request请求后端接口拿到 JSON 数据后用setData更新页面变量WXML 再通过wx:for循环渲染卡片列表。// pages/books/books.js 列表页核心逻辑 Page({ data: { bookList: [], loading: true }, onLoad: function () { this.fetchBooks(); }, fetchBooks: function () { const apiBaseUrl getApp().globalData.apiBaseUrl; wx.request({ url: apiBaseUrl /book/list, method: GET, success: (res) { if (res.statusCode 200 res.data.code 200) { this.setData({ bookList: res.data.data, loading: false }); } else { wx.showToast({ title: 加载失败, icon: none }); } }, fail: () { wx.showToast({ title: 网络异常, icon: none }); this.setData({ loading: false }); } }); } });这段代码有几个关键点。success回调里先判断statusCodeHTTP 状态码再判断业务状态码code这是前后端约定好的返回格式。后端返回的 JSON 结构是{ code: 200, data: [...], msg: success }data才是真正的图书数组。如果你拿到数据但渲染不出来八成是res.data.data这层结构对不上。setData是小程序官方推荐的更新数据方式直接赋值不会触发视图刷新。WXML 侧的渲染逻辑同样需要注意。wx:for的wx:key必须绑定唯一标识源码里用的是item.id这能避免列表更新时的渲染错乱。!-- pages/books/books.wxml 列表渲染片段 -- view classbook-card wx:for{{bookList}} wx:keyid view classbook-title{{item.bookName}}/view view classbook-author{{item.author}}/view view classbook-price{{item.price}}/view /view列表渲染本身不复杂真正容易出问题的是后端返回的字段名和 WXML 里绑定的字段名不一致。比如后端叫book_name前端写bookName页面就会空白。遇到这种情况优先去后端实体类的JsonProperty注解或 MyBatis 的resultMap里查字段映射。2.3 录入与编辑页面表单校验与提交数据的格式约定图书录入和编辑共享一个表单页面通过 URL 参数区分新增和编辑模式。编辑时页面onLoad接收id参数然后根据 id 调详情接口回填表单。表单提交是wx.request的 POST 请求这里要特别注意Content-Type的设置。源码中用的是application/json后端 SpringMVC 用RequestBody接收如果前端写成application/x-www-form-urlencoded后端会直接报参数不匹配。// pages/edit/edit.js 表单提交核心逻辑 Page({ data: { formData: { bookName: , author: , isbn: , price: 0, category: }, id: null }, onLoad: function (options) { if (options.id) { this.setData({ id: options.id }); this.fetchBookDetail(options.id); } }, fetchBookDetail: function (id) { const apiBaseUrl getApp().globalData.apiBaseUrl; wx.request({ url: apiBaseUrl /book/detail?id id, method: GET, success: (res) { if (res.data.code 200) { // 回填表单注意字段映射 this.setData({ formData: res.data.data }); } } }); }, submitForm: function () { const { formData, id } this.data; if (!formData.bookName) { wx.showToast({ title: 书名不能为空, icon: none }); return; } const apiBaseUrl getApp().globalData.apiBaseUrl; const url id ? apiBaseUrl /book/update : apiBaseUrl /book/add; wx.request({ url: url, method: POST, header: { Content-Type: application/json }, data: JSON.stringify(formData), success: (res) { if (res.data.code 200) { wx.showToast({ title: id ? 修改成功 : 添加成功, icon: success }); // 返回上一页并刷新列表 wx.navigateBack(); } } }); } });这里有两个实用技巧。第一新增和编辑共用一个submitForm通过this.data.id是否存在来判断请求地址减少代码冗余。第二data: JSON.stringify(formData)手动序列化确保后端RequestBody能正确解析。如果你发现提交后后端收到的是空对象先检查header里的Content-Type是不是application/json再看data是否被字符串化。2.4 搜索筛选前端传参与后端模糊查询的配合搜索功能在列表页顶部用户输入关键词后点击搜索前端把关键词拼到 URL 参数里后端用 MyBatis 的模糊查询处理。// 列表页搜索逻辑 searchBooks: function (e) { const keyword e.detail.value; const apiBaseUrl getApp().globalData.apiBaseUrl; wx.request({ url: apiBaseUrl /book/search?keyword encodeURIComponent(keyword), method: GET, success: (res) { if (res.data.code 200) { this.setData({ bookList: res.data.data }); } } }); }encodeURIComponent在这里是必须的如果关键词里包含中文或特殊字符比如、不编码会导致后端拿到的参数被截断。后端接收keyword参数后在 SQL 里写成WHERE book_name LIKE CONCAT(%, #{keyword}, %)这是 MyBatis 中避免%拼接注入的推荐写法。注意不要用${keyword}直接拼 SQL那样会有注入风险。搜索这块有一个体验优化点你可以给wx.request加一个防抖用户停止输入 300ms 后再发请求避免每敲一个字符就打一次接口。源码里没有做这个优化但实际使用中这是高频需求。3. SSM 后端与数据库设计SpringMVC 分层、RESTful 接口、MyBatis 映射3.1 SSM 框架整合的关键配置与启动顺序后端工程是标准的 Maven 结构pom.xml里引入了 Spring、SpringMVC、MyBatis、Druid 连接池、MySQL 驱动等依赖。拿到源码后第一步不是看代码而是先检查applicationContext.xml和spring-mvc.xml两份配置因为框架整合的大部分坑都集中在这两个文件里。!-- spring-mvc.xml 核心配置片段 -- mvc:annotation-driven / context:component-scan base-packagecom.book.controller / bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views/ / property namesuffix value.jsp / /bean mvc:default-servlet-handler /这里的base-package必须指向你的 controller 包路径如果扫错包接口 404 是最轻的症状。InternalResourceViewResolver的存在说明工程保留了 JSP 视图支持但实际上接口都返回 JSON所以这个视图解析器只对异常页面生效不影响接口功能。数据源配置通常在jdbc.properties或applicationContext.xml里。拿到源码后先把数据库名、用户名、密码改成你自己的然后确认 Druid 连接池参数是否合理。initialSize设为 5、maxActive设为 20 是常规配置如果你本地调试经常报连接超时把maxWait调大一些即可。# jdbc.properties 本地调试常用配置 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/bookdb?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.passwordyourpasswordserverTimezoneAsia/Shanghai是 MySQL 8.x 的必填项不加会报时区异常。useSSLfalse是避免本地 MySQL 没有配置 SSL 证书时的警告。如果你用的是 MySQL 5.7驱动可以改回com.mysql.jdbc.Driver但更推荐直接用 8.x 驱动向后兼容没问题。3.2 数据库表结构图书表字段设计与 SQL 脚本拆解压缩包里带了一份 SQL 文件这是整套系统能跑起来的前提。先看核心的图书表设计字段覆盖书名、作者、ISBN、价格、分类、库存、出版时间、封面图地址等。为了保持表结构简单这里展示最小化字段版本。-- 图书信息表 CREATE TABLE tb_book ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键, book_name varchar(100) NOT NULL COMMENT 书名, author varchar(50) DEFAULT NULL COMMENT 作者, isbn varchar(20) DEFAULT NULL COMMENT ISBN编号, price decimal(10,2) DEFAULT NULL COMMENT 定价, category varchar(50) DEFAULT NULL COMMENT 分类, stock int(11) DEFAULT 0 COMMENT 库存, publish_date date DEFAULT NULL COMMENT 出版日期, cover_url varchar(255) DEFAULT NULL COMMENT 封面图URL, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT图书信息表;表的命名用了tb_前缀这是后端实体类与表名映射的常见约定。create_time和update_time分别用DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP这样插入和更新时不需要手动维护时间字段。price用decimal(10,2)而不是float这是财务字段的基本常识——float会有精度丢失。导入 SQL 时要注意字符集。表设计用的是utf8mb4但如果你用 Navicat 导入时没有选择对应字符集中文会直接变成问号。导入后第一件事是执行SHOW VARIABLES LIKE character_set_database;查看当前库的字符集如果不是utf8mb4用ALTER DATABASE bookdb CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;修正。3.3 Controller 层接口设计统一返回格式与参数接收方式后端接口的返回格式统一是Result对象包含code、msg、data三个字段。这种设计是为了前端能统一处理业务异常和正常数据不需要每次请求都去解析不同的结构。// Result.java 统一返回体 public class ResultT { private Integer code; // 200 表示成功 private String msg; // 提示信息 private T data; // 业务数据 public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(success); result.setData(data); return result; } public static T ResultT error(String msg) { ResultT result new Result(); result.setCode(500); result.setMsg(msg); return result; } // getter/setter 省略 }Controller 层负责接收请求参数并调用 Service。查询详情和删除都是典型的 GET 请求新增和修改是 POST。参数接收方式上源码里值得学习的一点是单个参数用RequestParam对象参数用RequestBody。RestController RequestMapping(/book) public class BookController { Autowired private BookService bookService; GetMapping(/list) public ResultListBook list() { return Result.success(bookService.listAll()); } GetMapping(/detail) public ResultBook detail(RequestParam(id) Integer id) { return Result.success(bookService.getById(id)); } PostMapping(/add) public ResultVoid add(RequestBody Book book) { bookService.add(book); return Result.success(null); } PostMapping(/update) public ResultVoid update(RequestBody Book book) { bookService.update(book); return Result.success(null); } GetMapping(/delete) public ResultVoid delete(RequestParam(id) Integer id) { bookService.delete(id); return Result.success(null); } }RestController是Controller ResponseBody的组合注解所有接口默认返回 JSON不用在每个方法上单独加ResponseBody。RequestMapping(/book)定义了类级别的 URL 前缀方法级别的GetMapping/PostMapping负责具体路径。这套接口设计很标准前端小程序端只用GET、POST两种方法就能覆盖全部功能。这里有一个容易被忽略的点update接口用RequestBody接收完整对象那么前端提交时后端如何判断是更新还是新增答案是看id字段。如果id不为空Service 层执行updateById如果为空执行insert。这也是为什么前端submitForm里要区分两个 URL 的原因。3.4 Service 与 Mapper 层事务边界与动态 SQL 的写法Service 层是业务逻辑的载体源码里直接调用 Mapper 接口的方法。要留意的是Transactional注解的使用位置和范围。Service public class BookServiceImpl implements BookService { Autowired private BookMapper bookMapper; Override Transactional public void add(Book book) { // 校验业务规则比如检查 ISBN 是否重复 if (bookMapper.countByIsbn(book.getIsbn()) 0) { throw new RuntimeException(ISBN已存在); } bookMapper.insert(book); } Override Transactional public void update(Book book) { bookMapper.updateById(book); } Override public ListBook listAll() { return bookMapper.selectAll(); } Override public ListBook search(String keyword) { return bookMapper.searchByKeyword(keyword); } }Transactional加在add和update方法上保证多个 SQL 操作在同一个事务里执行。这里有一个细节值得注意RuntimeException会触发事务回滚但如果直接catch掉异常然后返回false事务不会回滚数据就半写进去了。所以 Service 层通常不捕获异常直接往上抛由全局异常处理器统一封装返回。Mapper 层是 MyBatis 的接口定义SQL 写在 XML 文件里。查询部分最核心的是searchByKeyword它涉及多条件动态拼接。!-- BookMapper.xml 模糊查询片段 -- select idsearchByKeyword resultTypecom.book.entity.Book SELECT * FROM tb_book where if testkeyword ! null and keyword ! book_name LIKE CONCAT(%, #{keyword}, %) OR author LIKE CONCAT(%, #{keyword}, %) OR isbn LIKE CONCAT(%, #{keyword}, %) /if /where ORDER BY create_time DESC /selectwhere标签会自动去掉多余的AND或OR这是 MyBatis 动态 SQL 的典型写法。LIKE CONCAT(%, #{keyword}, %)中的#{}预编译方式可以防止 SQL 注入。这里要注意的是如果keyword为 null 或空字符串where标签内部为空查询会退化成SELECT * FROM tb_book所以前端传参时如果没输入关键词最好直接跳过后端搜索请求避免全表查询。4. 前后端联调与部署配置接口对接的格式约定与常见报错排查4.1 请求报文与响应报文的字段映射小程序端和后端能正常通信依赖一套双方认可的字段约定。前端提交 JSON 时字段是驼峰命名bookName、author、price后端实体类如果也是驼峰属性MyBatis 的mapUnderscoreToCamelCase配置会自动把数据库的下划线字段映射到实体属性。# mybatis-config.xml 或 application.yml 中的关键配置 mybatis.configuration.map-underscore-to-camel-casetrue打开这个配置后数据库的book_name自动映射为实体类的bookNamecreate_time映射为createTime。如果这个配置没开你就必须在resultMap里手动写字段映射关系。拿到源码后先确认这个开关是true否则查询结果里所有带下划线的字段都是null但单个单词字段如author、price正常这个现象迷惑性很强。Result响应体的三层结构code / msg / data是前后端沟通的“协议”。前端所有success回调里必须先判断res.data.code 200再取res.data.data。后端所有业务异常通过Result.error(msg)返回这时候code是 500msg是给用户看的提示。这套格式约定在源码里贯彻得很彻底。4.2 真机预览与本地联调的网络配置本地联调时小程序开发者工具默认可以访问http://localhost:8080但要开启“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”选项。这个选项在开发者工具的“详情 → 本地设置”里。真机预览时localhost就不好使了。你需要把app.js里的apiBaseUrl改成电脑在局域网内的 IP 地址比如http://192.168.1.100:8080/booksys。同时要保证手机和电脑连的是同一个 Wi-Fi且电脑防火墙允许 8080 端口入站。如果手机真机访问不到接口先不要怀疑代码用以下顺序排查# 1. 电脑上确认服务已启动 curl http://localhost:8080/booksys/book/list # 2. 确认局域网 IP ipconfig # Windows ifconfig # Mac/Linux # 3. 手机浏览器直接访问接口确认网络链路通 # 如果能返回 JSON说明网络没问题问题在小程序配置真机调试时最容易翻车的点是防火墙拦截。Windows 系统在第一次启动 Tomcat 或 SpringBoot 内置容器时会弹窗询问是否允许 Java 通过防火墙如果你点了“取消”局域网内其他设备就访问不了。解决方法是去“Windows Defender 防火墙 → 允许应用通过防火墙”里把 Java 的“专用”和“公用”都勾上。4.3 接口返回 404 / 405 / 500 的定位路径联调过程中三个状态码几乎避不开。404 分两种情况。第一种是请求路径不对比如前端请求/book/list后端映射的是/book/lsit拼写错误直接 404。第二种是前端请求了/booksys/book/list但后端context-path配的不是booksys导致整个路径对不上。定位方法是先看后端控制台有没有打印请求日志如果啥都没有说明请求根本没进 SpringMVC是路径映射问题如果打印了日志但返回 404说明路由匹配失败。405 是方法不匹配。前端用POST请求了一个只配置了GetMapping的接口SpringMVC 会返回 405。排查时看前端代码的method字段和后端注解是否一致这个比较直观。500 是后端逻辑异常。最典型的场景是参数类型转换失败前端传了字符串abc给Integer类型的id或者price字段传了空字符串MyBatis 映射到Decimal时直接报错。遇到 500直接看后端控制台的异常堆栈重点看Caused by那一行大部分问题都能在堆栈里找到答案。# 后端启动时带着日志级别方便联调 mvn spring-boot:run -Dspring-boot.run.jvmArguments-Dlogging.level.rootDEBUG把日志级别调成DEBUG后MyBatis 会打印执行的 SQL 和入参能直接看到前端传来的参数是否正常。这个习惯在联调阶段非常省时间。5. 避坑指南图书管理系统部署与二次开发的六个常见问题5.1 数据库导入后中文乱码页面显示问号现象导入 SQL 后执行查询中文全部显示为???小程序端列表页的图书名称无法正常读取。原因数据库连接 URL 里没有指定characterEncodingutf8或者数据库本身的字符集是latin1。MySQL 在客户端连接时没有协商字符集导致中文在写入时被转成latin1存储。解决修改jdbc.properties里的连接 URL加上characterEncodingutf8参数。如果数据库已经建好了执行下面的 SQL修改库、表、字段的字符集。-- 修改数据库字符集 ALTER DATABASE bookdb CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 修改表字符集 ALTER TABLE tb_book CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;5.2 小程序请求报“网络异常”或errno: 600001现象模拟器里请求接口一直转圈最后提示errno: 600001网络异常。原因开发者工具默认开启了“校验合法域名”http://localhost不是 HTTPS 或合法域名请求被拦截。解决点击开发者工具右上角的“详情” → “本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这个选项只对开发环境生效正式发布时小程序必须用 HTTPS 域名。5.3 新增图书后列表不刷新必须重新进入页面现象在录入页提交成功后wx.navigateBack()返回列表页但新加的图书没有出现在列表里。原因列表页onLoad只在页面创建时执行一次navigateBack返回的是缓存页面不会重新触发onLoad。解决在列表页的onShow生命周期里调用fetchBooks()每次页面从后台切换到前台都会执行。源码里如果只有onLoad需要手动改一下// 列表页增加 onShow 刷新逻辑 onShow: function () { this.fetchBooks(); }, onLoad: function () { // onLoad 里不再重复请求避免首屏双请求 }onLoad只保留初始化逻辑比如读取全局变量onShow负责数据加载这是小程序列表页刷新的通用解法。5.4 修改图书时勾选“全部属性修改”但 ISBN 没有被更新现象前端编辑页传了isbn后端update接口执行成功但数据库里isbn字段没有变化。原因Service 层update方法调用的 Mapper SQL 可能只更新了非空字段动态 SQL 用if标签判断或者前端序列化时isbn字段因为值为空字符串被 MyBatis 判定为“不更新”。解决如果业务上允许清空 ISBN需要在 Mapper XML 的set标签里把isbn字段的if条件去掉或者用isbn #{isbn}直接赋值。更稳妥的做法是后端 Controller 里先查一次原数据把isbn为 null 的字段用原值补全再执行更新。5.5 Druid 连接池启动报错提示discard long time none received connection现象后端启动正常但运行一段时间后日志里频繁出现discard long time none received connection随后接口偶发超时。原因MySQL 8.x 的wait_timeout默认是 8 小时Druid 连接池里的连接如果长时间空闲会被数据库服务端关闭但连接池不知道继续使用这些“死连接”导致查询时连接失效。解决在 Druid 配置里增加testWhileIdle和validationQuery# Druid 连接池防死连接配置 spring.datasource.druid.test-while-idletrue spring.datasource.druid.validation-querySELECT 1 spring.datasource.druid.test-on-borrowfalsetestWhileIdle让连接池在空闲时定期检测连接是否可用validationQuery指定检测语句。test-on-borrow保持false否则每次从池子里拿连接都会先执行一次检测性能损耗明显。5.6 小程序上传的封面图片后端存的是本地路径真机访问失败现象录入图书时选择了图片提交后cover_url存的是C:/Users/xxx/upload/xx.jpg这样的本地路径手机真机上图片无法显示。原因图片上传接口用了MultipartFile但保存路径写死了本机磁盘路径真机通过http://192.168.1.100:8080访问时这个本地路径对手机不可见。解决把上传路径改成可配置的静态资源映射。在application.properties里添加# 上传目录改成你的实际路径 file.upload-dir/home/booksys/uploads/ spring.web.resources.static-locationsclasspath:/static/,file:${file.upload-dir}这样图片保存到服务器磁盘前端访问时用http://域名/uploads/xxx.jpg就能加载。本地没有一台 Linux 服务器的话先用D:/booksys/uploads/这类绝对路径也能暂时跑通但换机器就得改。6. 接口自测与调试习惯这套源码最值得薅的最后一个技巧先聊一个判断后端是否合格的笨办法把所有业务接口单独列一张表按照“正常入参、边界入参、错误入参”三个维度各测一遍。把这套图书管理系统跑通后我建议你挑出update和search两个接口重点测试——它们是最容易掩藏问题的。update接口要测的是“空字段覆盖”。前端提交了一个price为 null 的 JSON后端 Service 是执行了SET price null还是直接忽略了这个字段如果是前者图书的价格就丢失了如果是后者用户想清空价格的需求就得不到满足。测试方法很简单先用 Postman 发一个只有id和bookName的 JSON看数据库里price字段是变成了空还是保留了原值。根据结果决定是否要在 Service 层加判空逻辑。search接口要测的是“空关键词”。前端传keyword时后端是返回空列表还是全量数据如果返回全量数据那当用户清空搜索框时列表应该回到初始状态这逻辑说得通但如果返回空列表用户体验就断裂了。这个问题在代码层面很难一眼看出必须实测。我养成的调试习惯是每个接口写完后先不急着接小程序端用 Postman 或 Apifox 把后端接口全部测一遍重点检查返回格式是不是Result结构。确认后端稳定再打开微信开发者工具联调。这个习惯帮我省掉了很多“前后端同时出错互相甩锅”的场面。如果 Postman 测通了但小程序端报错90% 的原因出在请求头或数据格式上。比如 Postman 自动帮你做了 JSON 序列化但小程序端wx.request需要你手动JSON.stringify。这类问题排查时可以直接在小程序代码里加一行console.log(res.data)对比和 Postman 返回的数据结构差异。这套源码里隐藏着一个比较实用的技巧search接口的where动态 SQL 是可以扩展的。你可以在if testcategory ! null and category ! 里加一个分类条件前端筛选功能就出来了。修改 Mapper XML 后重启后端即可生效不需要改动 Java 代码。从那以后我每次拿到类似的 SSM 小程序源码都会强制走一遍“数据库导入 → 后端启动 → 接口自测 → 小程序联调”的完整流程再开始动代码。这个顺序能保证你拿到手的工程是能跑的而不是改了三小时才发现环境没配好。希望帮到你。本文还有配套的精品资源点击获取
返回列表