
一、项目背景与意义随着户外运动的兴起和人们对自然生活方式的向往露营探险活动日益普及。然而传统的露营活动组织、营地信息查询、装备管理、行程规划等环节往往依赖零散的社交群组、纸质记录或简单的电子表格存在信息不对称、管理效率低下、安全难以保障等问题。因此开发一个集营地信息管理、活动组织、装备租赁、行程规划、社交分享于一体的综合性露营探险系统具有重要的现实意义提升用户体验为用户提供一站式的露营探险服务简化从计划到出行的全流程。促进信息共享构建一个公开、透明的营地与活动信息平台打破信息壁垒。保障活动安全通过行程规划、天气预报集成、紧急联系人等功能提升户外活动的安全性。推动产业发展为营地经营者、装备商、活动组织者提供一个高效的商业对接平台。本系统旨在利用现代Web技术构建一个功能完善、用户体验优良、可扩展性强的露营探险管理平台。二、技术栈选型系统采用前后端分离的架构设计后端提供RESTful API前端负责用户交互与数据展示。2.1 后端技术栈 (SpringBoot)核心框架Spring Boot 2.7.x / 3.xWeb框架Spring MVC数据持久层Spring Data JPA Hibernate数据库MySQL 8.0缓存Redis (用于会话管理、热点数据缓存)安全框架Spring Security JWT (JSON Web Token)API文档SpringDoc OpenAPI (Swagger UI)构建工具Maven 或 Gradle其他Lombok (简化代码) MapStruct (对象映射) Hutool (工具集)2.2 前端技术栈 (Vue.js)核心框架Vue 3 (Composition API)构建工具Vite路由Vue Router 4状态管理PiniaUI组件库Element Plus 或 Ant Design VueHTTP客户端Axios地图集成高德地图 JavaScript API 或 Leaflet图表ECharts 或 AntV包管理npm 或 yarn2.3 开发与部署版本控制Git接口调试Postman / Apifox容器化Docker, Docker Compose部署后端可部署于Linux服务器如使用Nginx反向代理前端静态资源可部署于Nginx或对象存储如OSS、COS。三、系统核心功能模块设计系统主要分为前台用户端和后台管理端。3.1 前台用户端功能用户中心注册、登录含第三方登录、个人信息管理。营地探索按位置、评分、设施等条件筛选和查看营地详情图片、介绍、价格、评论。活动广场浏览、搜索、报名参加由用户或官方发布的露营活动。装备商城/租赁浏览、购买或租赁露营装备管理购物车与订单。行程规划在地图上规划露营路线添加途经点估算时间和费用。社区分享发布露营游记、图片、视频点赞、评论、收藏。消息通知系统通知、活动提醒、订单状态变更等。3.2 后台管理端功能仪表盘关键数据概览用户数、订单量、营地数等。营地管理营地的增删改查、审核、上下架。活动管理活动的审核、推荐、置顶。商品管理装备商品的分类、SKU、库存、价格管理。订单管理处理用户订单租赁、购买、退款。用户管理用户列表、权限控制、封禁/解封。内容审核审核用户发布的游记、评论等内容。系统设置轮播图、公告、基础参数配置。四、数据库核心表设计以下是部分核心数据表的结构概览-- 用户表 CREATE TABLE sys_user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(500) DEFAULT NULL COMMENT 头像URL, phone varchar(20) DEFAULT NULL COMMENT 手机号, email varchar(100) DEFAULT NULL COMMENT 邮箱, status tinyint DEFAULT 1 COMMENT 状态 (0:禁用1:正常), create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 营地信息表 CREATE TABLE camp_site ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 营地名称, province varchar(20) DEFAULT NULL COMMENT 省, city varchar(20) DEFAULT NULL COMMENT 市, district varchar(20) DEFAULT NULL COMMENT 区, address varchar(255) DEFAULT NULL COMMENT 详细地址, longitude decimal(10,7) DEFAULT NULL COMMENT 经度, latitude decimal(10,7) DEFAULT NULL COMMENT 纬度, description text COMMENT 营地描述, facilities varchar(500) DEFAULT NULL COMMENT 设施(JSON数组如[WiFi,淋浴,烧烤]), price_per_night decimal(10,2) DEFAULT NULL COMMENT 每晚价格, cover_image varchar(500) DEFAULT NULL COMMENT 封面图URL, status tinyint DEFAULT 0 COMMENT 状态 (0:待审核1:已上架2:已下架), create_user_id bigint DEFAULT NULL COMMENT 创建人, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT营地信息表; -- 活动表 CREATE TABLE camp_activity ( id bigint NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 活动标题, description text COMMENT 活动详情, cover_image varchar(500) DEFAULT NULL COMMENT 封面图, start_time datetime NOT NULL COMMENT 开始时间, end_time datetime NOT NULL COMMENT 结束时间, location varchar(255) DEFAULT NULL COMMENT 集合地点, max_participants int DEFAULT NULL COMMENT 最大参与人数, current_participants int DEFAULT 0 COMMENT 当前报名人数, fee decimal(10,2) DEFAULT 0.00 COMMENT 费用, status tinyint DEFAULT 0 COMMENT 状态 (0:草稿1:已发布2:已结束3:已取消), organizer_id bigint NOT NULL COMMENT 组织者ID, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_organizer (organizer_id), KEY idx_time (start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT露营活动表;五、核心代码示例5.1 后端SpringBoot核心代码1. JPA实体类 (CampSite.java)import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; import java.time.LocalDateTime; Entity Table(name camp_site) Data public class CampSite { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false, length 100) private String name; private String province; private String city; private String district; private String address; Column(precision 10, scale 7) private BigDecimal longitude; Column(precision 10, scale 7) private BigDecimal latitude; Lob private String description; Column(length 500) private String facilities; // 存储为JSON字符串如 [WiFi,淋浴] Column(name price_per_night, precision 10, scale 2) private BigDecimal pricePerNight; Column(name cover_image, length 500) private String coverImage; private Integer status 0; // 0:待审核1:已上架2:已下架 Column(name create_user_id) private Long createUserId; Column(name create_time, updatable false) private LocalDateTime createTime LocalDateTime.now(); Column(name update_time) private LocalDateTime updateTime LocalDateTime.now(); }2. Spring Data JPA 仓库接口 (CampSiteRepository.java)import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.math.BigDecimal; import java.util.List; public interface CampSiteRepository extends JpaRepositoryCampSite, Long, JpaSpecificationExecutorCampSite { PageCampSite findByStatusOrderByCreateTimeDesc(Integer status, Pageable pageable); // 根据城市和状态查询 PageCampSite findByCityAndStatus(String city, Integer status, Pageable pageable); // 复杂查询根据地理范围查找营地 (简化示例) Query(SELECT cs FROM CampSite cs WHERE cs.status 1 AND cs.latitude BETWEEN :minLat AND :maxLat AND cs.longitude BETWEEN :minLng AND :maxLng) ListCampSite findWithinBounds(Param(minLat) BigDecimal minLat, Param(maxLat) BigDecimal maxLat, Param(minLng) BigDecimal minLng, Param(maxLng) BigDecimal maxLng); }3. Spring Security JWT 配置与过滤器 (JwtAuthenticationFilter.java)import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenUtil jwtTokenUtil; private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsService) { this.jwtTokenUtil jwtTokenUtil; this.userDetailsService userDetailsService; } Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader request.getHeader(Authorization); if (authHeader ! null authHeader.startsWith(Bearer )) { String token authHeader.substring(7); String username jwtTokenUtil.getUsernameFromToken(token); if (username ! null SecurityContextHolder.getContext().getAuthentication() null) { UserDetails userDetails userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(request, response); } }5.2 前端Vue核心代码1. 营地列表页面组件 (CampSiteList.vue)template div classcamp-site-list div classfilter-bar el-input v-modelsearchParams.keyword placeholder搜索营地名称 stylewidth: 200px; keyup.enterhandleSearch / el-select v-modelsearchParams.city placeholder选择城市 clearable changehandleSearch el-option v-forcity in cityOptions :keycity.value :labelcity.label :valuecity.value / /el-select el-button typeprimary clickhandleSearch搜索/el-button /div el-row :gutter20 el-col :span8 v-forsite in campSiteList :keysite.id el-card classcamp-card :body-style{ padding: 0px } clickgoToDetail(site.id) img :srcsite.coverImage || defaultImage classcamp-image / div stylepadding: 14px; div classcamp-title{{ site.name }}/div div classcamp-locationi classel-icon-location/i {{ site.city }} {{ site.district }}/div div classcamp-facilities el-tag v-forfacility in (site.facilities || []) :keyfacility sizesmall{{ facility }}/el-tag /div div classcamp-price span classprice¥{{ site.pricePerNight }}/span / 晚 /div /div /el-card /el-col /el-row div classpagination el-pagination current-changehandlePageChange :current-pagepagination.current :page-sizepagination.size :totalpagination.total layoutprev, pager, next /el-pagination /div /div /template script setup import { ref, onMounted } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import { getCampSiteList } from /api/camp const router useRouter() const defaultImage https://via.placeholder.com/300x200?textCampSite const searchParams ref({ keyword: , city: }) const cityOptions ref([ { label: 北京市, value: 北京 }, { label: 上海市, value: 上海 }, { label: 杭州市, value: 杭州 } ]) const campSiteList ref([]) const pagination ref({ current: 1, size: 9, total: 0 }) const loadCampSites async () { try { const params {