最近在指导计算机专业学生做毕业设计时,发现很多同学对微信小程序开发既感兴趣又有些无从下手。特别是旅游类小程序项目,既要考虑前端交互体验,又要处理后端数据接口,技术栈跨度较大。本文将完整分享一个开源的特色旅游小程序毕业设计方案,从技术选型到代码实现,带你一步步完成可落地的项目。
这个项目采用微信小程序作为前端,配合Spring Boot后端API,实现景点展示、路线规划、在线预订等核心功能。无论是计算机专业的毕业设计,还是想入门全栈开发的初学者,都能通过本文获得完整的开发思路和可运行的代码示例。
1. 项目背景与需求分析
1.1 为什么选择旅游小程序作为毕业设计
旅游类小程序是当前移动互联网的热门应用场景,具有以下技术特点:
- 技术综合性:涉及前端UI设计、后端API开发、数据库设计等多个技术层面
- 业务完整性:包含用户管理、数据展示、交易流程等典型业务模块
- 实战价值高:所学技术可直接应用于实际工作场景
- 扩展性强:可在基础功能上添加地图导航、智能推荐等进阶功能
1.2 核心功能需求
基于典型的旅游业务场景,我们规划了以下核心功能模块:
- 用户系统:微信授权登录、用户信息管理
- 景点展示:景点列表、详情介绍、图片展示
- 路线推荐:特色旅游路线、智能推荐算法
- 预订功能:门票预订、酒店预订、订单管理
- 收藏评论:景点收藏、用户评价互动
- 地图导航:基于位置服务的周边景点发现
1.3 技术栈选型理由
前端技术栈:微信小程序原生框架
- 开发门槛低,文档完善,生态成熟
- 无需考虑跨平台兼容性问题
- 毕业设计评审老师熟悉度较高
后端技术栈:Spring Boot + MySQL
- Spring Boot简化了后端开发配置
- MySQL是成熟稳定的关系型数据库
- 易于部署和演示,适合毕业设计场景
2. 开发环境准备
2.1 硬件与软件要求
开发设备配置:
- 操作系统:Windows 10/11 或 macOS 10.14+
- 内存:8GB及以上(推荐16GB)
- 存储空间:至少10GB可用空间
必要软件安装:
- 微信开发者工具(最新稳定版)
- JDK 1.8或更高版本
- IntelliJ IDEA或Eclipse
- MySQL 5.7或8.0版本
- Maven 3.6+ 或 Gradle
- Postman(用于API测试)
2.2 微信小程序环境配置
首先需要注册微信小程序账号并完成开发者认证:
# 访问微信公众平台注册小程序账号 # 完成企业或个人主体认证 # 获取AppID用于开发调试在微信开发者工具中创建新项目:
- 项目名称:特色旅游小程序
- 目录:选择本地开发目录
- AppID:使用测试号或正式AppID
- 开发模式:小程序
- 后端服务:不使用云开发
2.3 后端开发环境搭建
创建Spring Boot项目结构:
# 使用Spring Initializr创建项目 # 选择依赖:Web、JPA、MySQL、Lombok mvn archetype:generate -DgroupId=com.tourism -DartifactId=tourism-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false项目基础依赖配置(pom.xml):
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>com.tourism</groupId> <artifactId>tourism-app</artifactId> <version>1.0.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> </project>3. 数据库设计与建模
3.1 数据库表结构设计
根据业务需求,设计以下核心数据表:
用户表(users):存储用户基本信息
CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(100) UNIQUE NOT NULL COMMENT '微信openid', nickname VARCHAR(100) COMMENT '用户昵称', avatar_url VARCHAR(500) COMMENT '头像URL', phone VARCHAR(20) COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );景点表(scenic_spots):存储景点详细信息
CREATE TABLE scenic_spots ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(200) NOT NULL COMMENT '景点名称', description TEXT COMMENT '景点描述', address VARCHAR(500) COMMENT '详细地址', latitude DECIMAL(10, 6) COMMENT '纬度', longitude DECIMAL(10, 6) COMMENT '经度', images TEXT COMMENT '图片URL列表', price DECIMAL(10, 2) DEFAULT 0 COMMENT '门票价格', open_time VARCHAR(100) COMMENT '开放时间', rating DECIMAL(3, 1) DEFAULT 0 COMMENT '评分', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );订单表(orders):管理用户预订信息
CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '用户ID', spot_id BIGINT NOT NULL COMMENT '景点ID', order_number VARCHAR(100) UNIQUE NOT NULL COMMENT '订单号', quantity INT DEFAULT 1 COMMENT '购买数量', total_amount DECIMAL(10, 2) COMMENT '总金额', status TINYINT DEFAULT 0 COMMENT '订单状态', visit_date DATE COMMENT '游览日期', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (spot_id) REFERENCES scenic_spots(id) );3.2 实体类设计
使用JPA注解定义数据实体:
// 用户实体类 @Entity @Table(name = "users") @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "openid", unique = true, nullable = false) private String openid; @Column(name = "nickname") private String nickname; @Column(name = "avatar_url") private String avatarUrl; @Column(name = "phone") private String phone; @CreationTimestamp @Column(name = "create_time") private LocalDateTime createTime; @UpdateTimestamp @Column(name = "update_time") private LocalDateTime updateTime; } // 景点实体类 @Entity @Table(name = "scenic_spots") @Data public class ScenicSpot { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "description", columnDefinition = "TEXT") private String description; @Column(name = "address") private String address; @Column(name = "latitude", precision = 10, scale = 6) private BigDecimal latitude; @Column(name = "longitude", precision = 10, scale = 6) private BigDecimal longitude; @Column(name = "images", columnDefinition = "TEXT") private String images; // JSON格式存储图片列表 @Column(name = "price") private BigDecimal price; @Column(name = "rating", precision = 3, scale = 1) private BigDecimal rating; @CreationTimestamp @Column(name = "create_time") private LocalDateTime createTime; }4. 后端API开发实战
4.1 Spring Boot基础配置
应用配置文件(application.yml):
server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/tourism_db?useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect format_sql: true logging: level: com.tourism: DEBUG org.hibernate.SQL: DEBUG主启动类配置:
@SpringBootApplication @EnableJpaRepositories @EntityScan("com.tourism.entity") public class TourismApplication { public static void main(String[] args) { SpringApplication.run(TourismApplication.class, args); } }4.2 用户认证接口实现
微信小程序登录接口:
@RestController @RequestMapping("/auth") public class AuthController { @Autowired private UserService userService; @PostMapping("/wxlogin") public ApiResponse wxLogin(@RequestBody WxLoginRequest request) { try { // 调用微信接口验证code String wxUrl = "https://api.weixin.qq.com/sns/jscode2session"; Map<String, String> params = new HashMap<>(); params.put("appid", appId); params.put("secret", appSecret); params.put("js_code", request.getCode()); params.put("grant_type", "authorization_code"); // 发送HTTP请求获取openid String response = restTemplate.getForObject(wxUrl, String.class, params); JSONObject json = JSONObject.parseObject(response); String openid = json.getString("openid"); if (openid != null) { // 查找或创建用户 User user = userService.findOrCreateUser(openid, request.getUserInfo()); String token = jwtUtil.generateToken(user.getId().toString()); return ApiResponse.success("登录成功", new LoginResponse(token, user)); } else { return ApiResponse.error("微信登录失败"); } } catch (Exception e) { return ApiResponse.error("登录异常:" + e.getMessage()); } } } // 登录请求DTO @Data class WxLoginRequest { private String code; private WxUserInfo userInfo; } @Data class WxUserInfo { private String nickName; private String avatarUrl; private Integer gender; }4.3 景点管理接口开发
景点列表分页查询接口:
@RestController @RequestMapping("/spots") public class ScenicSpotController { @Autowired private ScenicSpotService spotService; @GetMapping("/list") public ApiResponse getSpotList( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String keyword) { Pageable pageable = PageRequest.of(page - 1, size, Sort.by("createTime").descending()); Page<ScenicSpot> spots = spotService.findSpots(keyword, pageable); Map<String, Object> result = new HashMap<>(); result.put("list", spots.getContent()); result.put("total", spots.getTotalElements()); result.put("pages", spots.getTotalPages()); return ApiResponse.success("查询成功", result); } @GetMapping("/detail/{id}") public ApiResponse getSpotDetail(@PathVariable Long id) { ScenicSpot spot = spotService.findById(id); if (spot == null) { return ApiResponse.error("景点不存在"); } return ApiResponse.success("查询成功", spot); } } // 服务层实现 @Service public class ScenicSpotService { @Autowired private ScenicSpotRepository spotRepository; public Page<ScenicSpot> findSpots(String keyword, Pageable pageable) { if (keyword != null && !keyword.trim().isEmpty()) { return spotRepository.findByNameContainingOrAddressContaining(keyword, keyword, pageable); } return spotRepository.findAll(pageable); } public ScenicSpot findById(Long id) { return spotRepository.findById(id).orElse(null); } }4.4 订单业务逻辑实现
订单创建和状态管理:
@Service public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired private ScenicSpotRepository spotRepository; @Transactional public Order createOrder(Long userId, Long spotId, Integer quantity, LocalDate visitDate) { // 验证景点存在性 ScenicSpot spot = spotRepository.findById(spotId) .orElseThrow(() -> new RuntimeException("景点不存在")); // 生成订单号 String orderNumber = generateOrderNumber(); // 计算总金额 BigDecimal totalAmount = spot.getPrice().multiply(BigDecimal.valueOf(quantity)); Order order = new Order(); order.setUserId(userId); order.setSpotId(spotId); order.setOrderNumber(orderNumber); order.setQuantity(quantity); order.setTotalAmount(totalAmount); order.setVisitDate(visitDate); order.setStatus(0); // 待支付 return orderRepository.save(order); } private String generateOrderNumber() { return "TO" + System.currentTimeMillis() + RandomUtil.randomNumbers(4); } @Transactional public boolean payOrder(Long orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new RuntimeException("订单不存在")); if (order.getStatus() != 0) { throw new RuntimeException("订单状态异常"); } order.setStatus(1); // 已支付 orderRepository.save(order); return true; } }5. 微信小程序前端开发
5.1 项目结构与配置
小程序目录结构:
tourism-miniprogram/ ├── pages/ │ ├── index/ # 首页 │ ├── spots/ # 景点列表 │ ├── detail/ # 景点详情 │ ├── order/ # 订单页面 │ └── profile/ # 个人中心 ├── components/ # 公共组件 ├── utils/ # 工具类 ├── app.js # 小程序入口 ├── app.json # 全局配置 ├── app.wxss # 全局样式 └── project.config.json # 项目配置全局配置文件(app.json):
{ "pages": [ "pages/index/index", "pages/spots/list", "pages/spots/detail", "pages/order/create", "pages/order/list", "pages/profile/index" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#07c160", "navigationBarTitleText": "特色旅游", "navigationBarTextStyle": "white", "enablePullDownRefresh": true }, "tabBar": { "color": "#666", "selectedColor": "#07c160", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "images/home.png", "selectedIconPath": "images/home-active.png" }, { "pagePath": "pages/spots/list", "text": "景点", "iconPath": "images/spots.png", "selectedIconPath": "images/spots-active.png" }, { "pagePath": "pages/profile/index", "text": "我的", "iconPath": "images/profile.png", "selectedIconPath": "images/profile-active.png" } ] }, "permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序位置接口的效果展示" } } }5.2 首页设计与实现
首页页面结构(index.wxml):
<view class="container"> <!-- 搜索框 --> <view class="search-box"> <input class="search-input" placeholder="搜索景点名称或地址" bindinput="onSearchInput" /> <button class="search-btn" bindtap="onSearch">搜索</button> </view> <!-- 轮播图 --> <swiper class="banner-swiper" indicator-dots="true" autoplay="true" interval="3000"> <swiper-item wx:for="{{banners}}" wx:key="id"> <image class="banner-image" src="{{item.imageUrl}}" mode="aspectFill" /> </swiper-item> </swiper> <!-- 分类导航 --> <view class="category-nav"> <view class="category-item" wx:for="{{categories}}" wx:key="id" bindtap="onCategoryTap">Page({ data: { banners: [], categories: [ { id: 1, name: '自然风光', icon: '/images/nature.png' }, { id: 2, name: '人文古迹', icon: '/images/culture.png' }, { id: 3, name: '休闲度假', icon: '/images/relax.png' }, { id: 4, name: '特色美食', icon: '/images/food.png' } ], recommendSpots: [], searchKeyword: '' }, onLoad: function() { this.loadBanners(); this.loadRecommendSpots(); }, onPullDownRefresh: function() { this.loadBanners(); this.loadRecommendSpots().then(() => { wx.stopPullDownRefresh(); }); }, loadBanners: function() { // 模拟banner数据 this.setData({ banners: [ { id: 1, imageUrl: '/images/banner1.jpg' }, { id: 2, imageUrl: '/images/banner2.jpg' }, { id: 3, imageUrl: '/images/banner3.jpg' } ] }); }, loadRecommendSpots: function() { return new Promise((resolve) => { wx.request({ url: 'http://localhost:8080/api/spots/list?page=1&size=6', method: 'GET', success: (res) => { if (res.data.code === 200) { this.setData({ recommendSpots: res.data.data.list }); } resolve(); }, fail: () => { // 失败时使用模拟数据 this.setData({ recommendSpots: this.getMockSpots() }); resolve(); } }); }); }, onSearchInput: function(e) { this.setData({ searchKeyword: e.detail.value }); }, onSearch: function() { if (this.data.searchKeyword.trim()) { wx.navigateTo({ url: `/pages/spots/list?keyword=${this.data.searchKeyword}` }); } }, onCategoryTap: function(e) { const categoryId = e.currentTarget.dataset.id; wx.navigateTo({ url: `/pages/spots/list?category=${categoryId}` }); }, onSpotTap: function(e) { const spotId = e.currentTarget.dataset.id; wx.navigateTo({ url: `/pages/spots/detail?id=${spotId}` }); }, onMoreSpots: function() { wx.switchTab({ url: '/pages/spots/list' }); }, getMockSpots: function() { return [ { id: 1, name: '西湖风景区', address: '浙江省杭州市西湖区', price: 0, rating: 4.8, images: ['/images/spot1.jpg'] } // 更多模拟数据... ]; } });5.3 景点详情页开发
详情页核心功能实现:
Page({ data: { spot: null, isCollected: false, currentImageIndex: 0 }, onLoad: function(options) { this.spotId = options.id; this.loadSpotDetail(); this.checkCollectionStatus(); }, loadSpotDetail: function() { wx.showLoading({ title: '加载中...' }); wx.request({ url: `http://localhost:8080/api/spots/detail/${this.spotId}`, method: 'GET', success: (res) => { wx.hideLoading(); if (res.data.code === 200) { const spot = res.data.data; // 处理图片数据 if (typeof spot.images === 'string') { spot.images = JSON.parse(spot.images); } this.setData({ spot }); } else { wx.showToast({ title: '加载失败', icon: 'none' }); } }, fail: () => { wx.hideLoading(); wx.showToast({ title: '网络错误', icon: 'none' }); } }); }, onImageChange: function(e) { this.setData({ currentImageIndex: e.detail.current }); }, onCollectTap: function() { if (!this.data.spot) return; const newStatus = !this.data.isCollected; this.setData({ isCollected: newStatus }); // 调用收藏接口 wx.request({ url: 'http://localhost:8080/api/collection/toggle', method: 'POST', data: { spotId: this.spotId, action: newStatus ? 'add' : 'remove' }, header: { 'Authorization': wx.getStorageSync('token') } }); }, onBookTap: function() { if (!this.data.spot) return; wx.navigateTo({ url: `/pages/order/create?spotId=${this.spotId}` }); }, onLocationTap: function() { const spot = this.data.spot; if (spot && spot.latitude && spot.longitude) { wx.openLocation({ latitude: parseFloat(spot.latitude), longitude: parseFloat(spot.longitude), name: spot.name, address: spot.address }); } } });6. 项目部署与测试
6.1 后端服务部署
使用Docker简化部署流程:
# Dockerfile FROM openjdk:8-jre-slim WORKDIR /app COPY target/tourism-app-1.0.0.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]数据库部署配置:
# application-prod.yml spring: datasource: url: jdbc:mysql://mysql-server:3306/tourism_db?useSSL=false username: prod_user password: ${DB_PASSWORD} jpa: hibernate: ddl-auto: validate server: port: 80806.2 小程序发布流程
测试阶段:
- 在微信开发者工具中完成功能测试
- 使用真机调试验证各功能模块
- 提交体验版供导师或同学测试
发布准备:
# 小程序代码上传 # 在开发者工具中点击"上传" # 填写版本号和项目备注审核发布:
- 登录微信公众平台提交审核
- 等待1-7个工作日审核结果
- 审核通过后发布上线
6.3 功能测试用例
用户登录测试:
// 测试用例描述:验证微信登录功能 // 前置条件:小程序已授权获取用户信息 // 测试步骤: // 1. 点击登录按钮 // 2. 授权用户信息 // 3. 验证登录状态 // 预期结果:登录成功,显示用户昵称和头像景点浏览测试:
// 测试用例描述:验证景点列表和详情浏览 // 前置条件:网络连接正常 // 测试步骤: // 1. 进入景点列表页 // 2. 滑动浏览列表 // 3. 点击进入详情页 // 4. 查看图片和详细信息 // 预期结果:数据加载正常,图片显示清晰7. 常见问题与解决方案
7.1 开发环境问题
问题1:微信开发者工具无法真机调试
- 现象:预览二维码无法扫描或提示网络错误
- 原因:网络设置问题或开发者账号权限不足
- 解决方案:
- 检查电脑和手机是否在同一WiFi网络
- 确认开发者工具登录账号与小程序的开发者权限一致
- 尝试重启开发者工具或更换网络环境
问题2:Spring Boot服务无法连接MySQL
- 现象:应用启动时报数据库连接错误
- 原因:数据库配置错误或服务未启动
- 解决方案:
# 检查application.yml配置 spring: datasource: url: jdbc:mysql://localhost:3306/tourism_db?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 正确密码 driver-class-name: com.mysql.cj.jdbc.Driver- 确认MySQL服务已启动
- 检查数据库名、用户名、密码是否正确
- 验证MySQL版本与驱动兼容性
7.2 业务逻辑问题
问题3:微信登录获取不到openid
- 现象:登录接口返回"微信登录失败"
- 原因:AppID和AppSecret配置错误或code失效
- 解决方案:
// 确保小程序AppID配置正确 // 检查微信开发者工具中的AppID设置 // 验证后端配置的AppSecret是否正确问题4:图片上传失败
- 现象:上传图片时提示权限错误或网络错误
- 原因:服务器配置问题或域名未备案
- 解决方案:
- 确认服务器文件目录有写权限
- 检查域名是否完成ICP备案
- 验证图片大小是否符合限制
7.3 性能优化建议
数据库优化:
-- 为常用查询字段添加索引 CREATE INDEX idx_spot_name ON scenic_spots(name); CREATE INDEX idx_spot_address ON scenic_spots(address); CREATE INDEX idx_order_user ON orders(user_id);前端优化措施:
// 图片懒加载实现 Page({ onReachBottom: function() { // 分批加载数据,避免一次性加载过多 this.loadMoreData(); }, // 使用缓存减少请求 getSpotDetail: function(id) { const cacheKey = `spot_${id}`; let spot = wx.getStorageSync(cacheKey); if (!spot) { // 从服务器获取并缓存 spot = this.fetchSpotDetail(id); wx.setStorageSync(cacheKey, spot); } return spot; } });8. 项目扩展与进阶功能
8.1 智能推荐功能
基于用户行为实现个性化推荐:
@Service public class RecommendationService { public List<ScenicSpot> recommendSpots(Long userId) { // 基于协同过滤算法 List<Long> similarUsers = findSimilarUsers(userId); List<Long> recommendedSpotIds = findPopularSpots(similarUsers); return spotRepository.findByIdIn(recommendedSpotIds); } private List<Long> findSimilarUsers(Long userId) { // 实现用户相似度计算 // 基于浏览历史、收藏行为等 return Collections.emptyList(); } }8.2 地图导航集成
集成腾讯地图实现导航功能:
// 地图组件集成 Page({ onNavigate: function() { const spot = this.data.spot; wx.getLocation({ type: 'gcj02', success: (res) => { const startLat = res.latitude; const startLng = res.longitude; const endLat = spot.latitude; const endLng = spot.longitude; // 调用地图导航 wx.openLocation({ latitude: endLat, longitude: endLng, name: spot.name, address: spot.address }); } }); } });8.3 后台管理系统
使用Vue+Element UI开发管理后台:
<template> <div class="admin-container"> <el-table :data="spotList"> <el-table-column prop="name" label="景点名称"></el-table-column> <el-table-column prop="address" label="地址"></el-table-column> <el-table-column prop="price" label="价格"></el-table-column> <el-table-column label="操作"> <template slot-scope="scope"> <el-button @click="editSpot(scope.row)">编辑</el-button> <el-button type="danger" @click="deleteSpot(scope.row)">删除</el-button> </template> </el-table-column> </el-table> </div> </template>这个特色旅游小程序项目涵盖了微信小程序开发的全流程,从需求分析到技术实现,再到部署测试,为计算机专业毕业设计提供了完整的参考方案。项目采用主流技术栈,代码结构清晰,功能模块完整,既适合作为学习练手项目,也具备进一步商业化的潜力。
在实际开发过程中,建议先完成核心功能的最小可行版本,再逐步添加扩展功能。遇到技术难题时,可以查阅微信官方文档和Spring Boot官方指南,这两个技术栈的社区资源都非常丰富。