全栈项目:旅游攻略系统

1. 项目概述

1.1 项目背景

随着旅游业的快速发展,人们对于旅游信息的需求日益增长。本项目旨在构建一个集攻略分享、社交互动、问答交流于一体的综合性旅游平台,为用户提供全方位的旅游信息服务。

1.2 项目目标

  • ✅ 构建用户友好的旅游攻略分享平台
  • ✅ 实现社交化的旅游信息交流
  • ✅ 提供专业的旅游问答服务
  • ✅ 建立完善的后台管理体系
  • ✅ 确保系统安全性和高性能

1.3 目标用户

用户类型 描述 主要需求
旅游爱好者 热爱旅行的普通用户 浏览攻略、提问、分享经验
旅游达人 经验丰富的旅行者 发布攻略、回答问题、积累粉丝
自由行用户 计划自助旅行的用户 获取详细攻略、路线规划
平台管理员 运营和审核人员 内容管理、用户管理、数据统计

1.4 核心功能模块

旅游攻略系统(用户端)

  • 👤 用户管理:注册/登录、个人信息管理、关注系统
  • 📝 攻略管理:发布、浏览、搜索、收藏攻略
  • 💬 社交互动:点赞、评论、关注、分享
  • ❓ 问答社区:提问、回答、最佳答案评选

后台管理系统

  • 👥 用户管理:用户列表、状态控制、权限管理
  • 📋 内容管理:攻略审核、问答管理、分类管理
  • 📊 数据统计:用户统计、内容分析、访问量统计

1.5 项目截图

2. 需求分析

2.1 功能需求详解

2.1.1 用户管理模块

核心功能:

  • 用户注册与登录(邮箱注册、用户名/邮箱登录)
  • 个人信息管理(基本信息编辑、头像上传、个人介绍)
  • 密码修改与找回
  • 用户关注与粉丝系统

2.1.2 攻略管理模块

核心功能:

  • 攻略发布与编辑(富文本编辑器、图片上传)
  • 攻略浏览与搜索(关键词搜索、分类筛选)
  • 攻略分类管理(6个主要分类)
  • 攻略收藏与分享

2.1.3 问答社区模块

核心功能:

  • 问题发布与管理
  • 答案提交与评选
  • 问答分类系统
  • 最佳答案机制

2.1.4 后台管理模块

用户管理:

  • 用户列表管理(搜索、筛选、分页)
  • 用户状态控制(启用/禁用)
  • 权限分配管理(用户/管理员)
  • 用户行为分析

内容管理:

  • 攻略内容审核(待审核、已发布、已拒绝)
  • 问答内容管理(问题、答案管理)
  • 分类标签管理(新增、编辑、删除)
  • 敏感词过滤

数据统计:

  • 用户数据统计(注册量、活跃度)
  • 内容数据分析(发布量、互动量)
  • 访问量统计(PV、UV)
  • 趋势分析报告

2.2 数据模型设计

核心数据模型

User(用户模型)

javascript 复制代码
{
  username: String,        // 用户名(唯一,3-20字符)
  email: String,          // 邮箱(唯一)
  password: String,       // 密码(加密存储)
  nickname: String,       // 昵称
  avatar: String,         // 头像URL
  bio: String,            // 个人简介(最多200字符)
  location: String,       // 位置
  role: String,           // 角色(user/admin)
  status: String,         // 状态(active/disabled)
  followers: [ObjectId],  // 粉丝列表
  following: [ObjectId],  // 关注列表
  created_at: Date,       // 创建时间
  updated_at: Date        // 更新时间
}

Guide(攻略模型)

