尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

校园红娘微信小程序开发实战:Spring Boot+MySQL前后端分离毕设全流程

校园红娘微信小程序开发实战:Spring Boot+MySQL前后端分离毕设全流程 校园红娘微信小程序是一个面向高校学生的实名制交友与表白平台核心是解决“校园内认识人难、社交圈窄”的问题。从毕业设计角度讲这个选题比普通商城或博客项目更有展示价值因为它在一个小程序里同时覆盖微信登录、内容发布、权限审核、消息会话、数据统计和匹配推荐六类常见业务场景。读者如果能把这一套流程完整跑通不只是完成毕业设计也能把微信小程序从开发到上线的常见链路过一遍。本文以原生微信小程序作为客户端后端使用 Spring Boot 和 MySQL采用普通前后端分离方式实现。文章会先说明系统模块怎么拆分再搭建环境与数据库然后逐步实现登录、发布、心动匹配、牵线私信和管理审核最后给出真机调试、常见报错和生产环境建议。所有代码都给出核心片段落地时需要结合自己的包名、appid 和数据库配置调整。1. 先理解项目边界校园红娘为什么不是简单“表白墙”很多人看到“校园红娘”四个字第一反应是做一个发帖表白墙。如果只按这个思路做系统会变成一个内容列表加发布表单答辩时很难展示技术深度。这个项目的价值在于它把社交产品的核心链路压缩到了一个适合高校场景的闭环里用户身份认证、内容发布、内容审核、匹配推荐、牵线确认、私信沟通六个环节缺一不可。1.1 系统要解决的真实痛点高校学生认识人的方式高度依赖线下场景社团、班级、老乡群信息严重不透明。线上匿名表白墙虽然活跃但缺乏后续转化路径用户发完帖子后无法进一步建立联系。校园红娘系统的核心需求是既保留“公开表达”的入口又提供“私下建立联系”的通道。因此系统要同时具备两类功能公开内容流用户可以发布表白、征友、树洞三类内容可选择匿名或实名并允许其他用户点赞、评论、表达心动。私密关系链用户看到感兴趣的帖子或推荐人选后可以发起“牵线申请”对方同意后自动建立会话进入一对一私信。这个设计比单纯表白墙多了一个“双方确认”的环节更接近真实社交产品的逻辑也更容易在答辩时讲清楚数据表之间的关系。1.2 功能边界从答辩展示和可运行两个角度看模块划分毕业设计不能只追求功能多一定要保证每个模块都能演示出完整流程。建议把功能边界控制在七个模块内模块核心功能演示重点用户认证微信登录、头像昵称填写、个人资料code 换 openid 的完整流程内容发布发布表白/征友/树洞内容支持匿名图文内容入库与状态流转内容审核管理端审核帖子通过/拒绝状态机设计与操作日志互动点赞点赞、心动、取消点赞唯一索引防重复操作牵线匹配候选推荐、发起牵线、同意/拒绝双向确认关系表设计私信会话会话列表、消息发送、未读数会话与消息分表数据统计用户数、帖子数、牵线成功率、七日活跃聚合 SQL 与图表展示这个划分足够覆盖微信小程序开发的主要知识点又不会让实现量失控。如果时间充裕可以在统计模块基础上扩展“按学院分布”“按年级分布”等维度答辩时更能体现数据分析能力。1.3 技术选型为什么用原生微信小程序加自建后端客户端有两种常见方案原生微信小程序和 uni-app。后者可以用 Vue 语法开发并编译到多个平台兼容性看起来更好但毕业设计项目整体体量不大多端发布并不是核心目标原生小程序反而能减少编译链路带来的不确定性。后端也有三条路线方案优点缺点适用场景Spring Boot MySQL资料多、答辩问不倒、易扩展需要自己处理服务器部署计算机专业毕设首选Node.js Express MySQL上手快、前端同学友好类型约束弱、资料偏少前端基础强的同学微信云开发免服务器、快速上线数据库和云函数绑定平台纯前端无后端基础下面代码以 Spring Boot MySQL 为例因为它在国内高校中使用面最广数据库表设计、接口设计、事务处理都可以展开讲解答辩时更容易应对关于并发、字段归一化、查询优化这类问题。注意如果原始项目只提供了前端页面而没有后端代码建议不要直接用云开发糊弄毕业设计评审更看重后端表设计和接口逻辑。2. 环境准备与项目初始化先把账号、工具和后端骨架对齐这个阶段的错误通常不是代码问题而是账号和工具配置不一致。很多同学把项目复制到本地后模拟器里能打开登录页但点击登录就报错原因往往是小程序 appid 和后台配置不匹配。2.1 微信公众平台账号与开发者工具开发微信小程序前需要在微信公众平台注册一个账号。个人主体可以注册但部分接口如订阅消息、部分内容安全能力可能受主体类型影响。学生做毕设通常注册“个人”主体即可正式上线时再根据学校或实习单位情况调整。需要准备三样东西小程序 appid在“开发管理-开发设置”中查看前端请求和后端登录接口都会用到。小程序 secret与 appid 配套使用用于后端调用code2Session接口换取 openid。微信开发者工具建议使用稳定版不要长期使用 RC 版本避免出现模拟器与真机行为不一致。在开发者工具导入项目时不要勾选“测试号”必须填入真实 appid否则wx.login获取的 code 无法在微信服务器上兑换 openid。2.2 后端工程骨架与依赖后端建议按标准 Spring Boot 工程组织Maven 依赖至少包含以下几类dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesMyBatis-Plus 可以减少单表 CRUD 代码量适合毕设这种需要在短时间内展示完整功能的项目。生产环境如果对 SQL 可控性要求高也可以换成 MyBatis 原生 XML但代码量会明显增加。application.yml中只需要把数据源、端口和微信小程序配置写清楚server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_match?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 wx: mini: appid: 你的appid secret: 你的secret mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl注意wx.mini.appid和wx.mini.secret不要写在代码里也不要提交到 Git 仓库至少用application-dev.yml和application-prod.yml区分环境。如果是演示项目暴露在本地问题不大但一旦准备部署到公网服务器必须通过环境变量注入。2.3 数据库设计核心表字段与关系校园红娘系统的核心表有六张下面给出可直接执行的建表 SQL 片段。用户表student_userCREATE TABLE student_user ( id BIGINT NOT NULL AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信openid, nickname VARCHAR(50) DEFAULT COMMENT 昵称, avatar_url VARCHAR(500) DEFAULT COMMENT 头像, gender TINYINT DEFAULT 0 COMMENT 0未知 1男 2女, school VARCHAR(100) DEFAULT COMMENT 学校, major VARCHAR(100) DEFAULT COMMENT 专业, grade VARCHAR(20) DEFAULT COMMENT 年级, tags VARCHAR(255) DEFAULT COMMENT 兴趣标签逗号分隔, bio VARCHAR(500) DEFAULT COMMENT 个人简介, status TINYINT DEFAULT 1 COMMENT 1正常 0禁用, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;帖子表postCREATE TABLE post ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 发布者, content VARCHAR(2000) NOT NULL COMMENT 内容, images TEXT COMMENT 图片JSON数组, is_anonymous TINYINT DEFAULT 0 COMMENT 0实名 1匿名, post_type TINYINT DEFAULT 1 COMMENT 1表白 2征友 3树洞, status TINYINT DEFAULT 0 COMMENT 0待审核 1已发布 2已拒绝 3已删除, reject_reason VARCHAR(255) DEFAULT , like_count INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status_created (status, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT表白墙帖子;互动表post_reactionCREATE TABLE post_reaction ( id BIGINT NOT NULL AUTO_INCREMENT, post_id BIGINT NOT NULL, user_id BIGINT NOT NULL, reaction_type TINYINT NOT NULL COMMENT 1点赞 2心动, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_post_user_type (post_id, user_id, reaction_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT帖子互动表;牵线申请表match_applyCREATE TABLE match_apply ( id BIGINT NOT NULL AUTO_INCREMENT, from_user_id BIGINT NOT NULL COMMENT 发起方, to_user_id BIGINT NOT NULL COMMENT 接收方, apply_content VARCHAR(500) DEFAULT COMMENT 申请附言, status TINYINT DEFAULT 0 COMMENT 0待处理 1同意 2拒绝, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, handled_at DATETIME DEFAULT NULL, PRIMARY KEY (id), KEY idx_to_status (to_user_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT牵线申请;会话表conversation和消息表chat_messageCREATE TABLE conversation ( id BIGINT NOT NULL AUTO_INCREMENT, user_a BIGINT NOT NULL, user_b BIGINT NOT NULL, last_message VARCHAR(500) DEFAULT , last_message_time DATETIME DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_pair (user_a, user_b) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT会话表; CREATE TABLE chat_message ( id BIGINT NOT NULL AUTO_INCREMENT, conversation_id BIGINT NOT NULL, sender_id BIGINT NOT NULL, receiver_id BIGINT NOT NULL, content VARCHAR(2000) NOT NULL, is_read TINYINT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_conversation_id (conversation_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT聊天消息表;conversation表用user_a和user_b两个字段固定会话双方写入时始终把较小 id 放在user_a可以避免同一对用户出现两条会话记录。post_reaction表用联合唯一索引防止重复点赞和重复心动这个细节在答辩时很加分。2.4 创建最小可运行项目小程序端小程序端新建项目后先把全局配置app.json写完整{ pages: [ pages/index/index, pages/publish/publish, pages/detail/detail, pages/match/match, pages/message/message, pages/chat/chat, pages/profile/profile, pages/admin/audit/audit, pages/admin/stats/stats ], window: { navigationBarTitleText: 校园红娘, navigationBarBackgroundColor: #fa5151, navigationBarTextStyle: white }, tabBar: { list: [ { pagePath: pages/index/index, text: 墙 }, { pagePath: pages/match/match, text: 缘分 }, { pagePath: pages/message/message, text: 消息 }, { pagePath: pages/profile/profile, text: 我的 } ] } }tabBar 页面只能是四个属于顶层页面。发布页和聊天页使用wx.navigateTo跳转不放进 tabBar。管理端页面建议通过“我的”页面里的管理员入口进入不单独占 tab。3. 核心模块实现从登录到发布再到牵线这个阶段是整个系统的技术主干。每个模块都要做到“能运行、能演示、能讲清楚为什么这样实现”。3.1 微信登录与用户身份绑定微信小程序登录的标准流程是前端调用wx.login获取临时 code。前端把 code 发送到后端。后端调用微信接口code2Session用 appid、secret、code 换取 openid 和 session_key。后端用 openid 查用户表不存在则自动注册。后端生成自定义登录态 token 返回前端前端存入wx.setStorageSync。后端核心逻辑如下public LoginResult wxLogin(String code, String nickname, String avatarUrl) { String url https://api.weixin.qq.com/sns/jscode2session ?appid appid secret secret js_code code grant_typeauthorization_code; String response restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(response); String openid json.getString(openid); if (StringUtils.isBlank(openid)) { throw new BusinessException(登录失败 json.getString(errmsg)); } StudentUser user userMapper.selectByOpenid(openid); if (user null) { user new StudentUser(); user.setOpenid(openid); user.setNickname(StringUtils.isBlank(nickname) ? 微信用户 : nickname); user.setAvatarUrl(avatarUrl); userMapper.insert(user); } String token UUID.randomUUID().toString().replace(-, ); // token 存入 Redis 或内存缓存并设置过期时间 loginCache.put(token, user.getId()); return new LoginResult(token, user); }这段代码有三个关键点不要把 appid 和 secret 写在小程序前端微信接口调用必须放到后端否则 secret 会泄露。code 只能使用一次如果用户连续点击登录按钮第二次请求会报40163 code been used。token 本质上是框架自己维护的会话标识使用 Redis 存储有效期更合理学习环境可以用ConcurrentHashMap代替但要声明这是简化方案。前端登录页在一个按钮中完成全部操作async onLogin(e) { const { nickName, avatarUrl } e.detail const loginRes await wx.login() const res await request.post(/api/auth/login, { code: loginRes.code, nickname: nickName, avatarUrl: avatarUrl }) wx.setStorageSync(token, res.data.token) wx.setStorageSync(userInfo, res.data.user) wx.switchTab({ url: /pages/index/index }) }这里使用了微信提供的头像昵称填写能力通过button open-typechooseAvatar选头像、input typenickname填写昵称不需要再调用旧的wx.getUserProfile避免新版本接口调整导致兼容问题。3.2 表白墙发布与内容安全检测发布接口不能只是简单 insert 一条记录。作为校园社交平台内容审核是必须考虑的环节。设计上让新帖子默认进入“待审核”状态只有管理员审核通过后才在首页展示。发布接口核心逻辑PostMapping(/api/posts) public Result createPost(RequestBody PostCreateRequest request, RequestHeader(Authorization) String token) { Long userId loginService.getUserIdByToken(token); // 1. 内容安全检测防止违规内容进入内容池 String checkResult securityCheck(request.getContent()); if (!pass.equals(checkResult)) { return Result.error(内容包含不合适的信息请修改后重新发布); } // 2. 保存帖子初始状态为待审核 Post post new Post(); post.setUserId(userId); post.setContent(request.getContent()); post.setImages(JSON.toJSONString(request.getImages())); post.setIsAnonymous(request.getIsAnonymous()); post.setPostType(request.getPostType()); post.setStatus(0); postMapper.insert(post); return Result.success(post.getId()); }内容安全检测可以调用微信内容安全接口把用户发布内容交给平台判断。即便使用该接口也仍然要保留人工审核两者不冲突。小程序端发布页面使用textarea收集内容图片选择用wx.chooseMediaasync chooseImage() { const res await wx.chooseMedia({ count: 3, mediaType: [image], sizeType: [compressed] }) this.setData({ images: res.tempFiles.map(f f.tempFilePath) }) }选择图片后不能直接把临时路径传给后端上传真实项目需要先通过后端接口把图片传到云存储或自建文件服务只在数据库里保存图片 URL。毕设如果只做本地演示可以暂时把图片转 base64 存到images字段但要在答辩时说明这种方式只适合演示不适合生产。3.3 “心动”匹配与红娘牵线机制“心动”按钮和“点赞”按钮的差别在于语义。点赞只是认可内容心动代表用户想认识作者。在数据上两者都写入post_reaction表用reaction_type区分。一旦用户对某个帖子表达了心动帖子作者会收到一条通知可以在“缘分”页面看到“谁对我心动”的列表。此时作者可以选择“认识 Ta”发起牵线申请。牵线机制的核心代码PostMapping(/api/match/apply) public Result applyMatch(RequestBody MatchApplyRequest request, RequestHeader(Authorization) String token) { Long fromUserId loginService.getUserIdByToken(token); Long toUserId request.getToUserId(); // 防止重复申请 Long count matchApplyMapper.selectCount( new LambdaQueryWrapperMatchApply() .eq(MatchApply::getFromUserId, fromUserId) .eq(MatchApply::getToUserId, toUserId) .eq(MatchApply::getStatus, 0)); if (count 0) { return Result.error(已经发起过牵线申请请等待对方处理); } MatchApply apply new MatchApply(); apply.setFromUserId(fromUserId); apply.setToUserId(toUserId); apply.setApplyContent(request.getContent()); apply.setStatus(0); matchApplyMapper.insert(apply); return Result.success(); }接收方处理申请时如果点击“同意”系统要做两件事把申请状态改为“同意”。创建或获取双方会话把会话 id 返回给前端前端跳转到聊天页。这里使用了事务保证一致Transactional(rollbackFor Exception.class) public Long acceptApply(Long applyId) { MatchApply apply matchApplyMapper.selectById(applyId); if (apply null || apply.getStatus() ! 0) { throw new BusinessException(申请不存在或已处理); } apply.setStatus(1); apply.setHandledAt(new Date()); matchApplyMapper.updateById(apply); // 查询是否存在会话 Long conversationId conversationMapper.findConversationId( apply.getFromUserId(), apply.getToUserId()); if (conversationId null) { Conversation conversation new Conversation(); conversation.setUserA(Math.min(apply.getFromUserId(), apply.getToUserId())); conversation.setUserB(Math.max(apply.getFromUserId(), apply.getToUserId())); conversationMapper.insert(conversation); conversationId conversation.getId(); } return conversationId; }Transactional在这里是关键。如果只更新了申请状态而创建会话失败用户会看到“已经同意”但是没有聊天入口产生数据不一致。事务可以把两步操作合并成一个原子操作。3.4 私信会话与未读消息数消息模块不需要引入复杂的即时通讯组件用定时轮询就能满足教学演示需求。小程序端进入消息页时请求会话列表进入聊天页后每隔 3 到 5 秒轮询一次新消息。会话列表接口GetMapping(/api/conversations) public Result listConversations(RequestHeader(Authorization) String token) { Long userId loginService.getUserIdByToken(token); ListConversationVO list conversationMapper.selectListByUserId(userId); return Result.success(list); }ConversationVO需要包含对方昵称、头像、最后一条消息、最后消息时间和未读数。未读数用一条聚合 SQL 计算SELECT COUNT(*) FROM chat_message WHERE conversation_id #{conversationId} AND receiver_id #{userId} AND is_read 0聊天页发送消息时前端先调用后端接口保存消息然后立即把消息追加到本地列表。这里不要只依赖轮询否则用户会感觉发送延迟。轮询只负责接收对方新消息自己发出的消息应该由发送接口直接返回确认。注意轮询方案在用户量小的时候没有问题但如果要部署到公网服务需要评估接口频率。生产环境建议改为 WebSocket通过长连接推送新消息减少无效请求。4. 管理端与数据统计让答辩演示更有说服力管理端可以做成小程序内嵌页面也可以单独做一个简易 Web 管理后台。从毕业设计答辩流畅性出发小程序端嵌管理入口更省事不用部署两套前端。4.1 管理员审核列表与状态流转管理员角色不需要单独建表可以在student_user表增加一个role字段0 普通用户1 管理员。前端根据角色决定是否显示管理入口。审核列表接口GetMapping(/api/admin/posts) public Result listAuditPosts(RequestParam Integer status, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { PagePost postPage postMapper.selectPage( new Page(page, size), new LambdaQueryWrapperPost() .eq(Post::getStatus, status) .orderByDesc(Post::getCreatedAt)); return Result.success(postPage); }审核操作是简单状态更新但要记录拒绝原因PostMapping(/api/admin/posts/{id}/approve) public Result approvePost(PathVariable Long id) { Post post postMapper.selectById(id); if (post null) { return Result.error(帖子不存在); } post.setStatus(1); postMapper.updateById(post); return Result.success(); } PostMapping(/api/admin/posts/{id}/reject) public Result rejectPost(PathVariable Long id, RequestBody RejectRequest request) { Post post postMapper.selectById(id); if (post null) { return Result.error(帖子不存在); } post.setStatus(2); post.setRejectReason(request.getReason()); postMapper.updateById(post); return Result.success(); }帖子状态是一个典型状态机模型0 待审核 - 1 已发布0 待审核 - 2 已拒绝1 已发布 - 3 已删除。管理员不能直接把待审核帖子变成已删除前端要限制操作按钮后端也要做状态校验不能只靠前端隐藏按钮。4.2 核心统计指标与 SQL 写法统计模块建议展示四个指标指标SQL 思路展示形式用户总数COUNT(id) FROM student_user WHERE status 1数字卡片帖子总数COUNT(id) FROM post WHERE status 1数字卡片牵线成功数COUNT(id) FROM match_apply WHERE status 1数字卡片七日发帖趋势按DATE(created_at)分组统计折线图七日趋势统计可以把小程序端用echarts插件渲染后端只需返回日期和数量GetMapping(/api/admin/stats/post-trend) public Result postTrend(RequestParam Integer days) { ListMapString, Object list postMapper.selectPostTrend(days); return Result.success(list); }select idselectPostTrend resultTypemap SELECT DATE(created_at) AS date, COUNT(*) AS count FROM post WHERE created_at DATE_SUB(CURDATE(), INTERVAL #{days} DAY) AND status 1 GROUP BY DATE(created_at) ORDER BY date /select需要展示匹配成功率时用拒绝对比SELECT SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS success_count, SUM(CASE WHEN status 2 THEN 1 ELSE 0 END) AS reject_count, COUNT(*) AS total_count FROM match_apply统计模块不需要做得很复杂但要让评审老师看到“你确实理解了数据查询和聚合”。5. 联调与真机验证从模拟器到真机的关键步骤很多项目在开发者工具里能跑一上真机就黑屏或请求失败。原因集中在域名校验、appid 不一致、调试基础库版本不匹配三类问题。5.1 模拟器联调三步第一步确认后端能本地启动浏览器访问http://localhost:8080/api/admin/stats/overview有 JSON 返回。第二步在开发者工具“详情-本地设置”中勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。这个选项只用于本地开发正式版无法绕过域名校验。第三步在小程序端封装统一请求工具const request (url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: http://localhost:8080 url, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200 res.data.code 200) { resolve(res.data) } else if (res.statusCode 401) { wx.navigateTo({ url: /pages/profile/profile }) } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail: (err) reject(err) }) }) }这里把Authorization统一加到请求头后端通过拦截器校验 token 是否有效。如果后端返回 401前端统一跳转登录页避免每个页面单独处理登录失效。5.2 真机调试与合法域名真机调试时不能依赖“不校验合法域名”选项。微信要求所有wx.request请求地址必须是 HTTPS并且域名已经在小程序后台配置为 request 合法域名。真机请求失败常见提示是net::ERR_CONNECTION_RESET排查顺序是检查项操作结果请求地址是否 HTTPS查看 Network 面板请求 URL必须https://开头域名是否备案浏览器直接访问该域名能打开后端接口返回 JSON域名是否加入合法域名小程序后台“开发管理-开发设置-服务器域名”request 合法域名列表包含该域名证书是否有效浏览器检查锁标识证书链完整非自签名后端是否监听公网使用手机浏览器访问接口能在 4G 网络下返回 JSON本地联调阶段如果非要用局域网 IP开发者工具可以用“不校验合法域名”临时绕过但真机预览也会受限制。最稳妥的方案是租一台云服务器把后端部署到公网这样模拟器、真机、体验版行为一致。5.3 体验版与正式发布流程在开发者工具点击“上传”按钮代码会提交到微信后台然后在公众平台“版本管理”中把该版本设为体验版。体验成员需要在小程序后台“成员管理-体验成员”中手动添加添加后才能扫描体验版二维码。上传失败时先检查以下内容项目 appid 是否是小程序正式 appid而不是测试号。是否填写了用户隐私保护指引。微信后台的“设置-服务内容声明-用户隐私保护指引”需要声明收集用户信息如果未填写涉及头像、位置、相册的接口会被拦截。是否完善了小程序类目。校园交友类内容发布在小程序审核中可能要求选择“社交-社区”或“婚恋交友”类目不同类目需要的资质不同上线前要提前确认。代码包大小是否超过主包 2MB 限制。如果图片或组件过多可以开通“分包加载”把管理端、聊天页放到分包目录。6. 常见问题排查登录失败、白屏、消息不刷新6.1 小程序获取登录后的微信用户失败这个错误对应code2Session返回的非成功状态。前后端联调时最容易出现三个错误码错误码含义原因处理40029code 无效前端传的 code 已经过期或为空重新调用wx.login获取新 code40163code 已被使用同一个 code 被后端调用了两次检查登录接口是否被重复提交或重试41008缺少 code前端没有把 code 传到后端检查请求参数名是否一致排查这类问题不要盯着前端代码反复看先在后端日志里打印code2Session的完整响应确定微信服务器返回了什么。响应里出现errcode时直接对照上表处理。如果登录接口偶发失败常见原因是前端按钮没有做防重复提交用户连续点击导致两个请求使用同一个 code。登录按钮在请求期间应该禁用并使用loading状态提示。6.2 模拟器正常但真机请求失败模拟器能访问localhost:8080真机不能因为真机上的localhost指向手机自身。处理办法后端启动时监听0.0.0.0不要只监听127.0.0.1。前端请求地址改为电脑局域网 IP例如http://192.168.1.8:8080。手机和电脑连接同一个 Wi-Fi。在微信开发者工具中开启“真机调试”用手机扫码后Network 面板可以看到真机发出的请求。如果换成局域网 IP 后仍然请求失败检查电脑防火墙是否拦截了 8080 端口入站流量。Windows 系统可以在“高级安全 Windows Defender 防火墙”中添加入站规则放行对应端口。6.3 上传代码失败或体验版看不到上传代码失败通常是小程序后台配置问题。体验版二维码在当前小程序管理员微信的“小程序助手”里可以查看也可以让已经被添加为体验成员的人在“开发管理-版本管理”中找到。如果扫描二维码后显示“无法访问”检查该微信账号是否在体验成员列表中。6.4 私信消息不实时刷新前端进入聊天页后首次加载能显示历史消息但对方发来新消息一直不出现。问题通常出在轮询接口条件错误。比如后端查询时只查了conversation_id没有限定receiver_id导致消息把已读状态更新到了自己头上。查询新消息应使用SELECT * FROM chat_message WHERE conversation_id #{conversationId} AND id #{lastMessageId} AND is_read 0前端轮询时记录最后一条消息 id每次查询id lastMessageId的数据能减少重复消息带来的列表抖动。收到消息后再调用已读接口更新is_read。不要把所有消息都标记已读否则会干扰未读数统计。6.5 修改 appid 后模拟器仍显示旧的 appid使用 HBuilderX 开发 uni-app 时如果只修改了manifest.json里的 appid微信开发者工具可能仍显示旧 appid。这是因为微信开发者工具读取的是项目根目录下的project.config.json需要同步修改这个文件中的appid字段或删除该文件后重新导入项目。原生小程序项目则不存在这个问题appid 在创建项目时确定修改入口在project.config.json和公众平台后台两个地方的 appid 必须一致。7. 最佳实践与答辩扩展方向7.1 学习环境与生产环境的差异清单维度学习环境生产环境图片存储转 base64 存库对象存储服务登录态内存 MapRedis 过期时间消息推送前端轮询WebSocket 或微信订阅消息内容安全仅人工审核接入微信内容安全接口 人工复审配置管理application.yml 写死环境变量或配置中心HTTPS不校验域名合法域名 证书自动续期日志控制台输出文件日志 错误追踪平台做毕设时可以用左侧方式快速实现但答辩时应该主动说明“生产环境需要换成右侧方案”这比被动回答更有说服力。7.2 发布前检查清单上线体验版之前按下面清单逐项确认小程序 appid 与后端配置的 appid 一致。后端接口地址已从localhost改成线上 HTTPS 域名。微信公众平台已配置 request 合法域名。用户隐私保护指引已填写并提交。小程序类目和所需资质已完成。涉及微信登录的页面已验证 code 不能重复使用。管理员账号密码不能是弱口令。数据库已完成备份至少导出一次 SQL 文件保存到本地。发布内容的前端输入框已做长度限制防止超长内容写入数据库。管理端审核接口有权限校验不能使用普通用户 token 调用。7.3 后续扩展方向如果时间富余可以为这个项目增加三个实用功能。第一位置筛选。调用wx.getLocation获取用户经纬度筛选同校或附近用户。微信对位置接口有权限审核需要在小程序后台申请开通并在隐私协议中说明使用目的。第二订阅消息提醒。用户被心动或收到牵线申请时通过微信订阅消息通知对方。需要在小程序后台申请模板 ID在前端通过wx.requestSubscribeMessage弹窗请求用户授权。第三更合理的推荐算法。当前推荐可以简单实现为“标签相同优先 随机排序”后续可以改成兴趣标签权重计算为每个用户生成个性化候选列表。答辩时介绍推荐逻辑从规则到权重计算的演进技术深度会比简单列表更好。第四个可以顺手实现的是小程序之间的跳转。如果学校另有官方小程序可以在应用中配置wx.navigateToMiniProgram跳转到对方小程序。跳转前需要在app.json中声明navigateToMiniProgramAppIdList并在微信公众平台完成关联。这个功能适合演示开放平台能力但不建议作为核心功能投入过多时间。本项目最终要交付的不只是一份代码而是一条完整的业务流程。从用户进入小程序、完成微信登录、发布内容、表达心动、发起牵线、私信沟通到管理员审核和数据分析每个环节都对应一张数据库表和一组接口。把这条链路跑通、讲清楚毕业设计的核心目标就完成了。
返回列表