🌍 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 class="container">
    <!-- 🔍 搜索栏 -->
    <view class="search-bar">
      <input placeholder="搜索攻略:成都美食/三亚自由行..." 
             @confirm="handleSearch" />
      <image src="/static/search.png" @click="handleSearch" />
    </view>

    <!-- 🏷️ 标签筛选 -->
    <scroll-view scroll-x class="tag-scroll">
      <view v-for="tag in tags" :key="tag"
            :class="['tag', currentTag===tag?'active':'']"
            @click="selectTag(tag)">{{ tag }}</view>
    </scroll-view>

    <!-- 📖 攻略列表(瀑布流) -->
    <view class="guide-list">
      <view v-for="guide in guides" :key="guide.id" 
            class="guide-card" @click="goDetail(guide.id)">
        <image :src="guide.cover" class="cover" mode="aspectFill" />
        <view class="info">
          <text class="title">{{ guide.title }}</text>
          <text class="author">{{ guide.authorName }} | ⭐{{ guide.rating }}</text>
          <view class="tags">
            <text v-for="t in guide.tags" :key="t" class="tag-item">{{ t }}</text>
          </view>
        </view>
      </view>
    </view>

    <!-- 👥 附近搭子 -->
    <view class="buddy-section">
      <text class="section-title">🎯 附近搭子</text>
      <view v-for="buddy in nearbyBuddies" :key="buddy.id" 
            class="buddy-card" @click="goBuddy(buddy.id)">
        <image :src="buddy.avatar" class="avatar" />
        <view class="info">
          <text class="name">{{ buddy.name }} <text class="score">信用{{ buddy.creditScore }}</text></text>
          <text class="demand">{{ buddy.demand }}</text>
          <text class="distance">{{ buddy.distance }}km</text>
        </view>
        <button class="chat-btn" @click.stop="startChat(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内搭子,响应时间20ms


2️⃣ 攻略详情 — 查看 + 收藏 + 找搭子


vue

<!-- pages/guide/detail.vue -->
<template>
  <view class="detail">
    <!-- 封面图 -->
    <image :src="guide.cover" class="cover" mode="aspectFill" />
    
    <!-- 标题 + 作者 -->
    <view class="header">
      <text class="title">{{ guide.title }}</text>
      <view class="author">
        <image :src="guide.authorAvatar" class="avatar" />
        <text>{{ guide.authorName }}</text>
      </view>
    </view>

    <!-- 📋 攻略内容(富文本) -->
    <view class="content" v-html="guide.content" />

    <!-- 🗺️ 行程路线(地图选点) -->
    <view class="route-map">
      <map :latitude="guide.route[0].lat" 
           :longitude="guide.route[0].lng"
           :markers="markers"
           :polyline="polyline"
           style="height: 300rpx" />
    </view>

    <!-- 🎯 一键找搭子(核心功能!) -->
    <button class="find-buddy-btn" @click="findBuddy">
      👥 找人一起去({{ guide.buddyCount }}人已加入)
    </button>

    <!-- 底部操作 -->
    <view class="actions">
      <view class="action-item" @click="toggleLike">
        <text>{{ isLiked ? '❤️' : '🤍' }}</text>
        <text>{{ guide.likeCount }}</text>
      </view>
      <view class="action-item" @click="shareGuide">
        <text>📤</text>
        <text>分享</text>
      </view>
      <view class="action-item" @click="saveGuide">
        <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 class="publish">
    <view class="form">
      <view class="form-item">
        <text class="label">目的地</text>
        <input v-model="form.destination" placeholder="如:成都" />
      </view>
      
      <view class="form-item">
        <text class="label">出行时间</text>
        <picker mode="date" @change="onDateChange">
          <view class="picker">{{ form.travelDate }}</view>
        </picker>
      </view>

      <view class="form-item">
        <text class="label">天数</text>
        <picker :range="[1,2,3,4,5,6,7]" @change="onDaysChange">
          <view class="picker">{{ form.days }}天</view>
        </picker>
      </view>

      <view class="form-item">
        <text class="label">兴趣标签(多选)</text>
        <view class="tag-group">
          <view v-for="tag in allTags" :key="tag"
                :class="['tag', form.tags.includes(tag)?'selected':'']"
                @click="toggleTag(tag)">{{ tag }}</view>
        </view>
      </view>

      <view class="form-item">
        <text class="label">预算(元)</text>
        <input v-model="form.budget" type="digit" placeholder="如:3000" />
      </view>

      <view class="form-item">
        <text class="label">需求描述</text>
        <textarea v-model="form.description" placeholder="如:求8月5日成都3日游搭子,偏好美食与拍照" />
      </view>

      <button class="submit-btn" @click="publishDemand">
        🚀 发布需求,智能匹配搭子
      </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)
}
</script>