javascript 复制代码
{
  title: String,          // 标题(5-100字符)
  content: String,        // 内容(富文本)
  summary: String,        // 摘要(最多200字符)
  cover_image: String,    // 封面图
  author: ObjectId,       // 作者(关联User)
  category: ObjectId,     // 分类(关联Category)
  destination: String,    // 目的地
  tags: [String],         // 标签(最多5个)
  views_count: Number,    // 浏览量
  likes_count: Number,    // 点赞数
  favorites_count: Number,// 收藏数
  comments_count: Number, // 评论数
  status: String,         // 状态(draft/published/rejected)
  created_at: Date,
  updated_at: Date
}

Question(问题模型)

javascript 复制代码
{
  title: String,          // 标题(10-200字符)
  content: String,        // 内容(20-5000字符)
  author: ObjectId,       // 作者
  category: ObjectId,     // 分类
  tags: [String],         // 标签
  urgency: String,        // 紧急程度(low/normal/high/urgent)
  views_count: Number,    // 浏览量
  answers_count: Number,  // 回答数
  has_best_answer: Boolean, // 是否有最佳答案
  status: String,         // 状态
  created_at: Date,
  updated_at: Date
}

3. 技术架构与知识点

3.1 整体技术架构

yaml 复制代码
┌─────────────────────────────────────────────────────────┐
│                      用户层                              │
│  ┌──────────────┐              ┌──────────────┐        │
│  │ 旅游攻略系统  │              │ 后台管理系统  │        │
│  │ (Vue 3)      │              │ (Vue 3)      │        │
│  │ Port: 5173   │              │ Port: 5174   │        │
│  └──────────────┘              └──────────────┘        │
└─────────────────────────────────────────────────────────┘
                          │
                          │ HTTP/HTTPS (RESTful API)
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    应用层                                │
│  ┌──────────────────────────────────────────────────┐  │
│  │           后端API服务 (Koa)                       │  │
│  │           Port: 3000                              │  │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐      │  │
│  │  │ 路由层    │  │ 控制器层  │  │ 中间件层  │      │  │
│  │  └──────────┘  └──────────┘  └──────────┘      │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
                          │
                          │ Mongoose ODM
                          ▼
┌─────────────────────────────────────────────────────────┐
│                    数据层                                │
│  ┌──────────────────────────────────────────────────┐  │
│  │           MongoDB 数据库                          │  │
│  │           Port: 27017                             │  │
│  │  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐        │  │
│  │  │ Users│  │Guides│  │Questions│ │Categories│   │  │
│  │  └──────┘  └──────┘  └──────┘  └──────┘        │  │
│  └──────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

3.2 技术栈详解

3.2.1 前端技术栈

技术 版本 用途 核心知识点
Vue 3 ^3.3.0 前端框架 Composition API、响应式系统、组件化
TypeScript ^5.0.0 类型系统 类型定义、泛型、接口
Vite ^4.0.0 构建工具 快速开发、HMR、代码分割
Element Plus ^2.4.0 UI组件库 组件使用、主题定制、按需引入
Pinia ^2.1.0 状态管理 Store定义、Actions、Getters
Vue Router ^4.2.0 路由管理 路由配置、导航守卫、动态路由
Axios ^1.5.0 HTTP客户端 请求拦截、响应处理、错误处理
Sass ^1.69.0 CSS预处理器 变量、嵌套、混入

3.2.2 后端技术栈

技术 版本 用途 核心知识点
Node.js ^16.0.0 运行环境 异步编程、模块系统、事件循环
Koa ^2.14.2 Web框架 中间件机制、上下文对象、错误处理
MongoDB ^4.4.0 NoSQL数据库 文档模型、索引、聚合查询
Mongoose ^8.0.3 ODM Schema定义、模型方法、中间件
JWT ^9.0.2 身份认证 Token生成、验证、刷新
bcryptjs ^2.4.3 密码加密 哈希算法、盐值、密码比较
Multer ^1.4.5 文件上传 文件处理、存储配置、验证
Joi ^17.11.0 数据验证 Schema验证、自定义规则

3.3 核心技术知识点

3.3.1 Vue 3 Composition API

核心概念:

javascript 复制代码
import { ref, reactive, computed, onMounted, watch } from 'vue'

