1. 项目背景与技术选型解析
西安旅游系统是一个典型的B/S架构应用,采用前后端分离模式开发。这种架构选择在当前企业级应用开发中已成为主流方案,主要基于以下几个考量点:
- 前后端解耦:前后端通过RESTful API交互,前端专注UI/UX,后端专注业务逻辑,团队可并行开发
- 技术栈灵活性:前后端可独立升级技术栈,如本系统前端采用Vue3而非传统jQuery
- 性能优化空间:静态资源可单独部署CDN,减轻服务器压力
1.1 核心框架选型分析
SpringBoot (2.7.x)
作为后端基础框架,主要解决以下痛点:
- 自动配置:通过
@SpringBootApplication注解自动完成Spring和第三方库的基础配置 - 内嵌容器:默认集成Tomcat(可切换Jetty/Undertow),无需单独部署
- 起步依赖:通过
spring-boot-starter-*系列依赖简化依赖管理 - 健康检查:内置
/actuator/health等监控端点
实际开发中我们特别使用了:
java复制@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class // 手动配置多数据源时需要排除自动配置
})
public class TourismApplication {
public static void main(String[] args) {
SpringApplication.run(TourismApplication.class, args);
}
}
Vue3 (Composition API)
相比Vue2的Options API,主要优势体现在:
- 更好的TypeScript支持
- 逻辑复用能力(自定义hook)
- 更灵活的代码组织方式
典型页面组件结构示例:
javascript复制<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const attractions = ref([])
onMounted(async () => {
const res = await fetchAttractions(route.params.cityId)
attractions.value = res.data
})
</script>
1.2 数据持久层设计
MyBatis-Plus 3.5.x
在原生MyBatis基础上增强的功能包括:
- 通用Mapper:内置
BaseMapper提供CRUD基础方法 - 条件构造器:
QueryWrapper支持链式调用构建查询条件 - 分页插件:配合
Page对象实现物理分页 - 代码生成器:自动生成Entity/Mapper/Service代码
景点查询的典型实现:
java复制// Service层
public Page<Attraction> queryAttractions(AttractionQuery query) {
return attractionMapper.selectPage(
new Page<>(query.getPageNum(), query.getPageSize()),
new QueryWrapper<Attraction>()
.lambda()
.eq(Attraction::getCityId, query.getCityId())
.like(StringUtils.isNotBlank(query.getKeyword()),
Attraction::getName, query.getKeyword())
);
}
MySQL 8.0
数据库设计特别注意:
- 使用utf8mb4字符集支持完整Unicode(含emoji)
- 景点表添加空间索引(SPATIAL INDEX)支持地理位置查询
- 合理设置事务隔离级别(默认为REPEATABLE_READ)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计与核心模块
2.1 整体架构图
code复制[前端Vue3] ←HTTP→ [SpringBoot API] ←JDBC→ [MySQL]
↑ ↑
Nginx Redis缓存
2.2 核心功能模块
旅游景点模块
- 数据结构设计:
sql复制CREATE TABLE `attraction` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL COMMENT '景点名称',
`location` POINT NOT NULL COMMENT '经纬度坐标',
`description` TEXT COMMENT '详细介绍',
`cover_url` VARCHAR(255) COMMENT '封面图URL',
`open_hours` JSON COMMENT '开放时间配置',
`ticket_info` JSON COMMENT '票价信息',
PRIMARY KEY (`id`),
SPATIAL INDEX `idx_location` (`location`),
FULLTEXT INDEX `idx_search` (`name`,`description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
用户中心模块
- JWT认证流程:
- 登录成功生成token(包含userId、role等信息)
- 前端存储token于localStorage
- 每次请求携带在Authorization头
- 服务端通过拦截器验证
Spring Security配置要点:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
2.3 前后端交互规范
API设计原则
- 统一响应结构:
json复制{
"code": 200,
"message": "success",
"data": {...},
"timestamp": 1659324567890
}
- 错误码规范:
- 4xx 客户端错误(400参数错误,401未授权等)
- 5xx 服务端错误(500系统异常,503服务不可用等)
Axios封装示例
javascript复制const service = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 10000
})
// 请求拦截器
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage.error(res.message)
return Promise.reject(new Error(res.message))
}
return res.data
},
error => {
if (error.response.status === 401) {
router.push('/login')
}
return Promise.reject(error)
}
)
3. 关键技术实现细节
3.1 地图集成方案
高德地图API接入
- 前端加载SDK:
html复制<script src="https://webapi.amap.com/maps?v=2.0&key=您申请的key"></script>
- Vue组件封装:
javascript复制import { onMounted, ref } from 'vue'
export default {
setup() {
const map = ref(null)
onMounted(() => {
map.value = new AMap.Map('map-container', {
zoom: 13,
center: [108.94, 34.26] // 西安市中心坐标
})
// 添加景点标记
attractions.value.forEach(item => {
new AMap.Marker({
position: new AMap.LngLat(item.lng, item.lat),
title: item.name,
map: map.value
})
})
})
return { map }
}
}
3.2 性能优化实践
缓存策略
- Redis缓存热点数据:
java复制@Cacheable(value = "attractions", key = "#cityId")
public List<Attraction> getTopAttractions(Long cityId) {
return attractionMapper.selectList(
new QueryWrapper<Attraction>()
.eq("city_id", cityId)
.orderByDesc("visit_count")
.last("LIMIT 10")
);
}
- 前端资源优化:
- 路由懒加载
- 组件异步加载
- 图片懒加载
数据库优化
- 慢查询监控:
sql复制-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
- 索引优化案例:
sql复制-- 原低效查询
SELECT * FROM attraction WHERE description LIKE '%兵马俑%';
-- 优化方案
ALTER TABLE attraction ADD FULLTEXT INDEX idx_search (name, description);
SELECT * FROM attraction WHERE MATCH(name, description) AGAINST('兵马俑');
3.3 安全防护措施
XSS防御
- 前端过滤:
javascript复制import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(userInput)
- 后端校验:
java复制@PostMapping("/comments")
public Result addComment(@Valid @RequestBody CommentDTO dto) {
// 使用Jsoup清理HTML
String safeContent = Jsoup.clean(dto.getContent(), Whitelist.basic());
// ...
}
SQL注入防护
MyBatis自动使用预编译语句:
xml复制<select id="searchAttractions" resultType="Attraction">
SELECT * FROM attraction
WHERE name LIKE CONCAT('%', #{keyword}, '%') <!-- 安全 -->
<!-- 不要使用 ${} 直接拼接 -->
</select>
4. 开发环境与部署方案
4.1 本地开发配置
后端开发环境
- JDK 17 + IntelliJ IDEA
- 必备插件:
- Lombok
- MyBatisX
- Spring Assistant
前端开发环境
- Node.js 16+
- VS Code插件:
- Volar
- ESLint
- Prettier
数据库初始化
bash复制# 使用Docker快速启动MySQL
docker run --name mysql-tourism \
-e MYSQL_ROOT_PASSWORD=123456 \
-e MYSQL_DATABASE=tourism \
-p 3306:3306 \
-d mysql:8.0 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci
4.2 生产环境部署
后端部署
- 打包可执行JAR:
bash复制mvn clean package -DskipTests
- 启动脚本:
bash复制#!/bin/bash
nohup java -Xms512m -Xmx1024m \
-Dspring.profiles.active=prod \
-jar tourism-backend.jar \
> application.log 2>&1 &
前端部署
- 构建生产包:
bash复制npm run build
- Nginx配置示例:
nginx复制server {
listen 80;
server_name tourism.example.com;
location / {
root /var/www/tourism-frontend;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
4.3 持续集成方案
GitHub Actions配置
yaml复制name: CI Pipeline
on: [push]
jobs:
backend-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: '17'
- name: Build with Maven
run: mvn verify
frontend-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm install
- run: npm run build
5. 典型问题排查与解决
5.1 跨域问题处理
开发环境解决方案
SpringBoot配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*");
}
}
生产环境方案
Nginx反向代理配置:
nginx复制location /api {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend;
}
5.2 文件上传限制
SpringBoot默认1MB限制解决方案
- 配置application.yml:
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
- 阿里云OSS直传方案:
javascript复制// 前端获取临时凭证
const getOSSToken = async () => {
const res = await axios.get('/api/oss/token')
return res.data
}
// 使用ali-oss SDK直传
const client = new OSS({
region: 'oss-cn-xiang',
accessKeyId: token.accessKeyId,
accessKeySecret: token.accessKeySecret,
stsToken: token.securityToken,
bucket: 'tourism-images'
})
await client.put(`attractions/${file.name}`, file)
5.3 性能问题排查案例
景点列表加载缓慢分析
- 问题现象:页面加载超过5秒
- 排查步骤:
- 检查网络请求,发现API响应时间达4.8秒
- 后端添加日志,定位到SQL查询耗时
- 执行EXPLAIN分析查询计划
- 解决方案:
sql复制-- 优化前
SELECT * FROM attractions WHERE city_id = 1 ORDER BY create_time DESC;
-- 优化后
ALTER TABLE attractions ADD INDEX idx_city_create (city_id, create_time);
SELECT id,name,cover_url FROM attractions
WHERE city_id = 1
ORDER BY create_time DESC
LIMIT 10;
- 效果:响应时间降至300ms以内
6. 扩展功能与二次开发
6.1 推荐算法集成
基于用户行为的协同过滤
- 数据收集:
- 用户浏览记录
- 收藏行为
- 购买记录
- 算法实现:
java复制public List<Attraction> recommendAttractions(Long userId) {
// 1. 获取相似用户
List<Long> similarUsers = findSimilarUsers(userId);
// 2. 获取这些用户喜欢的景点
Set<Long> candidateAttractions = getLikedAttractions(similarUsers);
// 3. 过滤已访问过的
candidateAttractions.removeAll(getVisitedAttractions(userId));
// 4. 按热度排序
return sortByPopularity(candidateAttractions);
}
6.2 微信小程序适配
Uniapp跨端方案
- 项目结构调整:
code复制src/
├── main.js # 通用入口
├── App.vue # 根组件
├── common/ # 通用工具
├── h5/ # Web专属代码
├── mp-weixin/ # 微信小程序专属
└── pages/ # 页面组件
- 条件编译示例:
javascript复制// #ifdef H5
import AMap from '@/common/amap-web.js'
// #endif
// #ifdef MP-WEIXIN
import qqMap from '@/common/qqmap-wx.js'
// #endif
6.3 国际化支持
Vue i18n集成
- 语言包配置:
javascript复制// lang/zh-CN.js
export default {
attraction: {
title: '景点详情',
openingHours: '开放时间'
}
}
// lang/en-US.js
export default {
attraction: {
title: 'Attraction Detail',
openingHours: 'Opening Hours'
}
}
- 组件中使用:
vue复制<template>
<h1>{{ $t('attraction.title') }}</h1>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
console.log(t('attraction.openingHours'))
</script>
7. 项目经验总结
在实际开发西安旅游系统的过程中,有几个关键经验值得分享:
- 版本控制策略:
- 使用Git Flow工作流
- 功能分支命名规范:
feature/attraction-map - 发布标签格式:
v1.2.0-xian-20230615
- API文档管理:
- 使用Swagger UI自动生成文档
- 通过注解维护接口说明:
java复制@Operation(summary = "获取景点详情")
@GetMapping("/attractions/{id}")
public Result<AttractionVO> getAttractionDetail(
@Parameter(description = "景点ID") @PathVariable Long id) {
// ...
}
- 异常处理规范:
- 自定义业务异常体系:
java复制public class BusinessException extends RuntimeException {
private final ErrorCode errorCode;
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
// ...
}
- 前端错误监控:
- 集成Sentry捕获前端异常:
javascript复制import * as Sentry from '@sentry/vue'
app.use(Sentry, {
dsn: 'your-dsn',
integrations: [
new Sentry.BrowserTracing(),
],
tracesSampleRate: 0.2
})
- 性能监控:
- SpringBoot集成Prometheus:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
tags:
application: ${spring.application.name}
对于想要基于此系统进行二次开发的开发者,建议重点关注:
- 配置文件分离(application-dev.yml/prod.yml)
- 多环境部署脚本
- 数据库迁移工具(Flyway/Liquibase)的集成
- 前端组件库的按需引入策略
通过这个项目的实践,我们验证了SpringBoot+Vue3+MyBatis技术栈在中型旅游信息系统中的适用性,特别是在处理高并发查询、复杂数据展示和地图集成等场景时的表现令人满意。后续可考虑引入微服务架构拆分模块,以及尝试GraalVM原生镜像编译等新技术方向。
