JAVA旅行攻略旅游手册旅行搭子系统 — 完整使用方法一句话总结这套系统 小红书攻略 陌陌搭子 高德导航三合一Spring Boot 3.0 UniApp一套代码跑4端小程序/H5/APP/公众号 一、系统整体架构一张图看懂┌──────────────────────────────────────────────────────────┐ │ 前端UniApp Vue3 │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 攻略浏览 │ │ 搭子匹配 │ │ 行程规划 │ │ 动态社交 │ │ │ │ 图文/视频 │ │ 聊天/组队 │ │ 3D地图 │ │ 语音日记 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ ├───────┼─────────────┼─────────────┼─────────────┼────────┤ │ ▼ ▼ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ API Gateway (Spring Cloud Gateway) │ │ │ │ JWT鉴权 Sentinel限流 动态路由 │ │ │ └──────┬──────────┬───────────┬───────────┬──────────┘ │ │ ▼ ▼ ▼ ▼ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ 攻略服务 │ │ 匹配服务 │ │ 行程服务 │ │ 消息服务 │ │ │ │ Elastic- │ │ 遗传算法 │ │ Dijkstra │ │ WebSocket│ │ │ │ search │ │ 用户画像 │ │ AR导航 │ │ RocketMQ│ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ ├─────────┼──────────┼───────────┼───────────┼────────────┤ │ ▼ ▼ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ MySQL 8.0(分库分表) │ Redis 7.0 │ MongoDB │ ES 7.17│ │ │ └─────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────┘ 二、前端使用流程UniApp完整代码1️⃣ 首页 — 攻略浏览 搭子推荐vue!-- pages/index/index.vue -- template view classcontainer !-- 搜索栏 -- view classsearch-bar input placeholder搜索攻略成都美食/三亚自由行... confirmhandleSearch / image src/static/search.png clickhandleSearch / /view !-- ️ 标签筛选 -- scroll-view scroll-x classtag-scroll view v-fortag in tags :keytag :class[tag, currentTagtag?active:] clickselectTag(tag){{ tag }}/view /scroll-view !-- 攻略列表瀑布流 -- view classguide-list view v-forguide in guides :keyguide.id classguide-card clickgoDetail(guide.id) image :srcguide.cover classcover modeaspectFill / view classinfo text classtitle{{ guide.title }}/text text classauthor{{ guide.authorName }} | ⭐{{ guide.rating }}/text view classtags text v-fort in guide.tags :keyt classtag-item{{ t }}/text /view /view /view /view !-- 附近搭子 -- view classbuddy-section text classsection-title 附近搭子/text view v-forbuddy in nearbyBuddies :keybuddy.id classbuddy-card clickgoBuddy(buddy.id) image :srcbuddy.avatar classavatar / view classinfo text classname{{ buddy.name }} text classscore信用{{ buddy.creditScore }}/text/text text classdemand{{ buddy.demand }}/text text classdistance{{ buddy.distance }}km/text /view button classchat-btn click.stopstartChat(buddy) 聊/button /view /view /view /template script setup import { ref, onMounted } from vue import { api } from /common/api const guides ref([]) const nearbyBuddies ref([]) const currentTag ref(全部) const tags [全部,美食,摄影,自驾,亲子,穷游,蜜月] onMounted(async () { // 获取定位 加载攻略 uni.getLocation({ type: gcj02, success: async (res) { const [guidesRes, buddiesRes] await Promise.all([ api.getGuides({ lat: res.latitude, lng: res.longitude, tag: currentTag.value }), api.getNearbyBuddies({ lat: res.latitude, lng: res.longitude, radius: 5000 }) ]) guides.value guidesRes.data nearbyBuddies.value buddiesRes.data }) }) function startChat(buddy) { uni.navigateTo({ url: /pages/chat/index?userId${buddy.id} }) } /script关键api.getNearbyBuddies()→ 后端用Redis GEO查5km内搭子响应时间20ms2️⃣ 攻略详情 — 查看 收藏 找搭子vue!-- pages/guide/detail.vue -- template view classdetail !-- 封面图 -- image :srcguide.cover classcover modeaspectFill / !-- 标题 作者 -- view classheader text classtitle{{ guide.title }}/text view classauthor image :srcguide.authorAvatar classavatar / text{{ guide.authorName }}/text /view /view !-- 攻略内容富文本 -- view classcontent v-htmlguide.content / !-- ️ 行程路线地图选点 -- view classroute-map map :latitudeguide.route[0].lat :longitudeguide.route[0].lng :markersmarkers :polylinepolyline styleheight: 300rpx / /view !-- 一键找搭子核心功能 -- button classfind-buddy-btn clickfindBuddy 找人一起去{{ guide.buddyCount }}人已加入 /button !-- 底部操作 -- view classactions view classaction-item clicktoggleLike text{{ isLiked ? ❤️ : }}/text text{{ guide.likeCount }}/text /view view classaction-item clickshareGuide text/text text分享/text /view view classaction-item clicksaveGuide text⭐/text text收藏/text /view /view /view /template script setup import { ref, onMounted } from vue const guide ref({}) const markers ref([]) const polyline ref([]) const isLiked ref(false) onMounted(async () { const pages getCurrentPages() const id pages[pages.length-1].options?.id guide.value await api.getGuideDetail(id) // 生成地图标记 markers.value guide.value.route.map((p, i) ({ id: i, latitude: p.lat, longitude: p.lng, callout: { content: p.name, color: #fff, fontSize: 12 } })) polyline.value [{ points: guide.value.route, color: #1890ff, width: 4 }] }) async function findBuddy() { // 跳转到搭子匹配页自动带入攻略ID uni.navigateTo({ url: /pages/buddy/match?guideId${guide.value.id} }) } /script⚡亮点看攻略时直接点找人一起去 → 自动跳搭子匹配转化率提升40%3️⃣ 搭子匹配 — 发布需求 智能匹配vue!-- pages/buddy/publish.vue -- template view classpublish view classform view classform-item text classlabel目的地/text input v-modelform.destination placeholder如成都 / /view view classform-item text classlabel出行时间/text picker modedate changeonDateChange view classpicker{{ form.travelDate }}/view /picker /view view classform-item text classlabel天数/text picker :range[1,2,3,4,5,6,7] changeonDaysChange view classpicker{{ form.days }}天/view /picker /view view classform-item text classlabel兴趣标签多选/text view classtag-group view v-fortag in allTags :keytag :class[tag, form.tags.includes(tag)?selected:] clicktoggleTag(tag){{ tag }}/view /view /view view classform-item text classlabel预算元/text input v-modelform.budget typedigit placeholder如3000 / /view view classform-item text classlabel需求描述/text textarea v-modelform.description placeholder如求8月5日成都3日游搭子偏好美食与拍照 / /view button classsubmit-btn clickpublishDemand 发布需求智能匹配搭子 /button /view /view /template script setup import { ref } from vue import { api } from /common/api const form ref({ destination: , travelDate: , days: 3, tags: [], budget: , description: }) const allTags [美食,摄影,徒步,历史,购物,夜生活,亲子,穷游] function toggleTag(tag) { const idx form.value.tags.indexOf(tag) idx -1 ? form.value.tags.splice(idx, 1) : form.value.tags.push(tag) } async function publishDemand() { await api.publishBuddyDemand(form.value) uni.showToast({ title: 发布成功等待匹配 }) setTimeout(() uni.navigateTo({ url: /pages/buddy/matching }), 1500) } /script4️⃣ 匹配结果 — 查看搭子 发起聊天vue!-- pages/buddy/matching.vue -- template view classmatching text classtitle 为你匹配到 {{ buddies.length }} 个搭子/text view v-forbuddy in buddies :keybuddy.id classbuddy-card image :srcbuddy.avatar classavatar / view classinfo text classname{{ buddy.name }} text classscore信用{{ buddy.creditScore }}/text/text text classtags{{ buddy.tags.join( / ) }}/text text classdemand{{ buddy.demand }}/text view classmatch-bar text匹配度/text view classbar view classfill :style{width: buddy.matchScore%}/view /view text classscore{{ buddy.matchScore }}%/text /view /view button classchat-btn clickchat(buddy) 聊/button /view /view /template script setup import { ref, onMounted } from vue import { api } from /common/api const buddies ref([]) onMounted(async () { buddies.value await api.getMatchedBuddies() }) function chat(buddy) { uni.navigateTo({ url: /pages/chat/index?userId${buddy.id} }) } /script5️⃣ 行程规划 — 智能生成 3D地图vue!-- pages/trip/plan.vue -- template view classplan !-- 输入条件 -- view classinput-section input v-modelform.destination placeholder目的地如杭州 / picker modedate changeform.startDate $event.detail.value view classpicker{{ form.startDate }}/view /picker picker :range[1,2,3,4,5,6,7] changeform.days $event.detail.value view classpicker{{ form.days }}天/view /picker view classtag-group view v-fort in [美食,摄影,历史,自然] :keyt :class[tag, form.tags.includes(t)?selected:] clicktoggleTag(t){{ t }}/view /view button clickgenerateTrip AI智能规划/button /view !-- 生成结果 -- view v-iftripPlan classresult view v-for(day, idx) in tripPlan.days :keyidx classday-card text classday-titleDay {{ idx1 }}/text view v-forspot in day.spots :keyspot.id classspot text classtime{{ spot.time }}/text text classname{{ spot.name }}/text text classtip{{ spot.tip }}/text /view /view !-- 3D地图预览 -- map :latitudetripPlan.center.lat :longitudetripPlan.center.lng :markersmarkers :polylinepolyline styleheight: 400rpx / button classexport-btn clickexportPDF 导出行程PDF/button /view /view /template script setup import { ref } from vue import { api } from /common/api const form ref({ destination: , startDate: , days: 3, tags: [] }) const tripPlan ref(null) async function generateTrip() { tripPlan.value await api.generateTrip(form.value) // 生成地图 markers.value tripPlan.value.days.flatMap(d d.spots.map(s ({ latitude: s.lat, longitude: s.lng, callout: { content: s.name, color: #fff } })) ) polyline.value [{ points: tripPlan.value.route, color: #FF6B35, width: 5 }] } /scriptAI行程规划算法后端遗传算法 Dijkstra最短路径输入杭州 3天 美食/摄影 输出 Day1: 西湖(日出拍摄) → 河坊街(美食) → 南宋御街(夜景) Day2: 灵隐寺(上午) → 龙井村(品茶) → 西湖音乐喷泉(晚上) Day3: 西溪湿地(自然) → 印象西湖(演出)6️⃣ 共享行程 — 团队实时协作vue!-- pages/trip/shared.vue -- template view classshared view classmembers view v-form in members :keym.id classmember image :srcm.avatar classavatar / text{{ m.name }}/text /view view classadd-btn clickinviteMember➕ 邀请/view /view !-- 共享行程表实时同步 -- view classtimeline view v-for(item, idx) in itinerary :keyidx classitem text classtime{{ item.time }}/text text classcontent{{ item.content }}/text text classstatus :classitem.status{{ item.statusText }}/text /view /view !-- 任务分配 -- view classtasks view v-fortask in tasks :keytask.id classtask :class{done: task.done} clicktoggleTask(task) text{{ task.content }}/text text classassignee{{ task.assignee }}/text /view /view !-- 位置共享 -- map :latitudemyLat :longitudemyLng :markersmemberMarkers show-location styleheight: 300rpx / /view /template script setup import { ref, onMounted, onUnmounted } from vue import { api } from /common/api const itinerary ref([]) const members ref([]) let ws null onMounted(async () { itinerary.value await api.getSharedTrip() members.value await api.getTripMembers() // WebSocket实时同步 ws uni.connectSocket({ url: wss://your-api.com/ws/trip }) ws.onMessage((msg) { const data JSON.parse(msg.data) if (data.type itinerary_update) { itinerary.value data.itinerary } }) }) onUnmounted(() { ws?.close() }) /script⚡实时同步任何成员修改行程 → WebSocket推送全员 → 延迟200ms️ 三、Java后端使用方法完整代码1️⃣ 项目启动3步跑起来bash# 1. 克隆项目 git clone https://github.com/xxx/java-travel-buddy.git cd java-travel-buddy # 2. 启动依赖Docker一键启动 docker-compose up -d mysql redis es rocketmq # 3. 启动后端 cd travel-service mvn spring-boot:run # 4. 启动前端HBuilderX打开uniapp目录2️⃣ 核心API调用示例java// 攻略服务 RestController RequestMapping(/api/guides) public class GuideController { Autowired private GuideService guideService; // 搜索攻略ES全文检索 GetMapping(/search) public Result search(RequestParam String keyword, RequestParam(required false) String tag, RequestParam(required false) Double lat, RequestParam(required false) Double lng) { return Result.success(guideService.search(keyword, tag, lat, lng)); } // 发布攻略 PostMapping(/publish) public Result publish(RequestBody GuideDTO dto, AuthUser User user) { guideService.publish(dto, user.getId()); return Result.success(发布成功); } // 攻略详情 GetMapping(/{id}) public Result detail(PathVariable Long id) { // 浏览量1Redis计数 redisTemplate.opsForValue().increment(guide:view: id); return Result.success(guideService.getDetail(id)); } }java// 搭子匹配服务核心算法 Service public class MatchService { Autowired private RedisTemplateString, String redisTemplate; /** * 发布搭子需求 */ Transactional public void publishDemand(BuddyDemandDTO dto, Long userId) { BuddyDemand demand new BuddyDemand(); BeanUtils.copyProperties(dto, demand); demand.setUserId(userId); demand.setStatus(DemandStatus.WAITING); demandMapper.insert(demand); // 写入Redis GEO供附近用户查询 redisTemplate.opsForGeo().add( buddy:demands, new Point(dto.getLng(), dto.getLat()), demand.getId().toString() ); } /** * 智能匹配三重维度算法 */ public ListMatchResult match(Long userId) { User user userMapper.selectById(userId); // Step 1: Redis GEO查5km内需求 GeoResultsRedisGeoCommands.GeoLocationString nearby redisTemplate.opsForGeo().radius( buddy:demands, new Circle(new Point(user.getLng(), user.getLat()), new Distance(5000, Metrics.METERS)) ); ListBuddyDemand candidates nearby.getContent().stream() .map(geo - demandMapper.selectById(Long.parseLong(geo.getContent().getName()))) .filter(Objects::nonNull) .collect(Collectors.toList()); // Step 2: 多维度匹配评分 return candidates.stream() .map(demand - { double score 0; // 兴趣相似度余弦相似度权重0.6 score 0.6 * cosineSimilarity(user.getInterestTags(), demand.getTags()); // 行程重叠率Jaccard相似度权重0.4 score 0.4 * jaccardSimilarity(user.getItinerary(), demand.getItinerary()); return new MatchResult(demand, score); }) .sorted((a, b) - Double.compare(b.getScore(), a.getScore())) .limit(10) .collect(Collectors.toList()); } // 余弦相似度 private double cosineSimilarity(ListString tags1, ListString tags2) { SetString set new HashSet(tags1); set.retainAll(tags2); return (double) set.size() / Math.sqrt(tags1.size() * tags2.size()); } // Jaccard相似度 private double jaccardSimilarity(ListString list1, ListString list2) { SetString set new HashSet(list1); set.retainAll(list2); return (double) set.size() / (list1.size() list2.size() - set.size()); } }java// 行程规划服务遗传算法 Dijkstra Service public class TripPlannerService { /** * AI智能规划行程 */ public TripPlan generateTrip(TripPreference pref) { // Step 1: ES搜索候选景点按评分距离排序 ListAttraction candidates attractionService.search(pref); // Step 2: 遗传算法优化路线 ListAttraction optimized geneticAlgorithm(candidates, pref); // Step 3: Dijkstra计算最短路径 Graph graph buildGraph(optimized, pref.getStartLocation()); DijkstraAlgorithm dijkstra new DijkstraAlgorithm(graph); ListAttraction finalRoute dijkstra.findShortestPath(); // Step 4: 生成每日行程 return generateDailyPlan(finalRoute, pref.getStartDate(), pref.getDays()); } private ListAttraction geneticAlgorithm(ListAttraction attractions, TripPreference pref) { // 初始化种群100条随机路线 ListListAttraction population initPopulation(attractions, 100); for (int gen 0; gen 500; gen) { // 适应度计算考虑时间、交通成本、用户偏好 population.sort((a, b) - Double.compare(fitness(b, pref), fitness(a, pref)) ); // 选择 交叉 变异 population evolve(population); } return population.get(0); // 返回最优解 } }3️⃣ 实时消息WebSocketjavaConfiguration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker(/topic, /queue); config.setApplicationDestinationPrefixes(/app); } Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws).withSockJS(); } } Service public class ChatService { Autowired private SimpMessagingTemplate messagingTemplate; public void sendMessage(String fromUserId, String toUserId, String content) { messagingTemplate.convertAndSendToUser( toUserId, /queue/messages, Map.of(from, fromUserId, content, content, time, System.currentTimeMillis()) ); } // 搭子匹配成功通知 public void notifyMatch(Long userId, MatchResult match) { messagingTemplate.convertAndSendToUser( userId, /topic/match, Map.of(buddyId, match.getDemand().getUserId(), score, match.getScore(), demand, match.getDemand()) ); } } 四、各功能使用场景对照表用户场景前端页面后端接口核心技术 找攻略pages/index/index.vueGET /api/guides/searchElasticsearch全文检索 发攻略pages/guide/publish.vuePOST /api/guides/publish敏感词过滤 图片审核 找搭子pages/buddy/publish.vuePOST /api/buddy/demandRedis GEO 匹配算法 搭子聊天pages/chat/index.vueWebSocket /wsWebSocket AES加密️ 规划行程pages/trip/plan.vuePOST /api/trip/generate遗传算法 Dijkstra 共享行程pages/trip/shared.vueWebSocket /ws/trip实时同步 位置共享 语音日记pages/dynamic/voice.vuePOST /api/dynamic/voice科大讯飞TTS FFmpeg 打卡任务pages/task/checkin.vuePOST /api/task/checkin积分系统 优惠券