export default {
  setup() {
    // 响应式数据
    const count = ref(0)
    const user = reactive({
      name: '张三',
      age: 25
    })

    // 计算属性
    const doubleCount = computed(() => count.value * 2)

    // 侦听器
    watch(count, (newVal, oldVal) => {
      console.log(`count changed from ${oldVal} to ${newVal}`)
    })

    // 生命周期钩子
    onMounted(() => {
      console.log('组件已挂载')
    })

    // 方法
    const increment = () => {
      count.value++
    }

    return { count, user, doubleCount, increment }
  }
}

优势:

  • ✅ 更好的TypeScript支持
  • ✅ 逻辑复用更容易(组合式函数)
  • ✅ 代码组织更灵活
  • ✅ Tree-shaking友好

3.3.2 TypeScript 类型系统

类型定义示例:

typescript 复制代码
// 用户类型
interface User {
  id: string
  username: string
  email: string
  nickname: string
  avatar?: string
  created_at: string
}

// API响应类型
interface ApiResponse<T = any> {
  code: number
  message: string
  data: T
  timestamp: number
}

// 泛型应用
async function apiRequest<T>(
  url: string, 
  options?: RequestInit
): Promise<ApiResponse<T>> {
  const response = await fetch(url, options)
  return response.json()
}

// 使用示例
const userList = await apiRequest<ListResponse<User>>('/api/users')

3.3.3 Pinia 状态管理

Store定义:

javascript 复制代码
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null as User | null,
    token: localStorage.getItem('token') || '',
    isLoggedIn: false
  }),

  getters: {
    userId: (state) => state.userInfo?.id,
    username: (state) => state.userInfo?.username || '',
    isAdmin: (state) => state.userInfo?.role === 'admin'
  },

  actions: {
    async login(credentials: LoginForm) {
      const response = await authApi.login(credentials)
      this.token = response.data.token
      this.userInfo = response.data.user
      this.isLoggedIn = true
      localStorage.setItem('token', this.token)
    },

    logout() {
      this.token = ''
      this.userInfo = null
      this.isLoggedIn = false
      localStorage.removeItem('token')
    }
  }
})

3.3.4 Koa 中间件机制

中间件示例:

javascript 复制代码
import Koa from 'koa'
import cors from '@koa/cors'
import bodyParser from 'koa-bodyparser'

const app = new Koa()

// 错误处理中间件
app.use(async (ctx, next) => {
  try {
    await next()
  } catch (error) {
    ctx.status = error.status || 500
    ctx.body = {
      code: ctx.status,
      message: error.message || '服务器内部错误',
      timestamp: Date.now()
    }
  }
})

// 跨域处理
app.use(cors({
  origin: (ctx) => {
    const allowedOrigins = ['http://localhost:5173', 'http://localhost:5174']
    return allowedOrigins.includes(ctx.header.origin) ? ctx.header.origin : false
  },
  credentials: true
}))

// 请求体解析
app.use(bodyParser())

// JWT认证中间件
app.use(async (ctx, next) => {
  const token = ctx.headers.authorization?.replace('Bearer ', '')
  if (token) {
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET)
      ctx.user = decoded
    } catch (error) {
      // Token无效,继续执行
    }
  }
  await next()
})

3.3.5 Mongoose 数据建模

模型定义示例:

php 复制代码
import mongoose from 'mongoose'
import bcrypt from 'bcryptjs'

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: [true, '用户名不能为空'],
    unique: true,
    trim: true,
    minlength: [3, '用户名至少3个字符'],
    maxlength: [20, '用户名不能超过20个字符']
  },
  email: {
    type: String,
    required: [true, '邮箱不能为空'],
    unique: true,
    lowercase: true,
    match: [/^\w+@[a-zA-Z_]+?.[a-zA-Z]{2,3}$/, '邮箱格式不正确']
  },
  password: {
    type: String,
    required: [true, '密码不能为空'],
    minlength: [6, '密码至少6个字符'],
    select: false // 查询时默认不返回密码
  }
}, {
  timestamps: { 
    createdAt: 'created_at', 
    updatedAt: 'updated_at' 
  }
})