4️⃣ 匹配结果 — 查看搭子 + 发起聊天


vue

<!-- pages/buddy/matching.vue -->
<template>
  <view class="matching">
    <text class="title">🎯 为你匹配到 {{ buddies.length }} 个搭子</text>
    
    <view v-for="buddy in buddies" :key="buddy.id" class="buddy-card">
      <image :src="buddy.avatar" class="avatar" />
      <view class="info">
        <text class="name">{{ buddy.name }} <text class="score">信用{{ buddy.creditScore }}</text></text>
        <text class="tags">{{ buddy.tags.join(' / ') }}</text>
        <text class="demand">{{ buddy.demand }}</text>
        <view class="match-bar">
          <text>匹配度</text>
          <view class="bar">
            <view class="fill" :style="{width: buddy.matchScore+'%'}"></view>
          </view>
          <text class="score">{{ buddy.matchScore }}%</text>
        </view>
      </view>
      <button class="chat-btn" @click="chat(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}` })
}
</script>

5️⃣ 行程规划 — 智能生成 + 3D地图


vue

<!-- pages/trip/plan.vue -->
<template>
  <view class="plan">
    <!-- 输入条件 -->
    <view class="input-section">
      <input v-model="form.destination" placeholder="目的地:如杭州" />
      <picker mode="date" @change="form.startDate = $event.detail.value">
        <view class="picker">{{ form.startDate }}</view>
      </picker>
      <picker :range="[1,2,3,4,5,6,7]" @change="form.days = $event.detail.value">
        <view class="picker">{{ form.days }}天</view>
      </picker>
      <view class="tag-group">
        <view v-for="t in ['美食','摄影','历史','自然']" :key="t"
              :class="['tag', form.tags.includes(t)?'selected':'']"
              @click="toggleTag(t)">{{ t }}</view>
      </view>
      <button @click="generateTrip">🤖 AI智能规划</button>
    </view>

    <!-- 生成结果 -->
    <view v-if="tripPlan" class="result">
      <view v-for="(day, idx) in tripPlan.days" :key="idx" class="day-card">
        <text class="day-title">Day {{ idx+1 }}</text>
        <view v-for="spot in day.spots" :key="spot.id" class="spot">
          <text class="time">{{ spot.time }}</text>
          <text class="name">{{ spot.name }}</text>
          <text class="tip">{{ spot.tip }}</text>
        </view>
      </view>

      <!-- 3D地图预览 -->
      <map :latitude="tripPlan.center.lat" 
           :longitude="tripPlan.center.lng"
           :markers="markers"
           :polyline="polyline"
           style="height: 400rpx" />

      <button class="export-btn" @click="exportPDF">📄 导出行程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 }]
}
</script>

🤖 AI行程规划算法(后端遗传算法 + Dijkstra最短路径):


 
输入:杭州 + 3天 + 美食/摄影
输出:
  Day1: 西湖(日出拍摄) → 河坊街(美食) → 南宋御街(夜景)
  Day2: 灵隐寺(上午) → 龙井村(品茶) → 西湖音乐喷泉(晚上)
  Day3: 西溪湿地(自然) → 印象西湖(演出)

6️⃣ 共享行程 — 团队实时协作


