1. 项目概述与技术栈选型
这个基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的家政服务平台系统,是一个典型的现代化全栈Web应用。我在实际开发中发现,这种技术组合特别适合中小型服务类平台的快速迭代开发。整套系统采用前后端分离架构,后端基于SpringBoot2框架提供RESTful API,前端使用Vue3构建响应式用户界面,数据持久层采用MyBatis-Plus简化CRUD操作,MySQL8.0作为关系型数据库存储业务数据。
为什么选择这个技术栈?SpringBoot2的自动配置和起步依赖让后端服务搭建变得极其简单,Vue3的Composition API相比Options API更灵活,MyBatis-Plus的Wrapper条件构造器能大幅减少SQL编写,而MySQL8.0的窗口函数和CTE(Common Table Expressions)特性让复杂报表查询变得可行。这套组合在开发效率、性能表现和学习成本之间取得了很好的平衡。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计与模块划分
2.1 后端架构设计
后端采用经典的三层架构模式:
- 控制层(Controller):处理HTTP请求,参数校验,返回统一格式的JSON响应
- 服务层(Service):业务逻辑实现,事务控制
- 数据访问层(Mapper):通过MyBatis-Plus与数据库交互
我特别添加了一个中间层DTO(Data Transfer Object)来处理前后端数据格式转换。例如家政服务订单的创建流程:
java复制// OrderDTO.java
public class OrderDTO {
private Long serviceId;
private LocalDateTime serviceTime;
private String customerRemark;
// getters & setters
}
// OrderController.java
@PostMapping("/orders")
public Result createOrder(@Valid @RequestBody OrderDTO orderDTO) {
return orderService.createOrder(orderDTO);
}
2.2 前端架构设计
前端采用Vue3的组合式API开发,项目结构如下:
code复制src/
├── api/ # 接口请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
├── composables/ # 组合式函数
├── router/ # 路由配置
├── stores/ # Pinia状态管理
├── utils/ # 工具函数
└── views/ # 页面组件
使用Pinia替代Vuex进行状态管理,配合axios拦截器实现统一的请求/响应处理。一个典型的服务列表查询实现:
javascript复制// stores/serviceStore.js
export const useServiceStore = defineStore('service', {
state: () => ({
services: [],
loading: false
}),
actions: {
async fetchServices(params) {
this.loading = true
try {
const { data } = await getServices(params)
this.services = data
} finally {
this.loading = false
}
}
}
})
3. 核心功能实现细节
3.1 服务预约流程实现
家政服务的预约是系统的核心功能,涉及多个业务实体的关联操作:
- 客户选择服务类型(日常保洁、深度清洁等)
- 系统根据服务类型和地区筛选可用服务人员
- 客户选择时间 slot 并提交预约
- 系统创建订单并触发通知
后端使用Spring事件机制实现异步通知:
java复制// OrderService.java
@Transactional
public Order createOrder(OrderDTO dto) {
// 验证服务可用性
Service service = serviceMapper.selectById(dto.getServiceId());
if (service == null) {
throw new BusinessException("服务不存在");
}
// 创建订单
Order order = new Order();
BeanUtils.copyProperties(dto, order);
order.setStatus(OrderStatus.PENDING);
orderMapper.insert(order);
// 发布订单创建事件
applicationContext.publishEvent(new OrderCreatedEvent(this, order));
return order;
}
// OrderEventListener.java
@Component
public class OrderEventListener {
@Async
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 发送短信/邮件通知
notificationService.sendOrderCreatedNotice(event.getOrder());
}
}
3.2 服务人员调度算法
系统采用基于地理位置和技能匹配的调度算法:
java复制public List<Worker> findAvailableWorkers(Long serviceId, LocalDateTime time, Address address) {
// 1. 获取能提供该服务的工人
List<Worker> skilledWorkers = workerMapper.selectList(
new QueryWrapper<Worker>()
.inSql("id", "SELECT worker_id FROM worker_service WHERE service_id = " + serviceId)
);
// 2. 过滤出时间段可用的工人
List<Worker> availableWorkers = skilledWorkers.stream()
.filter(worker -> !isWorkerBooked(worker.getId(), time))
.collect(Collectors.toList());
// 3. 按距离排序
availableWorkers.sort(Comparator.comparingDouble(
worker -> calculateDistance(worker.getAddress(), address))
);
return availableWorkers;
}
4. 数据库设计与优化
4.1 MySQL8.0表结构设计
sql复制CREATE TABLE `service` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '服务名称',
`category_id` bigint NOT NULL COMMENT '分类ID',
`price` decimal(10,2) NOT NULL COMMENT '基础价格',
`duration` int NOT NULL COMMENT '预计时长(分钟)',
`description` text COMMENT '服务描述',
`cover_image` varchar(255) COMMENT '封面图',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-上架 0-下架',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `worker` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '关联用户ID',
`real_name` varchar(20) NOT NULL COMMENT '真实姓名',
`id_card` varchar(18) NOT NULL COMMENT '身份证号',
`gender` tinyint NOT NULL COMMENT '性别:1-男 2-女',
`birth_date` date COMMENT '出生日期',
`mobile` varchar(11) NOT NULL COMMENT '手机号',
`address` json NOT NULL COMMENT '常驻地址',
`score` decimal(3,1) DEFAULT '5.0' COMMENT '评分',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-可用 0-不可用',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user` (`user_id`),
KEY `idx_mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
4.2 性能优化实践
- 索引优化:为所有外键字段和常用查询条件创建索引,使用EXPLAIN分析慢查询
- JSON字段使用:MySQL8.0的JSON类型用于存储动态结构数据,如地址信息
- 连接池配置:使用HikariCP连接池,合理设置maxPoolSize和connectionTimeout
- 查询优化:对于复杂报表使用窗口函数替代多次查询
yaml复制# application.yml中的Hikari配置
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
pool-name: HikariCP-HomeService
5. 前后端交互与API设计
5.1 RESTful API规范
系统采用统一的API响应格式:
json复制{
"code": 200,
"message": "success",
"data": {
"id": 123,
"name": "深度保洁"
},
"timestamp": 1672531200000
}
通过自定义Result类实现:
java复制public class Result<T> implements Serializable {
private int code;
private String message;
private T data;
private long timestamp;
public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data);
}
// 其他工厂方法...
}
5.2 文件上传处理
家政服务需要上传服务前后对比照片,采用分块上传策略:
前端Vue3实现:
javascript复制const uploadFile = async (file) => {
const chunkSize = 2 * 1024 * 1024 // 2MB
const chunks = Math.ceil(file.size / chunkSize)
for (let i = 0; i < chunks; i++) {
const start = i * chunkSize
const end = Math.min(file.size, start + chunkSize)
const chunk = file.slice(start, end)
const formData = new FormData()
formData.append('file', chunk)
formData.append('chunkNumber', i)
formData.append('totalChunks', chunks)
formData.append('identifier', file.uniqueIdentifier)
await uploadChunk(formData)
}
await mergeChunks(file.name, file.uniqueIdentifier, chunks)
}
后端SpringBoot处理:
java复制@PostMapping("/upload/chunk")
public Result uploadChunk(@RequestParam MultipartFile file,
@RequestParam int chunkNumber,
@RequestParam int totalChunks,
@RequestParam String identifier) {
// 检查分片大小
if (file.isEmpty()) {
return Result.fail("分片不能为空");
}
// 存储分片到临时目录
String tempDir = getTempDir(identifier);
File chunkFile = new File(tempDir, chunkNumber + ".part");
file.transferTo(chunkFile);
return Result.success();
}
6. 安全防护与最佳实践
6.1 安全防护措施
- 认证授权:使用JWT实现无状态认证,结合Spring Security进行权限控制
- 数据校验:前后端双重校验,后端使用Hibernate Validator
- SQL防护:MyBatis-Plus内置SQL注入防护,禁止拼接SQL
- XSS防护:前端使用DOMPurify净化HTML输入,后端对输出进行转义
- CSRF防护:虽然REST API通常无状态,但关键操作仍验证Referer和自定义Header
JWT配置示例:
java复制@Configuration
public class JwtConfig {
@Value("${jwt.secret}")
private String secret;
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withSecretKey(new SecretKeySpec(secret.getBytes(), "HS256")).build();
}
@Bean
public JwtEncoder jwtEncoder() {
return new NimbusJwtEncoder(new ImmutableSecret<>(secret.getBytes()));
}
}
6.2 缓存策略
使用Redis缓存热点数据,如服务分类、热门服务人员等:
java复制@Service
@RequiredArgsConstructor
public class CategoryService {
private final CategoryMapper categoryMapper;
private final RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_KEY = "categories:all";
@Cacheable(value = CACHE_KEY, unless = "#result == null || #result.isEmpty()")
public List<Category> getAllCategories() {
return categoryMapper.selectList(null);
}
@CacheEvict(value = CACHE_KEY)
public void clearCategoryCache() {
// 缓存已清除
}
}
7. 部署与监控
7.1 多环境部署配置
使用Spring Profile管理不同环境配置:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/home_service_dev
username: dev_user
password: dev123
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/home_service_prod
username: ${DB_USER}
password: ${DB_PASSWORD}
redis:
host: redis-prod
7.2 健康检查与监控
集成Spring Boot Actuator提供系统监控端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
prometheus:
enabled: true
前端使用Sentry捕获运行时错误:
javascript复制import * as Sentry from '@sentry/vue'
app.use(Sentry, {
dsn: 'your-dsn',
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router)
})
],
tracesSampleRate: 0.2
})
8. 开发中的经验与坑点
8.1 MyBatis-Plus的坑
- 逻辑删除与唯一索引冲突:在逻辑删除字段上建立唯一索引时,需要将delete字段也包含在索引中
- 自动填充失效:在updateById时,如果字段值为null,MyBatis-Plus不会自动填充,需要手动设置
- Wrapper条件构造:复杂查询时,条件构造器的lambda表达式可能不会按预期工作
解决方案:对于复杂查询,可以混合使用Wrapper和XML映射文件:
java复制// ServiceImpl.java
public Page<Worker> queryComplex(WorkerQuery query) {
LambdaQueryWrapper<Worker> wrapper = new LambdaQueryWrapper<>();
// 简单条件使用Wrapper
wrapper.eq(query.getCategoryId() != null, Worker::getCategoryId, query.getCategoryId());
// 复杂条件使用XML
return workerMapper.selectComplex(query, wrapper);
}
8.2 Vue3组合式API的实践
- 响应式丢失:解构reactive对象会失去响应性,需要使用toRefs
- 生命周期混淆:setup()中使用的生命周期钩子需要加上"on"前缀
- TypeScript集成:为Pinia store定义类型时,需要使用泛型
正确的组合式函数写法:
typescript复制import { reactive, toRefs } from 'vue'
export function useService() {
const state = reactive({
services: [],
loading: false
})
const fetchServices = async () => {
state.loading = true
try {
state.services = await api.getServices()
} finally {
state.loading = false
}
}
return {
...toRefs(state),
fetchServices
}
}
9. 系统扩展与二次开发
9.1 支付模块集成
家政服务平台通常需要集成多种支付方式:
- 微信支付:使用官方SDK,注意证书管理和回调验签
- 支付宝:配置应用公钥和支付宝公钥
- 余额支付:实现账户余额系统,注意并发扣款问题
支付状态机设计:
java复制public enum PaymentStatus {
UNPAID, // 未支付
PAYING, // 支付中
PAID, // 支付成功
FAILED, // 支付失败
REFUNDING, // 退款中
PART_REFUNDED, // 部分退款
REFUNDED // 已退款
}
9.2 消息推送系统
使用WebSocket实现实时消息推送:
后端实现:
java复制@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
}
前端连接:
javascript复制import { Stomp } from '@stomp/stompjs'
const connectWebSocket = () => {
const client = Stomp.client('ws://your-websocket-endpoint')
client.connect({}, () => {
client.subscribe('/topic/notifications', (message) => {
showNotification(JSON.parse(message.body))
})
})
}
10. 文档编写与项目交接
完善的文档对于项目维护至关重要:
- 接口文档:使用Swagger或YAPI生成,包含请求示例和响应示例
- 数据库文档:使用PDManer或PowerDesigner设计ER图
- 部署文档:详细记录环境依赖、配置项和启动步骤
- 业务流程图:使用PlantUML绘制核心业务流程
Swagger配置示例:
java复制@Configuration
@OpenAPIDefinition(
info = @Info(
title = "家政服务平台API文档",
version = "1.0",
description = "家政服务预约系统接口文档"
)
)
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components())
.info(new Info().title("家政服务平台API").version("1.0"));
}
}
在实际项目交接中,我发现以下几个文档特别重要:
- 系统架构图:展示整体技术架构和模块划分
- 领域模型图:关键业务实体及其关系
- 部署拓扑图:生产环境部署结构
- 运维手册:日常维护和故障排查指南