// 密码加密中间件
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next()
  const salt = await bcrypt.genSalt(10)
  this.password = await bcrypt.hash(this.password, salt)
  next()
})

// 实例方法:验证密码
userSchema.methods.comparePassword = async function(candidatePassword) {
  return bcrypt.compare(candidatePassword, this.password)
}

export default mongoose.model('User', userSchema)

4. 项目实现详解

4.1 Monorepo 架构实现

本项目采用 pnpm workspace 进行 monorepo 管理,统一管理前后端依赖和脚本。

4.1.1 项目结构

perl 复制代码
travel-pc/                          # 项目根目录(Monorepo)
├── frontend/                       # 前端项目
│   ├── travel-system/             # 旅游攻略系统
│   │   ├── src/
│   │   │   ├── api/               # API接口封装
│   │   │   ├── components/        # 通用组件
│   │   │   ├── views/             # 页面组件
│   │   │   ├── router/            # 路由配置
│   │   │   ├── stores/            # Pinia状态管理
│   │   │   ├── utils/             # 工具函数
│   │   │   ├── types/             # TypeScript类型定义
│   │   │   └── main.ts            # 入口文件
│   │   └── package.json
│   │
│   └── admin-system/              # 后台管理系统
│       ├── src/                   # 源代码(结构同travel-system)
│       └── package.json
│
├── backend/                       # 后端项目
│   ├── src/
│   │   ├── controllers/           # 控制器层
│   │   ├── models/                # 数据模型层
│   │   ├── routes/                # 路由定义
│   │   ├── middleware/            # 中间件
│   │   ├── utils/                 # 工具函数
│   │   ├── config/                # 配置文件
│   │   └── app.js                 # 应用入口
│   ├── scripts/                   # 脚本文件
│   │   ├── import-mock-data.js    # 导入假数据
│   │   ├── check-categories.js    # 检查分类数据
│   │   └── create-admin.js        # 创建管理员
│   └── package.json
│
├── package.json                   # 根配置文件(Monorepo管理)
├── pnpm-workspace.yaml            # pnpm工作区配置
└── .npmrc                         # npm配置

4.2 核心功能实现

4.2.1 用户认证流程

javascript 复制代码
┌─────────┐         ┌─────────┐         ┌─────────┐
│  前端   │         │  后端   │         │ 数据库  │
└────┬────┘         └────┬────┘         └────┬────┘
     │                   │                   │
     │ 1. 提交登录表单    │                   │
     ├──────────────────>│                   │
     │                   │ 2. 验证用户信息    │
     │                   ├──────────────────>│
     │                   │ 3. 返回用户数据    │
     │                   │<──────────────────┤
     │                   │ 4. 生成JWT Token  │
     │ 5. 返回Token和用户信息                 │
     │<──────────────────┤                   │
     │ 6. 存储Token到localStorage            │
     │ 7. 后续请求携带Token                  │
     ├──────────────────>│                   │
     │                   │ 8. 验证Token      │
     │ 9. 返回数据        │                   │
     │<──────────────────┤                   │

前端实现:

javascript 复制代码
// stores/user.ts
export const useUserStore = defineStore('user', {
  state: () => ({
    token: localStorage.getItem('token') || '',
    userInfo: null as User | null,
    isLoggedIn: false
  }),

  actions: {
    async login(credentials: LoginForm) {
      const response = await authApi.login(credentials)
      this.token = response.data.token
      this.userInfo = response.data.user
      this.isLoggedIn = true
      localStorage.setItem('token', this.token)
    },

    logout() {
      this.token = ''
      this.userInfo = null
      this.isLoggedIn = false
      localStorage.removeItem('token')
    }
  }
})

