资讯详情

资讯详情

微信小程序+SSM+MySQL毕设实战:闲置物品交易系统从建库到部署

简介面向计算机专业毕业生的大学生闲置物品交易小程序基于微信小程序、SSM框架和MySQL数据库开发包含管理员、卖家、学生三类角色具备商品发布、商品购买、广场管理、商品类型管理、学生管理等模块覆盖从登录到订单完成的主要流程适合毕业设计、课程设计或项目实训参考。压缩包内共1180个文件大小约51.16MB含Java后端源码、Vue管理页面、微信小程序页面与逻辑代码、MySQL建表脚本、毕业论文文档和演示视频并配有一键安装、运行、打包的bat脚本目录按功能模块划分便于查阅和二次开发。文件类型包括PNG/SVG图片素材、JS逻辑脚本、VUE管理端组件、JAVA后端服务、JSON/XML配置、WXML/WXSS小程序页面、SQL数据库脚本及MP4视频能支撑界面、交互、接口、存储和部署全流程。已有162人学习下载适合需要参考项目整体架构、数据库设计和前后端实现细节的开发者可节省前期调研和编码时间。除源码和数据库外论文文档和视频演示可帮助对照设计思路与操作流程配合bat脚本可快速启动项目形成从阅读到落地的完整闭环适合答辩、二次开发和功能扩展。1. 微信小程序SSMMySQL做毕设为什么这套组合最不容易翻车每年毕设季都会看到有人在论坛里问技术栈太老会不会被导师嫌弃太新又怕自己写不完。如果你拿到的是“大学生闲置物品交易小程序”这类题目微信小程序SSMMySQL就是最稳的组合没有之一。这套方案前端天然适配移动交易场景后端三层架构让每个请求走向都清清楚楚MySQL做交易数据的落盘也够用。对毕设来说最大的风险不是技术旧而是写到一半跑不通而SSM的每一层都有大量现成资料可查几乎不存在黑匣子。这篇文章就把这套组合从建库到部署的完整落地路径讲清楚包括毕设交付时要同步准备的数据库脚本、论文素材和演示视频怎么弄。2. 这套组合的分工与数据流从wx.request到MySQL要过几道门2.1 为什么选SSM而不是SpringBoot答辩时“讲得清”比“用得新”重要很多同学一上来就想用SpringBoot理由是开发效率高、不用写一堆XML配置。这个想法本身没错但放到毕业设计答辩场景里要打个问号。导师问你“SpringBoot为什么能自动配置”答不上来的人不少但SSM里SpringMVC管表现层、Spring管业务层和事务、MyBatis管SQL映射每一层各管什么三句话就能说清楚这是答辩时最实用的优势。我一般的做法是如果导师没有明确要求SpringBoot就坚持用SSM因为SSM的代码结构天然适合写论文第三章“系统设计”里画分层架构图、写模块说明几乎可以照着代码结构抄。你不需要向导师解释“为什么用旧技术”——你只需要说“SSM分层清晰、维护成本低、适合中小型系统”这句话在系统设计章节里就是一段现成的技术选型理由。反过来如果导师明确要求SpringBoot那就换SpringBoot但MyBatis和MySQL这一层不变换掉的只是SpringMVC的基础设施部分核心业务代码差别不大。还有一个现实考量这套组合的资料密度极高。微信小程序端的接口调用、SSM的注解配置、MySQL的安装与SQL写法任何一个报错信息几乎都能搜到对应解决方案。毕设周期通常只有两三个月时间要花在业务功能上而不是消耗在框架版本的兼容性问题里。2.2 数据流与统一响应一个商品列表请求在服务端的完整旅程假设用户在首页看到了闲置商品列表这个请求在后端走的路是固定的小程序发起wx.request请求到后端ControllerController接收参数后调用Service层Service层里处理业务规则比如只查status为在售的商品然后通过Mapper接口执行SQLMySQL返回结果集MyBatis把结果映射成Java对象再逐层返回。整个过程听起来长但因为每一层职责固定排查问题时反而好定位——商品列表为空先看SQL能不能查出数据再往前查Service层是否过滤了状态最后看Controller返回的JSON结构前端是否解析正确。为了让前端处理数据时不用猜几乎所有SSM项目都会定义一个统一的响应对象叫RespBean或者Result字段通常是code、msg、data。code为200表示成功400是参数错误401是未登录500是服务端异常。前端拿到响应后只看code等于给前后端通信定了一个简单契约。这个类的代码很简单但不写的话接口就会各返回各的前端在页面里到处判断“res.data.success”之类的字段坑很大。下面是一个常用写法包含三个静态工厂方法覆盖成功、失败、异常三种情况。public class RespBean { private Integer code; private String msg; private Object data; public static RespBean ok(Object data) { return new RespBean(200, ok, data); } public static RespBean fail(String msg) { return new RespBean(400, msg, null); } public static RespBean error(String msg) { return new RespBean(500, msg, null); } private RespBean(Integer code, String msg, Object data) { this.code code; this.msg msg; this.data data; } // getter/setter 略 }参数说明ok方法用于查询成功时把业务数据放进data返回fail方法适合参数校验没过的情况error方法一般配合全局异常捕获使用。这样做的价值在于小程序端可以用一个统一的拦截器判断code等于给前后端通信定了一个简单契约。别小看这个类论文的“接口设计”小节里可以直接用它作为标准返回格式的示例。2.3 四张核心表用户、商品、订单、留言的字段取舍闲置物品交易的核心流程是“发布→浏览→下单→沟通”数据库里必须有用户、商品、订单、留言四张表就能跑通。字段设计的原则是够用就好不要为了显得专业而堆字段。比如用户表除了基本信息外最关键的是openid——微信小程序用户的唯一标识登录时用wx.login返回的code换来的整个业务里所有“当前用户是谁”的判断都靠它。商品表是这套系统里最复杂的一张表。类别、成色、原价、现价、图片列表、商品状态这些字段直接决定前端页面的展示逻辑。状态字段建议用tinyint0在售、1已售、2下架不要用字符串“onSale”这种数据库存储更省、查询更快而且前端判断时写0/1/2反而不会因为大小写问题出错。订单表需要同时存买家ID和卖家ID因为查询“我买到的”和“我卖出的”时都要用到它。留言表最简单存商品ID、用户ID和内容即可时间字段用create_time默认当前时间。四张表加起来的索引也值得设计一下。goods表的status和create_time建议建联合索引因为首页查询永远是“在售且按时间倒序”orders表的buyer_id和seller_id分别建普通索引供“我买到的”“我卖出的”两个列表查询。毕设数据量不大索引不会明显影响写入速度但对SQL的写法会友好很多。字段类型方面价格统一用decimal(10,2)而不是float涉及金额的字段用浮点类型会在计算时出现精度问题这一点答辩时被问到也能答得上来。3. 数据库与SSM后端落地建表、Mapper、下单事务的关键代码3.1 建库建表SQL用户、商品、订单、留言四张表的初始化脚本数据库是整套系统的地基表结构建不好后面所有代码都在补窟窿。下面这组建表SQL是我按闲置交易场景整理出来的版本去掉了冗余字段保留了完整业务闭环。执行时直接在Navicat或命令行里运行即可注意MySQL的版本建议5.7以上字符集统一用utf8mb4——微信用户的昵称里经常带emojiutf8mb4才能存得下。CREATE DATABASE IF NOT EXISTS second_hand DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE second_hand; CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, openid varchar(64) NOT NULL COMMENT 微信openid唯一标识, nickname varchar(64) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, phone varchar(20) DEFAULT NULL COMMENT 联系电话, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE goods ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 卖家用户ID, title varchar(100) NOT NULL COMMENT 商品标题, description text COMMENT 商品描述, category varchar(30) DEFAULT NULL COMMENT 类别教材/数码/生活用品等, price decimal(10,2) NOT NULL COMMENT 转让价, original_price decimal(10,2) DEFAULT NULL COMMENT 原价, quality varchar(10) DEFAULT 九成新 COMMENT 成色, images varchar(1000) DEFAULT NULL COMMENT 图片URL多个用逗号分隔, status tinyint(4) DEFAULT 0 COMMENT 0在售 1已售 2下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status_time (status, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT闲置商品表; CREATE TABLE orders ( id int(11) NOT NULL AUTO_INCREMENT, goods_id int(11) NOT NULL COMMENT 商品ID, seller_id int(11) NOT NULL COMMENT 卖家ID, buyer_id int(11) NOT NULL COMMENT 买家ID, price decimal(10,2) NOT NULL COMMENT 成交价, status tinyint(4) DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_buyer (buyer_id), KEY idx_seller (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE comment ( id int(11) NOT NULL AUTO_INCREMENT, goods_id int(11) NOT NULL, user_id int(11) NOT NULL, content varchar(500) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_goods (goods_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT留言表;逻辑说明goods表里的images字段用逗号分隔存储多个图片URL这是一种比较取巧但也足够实用的做法配合Java后端用split拆分成数组给前端渲染避免为图片单独建表。orders表把卖家ID和买家ID都冗余进去是因为订单列表和商品列表都要展示省去每次连表查询。这里故意不建物理外键只用普通索引——毕设里逻辑外键够用物理外键会在删除商品或用户时带来一堆级联麻烦这是数据库设计里一个常见的取舍点。参数说明status字段都用tinyint而不是varchar一是节省存储二是避免字符串比较时的大小写问题price用decimal(10,2)10是总位数2是小数位这个精度对闲置交易完全够用。执行完之后建议顺手插入两三条测试数据比如一个测试用户和两件不同类别的商品这样后续跑通接口时不用临时造数。3.2 SSM工程结构与连接配置pom.xml和jdbc.properties的实际写法建好表之后开始搭SSM工程。IDEA里用Maven创建Web项目标准的包结构是controller、service、mapper、pojo四层resources目录下放Spring配置文件、MyBatis配置和数据库连接配置。第一件事是写pom.xmlSpring、SpringMVC、MyBatis、数据库驱动、Jackson这几组依赖缺一不可。下面这份依赖清单去掉了版本号细节因为具体版本要和你本地环境匹配不要照抄一个版本号就完事。dependencies !-- Spring核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version${mybatis-spring.version}/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql.version}/version /dependency !-- JSON转换 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version${jackson.version}/version /dependency /dependencies版本号用Maven的property统一管理这是SSM项目里常见的做法升级依赖时只改一处。数据库连接配置单独放在jdbc.properties里这样换环境时不用改Java代码。注意MySQL 8.0之后连接串里必须显式写明时区和SSL参数否则Tomcat启动时大概率报错这个问题在后面的避坑章节里会展开说。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/second_hand?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的数据库密码参数说明useSSLfalse是关闭SSL证书校验本地开发用不到加密连接开着反而因为证书问题连不上serverTimezoneAsia/Shanghai解决MySQL 8.0默认时区与本地不一致导致的报错allowPublicKeyRetrievaltrue是配合MySQL 8.0的caching_sha2_password认证插件使用的不加这个参数某些版本会报Public Key Retrieval is not allowed。这三个参数可以说是MySQL 8.0时代的标配记不住就照抄。Spring和MyBatis的XML配置里需要把jdbc.properties加载进来、配置数据源、SqlSessionFactory和Mapper扫描路径还有SpringMVC的注解驱动和视图解析器。SSM的常用注解在这一步开始发挥作用Controller标记控制器、Service标记业务类、Autowired做依赖注入、Transactional标记事务方法答辩时老师让你列举SSM常用注解这几个先说出来就拿到了基础分。3.3 Mapper与Service查询、发布、下单三块核心逻辑Mapper层是SSM里SQL最集中的地方商品列表的条件查询、用户登录的openid查询、订单的下单插入都在这里写。下面用GoodsMapper的接口和XML举例覆盖了“查在售列表”和“按条件搜索”这两个最核心的功能。注意XML里的SQL用了MyBatis的动态SQL和#{}占位符这是防止SQL注入的标准写法。public interface GoodsMapper { // 条件查询在售商品列表支持按类别筛选和关键词模糊查询 ListGoods selectOnSale(Param(category) String category, Param(keyword) String keyword, Param(offset) int offset, Param(pageSize) int pageSize); // 根据ID查询商品详情不过滤状态 Goods selectById(Integer id); // 修改商品状态在售/已售/下架 int updateStatus(Param(id) Integer id, Param(status) Integer status); // 发布商品 int insert(Goods goods); }select idselectOnSale resultTypecom.example.pojo.Goods SELECT id, user_id, title, description, category, price, original_price, quality, images, status, create_time FROM goods WHERE status 0 if testcategory ! null and category ! AND category #{category} /if if testkeyword ! null and keyword ! AND title LIKE CONCAT(%, #{keyword}, %) /if ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /select逻辑说明这里的selectOnSale解决的是首页和搜索页的列表数据status0确保只能看到在售商品category和keyword是可选条件用if标签动态拼接SQL。排序用ORDER BY create_time DESC这也是MySQL排序里最常用的写法——按发布时间倒序最新的闲置排在最前面。分页用LIMIT offset, pageSizeoffset由前端传入的页码计算得出。参数说明Param注解用来给Mapper方法参数命名这样XML里才能用#{category}这种形式引用CONCAT函数做模糊查询拼接不推荐直接写LIKE %${keyword}%那是SQL注入的高发写法。LIMIT的两个参数一个是从第几条开始查一个是查多少条前端传页码时在Service层计算offset (page - 1) * pageSize。Service层比Mapper多一些业务规则。最典型的是下单操作这里面有一个必须用事务的场景用户点击“我想要”后系统要做两件事——把商品状态改成已售同时生成一条订单记录。如果这两步只成功了一半就会出现商品显示已售但订单没生成的脏数据。用Transactional注解可以保证要么都成功要么都回滚。代码大致是这样一个结构。Service public class OrderService { Autowired private GoodsMapper goodsMapper; Autowired private OrderMapper orderMapper; Transactional public RespBean createOrder(Integer goodsId, Integer buyerId) { Goods goods goodsMapper.selectById(goodsId); if (goods null || goods.getStatus() ! 0) { return RespBean.fail(商品不存在或已下架); } // 防止卖家买自己的商品 if (goods.getUserId().equals(buyerId)) { return RespBean.fail(不能购买自己发布的商品); } goodsMapper.updateStatus(goodsId, 1); // 商品标记为已售 Order order new Order(); order.setGoodsId(goodsId); order.setSellerId(goods.getUserId()); order.setBuyerId(buyerId); order.setPrice(goods.getPrice()); order.setStatus(0); // 待付款 orderMapper.insert(order); return RespBean.ok(order.getId()); } }逻辑说明createOrder方法先校验商品是否存在且在售然后校验是否卖家本人购买这两个校验缺一不可否则会出现刷单或购买自己商品的逻辑漏洞。updateStatus把商品改成已售orderMapper.insert生成订单两处操作在同一个事务里中间任何一步抛异常商品状态也不会被改掉。参数说明事务默认只在RuntimeException上回滚如果你在业务代码里手动catch了异常记得调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手动回滚否则事务不会生效。这个是SSM里特别隐蔽的一个坑同学之间互相检查代码时常遇到。3.4 登录接口wx.login换openid并下发token的完整流程微信小程序的登录和后端传统登录完全不一样。前端拿不到用户名密码必须用wx.login获取一个临时code再把code发给后端后端拿code去微信的jscode2session接口换openid和session_key。openid是用户在微信生态里的唯一标识session_key用于解密用户手机号之类的敏感信息不能返回给前端。换到openid之后系统自己在后端生成一个token返回给小程序后续请求都带这个token。RestController RequestMapping(/api/wx) public class WxLoginController { Autowired private UserMapper userMapper; private MapString, Integer tokenStore new ConcurrentHashMap(); PostMapping(/login) public RespBean login(RequestBody MapString, String param) { String code param.get(code); if (StringUtils.isEmpty(code)) { return RespBean.fail(code不能为空); } // 调用微信接口换取openid这里的appid和secret配置在配置文件里 String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code code grant_typeauthorization_code; String result HttpUtils.get(url); // 用HttpClient或RestTemplate发起GET请求 JSONObject json JSONObject.parseObject(result); String openid json.getString(openid); if (StringUtils.isEmpty(openid)) { return RespBean.fail(微信登录失败); } // 查用户是否存在不存在则自动注册 User user userMapper.selectByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户 openid.substring(0, 6)); userMapper.insert(user); user userMapper.selectByOpenid(openid); } // 生成token并保存对应关系实际项目换用Redis更合适 String token UUID.randomUUID().toString().replace(-, ); tokenStore.put(token, user.getId()); return RespBean.ok(token); } }逻辑说明这里用一个内存Map临时保存token和用户ID的对应关系毕设系统里够用但Tomcat重启后token就失效了用户需要重新登录。实际生产中会用Redis保存并设置过期时间论文的“系统改进”小节里可以把这一点写成未来优化方向。HttpUtils封装的是HttpClient调用把微信接口返回的JSON字符串解析出openid。参数说明wx.login下发的code只能用一次有效期五分钟重复使用会报错。如果测试时遇到“invalid code”多半是同一个code被后端或调试工具消费了两次。另外这里不返回openid给前端只返回token避免前端拿到openid后直接伪造用户身份这个边界要跟答辩老师讲清楚。4. 小程序端从登录到发布列表加载、图片上传的完整写法4.1 app.json与tabBar页面注册和底部导航的配置细节微信小程序的骨架在app.json里页面路径、窗口样式、tabBar都在这一个文件里配置。闲置物品交易小程序的tabBar通常放四个页面首页、发布、消息、我的。tabBar的icon是有严格尺寸要求的81px*81px太大或太小都会被压缩变形。首页的window配置里navigationBarTitleText用来设置顶部导航标题navigationBarBackgroundColor设置背景色这个会在系统顶部状态栏区域生效所以不要在页面里再写一层自定义导航来抢高度。{ pages: [ pages/index/index, pages/release/release, pages/message/message, pages/mine/mine, pages/detail/detail, pages/order/order ], window: { backgroundTextStyle: dark, navigationBarBackgroundColor: #ffffff, navigationBarTitleText: 校园闲鱼, navigationBarTextStyle: black }, tabBar: { color: #999999, selectedColor: #1296db, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/release/release, text: 发布 }, { pagePath: pages/message/message, text: 消息 }, { pagePath: pages/mine/mine, text: 我的 } ] }, style: v2, sitemapLocation: sitemap.json }逻辑说明pages数组里第一个页面是启动页所以首页要放在最前面。detail和order两个页面不在tabBar里属于二级页面从列表跳转或订单状态跳转时进入。tabBar最多配置五个这是微信官方的硬限制四个刚好够用也符合用户对交易类小程序的操作预期。消息页如果只是展示留言暂时不用做即时会话可以把留言列表挪到详情页里tabBar的消息页改为“我的订单”入口毕设阶段更实用。参数说明navigationBarTextStyle只支持black和white两种值默认black。样式上注意tabBar的selectedColor要跟顶部导航的视觉风格一致P图时icon的未选中状态用#999999选中状态用#1296db比较稳妥。开发阶段有一个重要开关在微信开发者工具的“详情→本地设置”里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。因为本地调试时后端地址通常是http://192.168.x.x:8080没有HTTPS证书不勾选这个开关所有请求都会报“域名不合法”。上线前要记得取消勾选并配置合法域名这个后面讲避坑时会再提。4.2 登录状态管理把token存进storage并在请求里自动带上登录逻辑在小程序端是固定的调用wx.login拿到codePOST给后端拿到token后存进storage后续每个请求都在header里带token。这个流程用一个utils/request.js统一封装起来页面里不要再手动写wx.request这样每个请求都会自动带上登录态。下面是一个常用的封装写法拦截器逻辑直接集成在Promies里。function request(url, method, data) { return new Promise((resolve, reject) { const token wx.getStorageSync(token); wx.request({ url: getApp().globalData.baseUrl url, method: method || GET, data: data || {}, header: { Content-Type: application/json, token: token || }, success(res) { if (res.statusCode 200 res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { // token失效重新登录后重发请求 wx.removeStorageSync(token); loginAndResend(url, method, data, resolve, reject); } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } function loginAndResend(url, method, data, resolve, reject) { wx.login({ success(res) { request(/api/wx/login, POST, { code: res.code }) .then(token { wx.setStorageSync(token, token); request(url, method, data).then(resolve).catch(reject); }); } }); }逻辑说明request函数把token放在header里取名自定义为token后端SpringMVC里通过request.getHeader(token)取出来再根据token查用户ID。401的处理比较关键token过期后自动重新走wx.login再重发原请求用户在页面上是无感的。登录时机选在每次启动小程序或首次请求时触发不要在index页面onLoad里写死。参数说明getApp().globalData.baseUrl是后端接口地址比如http://192.168.31.100:8080/second_hand全局只维护这一处真机测试时只需改这里。getStorageSync和setStorageSync是同步读写storage的接口毕设里用同步版本最保险异步版本回调嵌套多了容易乱。4.3 商品列表下拉刷新与页面列表加载更多的双端配合商品列表页是小程序端最需要打磨的页面涉及下拉刷新、触底加载更多、分类筛选和搜索。这里的“加载更多”是整个页面体验的核心微信小程序里通过onReachBottom触底事件实现配合后端的分页参数来完成。下面这段js处理了页面的数据请求逻辑注意它用page和pageSize控制分页用isLoading防止重复请求。data: { goodsList: [], page: 1, pageSize: 10, hasMore: true, isLoading: false, category: }, // 首次加载和下拉刷新时调用 loadList(reset) { if (this.data.isLoading) return; if (reset) { this.setData({ page: 1, hasMore: true, goodsList: [] }); } if (!this.data.hasMore) return; this.setData({ isLoading: true }); const page this.data.page; request(/api/goods/list?page${page}pageSize${this.data.pageSize}category${this.data.category}) .then(res { const list res.list || []; this.setData({ goodsList: this.data.goodsList.concat(list), page: page 1, hasMore: list.length this.data.pageSize, isLoading: false }); }) .catch(() this.setData({ isLoading: false })); }, onPullDownRefresh() { this.loadList(true); wx.stopPullDownRefresh(); }, onReachBottom() { this.loadList(false); }逻辑说明hasMore的判断方式是看本次返回的条数是否等于pageSize如果等于说明可能还有下一页小于pageSize说明已经到底了。isLoading是防止下拉刷新和触底加载同时触发导致数据重复或丢失。onPullDownRefresh是微信小程序自带的下拉刷新生命周期onReachBottom是触底事件这两个函数只要在Page里定义开发者工具和真机上就会自动触发。注意下拉刷新要在index.json里配置enablePullDownRefresh为true。参数说明page从1开始pageSize前端定为10时翻页就是page1查10条page2查第11-20条。category参数用于分类筛选为空时后端不做过滤这个设计在接口层要保持简单。对列表页来说setData是关键操作大量拼接时先在临时变量里处理好再一次性setData避免频繁触发渲染层更新导致的卡顿。首次进入页面在onLoad里调用this.loadList(true)切tab再回来时数据也还在因为tabBar页面默认不销毁。如果希望每次进入都刷新最新状态可以在onShow里重新loadList(true)但要配合一个标记位避免频繁请求。4.4 发布商品图片上传和成色单选的处理方式发布页是最容易踩坑的地方因为涉及到图片上传和表单提交两块逻辑。用户在发布页填写标题、价格、描述、成色选择图片然后点击发布。成色这个字段在“我的闲置”里用上radio单选框对应的小程序原生组件是radio-group和radio下面是发布页的核心JS逻辑。data: { title: , price: , originalPrice: , description: , quality: 九成新, images: [] }, chooseImage() { wx.chooseMedia({ count: 9, mediaType: [image], success: (res) { const tempFiles res.tempFiles; const newImages this.data.images.concat(tempFiles.map(f f.tempFilePath)); this.setData({ images: newImages.slice(0, 9) }); } }); }, uploadImage(filePath) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.uploadFile({ url: getApp().globalData.baseUrl /api/upload, filePath: filePath, name: file, formData: { token: token }, success(res) { const data JSON.parse(res.data); if (data.code 200) { resolve(data.data); // 返回可访问的图片URL } else { reject(data.msg); } }, fail: reject }); }); }, async submit() { // 先传图片再提交表单 const uploadTasks this.data.images.map(img this.uploadImage(img)); const uploadedUrls await Promise.all(uploadTasks); // 把URL数组拼成一个逗号分隔的字符串传给后端 request(/api/goods/release, POST, { title: this.data.title, price: this.data.price, originalPrice: this.data.originalPrice, description: this.data.description, quality: this.data.quality, images: uploadedUrls.join(,) }).then(() { wx.showToast({ title: 发布成功, icon: success }); wx.switchTab({ url: /pages/index/index }); }); }逻辑说明chooseMedia是微信官方的媒体选择接口count限制最多9张tempFilePath是临时文件路径这个路径只在本次小程序运行期间有效不能直接存进数据库当作图片地址。uploadImage把临时文件上传到后端后端保存到服务器的upload目录后返回一个基于http的URL这个URL才能持久使用。submit里先并发上传全部图片等所有URL都回来后再提交商品表单避免图片没传完就被商品保存引用。参数说明wx.uploadFile的name字段是后端接收文件的参数名后端用MultipartFile file来接收namefile要和后端一致否则解析不到文件。formData是除了文件外额外传递的文本参数这里传了token用于后端识别用户身份。Promise.all是ES6的并发写法注意毕设项目中要保证后端上传接口能正确处理并发不然图片多的时候个别会失败。成色用radio-group来实现数据绑定到quality字段。取值可以固定为“全新/九成新/八成新/七成新及以下”几个档位这样一个字段在商品详情页和列表页都能直接用文原来展示不需要额外的字典表。提交表单时记得做必要校验例如标题不能为空、价格必须大于0、至少传一张图这些校验逻辑放在submit函数的开头避免把非法数据提交给后端。5. 毕设踩坑与排查从MySQL连接到重复请求的5个高频问题5.1 MySQL 8.0连接报SSL与时区错误本地能连但Tomcat连不上现象在Navicat里连接MySQL正常但启动Tomcat加载Spring配置时报错常见报错信息是“Communications link failure”和“The server time zone value Öйú±ê׼ʱ¼ä is unrecognized”。原因MySQL 8.0默认开启了SSL和UTC时区而JDBC驱动连接的参数没跟上。解决在jdbc.url后面加上useSSLfalse、serverTimezoneAsia/Shanghai、allowPublicKeyRetrievaltrue三个参数这组参数在3.2节已经列过。如果装的是MySQL 8.0驱动类也要从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver。这个问题是新手复现SSM项目时翻车率最高的一个报错信息里出现乱码时安心改配置即可。5.2 真机预览连不上后端request:fail的排查路径现象电脑上开发者工具调试一切正常但手机扫码预览时所有请求返回request:fail。排查路径有三步第一确认手机和电脑在同一个局域网第二把baseUrl从localhost改成电脑的局域网IP比如http://192.168.31.100:8080第三开发者工具里勾选“不校验合法域名”。三步走完一般就能通。还有个隐蔽问题要注意Windows防火墙默认拦截外部访问Tomcat端口需要在防火墙里放行8080端口或者临时关闭防火墙测试。真机预览是答辩前必须做的一次演练桌面端的Charles抓包等方式只能验证接口逻辑最终还是要以手机真机表现为准。5.3 上传图片成功但页面显示不了临时路径与永久URL的区别现象发布商品时图片上传成功后端也返回了路径但小程序页面里图片裂开不显示。原因前端保存的是uploadFile接口返回的tempFilePath临时路径这个路径在应用重启后失效或者后端把图片保存到了项目内部目录但SpringMVC没有配置静态资源映射外部无法通过URL访问。解决前端不要直接存临时路径等uploadFile返回后端的http地址再存后端图片保存到项目外或独立upload目录并配置资源映射例如mvc:resources mapping/upload/** location/upload/。还要注意用图片URL给image标签时域名要和request合法域名分开配置否则同样会被拦截。5.4 触底加载列表重复onReachBottom触发两次造成的数据翻倍现象商品列表拉到页面底部短时间内触发多次请求列表在同一屏里出现了重复商品。原因onReachBottom在滚动到底部的瞬间可能触发两次且页面没有加isLoading锁导致并发请求同一页数据。解决4.3节里loadList函数开头判断this.data.isLoading请求期间加锁请求完成后再释放。另外一个连带问题是page自增的逻辑放在then回调里如果两个请求同时发出page会变成同样值所以加锁必须放在请求前而不是回调里。这个坑用开发者工具一般不明显真机上手指快速滑动时最容易复现属于典型的真机玄学。5.5 #{}与${}混用排序字段上出现的SQL注入风险现象列表页支持按价格排序、按发布时间排序SQL里动态拼接ORDER BY字段时用了${sort}结果测试数据被莫名删除了几条。原因${}是文本替换直接把传入内容拼进SQL存在注入漏洞#{}是预编译参数不会有这个问题。解决排序字段不允许直接用${}正确做法是把排序字段做一层白名单映射后端接收sort参数后用Map或switch转成固定的列名比如switch里的值映射为“price”或“create_time”映射后再用#{}拼进SQL里。这个点答辩时主动讲出来是加分项说明你清楚MyBatis中两个占位符的本质区别。6. 论文、演示视频与部署验证把毕设从代码变成答辩分6.1 毕业论文的目录骨架让每个章节都能对应到代码毕设论文通常有固定模板但骨架大同小异包含绪论、相关技术、系统分析、系统设计、系统实现、系统测试、总结与展望。写系统设计时把建表SQL的字段说明、类图、时序图放进去从第三章里直接提取素材即可。写系统实现时选择登录、商品发布、下单这三个核心模块做详细描述每个模块配核心代码片段和运行效果截图。注意正文里代码不要贴大段控制在十行左右代码注释要精简老师看的是你对模块的理解不是代码量。论文里放截图时用真机截图代替开发者工具截图效果上会正式很多。6.2 视频演示录制脚本按功能模块录出90秒有效演示视频演示是很多毕设题目标配里的交付物录制时不要超过三分钟。脚本按功能顺序走打开小程序进入首页展示商品列表→ 点击商品进详情展示留言→ 返回首页按分类筛选 → 切换到发布页发布一件新商品重点演示图片上传→ 回到首页看到新发布的商品 → 演示订单列表状态变化。录制工具用OBS麦克风在系统设置里选“仅麦克风”避免把电脑提示音录进去。操作时每个步骤停顿两秒再继续方便答辩老师看清界面变化。录完后用剪映简单裁剪开头加一页标题“大学生闲置物品交易小程序演示”结尾加一页“演示结束”中间不需要任何特效。6.3 部署验证清单从SQL导入到真机预览的最后一小时答辩前最后一小时按下面的清单从头到尾过一遍能帮你避开大多数演示翻车现场。第一步把数据库脚本导入新环境确认四张表和测试数据都在第二步检查jdbc.properties的账号密码是否匹配当前数据库第三步启动Tomcat确认没有红色报错第四步打开开发者工具编译预览确认首页列表能加载第五步走一遍“登录→发布→下单→查看订单”主流程第六步用手机真机扫码把局域网IP的baseUrl替换到位再走一遍主流程。检查点用表格记录每一项都验证后划勾六项全过基本稳妥。我的习惯是在现场演示前保留一个旧版本备份一旦临时改动出问题可以立刻回滚相当于给自己留了一颗后悔药。这套项目做下来最深的感受是毕业设计和技术选型一样求稳远比炫技重要。SSM看起来老但每一层都有迹可循配合微信小程序成熟的生态和MySQL的扎实功底只要按部就班把表和接口设计好系统跑通是必然结果。技术上“讲得清”交付上“拿得出”答辩时就不会心虚。希望这篇文章能帮你在毕设季少走几个弯路把时间花在真正重要的事情上。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →