在美食文化与数字消费深度融合的今天,一个能够整合信息获取与消费决策的平台显得尤为重要。本系统采用经典的SSM(Spring + Spring MVC + MyBatis)技术栈构建,旨在打造一个集美食资讯分享与餐厅预订服务于一体的垂直领域平台。该系统不仅为美食爱好者提供了内容发现与交流的空间,更通过无缝衔接的预订功能,为餐饮商家创造了精准的营销渠道,实现了从内容到消费的完整闭环。
系统采用典型的三层架构设计,表现层由Spring MVC框架负责,通过精心设计的控制器(Controller)将前端请求分发给相应的业务处理器。业务逻辑层基于Spring框架的IoC容器进行Bean管理,利用其声明式事务管理确保数据操作的一致性。数据持久层则依托MyBatis框架,通过灵活的SQL映射配置,实现了对复杂查询操作的高效处理。这种分层架构确保了系统的高内聚、低耦合特性,为后续功能扩展和维护提供了良好的基础。
在数据库设计方面,系统采用了MySQL作为数据存储解决方案,共设计10个核心数据表来支撑业务运转。其中,餐厅信息表(restaurant)的设计体现了对业务细节的深度考量:
CREATE TABLE restaurant (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
description TEXT,
address VARCHAR(200),
phone VARCHAR(20),
cuisine_type VARCHAR(50),
price_range VARCHAR(20),
opening_hours VARCHAR(100),
capacity INT,
avg_rating DECIMAL(3,2),
featured BOOLEAN DEFAULT FALSE,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
该表结构不仅包含了餐厅的基本信息字段,还设计了特色菜品标识(featured)用于内容推荐,平均评分字段(avg_rating)通过触发器或应用层逻辑实时更新,确保用户能够获取最新的评价信息。时间戳字段的自动更新机制减少了人工维护成本。
用户表(user)的设计则充分考虑了权限管理和信息安全:
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
role ENUM('ADMIN','USER','BUSINESS') DEFAULT 'USER',
profile_picture VARCHAR(255),
phone VARCHAR(20),
status ENUM('ACTIVE','INACTIVE') DEFAULT 'ACTIVE',
last_login DATETIME,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
通过角色枚举(role)字段实现灵活的权限控制,密码字段采用加密存储确保安全性,状态字段(status)支持账户的软删除操作。这种设计为多角色协同管理提供了基础支撑。
美食分享表(food_share)作为内容核心,支持丰富的媒体表现形式:
CREATE TABLE food_share (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
author_id INT NOT NULL,
restaurant_id INT,
images JSON,
tags VARCHAR(500),
view_count INT DEFAULT 0,
like_count INT DEFAULT 0,
share_count INT DEFAULT 0,
status ENUM('DRAFT','PUBLISHED','REJECTED') DEFAULT 'DRAFT',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES user(id),
FOREIGN KEY (restaurant_id) REFERENCES restaurant(id)
);
JSON类型的images字段支持存储多张图片信息,标签字段(tags)便于内容分类和检索,各种计数字段为热门内容推荐提供数据基础。外键约束确保了数据的引用完整性。
预订表(booking)的设计体现了交易业务的核心要素:
CREATE TABLE booking (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
restaurant_id INT NOT NULL,
booking_time DATETIME NOT NULL,
party_size INT NOT NULL,
special_requests TEXT,
status ENUM('PENDING','CONFIRMED','CANCELLED','COMPLETED') DEFAULT 'PENDING',
contact_phone VARCHAR(20),
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES user(id),
FOREIGN KEY (restaurant_id) REFERENCES restaurant(id)
);
状态机设计覆盖了预订的完整生命周期,特殊要求字段(special_requests)满足个性化需求,预订时间与创建时间的分离便于后续的数据分析。
在核心功能实现方面,系统通过精心设计的业务逻辑代码支撑起完整的用户体验。餐厅搜索功能结合了多条件筛选和分页查询:
@Service
public class RestaurantService {
@Autowired
private RestaurantMapper restaurantMapper;
public PageInfo<Restaurant> searchRestaurants(RestaurantSearchDTO searchDTO,
Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Restaurant> restaurants = restaurantMapper.selectByConditions(searchDTO);
return new PageInfo<>(restaurant);
}
public Restaurant getRestaurantDetail(Integer id) {
Restaurant restaurant = restaurantMapper.selectById(id);
if (restaurant != null) {
// 加载关联的菜单信息和用户评价
List<Menu> menus = menuMapper.selectByRestaurantId(id);
List<Review> reviews = reviewMapper.selectByRestaurantId(id);
restaurant.setMenus(menus);
restaurant.setReviews(reviews);
}
return restaurant;
}
}
对应的MyBatis映射文件实现了动态SQL构建:
<select id="selectByConditions" parameterType="RestaurantSearchDTO"
resultType="Restaurant">
SELECT * FROM restaurant
<where>
<if test="cuisineType != null and cuisineType != ''">
AND cuisine_type = #{cuisineType}
</if>
<if test="priceRange != null and priceRange != ''">
AND price_range = #{priceRange}
</if>
<if test="minRating != null">
AND avg_rating >= #{minRating}
</if>
<if test="keyword != null and keyword != ''">
AND (name LIKE CONCAT('%', #{keyword}, '%')
OR description LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="featured != null">
AND featured = #{featured}
</if>
</where>
ORDER BY
<choose>
<when test="sortBy == 'rating'">avg_rating DESC</when>
<when test="sortBy == 'popular'">view_count DESC</when>
<otherwise>created_time DESC</otherwise>
</choose>
</select>

美食内容分享功能支持富文本编辑和多媒体上传:
@Controller
@RequestMapping("/food-share")
public class FoodShareController {
@Autowired
private FoodShareService foodShareService;
@PostMapping("/publish")
@ResponseBody
public Result publishArticle(@Valid FoodShareDTO foodShareDTO,
MultipartFile[] images,
HttpSession session) {
User user = (User) session.getAttribute("currentUser");
if (user == null) {
return Result.error("请先登录");
}
try {
List<String> imageUrls = new ArrayList<>();
for (MultipartFile image : images) {
if (!image.isEmpty()) {
String imageUrl = fileService.uploadImage(image);
imageUrls.add(imageUrl);
}
}
foodShareDTO.setAuthorId(user.getId());
foodShareDTO.setImageUrls(imageUrls);
foodShareService.publishArticle(foodShareDTO);
return Result.success("文章发布成功");
} catch (Exception e) {
return Result.error("发布失败:" + e.getMessage());
}
}
@GetMapping("/{id}")
public String getArticleDetail(@PathVariable Integer id, Model model) {
FoodShareDetailVO article = foodShareService.getArticleDetail(id);
foodShareService.incrementViewCount(id);
model.addAttribute("article", article);
return "food-share/detail";
}
}

预订业务流程包含完整的库存校验和状态管理:
@Service
@Transactional
public class BookingService {
@Autowired
private BookingMapper bookingMapper;
@Autowired
private RestaurantMapper restaurantMapper;
@Autowired
private NotificationService notificationService;
public Result createBooking(BookingDTO bookingDTO, Integer userId) {
// 校验餐厅是否存在且营业中
Restaurant restaurant = restaurantMapper.selectById(bookingDTO.getRestaurantId());
if (restaurant == null) {
return Result.error("餐厅不存在");
}
// 校验预订时间合理性
if (bookingDTO.getBookingTime().before(new Date())) {
return Result.error("预订时间不能早于当前时间");
}
// 检查同一时间段内的预订数量
int existingBookings = bookingMapper.countBookingsInTimeRange(
bookingDTO.getRestaurantId(),
bookingDTO.getBookingTime(),
bookingDTO.getPartySize()
);
if (existingBookings + bookingDTO.getPartySize() > restaurant.getCapacity()) {
return Result.error("该时间段座位已满,请选择其他时间");
}
// 创建预订记录
Booking booking = new Booking();
BeanUtils.copyProperties(bookingDTO, booking);
booking.setUserId(userId);
booking.setStatus(BookingStatus.PENDING);
booking.setCreatedTime(new Date());
bookingMapper.insert(booking);
// 发送通知
notificationService.sendBookingConfirmation(booking);
return Result.success("预订申请已提交,等待餐厅确认");
}
public Result confirmBooking(Integer bookingId, Integer restaurantId) {
Booking booking = bookingMapper.selectById(bookingId);
if (booking == null || !booking.getRestaurantId().equals(restaurantId)) {
return Result.error("预订记录不存在");
}
booking.setStatus(BookingStatus.CONFIRMED);
bookingMapper.updateById(booking);
notificationService.sendBookingConfirmed(booking);
return Result.success("预订已确认");
}
}

后台管理系统提供了完整的内容审核和业务管理功能:
@Controller
@RequestMapping("/admin")
public class AdminController {
@Autowired
private FoodShareService foodShareService;
@Autowired
private BookingService bookingService;
@GetMapping("/content/review")
public String contentReviewPage(Model model,
@RequestParam(defaultValue = "1") Integer page) {
PageInfo<FoodShare> pageInfo = foodShareService.getPendingReviewArticles(page, 10);
model.addAttribute("pageInfo", pageInfo);
return "admin/content-review";
}
@PostMapping("/content/approve")
@ResponseBody
public Result approveArticle(Integer articleId) {
return foodShareService.approveArticle(articleId);
}
@PostMapping("/content/reject")
@ResponseBody
public Result rejectArticle(Integer articleId, String reason) {
return foodShareService.rejectArticle(articleId, reason);
}
@GetMapping("/bookings")
public String bookingManagement(Model model,
BookingQueryDTO queryDTO,
@RequestParam(defaultValue = "1") Integer page) {
PageInfo<BookingVO> pageInfo = bookingService.getBookingsByCondition(queryDTO, page, 15);
model.addAttribute("pageInfo", pageInfo);
model.addAttribute("queryDTO", queryDTO);
return "admin/booking-management";
}
}

用户认证和权限控制通过拦截器实现:
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String requestURI = request.getRequestURI();
// 公开路径不需要认证
if (isPublicPath(requestURI)) {
return true;
}
HttpSession session = request.getSession();
User user = (User) session.getAttribute("currentUser");
if (user == null) {
response.sendRedirect("/login");
return false;
}
// 管理员路径需要权限校验
if (requestURI.startsWith("/admin") && !user.getRole().equals("ADMIN")) {
response.sendError(403, "权限不足");
return false;
}
return true;
}
private boolean isPublicPath(String path) {
return path.startsWith("/login") ||
path.startsWith("/register") ||
path.startsWith("/static") ||
path.equals("/") ||
path.startsWith("/api/public");
}
}
评论系统支持多层嵌套和实时更新:
@Service
public class CommentService {
@Autowired
private CommentMapper commentMapper;
public List<CommentVO> getCommentsByArticle(Integer articleId) {
// 获取顶级评论
List<Comment> topLevelComments = commentMapper.selectByArticleIdAndParentId(articleId, null);
return topLevelComments.stream().map(comment -> {
CommentVO commentVO = convertToVO(comment);
// 递归加载子评论
loadReplies(commentVO);
return commentVO;
}).collect(Collectors.toList());
}
private void loadReplies(CommentVO parentComment) {
List<Comment> replies = commentMapper.selectByParentId(parentComment.getId());
if (!replies.isEmpty()) {
List<CommentVO> replyVOs = replies.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
parentComment.setReplies(replyVOs);
// 递归加载更深层的回复
replyVOs.forEach(this::loadReplies);
}
}
public Result addComment(CommentDTO commentDTO, Integer userId) {
Comment comment = new Comment();
BeanUtils.copyProperties(commentDTO, comment);
comment.setUserId(userId);
comment.setCreatedTime(new Date());
comment.setLikeCount(0);
commentMapper.insert(comment);
// 更新文章评论数
updateArticleCommentCount(commentDTO.getArticleId());
return Result.success("评论成功");
}
}

在系统架构优化方面,未来可以考虑以下几个方向的扩展:
首先,引入Redis缓存层能够显著提升系统性能。热门餐厅信息、用户会话数据、首页推荐内容等都可以通过缓存减少数据库访问压力。实现缓存策略时需要注意数据一致性,采用适当的过期策略和更新机制。
其次, Elasticsearch的集成将极大改善搜索体验。基于倒排索引的全文搜索能够支持更复杂的查询条件,如模糊匹配、同义词扩展、地理位置搜索等。餐厅名称、菜品描述、用户评价等内容都可以建立索引,提供更精准的搜索结果。
第三,微服务架构的改造将提升系统可扩展性。可以将用户服务、内容服务、预订服务、支付服务等拆分为独立的微服务,通过API网关进行统一管理。这种架构便于团队并行开发和部署,也提高了系统的容错能力。
第四,实时通信功能的加入将增强用户体验。通过WebSocket技术实现预订状态的实时推送、用户间的即时消息、餐厅桌位的动态更新等功能。当用户完成预订后,可以立即收到确认通知,而不需要手动刷新页面。
最后,大数据分析能力的建设将为业务决策提供支持。收集用户行为数据、预订模式、内容偏好等信息,通过数据分析生成个性化推荐、销量预测、热门趋势等洞察。这不仅能提升用户体验,也能帮助商家优化运营策略。
数据表之间的关联关系通过外键约束确保了一致性,同时为复杂的业务查询提供了基础。实体关系模型涵盖了用户、内容、交易等核心领域,支持系统的持续演进和功能扩展。
系统的配置管理通过Spring的Profile机制实现环境隔离,数据库连接池优化了资源利用效率,日志记录为问题排查和性能监控提供了必要的信息。这些基础设施的完善为系统的稳定运行提供了保障。
通过精心设计的架构和实现,该平台成功构建了一个完整的美食生态圈,连接了内容创作者、美食爱好者和餐饮商家,实现了信息价值与商业价值的有机统一。系统的模块化设计和清晰的代码结构为后续的功能迭代和技术升级奠定了坚实基础。