首页 / 资讯中心 / 文章详情

Spring Boot + 微信小程序户外骑行项目:从数据库设计到部署排坑全解析

Spring Boot + 微信小程序户外骑行项目:从数据库设计到部署排坑全解析 ★ FEATURED ARTICLE
1. 项目定位与整体设计思路最近在梳理手头的全栈项目这阵子不少读者来问Spring Boot小程序方向怎么做刚好把这个“户外骑行运动小程序”翻出来重新整理了一遍。项目编号40916是一个前后端分离的完整源码工程后端用Spring Boot前端是微信小程序覆盖了骑行运动中从记录轨迹、统计运动数据到参与约骑活动、查看排行榜的完整闭环。对于正在做Java课程设计、毕业设计或者想系统走一遍“Spring Boot 小程序”全流程的同学来说这是一个可以直接跑起来、也方便二次扩展的参考工程。先说结论这类项目最大的价值不在于功能多炫而在于它把“移动端采集数据 → 后端存储计算 → 前端展示反馈”这套链路完整打通了。骑行场景特别适合做小程序因为用户不需要下载独立App微信里扫一扫就能用而Spring Boot做后端胜在生态成熟Spring MVC、MyBatis-Plus、MySQL这套组合的资料多、坑少对新人来说遇到问题几乎都能搜到现成答案。1.1 为什么是Spring Boot 微信小程序组合选技术栈这件事很多时候不是选“最好的”而是选“最合适、最稳的”。这个项目选择Spring Boot核心原因有几个一是开发效率高。Spring Boot的自动配置把以前Spring MVC XML配置那一大堆繁琐工作省掉了一个启动类加几个注解就能把Web服务跑起来这对偏应用型的项目来说非常友好。加上内嵌Tomcat打出一个可执行Jar包就能部署不用单独装容器。二是生态完善组件丰富。项目里需要集成MyBatis-Plus做持久层操作、集成Redis做缓存、集成MinIO做文件存储、集成JWT做登录态这些都有非常成熟的Starter或封装库开发时基本是“拿来即用”。后面我会详细展开每个部分的具体配置和踩坑点。三是社区资料极度丰富。哪怕是完全没接触过Spring Boot的新手遇到“静态资源访问404”“MyBatis-Plus分页不生效”“跨域拦截器失效”这类问题随便一搜就是大量解决方案。这一点在项目周期紧张的时候价值甚至比框架本身更高。小程序端的选择则更贴近业务场景。骑行用户的使用特点是用完即走打开小程序记录一次骑行、看一次排行榜不需要复杂的下载安装流程。微信小程序提供的地理位置接口、地图组件、运动数据读取能力都能直接支撑骑行轨迹和运动统计的核心诉求。再加上微信生态里的分享能力活动约骑、社区晒图这类天然带有社交属性的功能传播起来也更顺畅。1.2 功能结构与业务流程拆解把整个项目的功能拆开看大致可以分成四个业务域运动数据域骑行开始、轨迹打点、里程计算、时长统计、配速计算、卡路里估算这是整个小程序最核心的数据生产链路。社交互动域骑行路线分享、动态发布、点赞评论、约骑活动创建与报名这些功能让用户不只是“一个人骑”而是能“找到人一起骑”。个人中心域微信授权登录、个人资料维护、运动历史查询、里程排行榜、成就体系用来沉淀用户数据和形成留存。后台管理域用户管理、活动审核、内容管理、数据统计这部分一般做在Web管理端供运营或管理员使用。业务流程可以简单概括为用户通过微信授权登录小程序发起骑行后小程序定时采集GPS坐标骑行结束时把轨迹点集和运动汇总数据一并提交到后端后端计算配速、里程、卡路里等指标存入数据库。用户可以在记录详情页回看轨迹、查看统计也可以参与平台上的约骑活动骑行完成后更新个人累计里程带动排行榜变动。理解了这四块再看源码的时候就不会迷路。建议阅读顺序是先看数据库表结构再看后端Controller层的接口列表最后对着小程序端的请求封装理解数据流向。下面我把整个项目拆成几个重点模块逐个说清楚实现思路和实操细节。2. 数据库设计与核心业务建模说句实在话看一个项目水平怎么样先看它的表结构设计。好设计的特征是你能不看一行代码光靠字段就能推演出业务规则。这个项目的表设计总体遵循了这个原则核心表大概有八到十张我把最关键的部分拆出来讲。2.1 用户与登录体系设计用户表是整个系统的基础但如果你直接照着“用户名 密码”去设计放在小程序场景里就行不通了。小程序用户没有密码这个概念也不需要注册流程微信的wx.login接口会给前端返回一个临时code后端拿这个code去向微信服务器换openid和session_key。openid是用户在微信体系内的唯一标识这才是真正的主键。用户表建议保留这些关键字段字段名类型说明idbigint主键openidvarchar(64)微信openid唯一索引nicknamevarchar(32)昵称avatar_urlvarchar(255)头像地址gendertinyint性别total_mileagedecimal(10,2)累计里程单位公里total_durationint累计时长单位分钟levelint用户等级created_timedatetime注册时间这里有个小技巧累计里程和累计时长这类“统计型”字段放在用户表里冗余维护比每次去ride_record表里SUM快得多。虽然存在数据冗余但骑行类项目的写入频率远低于读取频率牺牲一点写入成本换查询速度是完全划算的。对个人练手项目来说尤其适合。登录接口的设计也要注意安全细节。后端拿到code后调用微信的jscode2session接口获取openid和session_key随后可以自己生成一份JWT Token返回给小程序端。后续所有请求在Header里带上Token后端的拦截器解析Token、识别用户身份。整个流程里有个很关键的点code只能使用一次且有效期很短所以后端拿到code必须立即使用不能落库、不能反复提交。2.2 骑行记录与轨迹存储骑行的核心数据有两类一类是骑行完成后生成的汇总数据比如总里程、总时长、平均配速另一类是骑行过程中采集的轨迹点每隔几秒记录一个经纬度坐标。这两类数据建议分开存储因为它们的写入频率、查询频率、数据量级完全不同。骑行记录表ride_record字段名类型说明idbigint主键user_idbigint用户IDstart_timedatetime开始时间end_timedatetime结束时间distancedecimal(10,2)里程单位公里durationint运动时长单位秒avg_speeddecimal(5,2)平均速度单位km/havg_pacevarchar(16)平均配速如“05‘30\””caloriesint卡路里单位千卡max_speeddecimal(5,2)最大速度track_urlvarchar(255)轨迹文件路径可选轨迹点表ride_track_point字段名类型说明idbigint主键record_idbigint骑行记录ID普通索引latitudedecimal(10,6)纬度longitudedecimal(10,6)经度altitudedecimal(8,2)海拔可选字段speeddecimal(5,2)瞬时速度record_timedatetime点位记录时间为什么经纬度要设计成decimal(10,6)而不是float或double因为GPS坐标的精度直接关系到轨迹还原的准确性。decimal(10,6)能精确到小数点后6位大约相当于厘米级精度存储上也不会像double那样产生误差。很多初学者用float存坐标结果轨迹回放的时候折线歪歪扭扭、点对不齐排查半天发现是精度问题。2.3 活动与排行榜的数据设计约骑活动表activity要支持活动标题、活动时间、集合地点、路线说明、人数上限、当前报名人数、活动状态报名中/进行中/已结束。这里要特别注意“报名人数”的并发控制。如果直接用update activity set signed_count signed_count 1 where id ?多人同时报名时字段会丢失更新。我在项目里用的是先Redis预扣减、再异步落库的方案这个小细节后面会单独展开。排行榜不需要单独建表直接从用户表按total_mileage或某个时间段的里程排序查询即可。如果数据量大了可以引入Redis的ZSet数据结构来维护排名写入时更新分数、查询时直接取TopN性能会好很多。小程序端“排行榜”页面一般只展示前50名和当前用户的排名用Redis一条命令就能搞定。3. 后端核心实现与关键技术细节后端这块是整个项目的“大脑”也是最值得花时间细读的部分。我按项目里实际用到的技术点挑几个容易踩坑、也最能体现工程水平的地方展开。3.1 项目分层与接口规范后端代码建议严格分层Controller接收参数并做基础校验Service处理业务逻辑Mapper负责数据库操作。这个项目里还加了一层DTO/VO对象转换请求参数一律用DTO接收响应数据一律用VO返回禁止把数据库实体类直接暴露给前端。这样做的原因是实体类字段往往包含一些不应泄露或不需要返回的信息比如openid、逻辑删除标记直接序列化出去既不安全也加重了传输负担。接口设计上约定统一的返回结构{ code: 200, message: 操作成功, data: {} }code为200代表成功其他为业务错误码。在小程序端封装一个request函数统一处理code非200的情况弹出错误提示。不要小看这个约定前后端联调时80%的沟通成本都出在“接口返回格式不统一”上越早定好越省事。Controller层还有一个细节路径参数规范的统一。比如按资源命名/api/ride/record、/api/ride/record/{id}、/api/activity、/api/activity/{id}/signup。不要出现/queryRideRecord这类动词开头的接口RESTful风格的接口天然自解释维护成本低很多。3.2 微信登录与Token鉴权实操微信登录是几乎所有小程序项目的入口实现步骤我完整走一遍第一步前端调用wx.login()拿到临时code。第二步前端把code通过请求发到后端/api/auth/login。第三步后端调微信接口https://api.weixin.qq.com/sns/jscode2session?appidxxxsecretxxxjs_codecodegrant_typeauthorization_code这个请求可以用Spring的RestTemplate发送也可以直接用Hutool的HttpUtil封装。第四步拿到openid后查用户表如果不存在就自动注册一个新用户并初始化累计里程为0。第五步生成JWT Token把openid和userId放进去设置过期时间。第六步返回Token和用户基本信息给前端。这里提醒几个细节appid和secret不要硬编码在代码里放到application.yml中并且用Value注解注入。如果项目要发布secret一定要保护好泄露等于任何人都能获取你用户的openid。JWT的密钥要足够长不要用“123456”这种建议至少32位随机字符串。拦截器里要把/api/auth/login这类接口加入白名单否则会出现“访问登录接口却被要求登录”的尴尬情况。Hutool的JWT工具很简洁生成Token就几行代码String token JWT.create() .setPayload(userId, user.getId()) .setPayload(openid, user.getOpenid()) .setExpiresAt(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000)) .setKey(secretKey.getBytes()) .sign();3.3 运动数据的计算逻辑骑行者提交的原始数据通常只有轨迹点和起止时间里程、配速、卡路里这些指标都需要后端计算。这一块其实是用到数学知识的地方也是面试官最喜欢追问的点。里程计算两条相邻轨迹点之间不能直接用平面几何算因为地球是球面两点间的实际距离要用Haversine公式public double distance(double lat1, double lon1, double lat2, double lon2) { double R 6371.0; // 地球半径单位公里 double dLat Math.toRadians(lat2 - lat1); double dLon Math.toRadians(lon2 - lon1); double a Math.sin(dLat / 2) * Math.sin(dLat / 2) Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; }所有轨迹点相邻间距累加就是本次骑行总里程。需要注意定位本身有漂移骑行中静止等待红绿灯时也会产生大量重复点所以我在实现时加了过滤速度小于0.5m/s且位移小于5米的连续点直接跳过不计入里程。配速计算配速表示每公里需要多少分钟计算公式很简单配速 运动总时长(秒) / 总里程(公里)再转成“分‘秒\””的展示格式比如05‘30\”就表示每公里5分30秒。这个数据和平均速度是互为倒数的关系但在骑行场景里配速比速度更常用因为骑行者习惯用“我骑到XXX路线配速是多少”来交流。卡路里估算骑行卡路里没有统一标准公式项目里用的是简化经验算法卡路里 体重(kg) × 运动时长(小时) × MET值。MET值是代谢当量休闲骑行取4中等强度骑行取6高强度骑行取8。小程序端可以让用户填写体重后端根据平均速度自动匹配对应的MET档位。当然这是估算值不适合医疗用途但对运动打卡来说足够参考了。3.4 MyBatis-Plus分页与查询优化列表类接口比如骑行记录列表、活动列表都要做分页。MyBatis-Plus的分页需要先配置分页插件这个插件必须放在MybatisPlusInterceptor里很多人漏掉这一步导致Page参数传了但SQL里始终不带LIMIT查出来全是全量数据。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页查询的代码也很简洁PageRideRecord page new Page(current, size); LambdaQueryWrapperRideRecord wrapper new LambdaQueryWrapper(); wrapper.eq(RideRecord::getUserId, userId) .orderByDesc(RideRecord::getStartTime); IPageRideRecord result rideRecordMapper.selectPage(page, wrapper);这里提醒一个分页排序的坑如果按start_time排序而这一列没有索引数据量大之后排序会非常慢。建议在ride_record表的user_id start_time上建立联合索引既满足查询条件过滤又满足排序需求。3.5 全局过滤器处理XSS与文件上传安全这个项目里引入了全局过滤器主要应付两类问题一类是JSON请求体里的XSS脚本注入另一类是文件上传时的内容校验。XSS过滤的原理是在请求进入Controller之前先经过一个Filter对请求体里的内容做清洗和转义。比如把script标签转义成lt;scriptgt;这样即使用户恶意提交也不会在页面上被执行。Spring Boot里实现方式通常是自定义一个OncePerRequestFilter包装HttpServletRequest覆盖getParameter()和getInputStream()方法。不过HttpServletRequest的输入流只能读取一次想要在Filter里读JSON再放回去得用ContentCachingRequestWrapper或者把读过的字节缓存到自定义包装类里。这个坑我自己踩过一开始只是简单实现了HttpServletRequestWrapper结果Controller里一获取请求体就报IOException: Stream closed折腾了半天才回想起来输入流不能重复读取。解决方法是把request的body先读出来放进byte数组后续所有getInputStream()调用都从这个byte数组取数据。文件上传安全主要有三个维度文件类型校验、文件大小限制、存储路径防穿越。文件类型不要只看扩展名一定要校验文件头的魔数。比如图片FF D8 FF开头的才是JPEG89 50 4E 47才是PNG。有人改个扩展名就能绕过检查这是非常基础但容易忽略的点。项目里我写了一个简单的魔数校验工具上传头像和路线图时都会过一遍。public boolean isImage(byte[] bytes) { if (bytes null || bytes.length 4) return false; return (bytes[0] 0xFF) 0xFF (bytes[1] 0xFF) 0xD8 || (bytes[0] 0xFF) 0x89 (bytes[1] 0xFF) 0x50; }文件大小限制建议双端做小程序端选择图片时做一次校验超过2MB直接提示后端接口再设置一次防止有人绕过前端直接调接口传大文件。Spring Boot里可以通过spring.servlet.multipart.max-file-size配置但我更推荐在代码里做二次判断因为配置参数只是个兜底不同接口对文件大小的要求可能不同。3.6 文件存储MinIO与本地存储的取舍骑行项目必然涉及图片头像算一个路线封面图算一个动态分享里每次骑行完晒轨迹截图也算一个。这些文件存哪里项目里同时支持了本地存储和MinIO本地存储是默认选项零依赖、跑起来就能用适合开发测试MinIO是生产环境的推荐方案适合数据量大、需要对象存储能力的场景。MinIO接入Spring Boot的要点是minio: endpoint: http://127.0.0.1:9000 access-key: minioadmin secret-key: minioadmin bucket-name: cyclingConfiguration public class MinioConfig { Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } }上传文件的代码核心就两步putObject上传然后getPresignedObjectUrl生成临时访问链接。注意MinIO的桶默认是私有的直接用http://localhost:9000/bucket/file.jpg访问不到。要么把桶设为公共读要么生成预签名URL。生产环境我倾向生成预签名URL因为桶设为公共读后任何人知道地址都能访问文件还有被盗链的风险。还有一个小经验前端传图片前最好先压缩再上传。小程序端wx.compressImage可以把图像质量压到80%体积能减少一半以上上传速度明显变快后端存储压力也小。这个优化用户感知很直接加载图片都变快了。3.7 并发场景与事务处理约骑活动的“名额扣减”是典型的并发场景。多个用户同时报名一个剩余名额只剩1个的活动不能直接做“先查后更”否则会出现超卖。项目里的处理方式是报名前先从Redis读取当前报名人数大于等于人数上限直接返回失败。扣减名额用Redis的decr命令原子操作返回值小于0说明名额被抢完需要把值加回去。扣减成功后再异步写MySQL报名记录保证最终一致。这套方案的好处是Redis的高性能和原子性可以扛住高并发MySQL只做最终持久化。如果你的项目没有Redis依赖退而求其次的做法是在数据库里用乐观锁update activity set signed_count signed_count 1 where id ? and signed_count max_count这条SQL本身是原子的signed_count max_count条件保证不超卖受影响行数为0说明报名失败。两种方案的侧重点不同Redis方案更适合读多写少、并发大的场景SQL方案则实现更简单、不引入额外依赖。业务上的事务控制也值得一提。比如“骑行者提交骑行记录”这个动作涉及ride_record表插入、user表累计里程更新、活动表参与计数三处变更只要任何一步失败整个提交都应该回滚。项目里用Transactional(rollbackFor Exception.class)标注Service方法注意rollbackFor里指定Exception.class否则只在抛出RuntimeException时才会回滚检查异常默认不回滚这里也是新手容易踩的坑。3.8 项目打包、部署与源码保护后端项目最终会打成可执行Jar包部署。打包前要注意application.yml里把数据库地址、Redis地址、MinIO地址都改成服务器实际配置同时把spring.profiles.active按环境拆分dev和prod分开管理。很多人在网上搜“怎么将springboot jar反编译成项目”其实就是因为担心源码泄露。Java的class文件是可以被反编译的Jar包本质上是个Zip压缩包用JD-GUI、IDEA自带的反编译工具都能看到你的代码逻辑。所以涉及数据库密码、微信secret、JWT密钥这些敏感配置无论如何都不能硬编码在Jar包里。生产环境建议用环境变量注入或者放在外部配置文件里通过spring.config.additional-location指定外部路径加载这样打包的Jar里只有默认配置敏感信息都在服务器上单独管理。另外一个部署细节Spring Boot项目默认端口是8080小程序端要求的HTTPS域名通常走443端口所以部署时一般要加一层Nginx反向代理把https://api.example.com转发到http://127.0.0.1:8080。Nginx上还要配置静态文件服务或者代理MinIO的访问路径这样图片URL才能统一走HTTPS域名否则小程序端会因“非HTTPS域名”而拒绝加载图片。4. 小程序端实现与联调要点小程序端是整个系统的“门面”用户日常接触的就是这一层。从技术选型上说可以用微信原生小程序也可以用uni-app跨端框架。项目采用的是原生微信小程序实现原因在于骑行场景对地图、定位、运动数据接口的依赖很强原生小程序调用这些能力最直接同时如果要兼容iOS和Android微信端原生方式不需要额外垫片。4.1 小程序端整体结构小程序端按页面维度拆分大致有这么几个核心页面首页展示今日骑行概况、推荐路线、快捷发起骑行入口。骑行页核心页面展示地图、开始/暂停/结束骑行、实时速度、里程、时长。记录页历史骑行记录列表点进详情可回看轨迹和统计数据。活动页约骑活动列表、活动详情、报名入口。我的页个人资料、累计数据、排行榜入口、设置。页面间跳转用微信原生的wx.navigateTo和wx.switchTab。注意活动页、记录页这类二级页面用navigateTo而首页、骑行、我的这类底部Tab页必须用switchTab否则页面栈会越堆越深返回逻辑会混乱。4.2 定位与轨迹采集的实现细节小程序采集位置主要依赖wx.getLocation但注意这个接口的type参数要传gcj02返回的是火星坐标系坐标直接用高德、腾讯地图组件展示就不需要额外纠偏。如果后端要在其他地图上展示坐标系不统一会导致轨迹偏移几十到几百米这是很多轨迹错位的根本原因。骑行过程中采集轨迹点不能像一般请求那样每秒钟发起一次网络请求太耗电也容易造成请求堆积。项目里的做法是在小程序端用一个定时器每5秒或10秒记录一次当前位置缓存到本地数组。骑行结束时一次性提交全部轨迹点或者每积累20个点批量上传一次。以最后一次定位为准计算里程同时做一个平滑处理去除明显漂移点。前端定位开关和权限引导也要处理好。用户拒绝授权后小程序会一直拿不到位置。项目里在发起骑行前先检查wx.getSetting中的权限状态如果用户之前拒绝了就通过wx.openSetting引导去设置页面重新打开权限否则只能提示用户无法记录轨迹。4.3 请求封装与登录态维护小程序端所有请求建议抽成一个公共request函数统一做三件事拼接基础域名、注入Token、统一处理错误码。const request (url, data, method GET) { return new Promise((resolve, reject) { wx.request({ url: baseUrl url, method, data, header: { Authorization: wx.getStorageSync(token) }, success: (res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { // Token过期重新走登录流程 wx.removeStorageSync(token); login().then(() request(url, data, method)); } else { wx.showToast({ title: res.data.message, icon: none }); reject(res.data); } } }); }); };Token过期自动续期的逻辑特别实用。后端JWT有效期一般设置7天到期后用户所有请求都会返回401。如果没有自动续期用户就需要重新打开小程序走一遍登录体验很差。上面的处理方式是遇到401时先静默重新wx.login()换取新Token再重发刚才失败的请求整个过程用户无感知。登录流程在小程序启动时执行最好放在App.onLaunch里。但注意wx.login拿到的code有效期只有5分钟且只能用一次用完就要重新调。4.4 小程序端容易被忽视的细节有几个小细节虽然不起眼但在实际使用中体验差异很大。第一个是动态设置页面标题。微信小程序的页面标题默认在app.json里写死但“活动详情页”这类页面的标题应该跟着活动名称走。可以用wx.setNavigationBarTitle({title: xxx})在onLoad里动态设置。这个搜索词热度很高说明不少人在做类似功能时卡住了。第二个是顶部导航栏高度适配。小程序页面里如果要做自定义导航栏不同手机的顶部状态栏高度不一样。获取方式是通过wx.getSystemInfoSync().statusBarHeight拿到状态栏高度再结合胶囊按钮位置计算导航栏高度。这块适配不好在一台手机上正常、另一台手机上就重叠是真实发生过的坑。第三个是缓存设置。运动数据和基础配置类信息可以设置本地缓存避免每次打开页面都调接口。项目里对首页推荐路线做了30分钟的缓存对用户基本信息做了7天的缓存。设置缓存时顺手把过期时间一起存进去读取时判断是否过期防止缓存永不过期导致数据陈旧。第四个也是很多新手栽跟头的点小程序request的合法域名必须是HTTPS。开发阶段可以在微信开发者工具里勾选“不校验合法域名”来联调但发布上线前必须把后端接口域名和文件访问域名都配置到小程序后台的request合法域名里并且一定要是备案过的HTTPS域名。这里强调一下本地localhost、局域网IP在真机预览时是无法访问的。5. 联调部署与典型问题排查实录项目开发中最耗时间的往往不是写新代码而是排故。我把这几个月里遇到的典型问题整理了一份速查表遇到同类问题直接按表排查能省很多时间。5.1 真机定位失败与坐标漂移问题现象模拟器上定位正常真机上线没有定位权限弹窗或者地图上的轨迹点到处飞。排查思路先看app.json里有没有声明requiredPrivateInfos: [getLocation]以及permission里的scope.userLocation字段。从2022年起微信小程序对地理位置接口管控很严隐私声明缺失会导致真机直接无法调用。再检查后端存坐标的字段精度如果数据库用的是float也会出现坐标被截断导致轨迹偏移。解决建议模拟器上的定位数据很多是模拟的真机调试前一定先确认权限弹窗和app.json配置。采集频率不要太高5秒一个点足够过高反而容易累积大量漂移点。轨迹渲染前做一个抽稀处理把连续且几乎重合的点合并掉轨迹线会干净很多。5.2 前端请求后端接口一直404或500问题现象小程序端发起请求network面板显示请求能发出但返回404或500。排查思路404一般是路径不匹配。检查后端的RequestMapping路径看看Context Path是否配置了server.servlet.context-path。很多人习惯在application.yml里配了/api开头结果前端请求路径又带了一次/api双重拼接后自然404。500则先看后端日志多数是参数类型不匹配或数据库字段映射错误。解决建议后端统一配上全局异常处理器RestControllerAdvice返回友好的JSON错误信息而不是把异常堆栈直接抛给前端。这样即使出错了小程序端也能看到“某某字段格式错误”之类的内容而不是一个大白屏。5.3 跨域请求被拦截问题现象浏览器里用Axios请求后端接口一切正常小程序端却报“url not in domain list”或跨域错误。排查思路小程序端请求不存在传统意义上的浏览器跨域限制如果你遇到的是“url not in domain list”那是开发工具校验了合法域名。解决办法是在开发工具详情里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”前提是仅限开发阶段。如果你把项目跑在H5端或者调试工具里遇到CORS错误那就在后端加跨域配置Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这里的allowCredentials(true)要注意如果设为trueallowedOrigins不能直接用*要用allowedOriginPatterns(*)否则Spring会直接拒绝配置并启动报错。5.4 数据库时区导致时间差8小时问题现象数据库存的时间比实际时间早8小时或者前端展示时间比真实时间晚8小时。排查思路这是时区问题。MySQL的datetime类型不带时区存入的字符串是“无时区”的。而Java侧如果用的是LocalDateTime默认用JVM所在时区中国是东八区显示上自然差8小时。解决建议连接串上明确设置时区参数spring: datasource: url: jdbc:mysql://localhost:3306/cycling?serverTimezoneAsia/ShanghaiuseSSLfalsecharacterEncodingutf8同时Jackson序列化时统一格式和时区spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai一个统一原则前后端之间传输时间一律用“yyyy-MM-dd HH:mm:ss”字符串不传时间戳。时间戳在不同时区的设备上展示会有偏差字符串则天然无歧义。5.5 用普通电脑或家用服务器部署的注意点有不少读者问“家里电脑能部署小程序后端吗”答案是可以但有几个现实问题要提前想清楚必须有公网IP或内网穿透否则微信服务器无法访问到你电脑上的8080端口。没有公网IP的话用内网穿透工具把本地端口映射到公网域名。但免费版一般不太稳定连接数一多就断。HTTPS证书是小程序上线刚需域名必须备案、必须HTTPS。这一块可以用Nginx配合免费证书解决但要保证域名是备案状态否则微信后台不认。家用宽带的稳定性家庭宽带上下行不对称上行带宽通常较小多人同时访问时接口响应会明显变慢。文件上传功能受影响最直接几MB的图片传上去可能要好几十秒。机器性能倒不用太担心Spring Boot应用在2核4G的服务器上带一个小程序项目问题不大真正的瓶颈在带宽和公网网络的稳定性。6. 从源码中学到的东西最后聊点个人的体会。这个项目源码我前后读过两遍第一遍纯粹是为跑通功能第二遍才是真正理解设计。建议拿到源码后不要急着打开IDE跑代码先做三件事第一件看README或数据库初始化脚本把表结构梳理一遍画一张简单的业务ER图。等你能不看代码说出每张表是干什么的、表之间怎么关联的读代码效率会翻倍。第二件从登录接口开始读这个小程序所有功能都依赖于登录态。读懂了Token生成、拦截器校验、用户信息获取后面的业务接口都有共通之处。第三件找一个核心业务比如骑行记录提交从头到尾追一遍小程序端请求 → Controller接收 → Service业务计算 → Mapper持久化 → 前端回显。把这条链路走通整个项目的骨架也就摸透了。这个项目后续可以扩展的方向也不少接入蓝牙踏频器和心率带、生成骑行轨迹的动态视频、引入队伍PK赛制、增加运动数据的周报月报推送。底子打好了在这些方向上做增量开发技术难度都不算高。实际开发中最大的体会是这类全栈项目真正的难点永远在数据如何流转、异常如何处理、以及边界情况怎么兜底。功能界面只是表面代码组织才是内核。把项目跑起来只是第一步能把它拆开、讲清楚、改得动这才是真正学到手了。
阅读完成 · 觉得有帮助?
咨询建站