作为一名长期从事校园信息化系统开发的工程师,我最近完成了一个基于SpringBoot+Vue的校园周边美食探索平台项目。这个项目源于一个真实的痛点:每到饭点,校园里总能看到学生们三五成群地讨论"今天吃什么"的问题。传统的解决方案无非是口口相传或查看大众点评这类通用平台,但这些方式要么信息有限,要么缺乏校园特色。
我们的平台专门针对校园场景设计,整合了周边500米范围内的餐饮信息。与通用点评平台相比,它有三个显著优势:一是信息更贴近学生消费水平,二是评价体系更符合学生用餐习惯,三是增加了课表关联等校园特色功能。平台上线三个月内,日均活跃用户就突破了2000人,验证了其市场价值。
选择SpringBoot作为后端框架是经过深思熟虑的。相比传统的SSM框架,SpringBoot的自动配置特性让我们节省了约40%的初始配置时间。特别是在处理多环境配置时,通过简单的profile配置就能实现开发、测试、生产环境的无缝切换。
数据库方面,我们选择了MySQL 8.0而非5.7版本,主要看中了其JSON字段支持和更好的性能表现。考虑到校园美食数据的读多写少特性,我们在商品表上特别设计了覆盖索引,使查询性能提升了3倍左右。
java复制// 典型的分页查询实现
@GetMapping("/shops")
public PageResult<ShopVO> getShops(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
LambdaQueryWrapper<Shop> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(keyword)) {
wrapper.like(Shop::getShopName, keyword)
.or()
.like(Shop::getDescription, keyword);
}
Page<Shop> pageInfo = new Page<>(page, size);
IPage<Shop> shopPage = shopService.page(pageInfo, wrapper);
return new PageResult<>(
shopPage.getCurrent(),
shopPage.getSize(),
shopPage.getTotal(),
shopPage.getRecords().stream()
.map(this::convertToVO)
.collect(Collectors.toList()));
}
Vue 3的组合式API让我们能够更好地组织前端代码逻辑。特别是使用Pinia替代Vuex后,状态管理代码量减少了约30%。Element Plus的按需引入配置也使得最终打包体积控制在200KB以内。
我们特别优化了移动端体验:
javascript复制// 店铺搜索组件核心逻辑
const searchShops = async () => {
loading.value = true;
try {
const params = {
keyword: searchText.value,
page: pagination.currentPage,
size: pagination.pageSize
};
const res = await api.getShops(params);
shopList.value = res.data.list;
pagination.total = res.data.total;
} catch (error) {
ElMessage.error('获取店铺列表失败');
} finally {
loading.value = false;
}
};
采用JWT+Redis的方案实现无状态认证。考虑到校园环境的特殊性,我们增加了以下安全措施:
用户表设计特别注意了密码安全:
sql复制CREATE TABLE `user` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '学号',
`password_hash` varchar(100) NOT NULL COMMENT 'BCrypt加密',
`salt` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`phone` varchar(20) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`status` tinyint DEFAULT '1' COMMENT '0-禁用 1-正常',
`last_login` datetime DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
我们设计了一个混合推荐策略:
java复制public List<Shop> recommendShops(Long userId, int size) {
// 获取用户最近浏览记录
List<Long> viewHistory = redisTemplate.opsForList()
.range("user:view:" + userId, 0, 9);
// 获取用户收藏的店铺类别
List<Long> favoriteCategories = favoriteService
.getUserFavoriteCategories(userId);
// 构建查询条件
LambdaQueryWrapper<Shop> wrapper = new LambdaQueryWrapper<>();
wrapper.in(Shop::getCategoryId, favoriteCategories)
.or()
.in(Shop::getShopId, viewHistory)
.orderByDesc(Shop::getAverageRating)
.last("LIMIT " + size);
return shopService.list(wrapper);
}
针对高频查询场景,我们设计了以下索引策略:
使用EXPLAIN分析慢查询时,我们发现分页查询在大数据量时性能下降明显。通过以下方案优化:
sql复制-- 优化前的慢查询
SELECT * FROM shop ORDER BY average_rating DESC LIMIT 10000, 10;
-- 优化后的方案
SELECT * FROM shop WHERE shop_id > ? ORDER BY average_rating DESC LIMIT 10;
采用多级缓存架构:
缓存更新策略特别考虑了数据一致性:
java复制@CacheEvict(value = "shop", key = "#shopId")
public void updateShop(Shop shop) {
shopMapper.updateById(shop);
// 异步更新搜索索引
searchService.asyncUpdateIndex(shop);
}
使用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- ./redis/data:/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
集成Spring Boot Actuator和Prometheus:
properties复制# application.properties
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.metrics.export.prometheus.enabled=true
配置Grafana监控看板,重点关注以下指标:
在项目开发过程中,我们积累了一些宝贵经验:
这个项目让我深刻体会到,一个好的校园应用不仅要技术过关,更要深入理解学生群体的真实需求。比如我们最初设计的评分系统是5分制,但实际运营发现学生更习惯用"好吃"、"一般"、"难吃"这种简单评价,于是我们很快调整了交互设计。