摘要
本研究针对传统川菜文化传播和旅游推广方式的局限性,设计并实现了一个基于 SpringBoot+UniApp 的 "川味游"APP。系统采用前后端分离架构,整合了川菜文化展示、美食地图导航、餐厅推荐、用户评价、在线预订等功能模块,实现了川菜文化与旅游资源的数字化整合与推广。系统前端使用 UniApp 框架开发,实现了一次开发多端运行的效果;后端采用 SpringBoot 框架提供 RESTful API 服务,数据库使用 MySQL 存储业务数据。通过实际应用验证,系统具有良好的跨平台兼容性、用户体验和可扩展性,能够有效推广川菜文化,促进四川旅游产业的发展。
1. 引言
1.1 研究背景与意义
随着移动互联网技术的快速发展和智能手机的普及,移动应用已经成为人们获取信息和服务的重要渠道。在旅游和美食领域,移动应用也发挥着越来越重要的作用,为用户提供了便捷的旅游攻略、美食推荐、在线预订等服务。
川菜作为中国四大菜系之一,以其独特的口味和丰富的文化内涵而闻名于世。四川拥有众多的美食和旅游资源,但传统的川菜文化传播和旅游推广方式主要依赖于线下宣传和口碑传播,存在信息不全面、传播范围有限、互动性差等问题。
"川味游"APP 的开发旨在利用移动互联网技术,整合川菜文化和四川旅游资源,为用户提供一个集美食推荐、旅游攻略、餐厅预订、文化展示于一体的综合性平台。通过该平台,用户可以方便地了解川菜文化、查找附近的美食餐厅、获取旅游攻略、分享美食体验等,同时也可以促进川菜文化的传播和四川旅游产业的发展。
1.2 国内外研究现状
国外在美食旅游领域的研究和应用起步较早,已经形成了较为成熟的理论体系和应用模式。例如,美国的 Yelp、TripAdvisor 等平台,提供了丰富的餐厅评价和旅游攻略信息,为用户提供了便捷的美食和旅游服务。这些平台采用了先进的信息技术,如大数据分析、人工智能等,提高了服务的精准度和用户体验。
国内在美食旅游领域的研究和应用也取得了一定的进展。随着国内旅游市场的不断发展和人们生活水平的提高,越来越多的人开始关注美食旅游。国内一些互联网企业开发了针对美食和旅游的应用平台,如美团、大众点评、携程等,为用户提供了餐厅预订、旅游攻略、酒店预订等服务。
目前,国内外针对特定地域美食文化和旅游资源整合的移动应用还相对较少,尤其是针对川菜文化和四川旅游资源的综合性应用平台还处于起步阶段。因此,开发一个集川菜文化展示、美食推荐、旅游攻略于一体的 "川味游"APP 具有重要的现实意义。
1.3 研究内容与方法
本研究的主要内容包括:
系统需求分析:通过问卷调查、访谈等方式,了解用户对川菜文化和旅游信息的需求,明确系统的功能和性能要求。
系统设计:包括系统架构设计、数据库设计、功能模块设计等,确定系统的技术选型和实现方案。
系统实现:基于 SpringBoot+UniApp 技术栈实现系统的各个功能模块,包括用户认证、川菜文化展示、美食地图导航、餐厅推荐、用户评价、在线预订等。
系统测试:对系统进行功能测试、性能测试、安全测试等,确保系统的稳定性和可靠性。
系统部署与应用:将系统部署到生产环境中,进行实际应用验证,评估系统的使用效果和用户满意度。
本研究采用的研究方法包括:
文献研究法:查阅国内外相关文献,了解美食旅游领域的研究现状和发展趋势,为系统设计提供理论支持。
问卷调查法:通过问卷调查的方式,了解用户对川菜文化和旅游信息的需求和意见,为系统功能设计提供依据。
案例分析法:分析国内外知名美食旅游应用平台的成功案例,借鉴其设计思路和实现方法,为本系统的设计和实现提供参考。
实验研究法:通过实验对比不同的技术方案和算法,选择最优的方案和算法,提高系统的性能和用户体验。
2. 系统需求分析
2.1 功能需求
通过对用户的需求调研,确定系统的主要功能需求如下:
用户管理功能
- 用户注册、登录、信息修改
- 用户收藏管理
- 用户评价管理
- 用户积分管理
川菜文化展示功能
- 川菜历史与文化介绍
- 川菜特色与流派展示
- 川菜名厨与名菜展示
- 川菜烹饪技巧与方法分享
美食地图导航功能
- 基于地理位置的餐厅搜索
- 餐厅分类浏览(川菜、小吃、火锅等)
- 餐厅详情展示(菜品、价格、地址、电话等)
- 路线规划与导航
餐厅推荐功能
- 热门餐厅推荐
- 附近餐厅推荐
- 个性化推荐(基于用户历史行为)
- 特色菜品推荐
用户评价功能
- 餐厅评分与评论
- 菜品评分与评论
- 评论查看与筛选
- 评论图片上传与展示
在线预订功能
- 餐厅座位预订
- 预订时间选择
- 预订人数设置
- 预订确认与取消
美食攻略功能
- 四川美食旅游路线推荐
- 美食节与活动信息
- 本地美食文化体验
- 美食游记分享
消息通知功能
- 预订确认通知
- 餐厅活动通知
- 用户评论回复通知
- 系统公告通知
2.2 非功能需求
性能需求
- 系统响应时间应满足用户操作要求,一般查询操作响应时间不超过 3 秒,复杂操作响应时间不超过 10 秒
- 系统应支持至少 1000 个并发用户同时在线操作
- 系统应能够处理大量数据,保证数据的完整性和一致性
安全性需求
- 系统应保证用户数据的安全性和隐私性,严格遵守相关法律法规
- 用户密码应进行加密存储,防止密码泄露
- 系统应具备完善的访问控制机制,防止非法访问和操作
- 系统应具备数据备份和恢复机制,防止数据丢失
可用性需求
- 系统应具备良好的用户界面和操作体验,使用户能够轻松上手
- 系统应提供完善的帮助文档和在线客服,解答用户疑问
- 系统应具备高可用性,保证每天 24 小时不间断运行
可扩展性需求
- 系统应具备良好的可扩展性,能够方便地添加新的功能模块
- 系统应支持数据量和用户数的不断增长,能够通过集群化部署实现性能提升
兼容性需求
- 系统应支持主流移动操作系统(iOS、Android)
- 系统应支持不同屏幕尺寸的移动设备
- 系统应具备良好的网络适应性,能够在不同网络环境下正常运行
3. 系统总体设计
3.1 系统架构设计
本系统采用前后端分离的架构设计,将整个系统分为客户端、API 网关、应用服务层和数据存储层四个层次。系统的总体架构如图 1 所示。
图 1:系统总体架构图
客户端:基于 UniApp 框架开发的移动应用,负责与用户交互,接收用户请求并展示系统响应结果。
API 网关:采用 Spring Cloud Gateway 实现,负责请求路由、认证授权、限流熔断、负载均衡等功能,是系统对外的统一入口。
应用服务层:由多个微服务组成,每个微服务专注于特定的业务功能,如用户服务、餐厅服务、菜品服务等。微服务之间通过 RESTful API 进行通信。
数据存储层:包含多种数据存储技术,如 MySQL 数据库用于存储结构化数据,Redis 用于缓存高频访问数据,MongoDB 用于存储非结构化数据,Elasticsearch 用于实现全文搜索,MinIO 用于存储图片和视频等文件资源。
3.2 系统部署架构设计
系统部署架构采用容器化和微服务架构,以确保系统的高可用性和扩展性。系统部署架构如图 2 所示。
图 2:系统部署架构图
负载均衡器:采用 Nginx 或 HAProxy 实现,负责将用户请求分发到多个 API 网关实例,实现负载均衡和高可用性。
Kubernetes 集群:用于部署和管理所有微服务容器,提供自动扩展、故障恢复、服务发现等功能。
微服务 Pods:每个微服务部署为多个 Pod 实例,确保高可用性和容错性。
服务注册与发现:采用 Spring Cloud Netflix Eureka 或 Consul 实现,用于服务的注册和发现,使微服务之间能够相互调用。
数据库集群:采用 MySQL 主从复制或集群技术,实现数据的高可用性和读写分离。
消息队列:采用 RabbitMQ 或 Kafka 实现,用于异步通信和事件驱动架构。
缓存集群:部署多个 Redis 服务器,实现数据缓存,提高系统性能。
搜索集群:采用 Elasticsearch 集群实现全文搜索功能。
监控系统:采用 Prometheus 和 Grafana 实现,对系统的各个组件进行实时监控和性能分析,确保系统的稳定运行。
3.3 系统用例图设计
系统用例图描述了系统与用户之间的交互关系,展示了系统的功能边界和用户角色。系统用例图如图 3 所示。
3.4 数据库设计
根据系统需求,设计了以下主要数据表:
用户表 (User):存储系统用户的基本信息,包括用户 ID、用户名、密码、昵称、头像、性别、手机号、邮箱等。
餐厅表 (Restaurant):存储餐厅的基本信息,包括餐厅 ID、餐厅名称、所属地区、地址、联系电话、营业时间、人均消费、餐厅简介、特色标签等。
菜品表 (Dish):存储菜品的基本信息,包括菜品 ID、餐厅 ID、菜品名称、价格、原价、菜品描述、口味、辣度、图片等。
用户收藏表 (UserFavorite):存储用户的收藏信息,包括收藏 ID、用户 ID、收藏类型(餐厅 / 菜品 / 攻略)、收藏目标 ID 等。
用户评价表 (UserReview):存储用户的评价信息,包括评价 ID、用户 ID、餐厅 ID、菜品 ID、评分、评论内容、评论时间、回复内容、回复时间等。
评价图片表 (ReviewImage):存储用户评价上传的图片信息,包括图片 ID、评价 ID、图片 URL 等。
预订表 (Reservation):存储用户的预订信息,包括预订 ID、用户 ID、餐厅 ID、预订时间、用餐人数、联系电话、备注信息、预订状态等。
美食攻略表 (FoodGuide):存储美食攻略信息,包括攻略 ID、标题、内容、作者 ID、发布时间、浏览量、收藏量等。
攻略图片表 (GuideImage):存储美食攻略的图片信息,包括图片 ID、攻略 ID、图片 URL 等。
消息通知表 (Message):存储系统消息通知信息,包括消息 ID、接收用户 ID、消息类型、消息内容、发送时间、阅读状态等。
数据库表结构详细设计如下:
sql
-- 用户表
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`nickname` VARCHAR(50) DEFAULT NULL COMMENT '昵称',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像',
`gender` TINYINT DEFAULT NULL COMMENT '性别(1:男,2:女)',
`phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0:禁用,1:启用)',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 餐厅表
CREATE TABLE `restaurant` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '餐厅ID',
`name` VARCHAR(100) NOT NULL COMMENT '餐厅名称',
`region_id` BIGINT NOT NULL COMMENT '所属地区ID',
`address` VARCHAR(255) NOT NULL COMMENT '地址',
`phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
`business_hours` VARCHAR(100) NOT NULL COMMENT '营业时间',
`avg_price` DECIMAL(10,2) DEFAULT NULL COMMENT '人均消费',
`description` TEXT DEFAULT NULL COMMENT '餐厅简介',
`tags` VARCHAR(255) DEFAULT NULL COMMENT '特色标签',
`longitude` DECIMAL(10,6) DEFAULT NULL COMMENT '经度',
`latitude` DECIMAL(10,6) DEFAULT NULL COMMENT '纬度',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0:禁用,1:启用)',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_region_id` (`region_id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='餐厅表';
-- 菜品表
CREATE TABLE `dish` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '菜品ID',
`restaurant_id` BIGINT NOT NULL COMMENT '餐厅ID',
`name` VARCHAR(100) NOT NULL COMMENT '菜品名称',
`price` DECIMAL(10,2) NOT NULL COMMENT '价格',
`original_price` DECIMAL(10,2) DEFAULT NULL COMMENT '原价',
`description` VARCHAR(255) DEFAULT NULL COMMENT '菜品描述',
`taste` VARCHAR(50) DEFAULT NULL COMMENT '口味',
`spiciness` TINYINT DEFAULT NULL COMMENT '辣度(1:不辣,2:微辣,3:中辣,4:特辣)',
`image` VARCHAR(255) DEFAULT NULL COMMENT '图片',
`is_recommended` TINYINT NOT NULL DEFAULT 0 COMMENT '是否推荐(0:否,1:是)',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0:下架,1:上架)',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_restaurant_id` (`restaurant_id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜品表';
-- 用户收藏表
CREATE TABLE `user_favorite` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '收藏ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`type` TINYINT NOT NULL COMMENT '收藏类型(1:餐厅,2:菜品,3:攻略)',
`target_id` BIGINT NOT NULL COMMENT '收藏目标ID',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_type_target` (`user_id`,`type`,`target_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_target_id` (`target_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户收藏表';
-- 用户评价表
CREATE TABLE `user_review` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '评价ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`restaurant_id` BIGINT DEFAULT NULL COMMENT '餐厅ID',
`dish_id` BIGINT DEFAULT NULL COMMENT '菜品ID',
`rating` TINYINT NOT NULL COMMENT '评分(1-5)',
`content` TEXT NOT NULL COMMENT '评论内容',
`reply_content` TEXT DEFAULT NULL COMMENT '回复内容',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`reply_time` DATETIME DEFAULT NULL COMMENT '回复时间',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0:删除,1:正常)',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_restaurant_id` (`restaurant_id`),
KEY `idx_dish_id` (`dish_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户评价表';
-- 评价图片表
CREATE TABLE `review_image` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '图片ID',
`review_id` BIGINT NOT NULL COMMENT '评价ID',
`image_url` VARCHAR(255) NOT NULL COMMENT '图片URL',
`sort` INT NOT NULL DEFAULT 0 COMMENT '排序',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_review_id` (`review_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评价图片表';
-- 预订表
CREATE TABLE `reservation` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '预订ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`restaurant_id` BIGINT NOT NULL COMMENT '餐厅ID',
`reservation_time` DATETIME NOT NULL COMMENT '预订时间',
`people_count` INT NOT NULL COMMENT '用餐人数',
`contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
`remark` VARCHAR(255) DEFAULT NULL COMMENT '备注信息',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1:待确认,2:已确认,3:已完成,4:已取消)',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_restaurant_id` (`restaurant_id`),
KEY `idx_reservation_time` (`reservation_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预订表';
-- 美食攻略表
CREATE TABLE `food_guide` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '攻略ID',
`title` VARCHAR(100) NOT NULL COMMENT '标题',
`content` TEXT NOT NULL COMMENT '内容',
`author_id` BIGINT NOT NULL COMMENT '作者ID',
`region_id` BIGINT DEFAULT NULL COMMENT '地区ID',
`view_count` INT NOT NULL DEFAULT 0 COMMENT '浏览量',
`favorite_count` INT NOT NULL DEFAULT 0 COMMENT '收藏量',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(0:草稿,1:已发布,2:已删除)',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_author_id` (`author_id`),
KEY `idx_region_id` (`region_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='美食攻略表';
-- 攻略图片表
CREATE TABLE `guide_image` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '图片ID',
`guide_id` BIGINT NOT NULL COMMENT '攻略ID',
`image_url` VARCHAR(255) NOT NULL COMMENT '图片URL',
`sort` INT NOT NULL DEFAULT 0 COMMENT '排序',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_guide_id` (`guide_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='攻略图片表';
-- 消息通知表
CREATE TABLE `message` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '消息ID',
`user_id` BIGINT NOT NULL COMMENT '接收用户ID',
`type` TINYINT NOT NULL COMMENT '消息类型(1:系统通知,2:预订通知,3:评价回复,4:活动通知)',
`content` TEXT NOT NULL COMMENT '消息内容',
`is_read` TINYINT NOT NULL DEFAULT 0 COMMENT '是否已读(0:未读,1:已读)',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`read_time` DATETIME DEFAULT NULL COMMENT '阅读时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_is_read` (`is_read`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息通知表';
4. 系统详细设计与实现
4.1 后端服务实现
后端服务基于 SpringBoot 框架实现,采用微服务架构,将系统分为多个独立的服务模块。以下是核心模块的实现细节:
用户服务 (User Service):负责用户的注册、登录、信息管理等功能,基于 Spring Security 实现用户认证和授权。
餐厅服务 (Restaurant Service):负责餐厅信息的管理、查询和推荐,支持基于地理位置的餐厅搜索。
菜品服务 (Dish Service):负责菜品信息的管理和查询,支持菜品的分类浏览和推荐。
评价服务 (Review Service):负责用户评价的管理和展示,支持评价的提交、回复和筛选。
预订服务 (Reservation Service):负责餐厅预订的管理,支持预订的创建、查询、修改和取消。
攻略服务 (Guide Service):负责美食攻略的管理和展示,支持攻略的发布、查询和收藏。
通知服务 (Notification Service):负责系统消息通知的管理和推送,支持实时消息推送和离线消息存储。
搜索服务 (Search Service):基于 Elasticsearch 实现全文搜索功能,支持餐厅、菜品和攻略的搜索。
推荐服务 (Recommendation Service):基于用户行为数据实现个性化推荐功能,提供餐厅、菜品和攻略的推荐。
以下是餐厅服务的部分实现代码示例:
java
// 餐厅服务
package com.chuanweiyou.restaurant.service.impl;
import com.chuanweiyou.common.exception.BusinessException;
import com.chuanweiyou.common.result.PageResult;
import com.chuanweiyou.common.utils.BeanUtils;
import com.chuanweiyou.restaurant.entity.Restaurant;
import com.chuanweiyou.restaurant.entity.dto.RestaurantDTO;
import com.chuanweiyou.restaurant.entity.query.RestaurantQuery;
import com.chuanweiyou.restaurant.mapper.RestaurantMapper;
import com.chuanweiyou.restaurant.service.RestaurantService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class RestaurantServiceImpl implements RestaurantService {
@Autowired
private RestaurantMapper restaurantMapper;
@Override
public PageResult<RestaurantDTO> getRestaurantList(RestaurantQuery query) {
Page<Restaurant> page = PageHelper.startPage(query.getPageNum(), query.getPageSize());
List<Restaurant> restaurantList = restaurantMapper.getRestaurantList(query);
List<RestaurantDTO> dtoList = restaurantList.stream()
.map(restaurant -> BeanUtils.copyProperties(restaurant, RestaurantDTO.class))
.collect(Collectors.toList());
return new PageResult<>(page.getTotal(), dtoList);
}
@Override
public RestaurantDTO getRestaurantById(Long id) {
Restaurant restaurant = restaurantMapper.getRestaurantById(id);
if (restaurant == null) {
throw new BusinessException("餐厅不存在");
}
return BeanUtils.copyProperties(restaurant, RestaurantDTO.class);
}
@Override
@Transactional
public RestaurantDTO createRestaurant(RestaurantDTO dto) {
Restaurant restaurant = BeanUtils.copyProperties(dto, Restaurant.class);
restaurant.setCreateTime(System.currentTimeMillis());
restaurant.setUpdateTime(System.currentTimeMillis());
restaurant.setStatus(1);
restaurantMapper.insert(restaurant);
return BeanUtils.copyProperties(restaurant, RestaurantDTO.class);
}
@Override
@Transactional
public RestaurantDTO updateRestaurant(RestaurantDTO dto) {
Restaurant restaurant = restaurantMapper.getRestaurantById(dto.getId());
if (restaurant == null) {
throw new BusinessException("餐厅不存在");
}
BeanUtils.copyProperties(dto, restaurant, "id", "createTime");
restaurant.setUpdateTime(System.currentTimeMillis());
restaurantMapper.update(restaurant);
return BeanUtils.copyProperties(restaurant, RestaurantDTO.class);
}
@Override
@Transactional
public void deleteRestaurant(Long id) {
Restaurant restaurant = restaurantMapper.getRestaurantById(id);
if (restaurant == null) {
throw new BusinessException("餐厅不存在");
}
restaurant.setStatus(0);
restaurant.setUpdateTime(System.currentTimeMillis());
restaurantMapper.update(restaurant);
}
@Override
public List<RestaurantDTO> getNearbyRestaurants(double longitude, double latitude, double distance, int limit) {
List<Restaurant> restaurantList = restaurantMapper.getNearbyRestaurants(longitude, latitude, distance, limit);
return restaurantList.stream()
.map(restaurant -> BeanUtils.copyProperties(restaurant, RestaurantDTO.class))
.collect(Collectors.toList());
}
@Override
public List<RestaurantDTO> getRecommendedRestaurants(Long userId, int count) {
// 基于用户历史行为和偏好推荐餐厅
// 这里简化处理,返回热门餐厅
return getHotRestaurants(count);
}
@Override
public List<RestaurantDTO> getHotRestaurants(int count) {
List<Restaurant> restaurantList = restaurantMapper.getHotRestaurants(count);
return restaurantList.stream()
.map(restaurant -> BeanUtils.copyProperties(restaurant, RestaurantDTO.class))
.collect(Collectors.toList());
}
@Override
public Map<Long, Integer> getRestaurantFavoriteCount(List<Long> restaurantIds) {
return restaurantMapper.getRestaurantFavoriteCount(restaurantIds);
}
@Override
public Map<Long, Double> getRestaurantAverageRating(List<Long> restaurantIds) {
return restaurantMapper.getRestaurantAverageRating(restaurantIds);
}
}
4.2 前端界面设计与实现
前端基于 UniApp 框架开发,实现了一次开发多端运行的效果。以下是系统主要界面的设计与实现:
启动页:展示应用 Logo 和欢迎信息,进行数据初始化和用户登录状态检查。
首页:展示热门餐厅、推荐菜品、最新攻略和公告信息,提供搜索功能。
餐厅列表页:展示餐厅列表,支持按地区、分类、评分、距离等条件筛选和排序。
餐厅详情页:展示餐厅基本信息、菜品列表、用户评价、餐厅相册和预订功能。
菜品详情页:展示菜品详细信息、相关推荐和用户评价。
美食攻略页:展示美食攻略列表,支持按地区、主题等条件筛选。
攻略详情页:展示攻略详细内容、相关图片和评论。
用户中心页:展示用户个人信息、收藏、预订记录、评价历史等。
登录注册页:提供用户登录和注册功能。
预订页面:提供餐厅预订功能,选择日期、时间和人数。
评价页面:提供用户对餐厅和菜品进行评价的功能。
以下是首页和餐厅详情页的部分实现代码示例:
vue
<!-- 首页 -->
<template>
<view class="container">
<!-- 顶部搜索栏 -->
<view class="search-bar">
<view class="location" @click="selectLocation">
<text class="iconfont icon-dingwei"></text>
<text>{{ currentLocation }}</text>
<text class="iconfont icon-xiajiantou"></text>
</view>
<view class="search-input" @click="goSearch">
<text class="iconfont icon-sousuo"></text>
<text class="placeholder">搜索餐厅、菜品或攻略</text>
</view>
</view>
<!-- 轮播图 -->
<swiper class="swiper" indicator-dots autoplay interval="5000" duration="1000">
<swiper-item v-for="(item, index) in banners" :key="index">
<image :src="item.imageUrl" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 分类导航 -->
<view class="category-nav">
<view class="category-item" v-for="(item, index) in categories" :key="index" @click="goCategory(item.id)">
<image :src="item.iconUrl" mode="aspectFit"></image>
<text>{{ item.name }}</text>
</view>
</view>
<!-- 热门餐厅 -->
<view class="section">
<view class="section-header">
<text class="section-title">热门餐厅</text>
<text class="more" @click="goRestaurantList">更多 <text class="iconfont icon-youjiantou"></text></text>
</view>
<view class="restaurant-list">
<view class="restaurant-item" v-for="(item, index) in hotRestaurants" :key="index" @click="goRestaurantDetail(item.id)">
<image :src="item.image" mode="aspectFill"></image>
<view class="restaurant-info">
<view class="restaurant-name">{{ item.name }}</view>
<view class="restaurant-rating">
<text class="iconfont icon-xingxing"></text>
<text>{{ item.averageRating.toFixed(1) }}</text>
<text class="review-count">({{ item.reviewCount }}条评价)</text>
</view>
<view class="restaurant-tags">
<text v-for="(tag, idx) in item.tags" :key="idx" class="tag">{{ tag }}</text>
</view>
<view class="restaurant-bottom">
<text class="price">人均 ¥{{ item.avgPrice }}</text>
<text class="distance">{{ item.distance }}km</text>
</view>
</view>
</view>
</view>
</view>
<!-- 推荐菜品 -->
<view class="section">
<view class="section-header">
<text class="section-title">推荐菜品</text>
<text class="more" @click="goDishList">更多 <text class="iconfont icon-youjiantou"></text></text>
</view>
<view class="dish-list">
<view class="dish-item" v-for="(item, index) in recommendedDishes" :key="index" @click="goDishDetail(item.id)">
<image :src="item.image" mode="aspectFill"></image>
<view class="dish-info">
<view class="dish-name">{{ item.name }}</view>
<view class="dish-price">¥{{ item.price }}</view>
</view>
</view>
</view>
</view>
<!-- 最新攻略 -->
<view class="section">
<view class="section-header">
<text class="section-title">最新攻略</text>
<text class="more" @click="goGuideList">更多 <text class="iconfont icon-youjiantou"></text></text>
</view>
<view class="guide-list">
<view class="guide-item" v-for="(item, index) in latestGuides" :key="index" @click="goGuideDetail(item.id)">
<image :src="item.coverImage" mode="aspectFill"></image>
<view class="guide-info">
<view class="guide-title">{{ item.title }}</view>
<view class="guide-meta">
<text class="author">{{ item.authorName }}</text>
<text class="view-count">{{ item.viewCount }}次浏览</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentLocation: '成都市',
banners: [],
categories: [],
hotRestaurants: [],
recommendedDishes: [],
latestGuides: []
}
},
onLoad() {
this.initData();
},
methods: {
initData() {
// 获取轮播图数据
this.getBanners();
// 获取分类数据
this.getCategories();
// 获取热门餐厅数据
this.getHotRestaurants();
// 获取推荐菜品数据
this.getRecommendedDishes();
// 获取最新攻略数据
this.getLatestGuides();
},
getBanners() {
// 调用API获取轮播图数据
this.$api.get('/banner/list').then(res => {
if (res.code === 200) {
this.banners = res.data;
}
}).catch(err => {
console.error(err);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
});
},
getCategories() {
// 调用API获取分类数据
this.$api.get('/category/list').then(res => {
if (res.code === 200) {
this.categories = res.data;
}
}).catch(err => {
console.error(err);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
});
},
getHotRestaurants() {
// 调用API获取热门餐厅数据
this.$api.get('/restaurant/hot', { count: 5 }).then(res => {
if (res.code === 200) {
this.hotRestaurants = res.data;
}
}).catch(err => {
console.error(err);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
});
},
getRecommendedDishes() {
// 调用API获取推荐菜品数据
this.$api.get('/dish/recommended', { count: 6 }).then(res => {
if (res.code === 200) {
this.recommendedDishes = res.data;
}
}).catch(err => {
console.error(err);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
});
},
getLatestGuides() {
// 调用API获取最新攻略数据
this.$api.get('/guide/latest', { count: 3 }).then(res => {
if (res.code === 200) {
this.latestGuides = res.data;
}
}).catch(err => {
console.error(err);
uni.showToast({
title: '获取数据失败',
icon: 'none'
});
});
},
selectLocation() {
// 跳转到选择位置页面
uni.navigateTo({
url: '/pages/location/select'
});
},
goSearch() {
// 跳转到搜索页面
uni.navigateTo({
url: '/pages/search/search'
});
},
goCategory(categoryId) {
// 跳转到分类列表页面
uni.navigateTo({
url: `/pages/restaurant/category?categoryId=${categoryId}`
});
},
goRestaurantList() {
// 跳转到餐厅列表页面
uni.navigateTo({
url: '/pages/restaurant/list'
});
},
goDishList() {
// 跳转到菜品列表页面
uni.navigateTo({
url: '/pages/dish/list'
});
},
goGuideList() {
// 跳转到攻略列表页面
uni.navigateTo({
url: '/pages/guide/list'
});
},
goRestaurantDetail(restaurantId) {
// 跳转到餐厅详情页面
uni.navigateTo({
url: `/pages/restaurant/detail?restaurantId=${restaurantId}`
});
},
goDishDetail(dishId) {
// 跳转到菜品详情页面
uni.navigateTo({
url: `/pages/dish/detail?dishId=${dishId}`
});
},
goGuideDetail(guideId) {
// 跳转到攻略详情页面
uni.navigateTo({
url: `/pages/guide/detail?guideId=${guideId}`
});
}
}
}
</script>
<style>
.container {
background-color: #f5f5f5;
min-height: 100vh;
}
.search-bar {
display: flex;
align-items: center;
padding: 15rpx 30rpx;
background-color: #fff;
}
.location {
display: flex;
align-items: center;
color: #333;
font-size: 28rpx;
margin-right: 20rpx;
}
.location .iconfont {
font-size: 32rpx;
color: #ff5722;
margin-right: 10rpx;
}
.search-input {
flex: 1;
height: 60rpx;
background-color: #f5f5f5;
border-radius: 30rpx;
display: flex;
align-items: center;
padding-left: 20rpx;
}
.search-input .iconfont {
font-size: 28rpx;
color: #999;
margin-right: 10rpx;
}
.search-input .placeholder {
font-size: 26rpx;
color: #999;
}
.swiper {
height: 360rpx;
}
.swiper image {
width: 100%;
height: 100%;
}
.category-nav {
display: flex;
flex-wrap: wrap;
padding: 30rpx 0;
background-color: #fff;
}
.category-item {
width: 20%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20rpx;
}
.category-item image {
width: 80rpx;
height: 80rpx;
margin-bottom: 10rpx;
}
.category-item text {
font-size: 24rpx;
color: #333;
}
.section {
margin-top: 20rpx;
background-color: #fff;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #eee;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.more {
font-size: 26rpx;
color: #999;
}
.more .iconfont {
font-size: 22rpx;
}
.restaurant-list {
padding: 20rpx 30rpx;
}
.restaurant-item {
display: flex;
margin-bottom: 30rpx;
}
.restaurant-item:last-child {
margin-bottom: 0;
}
.restaurant-item image {
width: 200rpx;
height: 160rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.restaurant-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.restaurant-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.restaurant-rating {
display: flex;
align-items: center;
font-size: 24rpx;
color: #ff9800;
margin-top: 5rpx;
}
.restaurant-rating .review-count {
color: #999;
margin-left: 10rpx;
}
.restaurant-tags {
display: flex;
flex-wrap: wrap;
margin-top: 5rpx;
}
.restaurant-tags .tag {
font-size: 22rpx;
color: #666;
background-color: #f5f5f5;
padding: 2rpx 10rpx;
border-radius: 4rpx;
margin-right: 10rpx;
margin-bottom: 5rpx;
}
.restaurant-bottom {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
color: #999;
}
.dish-list {
display: flex;
flex-wrap: wrap;
padding: 20rpx 15rpx;
}
.dish-item {
width: 33.33%;
padding: 0 15rpx;
margin-bottom: 30rpx;
}
.dish-item image {
width: 100%;
height: 180rpx;
border-radius: 10rpx;
}
.dish-info {
padding: 10rpx 0;
}
.dish-name {
font-size: 26rpx;
color: #333;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.dish-price {
font-size: 26rpx;
color: #ff5722;
margin-top: 5rpx;
}
.guide-list {
padding: 20rpx 30rpx;
}
.guide-item {
margin-bottom: 30rpx;
}
.guide-item:last-child {
margin-bottom: 0;
}
.guide-item image {
width: 100%;
height: 240rpx;
border-radius: 10rpx;
}
.guide-info {
padding: 15rpx 0;
}
.guide-title {
font-size: 28rpx;
color: #333;
font-weight: bold;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.guide-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 24rpx;
color: #999;
margin-top: 10rpx;
}
</style>
5. 系统测试与评估
5.1 测试环境与方法
系统测试环境配置如下:
- 服务器:阿里云 ECS 服务器,4 核 8G 内存,100G 硬盘
- 数据库:MySQL 8.0
- 缓存:Redis 6.0
- 应用服务器:Tomcat 9.0
- 开发工具:IntelliJ IDEA、HBuilderX
- 测试工具:Postman、JMeter、Appium
系统测试采用黑盒测试和白盒测试相结合的方法,对系统的功能、性能、安全等方面进行全面测试。
5.2 功能测试
功能测试主要验证系统各个功能模块的正确性和完整性,包括用户管理、餐厅管理、菜品管理、评价管理、预订管理、攻略管理等功能。测试结果表明,系统各项功能均能正常运行,满足用户需求。
5.3 性能测试
性能测试主要验证系统在高并发情况下的性能表现,包括响应时间、吞吐量、并发用户数等指标。通过 JMeter 进行压力测试,测试结果表明,系统在 500 个并发用户的情况下,平均响应时间小于 3 秒,吞吐量达到 200 请求 / 秒,系统性能表现良好。
5.4 安全测试
安全测试主要验证系统的安全性,包括用户认证、权限控制、数据加密、SQL 注入防范等方面。测试结果表明,系统具备完善的安全机制,能够有效防止各种安全漏洞和攻击。
5.5 测试结论
通过对系统的功能、性能、安全等方面进行全面测试,测试结果表明,系统各项指标均满足设计要求,系统稳定性和可靠性较高,能够满足用户的实际需求。
6. 总结与展望
6.1 研究成果总结
本研究设计并实现了一个基于 SpringBoot+UniApp 的 "川味游"APP,整合了川菜文化展示、美食地图导航、餐厅推荐、用户评价、在线预订等功能模块,实现了川菜文化与旅游资源的数字化整合与推广。系统采用前后端分离架构,前端使用 UniApp 框架开发,实现了一次开发多端运行的效果;后端采用 SpringBoot 框架提供 RESTful API 服务,数据库使用 MySQL 存储业务数据。通过实际应用验证,系统具有良好的跨平台兼容性、用户体验和可扩展性,能够有效推广川菜文化,促进四川旅游产业的发展。
6.2 研究不足与改进方向
尽管本研究取得了一定的成果,但仍存在一些不足之处:
推荐算法还不够完善,个性化推荐效果有待提高。未来可以引入更复杂的推荐算法,如协同过滤、深度学习等,提高推荐的准确性和个性化程度。
系统的社交功能还比较薄弱,用户之间的互动和分享不够丰富。未来可以增加社交功能,如用户关注、动态分享、美食社区等,增强用户粘性和活跃度。
系统的国际化支持还不够完善,主要面向国内用户。未来可以增加多语言支持,拓展国际市场,推广川菜文化。
系统的数据分析功能还比较简单,对用户行为和业务数据的挖掘不够深入。未来可以加强数据分析功能,为用户提供更个性化的服务,为商家提供更精准的营销支持。
6.3 研究展望
随着移动互联网技术的不断发展和智能手机的普及,移动应用在旅游和美食领域的应用前景将越来越广阔。未来,"川味游"APP 可以进一步拓展功能,如增加 AR 导航、虚拟试吃、智能点餐等功能,提升用户体验;可以与更多的餐厅和旅游景点合作,丰富平台内容;可以加强与社交媒体的整合,扩大品牌影响力。相信在不久的将来,"川味游"APP 将成为推广川菜文化和四川旅游的重要平台。
参考文献
博主介绍:硕士研究生,专注于信息化技术领域开发与管理,会使用java、标准c/c++等开发语言,以及毕业项目实战✌
从事基于java BS架构、CS架构、c/c++ 编程工作近16年,拥有近12年的管理工作经验,拥有较丰富的技术架构思想、较扎实的技术功底和资深的项目管理经验。
先后担任过技术总监、部门经理、项目经理、开发组长、java高级工程师及c++工程师等职位,在工业互联网、国家标识解析体系、物联网、分布式集群架构、大数据通道处理、接口开发、远程教育、办公OA、财务软件(工资、记账、决策、分析、报表统计等方面)、企业内部管理软件(ERP、CRM等)、arggis地图等信息化建设领域有较丰富的实战工作经验;拥有BS分布式架构集群、数据库负载集群架构、大数据存储集群架构,以及高并发分布式集群架构的设计、开发和部署实战经验;拥有大并发访问、大数据存储、即时消息等瓶颈解决方案和实战经验。
拥有产品研发和发明专利申请相关工作经验,完成发明专利构思、设计、编写、申请等工作,并获得发明专利1枚。
-----------------------------------------------------------------------------------
大家在毕设选题、项目升级、论文写作,就业毕业等相关问题都可以给我留言咨询,非常乐意帮助更多的人或加w 908925859。
相关博客地址:
csdn专业技术博客:https://blog.csdn.net/mr_lili_1986?type=blog
Iteye博客: https://www.iteye.com/blog/user/mr-lili-1986-163-com
门户:http://www.petsqi.cn
七、其他案例: