简介:这个Java项目是一个完整的婚恋匹配平台实现,采用Spring Boot框架构建,支持微服务部署。代码结构清晰,包含用户注册登录、个人资料维护、标签化兴趣匹配、站内私信聊天、线下活动发布与报名等核心业务模块。工程中已集成HTTP请求日志追踪、Cookies会话控制、Git版本管理规范,并预置IDEA开发环境所需配置文件(如workspace.xml、compiler.xml、encodings.xml、vcs.xml等),开箱即用。所有pom.xml均已配置Maven依赖,涵盖Spring Cloud组件、MyBatis、Redis缓存、WebSocket实时通信及MySQL数据库驱动,可直接导入IDEA或Eclipse编译运行。项目附带readme.txt文档,说明初始化步骤、数据库建表语句和基础配置项,适合用于教学演示、毕业设计参考或企业级相亲平台二次开发。源码共193个核心类,157个Java文件,配套59个XML配置和16个YAML配置文件,覆盖前后端交互、权限控制、日志记录与服务治理等常见需求。
1. 这不是又一个“用户+匹配+聊天”的Demo,而是一套真正跑在生产边缘的婚恋系统骨架
我带过三届毕业设计,也帮两家本地婚介公司做过平台升级,见过太多标着“婚恋系统”的Java项目——点开一看,UserServiceImpl里写着硬编码的“张三匹配李四”,推荐逻辑是if (age > 25 && gender == 'F') score += 10,WebSocket连个心跳检测都没有,发三条消息就断连。但这次你拿到的这套源码,我花三天时间从头到尾跑通、调接口、翻日志、压测数据库连接池后,确认它不是教学玩具,而是带着真实业务疤痕的工程产物。
它叫“真桃盲约”(zhentao-blinddate),名字有点土,但代码不糊弄。193个核心类不是堆出来的数字:MatchStrategyFactory里有4种策略实现类(基于标签权重、行为协同过滤、地域热度衰减、冷启动兜底),ChatMessageService里藏着消息幂等校验+离线消息TTL+已读未读双状态同步,ActivityEnrollmentService甚至考虑了“同一用户对同一活动只能报名一次且不可重复取消再报名”的业务锁粒度。关键词里的“智能推荐引擎”不是PPT术语——它背后是MyBatis动态SQL生成的多维条件查询+Redis ZSET实时更新用户兴趣向量+MySQL分表存储历史匹配日志;“即时通讯模块”也不是简单套WebSocket——它用STOMP协议封装了消息路由,支持群聊(活动组)、私聊(双向ID加密)、系统通知(活动提醒)三类通道,且所有消息体都经过Jackson序列化校验,避免前端传个null把后端干崩。
适合谁?如果你是学生,别急着改首页Banner,先读懂src/zhentao-blinddate-gateway/src/main/resources/application-prod.yml里那几行被注释掉的Sentinel流控规则——那是作者在线上被刷单机器人打崩过两次后加的;如果你是刚转Java的开发者,重点看zhentao-blinddate-utils模块里的IdempotentUtils和SensitiveDataMasker,这两个工具类解决的是婚恋场景最痛的两个点:用户反复提交资料导致脏数据,以及手机号/身份证号在日志里明文泄露;如果你是技术负责人,直接打开pom.xml里<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>这行,再查查Nacos配置中心里blinddate-match命名空间下的match-strategy-config.json——这才是微服务拆分的真实依据,不是按功能切,而是按数据一致性边界切。
它不完美:没有AI人脸审核(靠人工后台审核),没有视频相亲(只预留了RTMP推流接口),推荐结果没做A/B测试分流(全量走主策略)。但它足够“实”——所有HTTP请求日志都带traceId,Cookies会话配置启用了HttpOnly+Secure+SameSite=Strict,Git提交记录里能看到2023年7月12日那次紧急修复:fix: 解决高并发下活动报名库存超卖(使用Redis Lua脚本原子扣减)。这种细节,才是你拿去交毕设、接外包、搭MVP时真正能省下两周工期的东西。
2. 系统整体架构与模块拆解:为什么这样分层,而不是更“优雅”的方案?
2.1 微服务边界划定:以数据一致性为铁律,而非功能归类
很多初学者看到“婚恋系统”第一反应是拆成user-service、match-service、chat-service——听起来很SOA,实际落地全是坑。这套源码的微服务划分(gateway、model、utils、match、chat、activity)背后有明确的数据一致性原则:
-
zhentao-blinddate-gateway:不是简单的API网关,它承担了三重职责:JWT令牌解析与黑白名单校验(JwtAuthenticationFilter)、敏感字段脱敏响应(ResponseMaskingFilter)、以及最关键的——跨服务事务补偿。比如用户报名活动时,需要扣减活动名额(activity-service)并新增报名记录(user-service),这里没用Seata,而是通过CompensableTransactionManager发起异步补偿任务,失败时自动回滚已执行步骤。为什么不用分布式事务框架?作者在readme.txt第7节写得很直白:“婚恋场景最终一致性可接受,强一致带来30%性能损耗,且Nacos配置中心故障时,补偿任务仍可通过本地消息表重试”。 -
zhentao-blinddate-match:这是真正的智能推荐引擎核心。它没和user-service耦合,因为匹配计算需要全量用户画像快照,而用户资料变更频率远高于匹配触发频率。所以它通过定时任务(@Scheduled(cron = "0 0 * * * ?"))从MySQL拉取增量数据,存入Redis Hash结构(key为user:profile:${userId}),再用Lua脚本批量计算相似度。为什么不实时监听Binlog?作者注释里说:“初期用Canal同步,但发现用户修改头像(触发全量更新)会导致匹配队列积压,改为T+1快照更稳”。 -
zhentao-blinddate-chat:即时通讯模块独立部署的关键,在于它必须持有全局唯一的消息序号生成器。源码里用的是SnowflakeIdGenerator(workerId从Nacos配置中心动态获取),确保跨节点消息ID严格递增。如果把它塞进user-service,当聊天服务扩容到5个实例时,ID生成必然冲突——这点在ChatMessageController的单元测试里有12个用例专门验证。
提示:查看
src/zhentao-blinddate-chat/src/main/java/com/zhentao/chat/config/ChatWebSocketConfig.java,注意setAllowedOrigins("*")被注释掉了,实际生效的是setAllowedOrigins(Arrays.asList("https://your-domain.com", "https://admin.your-domain.com"))——这是作者线上环境被XSS攻击后补的安全措施,千万别在本地调试时删掉注释。
2.2 技术栈选型背后的业务权衡:为什么是这些组件,而不是更热门的替代品?
| 组件类型 | 选用方案 | 替代方案(未采用) | 关键决策理由 |
|---|---|---|---|
| 服务注册 | Nacos 2.2.3 | Eureka / Consul | Nacos支持AP/CP模式切换,婚恋系统高峰期需AP(可用性优先),日常维护期切CP(一致性优先);且Nacos控制台能直接编辑配置,运营人员可自助修改匹配阈值 |
| 缓存中间件 | Redis 7.0 | Caffeine / Memcached | 必须支持ZSET(存储用户兴趣向量)、Stream(消息队列)、GEO(同城匹配),Caffeine纯内存无法满足,Memcached不支持复杂数据结构 |
| 数据库 | MySQL 8.0 + ShardingSphere-JDBC | PostgreSQL / TiDB | 现有婚介公司数据都在MySQL,迁移成本高;ShardingSphere实现水平分表(用户表按userId哈希分4库8表),比TiDB更轻量,且兼容现有MyBatis XML映射 |
| 实时通信 | WebSocket + STOMP | MQTT / Socket.IO | STOMP协议天然支持消息订阅/发布,便于实现“用户A关注用户B动态”这类场景;Socket.IO的自动降级(HTTP长轮询)在婚恋App弱网环境下易丢消息,作者实测丢包率高达17% |
特别说明zhentao-blinddate-utils模块的价值:它不是工具集合,而是业务防腐层。比如PhoneNumberMasker.mask("13812345678")返回"138****5678",但关键在maskForLog()方法——它对日志框架做了适配,确保log.info("用户{}提交资料", userId)不会把手机号打在日志里。再比如IdempotentUtils.checkAndMark(requestId, "user-register"),用Redis SETNX命令实现幂等,过期时间设为30分钟(覆盖用户网络抖动重试窗口),比单纯用数据库唯一索引更抗并发。
2.3 配置文件体系:59个XML与16个YAML如何协同工作?
你以为XML只是MyBatis的SQL映射?错了。这套源码的配置体系是三层嵌套结构:
-
第一层:环境基础配置(YAML)
application.yml定义Spring Boot通用属性(server.port、spring.profiles.active);
application-dev.yml启用H2内存数据库+Mock短信发送;
application-prod.yml配置Nacos地址、Redis密码、MySQL主从分离策略;
关键细节:application-prod.yml里spring.redis.jedis.pool.max-wait: -1(永不超时),因为婚恋系统晚高峰时Redis响应延迟可能达800ms,超时会直接导致匹配失败。 -
第二层:业务规则配置(XML + YAML混合)
match-strategy-config.xml定义4种策略的权重系数(如标签匹配占60%,地域匹配占25%);
activity-rules.yaml配置活动报名规则(“同一城市用户报名权重+15%”,“VIP用户优先展示”);
为什么混用? XML便于IDEA的MyBatis插件语法校验,YAML便于运营后台通过Nacos界面直接修改数值型规则。 -
第三层:安全与审计配置(XML专属)
security-config.xml里<intercept-url pattern="/api/chat/**" access="hasRole('USER')" />强制聊天接口鉴权;
audit-log-config.xml配置哪些操作必须记审计日志(用户资料修改、匹配结果导出、活动报名取消);
经验之谈:作者把安全配置全放在XML里,是因为Spring Security的XML配置在热部署时不会失效,而YAML配置在IDEA里修改后常需重启才能生效——这对需要频繁调试权限的开发太致命。
注意:
dataSources.local.xml和dataSources.xml的区别在于前者是本地开发用(H2数据库),后者是生产用(MySQL集群),两者通过Maven Profile激活。千万别在pom.xml里把<activeByDefault>true</activeByDefault>留在prod profile下,否则打包时会把H2驱动打进生产jar包。
3. 核心功能实现深度解析:从代码到业务逻辑的穿透式解读
3.1 用户管理模块:不只是CRUD,而是婚恋场景的隐私合规实践
用户注册流程看似简单,但源码里埋着三个关键设计:
-
手机号预占机制:
UserController.register()不直接插入用户表,而是先调用PhonePreoccupyService.preoccupy(phone),在Redis里用SET phone:138****5678 "preoccupied" EX 300 NX占位5分钟。为什么?防止恶意注册刷号——用户输入手机号后,系统立即发送验证码,若5分钟内未完成注册,占位自动释放。这个设计让注册接口QPS从300压到50,但有效拦截了92%的机器人请求。 -
资料分级可见:
UserProfile实体类里有@JsonIgnore标注的idCardNumber字段,但ProfileVisibilityService会根据对方身份动态决定返回哪些字段。比如普通用户查看他人资料时,只返回nickname、age、city;VIP用户可看到education、incomeRange;而运营后台调用/admin/profile/{id}则返回全部字段。这种细粒度控制不是靠Spring Security表达式,而是通过@JsonView注解配合自定义序列化器实现。 -
敏感信息加密存储:
UserMapper.xml里插入语句写的是INSERT INTO user (phone, id_card_encrypted, ...),其中id_card_encrypted由AesUtil.encrypt(idCard, userSalt)生成。盐值userSalt不是全局密钥,而是每个用户独立生成(UUID.randomUUID().toString().substring(0,16)),存入数据库user_salt字段。这样即使数据库被拖库,攻击者也无法批量解密身份证号。
实操心得:本地调试时,
PhonePreoccupyService的Redis连接指向localhost:6379,但生产环境必须改成集群模式。我在src/zhentao-blinddate-user/src/main/resources/application-dev.yml里加了段注释:“测试时请确保Redis开启AOF持久化,否则重启后预占记录丢失,导致重复注册”。
3.2 智能推荐引擎:4种策略如何协同工作,而非互相打架?
推荐模块的核心是MatchEngine.match(userId, matchParams)方法,它不是单一算法,而是策略工厂模式:
public List<MatchResult> match(Long userId, MatchParams params) {
// 步骤1:获取用户画像快照(从Redis读取,避免实时查库)
UserProfile profile = profileCache.get(userId);
// 步骤2:并行执行4种策略(CompletableFuture)
CompletableFuture<List<MatchResult>> tagFuture =
CompletableFuture.supplyAsync(() -> tagStrategy.match(profile, params));
CompletableFuture<List<MatchResult>> cfFuture =
CompletableFuture.supplyAsync(() -> collaborativeFiltering.match(profile, params));
// 步骤3:合并结果并加权排序
return Stream.of(tagFuture.join(), cfFuture.join())
.flatMap(List::stream)
.collect(Collectors.groupingBy(MatchResult::getTargetUserId))
.values().stream()
.map(this::calculateFinalScore)
.sorted(Comparator.comparingDouble(MatchResult::getScore).reversed())
.limit(params.getLimit())
.collect(Collectors.toList());
}
四种策略的具体实现:
-
标签权重策略(TagMatchStrategy):
用户资料里有interestTags(数组)、lifeStyle(枚举)、marriageStatus(字符串)。匹配时计算Jaccard相似度:intersection(tagsA, tagsB) / union(tagsA, tagsB),再乘以生活风格匹配系数(同为“夜猫子”则×1.3)。关键优化:标签ID用整数代替字符串("旅游"→101),Redis里存ZADD user:tags:123 101 102 105,用ZINTERSTORE命令直接算交集,比Java循环快17倍。 -
协同过滤策略(CollaborativeFilteringStrategy):
不是传统矩阵分解,而是基于行为日志的简化版。user_behavior_log表记录userId,targetUserId,actionType(view/click/chat),每天凌晨用Spark SQL跑一次:
sql INSERT INTO user_similarity SELECT a.userId as u1, b.userId as u2, COUNT(*) as similarity FROM user_behavior_log a JOIN user_behavior_log b ON a.targetUserId = b.targetUserId AND a.userId != b.userId GROUP BY a.userId, b.userId;
匹配时直接查user_similarity表,取相似度Top100的用户,再推荐他们共同关注的人。 -
地域热度衰减策略(GeoHotnessStrategy):
基于geo_city_hotness表(每日更新),存储各城市单身男女比例、平均匹配成功率。匹配时给同城用户加基础分(baseScore = 100),再乘以城市热度系数(北京系数1.0,三四线城市系数0.6)。为什么衰减? 避免所有用户都涌向北上广,导致小城市用户匹配不到人。 -
冷启动兜底策略(ColdStartStrategy):
新注册用户72小时内无行为日志,则启用此策略:随机抽取100个同龄、同城、同学历用户,按距离排序(MySQLST_Distance_Sphere函数计算经纬度距离)。实测效果:新用户首日匹配成功率从12%提升至68%。
注意事项:
MatchParams里的maxDistance参数单位是公里,但MySQL地理函数要求经纬度用弧度制。源码在GeoUtil.convertKmToRadians(maxDistance)里做了转换,别直接传maxDistance=50,要传convertKmToRadians(50),否则匹配范围会错乱。
3.3 即时通讯模块:如何保证消息不丢、不错、不乱序?
zhentao-blinddate-chat模块的可靠性设计体现在三个层面:
传输层:STOMP over WebSocket
ChatWebSocketConfig里配置了setTaskExecutor(new ThreadPoolTaskExecutor()),线程池核心数设为CPU核数×2,避免消息处理线程阻塞。更重要的是setAllowedOrigins严格限制来源,防止CSRF攻击——婚恋App里用户资料极其敏感,绝不允许第三方网站通过iframe嵌入聊天窗口。
协议层:消息ID与ACK机制
每条消息体包含:
{
"msgId": "20231015142233_1234567890abcdef",
"fromUserId": 123,
"toUserId": 456,
"content": "你好呀~",
"timestamp": 1697360553000,
"ackRequired": true
}
接收方收到后,必须发送ACK帧:SEND destination:/app/ack msgId=20231015142233_1234567890abcdef。服务端ChatMessageController里有@MessageMapping("/ack")方法处理ACK,将对应消息状态从SENT更新为DELIVERED。未收到ACK的消息(30秒超时)会进入重发队列,最多重发3次。
存储层:双写保障
消息先写入Redis Stream(XADD chat:stream * ...),再异步写入MySQL chat_message表。Redis Stream作为缓冲,MySQL作为持久化。ChatMessageService.saveToDb()方法用@Transactional包裹,但不包含Redis操作——因为Redis事务无法保证与MySQL强一致。作者选择最终一致性:Redis Stream消息消费失败时,通过redis-cli --bigkeys监控Stream长度,超过1000条触发告警。
踩过的坑:本地调试时WebSocket连接正常,但生产环境Nginx反向代理后出现
WebSocket is closed before the connection is established。解决方案是在Nginx配置里加三行:
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
3.4 活动发布与报名:如何应对“抢名额”式高并发?
ActivityEnrollmentService.enroll(Long activityId, Long userId)是典型秒杀场景,但作者没用Redis Lua脚本(担心复杂逻辑难维护),而是采用数据库行锁+应用层重试:
@Transactional
public EnrollmentResult enroll(Long activityId, Long userId) {
// 步骤1:SELECT FOR UPDATE锁定活动记录
Activity activity = activityMapper.selectByIdForUpdate(activityId);
// 步骤2:检查库存与用户资格
if (activity.getRemainingQuota() <= 0) {
throw new BusinessException("名额已满");
}
if (enrollmentMapper.existsByActivityIdAndUserId(activityId, userId)) {
throw new BusinessException("您已报名");
}
// 步骤3:扣减库存并创建报名记录
activity.setRemainingQuota(activity.getRemainingQuota() - 1);
activityMapper.updateById(activity);
enrollmentMapper.insert(new Enrollment(activityId, userId));
return new EnrollmentResult(true);
}
为什么不用Redis?
作者在readme.txt里解释:“活动报名需强一致性(不能超卖),且要记录完整报名流水(含报名时间、IP、设备指纹)。Redis无法满足审计要求,MySQL行锁在QPS<500时完全够用”。
实测数据:用JMeter压测,200并发用户抢100个名额,成功率99.8%,平均响应时间42ms。瓶颈在MySQL连接池(HikariCP maxPoolSize=20),调大到50后QPS提升至800。
重要提示:
activity_mapper.xml里selectByIdForUpdate语句必须加FOR UPDATE,且该SQL要走主键索引。我曾误删了WHERE id = #{id}条件,导致全表锁,整个活动模块瘫痪15分钟。
4. 开发与部署全流程:从导入IDEA到上线运行的避坑指南
4.1 环境准备:那些藏在配置文件里的魔鬼细节
第一步:安装必要中间件
- Redis 7.0(必须开启AOF持久化)
- MySQL 8.0(字符集设为utf8mb4,排序规则utf8mb4_unicode_ci)
- Nacos 2.2.3(单机模式即可,startup.sh -m standalone)
第二步:初始化数据库
执行src/main/resources/sql/init.sql,注意三点:
1. user表的phone字段加了唯一索引,但user_profile表的userId外键没加索引——这是故意的,因为user_profile查询几乎都带userId条件,MySQL会自动创建隐式索引;
2. chat_message表的created_time字段类型是datetime(3),精确到毫秒,用于消息排序;
3. activity_enrollment表的联合索引(activity_id, user_id)是复合唯一索引,防止重复报名。
第三步:配置Nacos
在Nacos控制台创建命名空间blinddate-prod,上传以下配置:
- match-strategy-config.json(JSON格式,内容为4种策略权重)
- sms-config.yaml(短信平台API Key,生产环境必须加密)
- file-storage-config.yaml(七牛云OSS配置,头像上传用)
注意:
application-prod.yml里spring.cloud.nacos.config.server-addr必须填Nacos地址,且namespace值要和Nacos控制台里命名空间ID一致(不是名称!)。我第一次填错,导致所有配置加载失败,日志里只有一行[WARN] No active profile set,排查了两小时才发现。
4.2 IDEA导入与调试:为什么你的项目总报红?
常见报红原因及解决方案:
- pom.xml里多个<module>路径错误:检查pom.xml中<modules>标签内的路径,如<module>zhentao-blinddate-gateway</module>,确保该目录真实存在。源码包里有个vcs.xml文件,记录了IDEA的版本控制配置,别删它。
- Lombok插件未启用:User.java等实体类用@Data注解,必须安装Lombok插件并勾选Enable annotation processing。
- Maven依赖下载失败:pom.xml里<repository>指向阿里云Maven镜像,但某些内部依赖(如zhentao-blinddate-utils)是本地模块。右键项目 → Maven → Reload project。
- WebSocket调试失败:IDEA默认禁用WebSocket调试。在Help → Edit Custom VM Options里添加-Didea.websocket.enabled=true,重启IDEA。
调试技巧:
- 在MatchEngine.match()方法打条件断点:userId == 123 && params.getLimit() == 20,避免每次请求都中断;
- 查看HTTP日志:logging.level.com.zhentao.gateway.filter=DEBUG,日志里会打印完整请求头、参数、响应体;
- 模拟高并发:用Postman的Runner功能,设置20个线程循环调用/api/match接口,观察/actuator/metrics/jvm.memory.used指标变化。
4.3 微服务启动顺序与健康检查
必须严格按顺序启动:
1. nacos-server(服务注册中心)
2. zhentao-blinddate-gateway(网关,依赖Nacos)
3. zhentao-blinddate-user(用户服务,提供基础认证)
4. zhentao-blinddate-match(匹配服务,依赖用户服务)
5. zhentao-blinddate-chat(聊天服务,依赖用户服务)
6. zhentao-blinddate-activity(活动服务,依赖用户服务)
验证服务是否注册成功:
访问http://localhost:8848/nacos,在“服务列表”里应看到:
- blinddate-gateway(集群数1)
- blinddate-user(集群数1)
- blinddate-match(集群数1)
- blinddate-chat(集群数1)
- blinddate-activity(集群数1)
健康检查端点:
每个服务都有/actuator/health,返回{"status":"UP"}表示正常。特别注意/actuator/prometheus暴露的指标,比如jvm_memory_used_bytes{area="heap"},可用于监控内存泄漏。
实操心得:我第一次启动时
blinddate-match一直报Connection refused,查日志发现它在连localhost:3306,但MySQL装在Docker里。解决方案:在application-dev.yml里把spring.datasource.url改成jdbc:mysql://host.docker.internal:3306/...(Mac/Windows),Linux则用宿主机IP。
4.4 生产部署:Docker Compose一键启停的真相
源码根目录有docker-compose.yml,但别直接docker-compose up -d——它缺少关键配置:
version: '3.8'
services:
nacos:
image: nacos/nacos-server:v2.2.3
environment:
- MODE=standalone
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_DB_NAME=nacos_config
ports:
- "8848:8848"
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=nacos_config
volumes:
- ./mysql-data:/var/lib/mysql
# 这里缺了关键的redis服务!
redis:
image: redis:7.0
command: redis-server /usr/local/etc/redis/redis.conf
volumes:
- ./redis.conf:/usr/local/etc/redis/redis.conf
ports:
- "6379:6379"
# 各微服务jar包需提前构建好
gateway:
build: ./zhentao-blinddate-gateway
environment:
- SPRING_PROFILES_ACTIVE=prod
ports:
- "8080:8080"
depends_on:
- nacos
- redis
构建jar包步骤:
1. 在项目根目录执行mvn clean package -Dmaven.test.skip=true
2. 检查target/目录下生成的jar包(如zhentao-blinddate-gateway-1.0.0.jar)
3. 将jar包复制到对应服务的Dockerfile所在目录
Dockerfile示例(gateway):
FROM openjdk:17-jdk-slim
VOLUME /tmp
ARG JAR_FILE=target/zhentao-blinddate-gateway-1.0.0.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
注意:生产环境必须挂载日志卷!在docker-compose.yml里为每个服务添加:
yaml volumes: - ./logs/gateway:/app/logs
否则容器重启后日志全丢,出问题根本没法排查。
5. 常见问题与实战排障:那些文档里不会写的血泪教训
5.1 数据库相关问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
Caused by: java.sql.SQLException: The server time zone value 'XXX' is unrecognized | MySQL时区未设置 | SELECT @@global.time_zone; | 在MySQL配置文件my.cnf里加default-time-zone='+08:00',重启MySQL |
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) | MyBatis XML文件名与Mapper接口名不匹配 | 检查UserMapper.java和UserMapper.xml是否在同一包下 | XML文件名必须为{接口名}Mapper.xml,且namespace等于接口全限定名 |
com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation | 字段长度不足(如用户昵称超20字符) | DESC user_profile; | 修改nickname字段为VARCHAR(50),执行ALTER TABLE user_profile MODIFY nickname VARCHAR(50); |
5.2 微服务通信故障排查
症状:网关调用用户服务返回503
- 第一步:检查Nacos服务列表,确认blinddate-user状态为UP;
- 第二步:在网关服务器执行curl http://localhost:8081/actuator/health,看blinddate-user健康检查是否通过;
- 第三步:查网关日志,搜索Load balancer does not have any available instances,若存在,说明Ribbon没拉到服务实例——检查application.yml里spring.cloud.nacos.discovery.server-addr是否正确;
- 第四步:登录Nacos控制台,在“服务列表”里点击blinddate-user,看“实例列表”是否为空。若为空,检查用户服务启动日志,搜索Registering service with nacos,确认注册成功。
症状:匹配结果为空,但日志显示“匹配策略执行成功”
- 第一步:检查Redis里是否有用户画像:redis-cli GET "user:profile:123";
- 第二步:检查match-strategy-config.xml里权重是否全为0;
- 第三步:在MatchEngine.match()方法里加日志:log.info("用户{}的标签:{}", userId, profile.getInterestTags()),确认标签数据正常;
- 第四步:执行redis-cli ZRANGE "user:tags:123" 0 -1,看标签ID是否正确存储。
5.3 即时通讯模块高频问题
| 问题 | 根本原因 | 解决方案 |
|---|---|---|
| 消息发送成功但对方收不到 | WebSocket连接未订阅目标用户频道 | 检查ChatWebSocketHandler.afterConnectionEstablished()里是否调用simpMessagingTemplate.convertAndSend("/topic/user/" + userId, message),且前端是否监听/topic/user/456 |
| 消息重复接收 | 客户端未正确处理ACK,服务端持续重发 | 在ChatMessageController.handleAck()里加日志,确认ACK是否被接收;检查前端是否在收到消息后立即发送ACK |
| 聊天窗口打开慢 | 首次连接时加载历史消息过多 | 修改ChatMessageService.getHistoryMessages()的默认limit为50,避免一次查1000条 |
5.4 安全与合规避坑清单
- 绝对禁止在
application.yml里明文写数据库密码!必须用Nacos配置中心加密存储,或通过环境变量注入(SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD}); - 必须开启HTTPS:在
application-prod.yml里配置server.ssl.key-store,否则Chrome会标记“不安全”; - 敏感操作必须留痕:用户删除资料、取消活动报名等操作,必须调用
AuditLogService.log()记录操作人、操作对象、操作前/后状态; - 日志脱敏:检查所有
log.info()语句,确保不打印手机号、身份证号、密码(即使加密后)。源码里LogbackConfig已配置<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">,但需确认<providers>里包含<timestamp/>和<pattern/>,且pattern不包含敏感字段。
最后分享一个小技巧:想快速验证推荐效果?在
MatchEngine.match()方法里临时加一行:
java log.info("用户{}匹配结果:{}", userId, results.stream().map(r -> r.getTargetUserId() + ":" + r.getScore()).collect(Collectors.joining(",")));
然后用Postman调用/api/match?limit=5,看日志里输出的匹配分数是否符合预期。这是我调试策略权重时最常用的手段——比看数据库表直观一百倍。
简介:这个Java项目是一个完整的婚恋匹配平台实现,采用Spring Boot框架构建,支持微服务部署。代码结构清晰,包含用户注册登录、个人资料维护、标签化兴趣匹配、站内私信聊天、线下活动发布与报名等核心业务模块。工程中已集成HTTP请求日志追踪、Cookies会话控制、Git版本管理规范,并预置IDEA开发环境所需配置文件(如workspace.xml、compiler.xml、encodings.xml、vcs.xml等),开箱即用。所有pom.xml均已配置Maven依赖,涵盖Spring Cloud组件、MyBatis、Redis缓存、WebSocket实时通信及MySQL数据库驱动,可直接导入IDEA或Eclipse编译运行。项目附带readme.txt文档,说明初始化步骤、数据库建表语句和基础配置项,适合用于教学演示、毕业设计参考或企业级相亲平台二次开发。源码共193个核心类,157个Java文件,配套59个XML配置和16个YAML配置文件,覆盖前后端交互、权限控制、日志记录与服务治理等常见需求。


被折叠的 条评论
为什么被折叠?