后端实现:

javascript 复制代码
// controllers/authController.js
export const login = async (ctx) => {
  const { username, password } = ctx.request.body

  // 查找用户
  const user = await User.findOne({
    $or: [{ username }, { email: username }]
  }).select('+password')

  if (!user) {
    ctx.throw(401, '用户名或密码错误')
  }

  // 验证密码
  const isMatch = await user.comparePassword(password)
  if (!isMatch) {
    ctx.throw(401, '用户名或密码错误')
  }

  // 生成Token
  const token = generateToken({
    id: user._id,
    username: user.username,
    role: user.role
  })

  ctx.body = {
    code: 200,
    message: '登录成功',
    data: {
      token,
      user: {
        id: user._id,
        username: user.username,
        email: user.email,
        nickname: user.nickname,
        avatar: user.avatar,
        role: user.role
      }
    }
  }
}

4.2.2 攻略发布流程

前端实现:

javascript 复制代码
// views/guides/CreateGuide.vue
const handleSubmit = async () => {
  try {
    // 1. 上传封面图
    if (coverFile.value) {
      const formData = new FormData()
      formData.append('file', coverFile.value)
      const uploadRes = await uploadApi.uploadImage(formData)
      form.cover_image = uploadRes.data.url
    }

    // 2. 提交攻略数据
    const response = await guideApi.createGuide(form)
    
    ElMessage.success('发布成功')
    router.push(`/guides/${response.data.id}`)
  } catch (error) {
    ElMessage.error('发布失败')
  }
}

后端实现:

javascript 复制代码
// controllers/guideController.js
export const createGuide = async (ctx) => {
  const { title, content, summary, category_id, destination, tags, cover_image } = ctx.request.body

  // 验证分类是否存在
  const category = await Category.findById(category_id)
  if (!category) {
    ctx.throw(400, '分类不存在')
  }

  // 创建攻略
  const guide = new Guide({
    title,
    content,
    summary,
    cover_image,
    author: ctx.user._id,
    category: category_id,
    destination,
    tags,
    status: 'published'
  })

  await guide.save()

  // 填充关联数据
  await guide.populate('author', 'username nickname avatar')
  await guide.populate('category', 'name')

  ctx.body = {
    code: 200,
    message: '发布成功',
    data: guide
  }
}

5. 运行命令

python 复制代码
# 安装依赖
pnpm install

# 导入假数据
pnpm run import

# 启动开发环境
pnpm run dev

6. 快速修复清单

遇到问题时,按以下顺序检查:

  1. ✅ MongoDB服务是否启动
  2. ✅ 后端服务是否正常运行
  3. ✅ 前端服务是否正常运行
  4. ✅ 环境变量配置是否正确
  5. ✅ 数据库中是否有数据
  6. ✅ 浏览器控制台是否有错误
  7. ✅ 网络请求是否成功
  8. ✅ Token是否有效
相关推荐
我血条子呢1 小时前
【Vite】离线打包@iconify/vue的图标
前端·javascript·vue.js
小周在成长1 小时前
Java 面相对象继承(Inheritance)指南
后端
该用户已不存在1 小时前
一句话让一个AI为我花了(划掉)生成一个APP,Google Antigravity 实操
后端·ai编程·gemini
一个有理想的摸鱼选手1 小时前
CesiumLite-一行代码让你在Cesium中实现标绘测量
前端·gis·cesium
苏禾1 小时前
Spring 事务全面详解
后端
1024小神1 小时前
swiftui和uikit的桥梁:UIViewRepresentable、Coordinator
前端
t***p9351 小时前
springboot项目读取 resources 目录下的文件的9种方式
java·spring boot·后端
岁月宁静1 小时前
从 JavaScript 到 Python:前端工程师的完全转换指南
前端·javascript·python
哈哈哈笑什么1 小时前
微服务间进行调用进行失败后重试
后端