最近在重构一个旅游网站项目时,我选择了SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0这套技术栈。这套组合在当前的Java Web开发领域可以说是黄金搭档,尤其适合需要快速迭代的中小型项目。
为什么选择这个技术组合?首先,SpringBoot2提供了开箱即用的企业级开发体验,内置Tomcat服务器和自动配置机制,让开发者可以专注于业务逻辑而非环境搭建。Vue3作为前端框架,其响应式系统和组合式API让复杂交互的开发变得异常简单。MyBatis-Plus在MyBatis基础上做了大量增强,特别是它的Wrapper条件构造器和ActiveRecord模式,能显著减少样板代码。MySQL8.0则带来了窗口函数、CTE等高级特性,以及更好的JSON支持。
这套架构最大的特点是前后端完全分离。后端通过RESTful API提供数据服务,前端通过axios消费这些API。这种架构让前后端开发可以并行进行,也便于后期维护和扩展。在实际开发中,我使用Swagger生成API文档,前后端约定好接口规范后就能各自开发,效率提升非常明显。
用户模块采用了经典的RBAC(基于角色的访问控制)模型。user_profile表存储用户基本信息,account_status字段特别设计为TINYINT类型,使用0和1表示不同状态,这种设计比直接存储字符串更节省空间,查询效率也更高。
密码存储方面,我使用了BCryptPasswordEncoder进行加密。这是Spring Security推荐的密码加密方式,相比MD5和SHA系列算法,BCrypt自带盐值且计算速度可调,能有效抵御彩虹表攻击。核心代码如下:
java复制@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
scenic_spot表中,location字段存储的是经纬度坐标,格式为"经度,纬度"。这样设计是为了方便后续集成地图服务。在实际项目中,我使用了MySQL的空间索引和ST_Distance_Sphere函数来实现附近景点查询:
sql复制SELECT spot_id, spot_name,
ST_Distance_Sphere(point(?, ?), point(lng, lat)) as distance
FROM scenic_spot
WHERE ST_Distance_Sphere(point(?, ?), point(lng, lat)) < 5000
ORDER BY distance;
ticket_price使用DECIMAL(10,2)类型,确保金额计算的精确性。这里有个经验:所有涉及金额的字段都应该使用DECIMAL,而不是FLOAT或DOUBLE,避免浮点数计算带来的精度问题。
景点搜索采用了Elasticsearch来实现全文检索,配合Vue3的防抖优化,确保搜索体验流畅。后端使用Spring Data Elasticsearch进行集成:
java复制public interface ScenicSpotRepository extends ElasticsearchRepository<ScenicSpot, Long> {
List<ScenicSpot> findBySpotNameOrDescription(String spotName, String description);
}
前端使用Vue3的setup语法配合watchEffect实现输入防抖:
javascript复制const searchText = ref('');
const searchResults = ref([]);
watchEffect(() => {
const timer = setTimeout(async () => {
if (searchText.value.trim()) {
const res = await axios.get(`/api/spots/search?q=${searchText.value}`);
searchResults.value = res.data;
}
}, 300);
return () => clearTimeout(timer);
});
订单系统采用了状态机模式,使用Spring StateMachine来管理订单状态流转。这比简单的if-else判断更加清晰和可维护:
java复制@Configuration
@EnableStateMachineFactory
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("UNPAID")
.states(EnumSet.allOf(OrderStatus.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("UNPAID").target("PAID")
.event("PAY")
.and()
.withExternal()
.source("PAID").target("COMPLETED")
.event("CONFIRM");
}
}
支付接口集成了支付宝和微信支付两种方式,使用策略模式来封装不同的支付实现:
java复制public interface PaymentStrategy {
PaymentResult pay(Order order);
}
@Service
public class AlipayStrategy implements PaymentStrategy {
// 支付宝具体实现
}
@Service
public class WechatPayStrategy implements PaymentStrategy {
// 微信支付具体实现
}
@Service
public class PaymentService {
private final Map<String, PaymentStrategy> strategies;
public PaymentResult pay(String type, Order order) {
return strategies.get(type).pay(order);
}
}
使用Redis作为缓存层,采用多级缓存策略:
配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
MyBatis-Plus虽然方便,但也要注意避免N+1查询问题。我总结了几点经验:
java复制// 不好的做法:循环中单条查询
for (Order order : orders) {
User user = userMapper.selectById(order.getUserId());
// ...
}
// 好的做法:先收集ID,然后批量查询
List<Long> userIds = orders.stream().map(Order::getUserId).collect(Collectors.toList());
Map<Long, User> userMap = userMapper.selectBatchIds(userIds).stream()
.collect(Collectors.toMap(User::getUserId, Function.identity()));
使用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
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=health,info,metrics,prometheus
management.metrics.export.prometheus.enabled=true
配合Grafana展示监控数据,关键指标包括:
开发环境遇到的前端跨域问题,解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
生产环境建议使用Nginx反向代理解决跨域,而不是直接开放所有源。
前后端日期格式不一致是个常见问题。我的解决方案:
后端配置:
java复制@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.timeZone(TimeZone.getTimeZone("UTC"));
builder.simpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
};
}
这个基础架构可以进一步扩展:
对于想学习这套技术栈的开发者,我的建议是从小模块开始,逐步扩展。比如先实现用户登录和景点列表,再慢慢添加订单、支付等复杂功能。