vue

<!-- pages/trip/shared.vue -->
<template>
  <view class="shared">
    <view class="members">
      <view v-for="m in members" :key="m.id" class="member">
        <image :src="m.avatar" class="avatar" />
        <text>{{ m.name }}</text>
      </view>
      <view class="add-btn" @click="inviteMember">➕ 邀请</view>
    </view>

    <!-- 共享行程表(实时同步) -->
    <view class="timeline">
      <view v-for="(item, idx) in itinerary" :key="idx" class="item">
        <text class="time">{{ item.time }}</text>
        <text class="content">{{ item.content }}</text>
        <text class="status" :class="item.status">{{ item.statusText }}</text>
      </view>
    </view>

    <!-- 任务分配 -->
    <view class="tasks">
      <view v-for="task in tasks" :key="task.id" class="task" 
            :class="{done: task.done}" @click="toggleTask(task)">
        <text>{{ task.content }}</text>
        <text class="assignee">{{ task.assignee }}</text>
      </view>
    </view>

    <!-- 📍 位置共享 -->
    <map :latitude="myLat" :longitude="myLng"
         :markers="memberMarkers"
         show-location style="height: 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) {
        // 浏览量+1(Redis计数)
        redisTemplate.opsForValue().increment("guide:view:" + id);
        return Result.success(guideService.getDetail(id));
    }
}

java

// ========== 搭子匹配服务(核心算法)==========
@Service
public class MatchService {

    @Autowired
    private RedisTemplate<String, 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 List<MatchResult> match(Long userId) {
        User user = userMapper.selectById(userId);
        
        // Step 1:  Redis GEO查5km内需求
        GeoResults<RedisGeoCommands.GeoLocation<String>> nearby = 
            redisTemplate.opsForGeo().radius(
                "buddy:demands",
                new Circle(new Point(user.getLng(), user.getLat()), 
                          new Distance(5000, Metrics.METERS))
            );

        List<BuddyDemand> 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(List<String> tags1, List<String> tags2) {
        Set<String> set = new HashSet<>(tags1);
        set.retainAll(tags2);
        return (double) set.size() / Math.sqrt(tags1.size() * tags2.size());
    }

    // Jaccard相似度
    private double jaccardSimilarity(List<String> list1, List<String> list2) {
        Set<String> 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搜索候选景点(按评分+距离排序)
        List<Attraction> candidates = attractionService.search(pref);

        // Step 2: 遗传算法优化路线
        List<Attraction> optimized = geneticAlgorithm(candidates, pref);

        // Step 3: Dijkstra计算最短路径
        Graph graph = buildGraph(optimized, pref.getStartLocation());
        DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(graph);
        List<Attraction> finalRoute = dijkstra.findShortestPath();

        // Step 4: 生成每日行程
        return generateDailyPlan(finalRoute, pref.getStartDate(), pref.getDays());
    }

    private List<Attraction> geneticAlgorithm(List<Attraction> attractions, 
                                               TripPreference pref) {
        // 初始化种群(100条随机路线)
        List<List<Attraction>> 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️⃣ 实时消息(WebSocket)


java

@Configuration
@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.vue GET /api/guides/search Elasticsearch全文检索
📝 发攻略 pages/guide/publish.vue POST /api/guides/publish 敏感词过滤 + 图片审核
👥 找搭子 pages/buddy/publish.vue POST /api/buddy/demand Redis GEO + 匹配算法
💬 搭子聊天 pages/chat/index.vue WebSocket /ws WebSocket + AES加密
🗺️ 规划行程 pages/trip/plan.vue POST /api/trip/generate 遗传算法 + Dijkstra
📍 共享行程 pages/trip/shared.vue WebSocket /ws/trip 实时同步 + 位置共享
🎤 语音日记 pages/dynamic/voice.vue POST /api/dynamic/voice 科大讯飞TTS + FFmpeg
🎯 打卡任务 pages/task/checkin.vue POST /api/task/checkin 积分系统 + 优惠券

更多推荐