去年帮朋友改造他的宠物店管理系统时,我深刻体会到传统宠物行业对数字化管理的迫切需求。这个基于Java+Vue的宠物管理系统,正是为解决宠物医院、宠物商店等场景中的日常运营痛点而生。系统采用经典的前后端分离架构,后端使用SpringBoot提供RESTful API,前端通过Vue.js构建响应式界面,配合MySQL关系型数据库实现数据持久化。
在实际业务场景中,系统需要处理宠物档案管理、会员服务、商品库存、预约服务等核心模块。比如当客户带着宠物来店消费时,店员可以快速调取该宠物的历史病历、过敏记录;当库存中的某款狗粮低于安全阈值时,系统会自动触发补货提醒;通过可视化的日历视图,美容师能清晰掌握每天的洗澡、美容预约时段分布。
技术选型心得:SpringBoot的约定优于配置特性大幅减少了XML配置,Vue的组件化开发让前端模块可以像搭积木一样灵活组合。这种技术栈组合既保证了后端服务的稳定性,又兼顾了前端交互的流畅体验。
采用SpringBoot 2.7.x作为基础框架,其内嵌Tomcat容器省去了传统War包部署的繁琐。在宠物管理这种并发量适中的场景下,我们通过以下配置优化性能:
yaml复制# application-prod.yml关键配置
server:
tomcat:
max-threads: 200 # 根据压力测试调整线程数
min-spare-threads: 20
spring:
datasource:
hikari:
maximum-pool-size: 15 # 数据库连接池大小
connection-timeout: 30000
jpa:
show-sql: true
hibernate:
ddl-auto: update # 生产环境建议改为validate
数据库设计特别注意了宠物相关业务的特殊性。例如pet表包含芯片号、绝育状态等字段,与owner表形成多对一关系。为提高查询效率,对宠物种类、就诊记录等高频查询字段添加了复合索引:
sql复制CREATE INDEX idx_pet_species ON pet(species, breed);
CREATE INDEX idx_visit_date ON medical_record(pet_id, visit_date);
Vue 3的组合式API大幅提升了代码可维护性。通过CLI创建项目时,我推荐选择以下配置:
宠物照片上传组件是典型的前端难点,需要处理图片压缩、格式转换等操作:
vue复制<template>
<el-upload
:before-upload="compressImage"
:on-success="handleSuccess">
<template #trigger>
<el-button type="primary">上传宠物照片</el-button>
</template>
</el-upload>
</template>
<script setup>
import Compressor from 'compressorjs';
const compressImage = (file) => {
return new Promise((resolve) => {
new Compressor(file, {
quality: 0.6,
success(result) {
resolve(result);
}
});
});
};
</script>
从出生到离世的全周期记录是系统的核心价值。在数据库设计中,我们采用状态机模式管理宠物状态流转:
java复制@Entity
public class Pet {
@Id @GeneratedValue
private Long id;
@Enumerated(EnumType.STRING)
private PetStatus status; // NEW, ACTIVE, DECEASED等
@OneToMany(mappedBy = "pet")
private List<StatusTransition> transitions;
}
@Entity
public class StatusTransition {
@ManyToOne
private Pet pet;
private LocalDateTime transitionTime;
private String reason;
private String vetNotes;
}
前端通过时间轴组件直观展示关键事件:
vue复制<el-timeline>
<el-timeline-item
v-for="event in petTimeline"
:key="event.id"
:timestamp="formatDate(event.time)">
{{ event.type }} - {{ event.detail }}
</el-timeline-item>
</el-timeline>
美容、医疗等服务的预约冲突是常见痛点。我们实现了基于时间块的调度算法:
java复制public List<TimeSlot> findAvailableSlots(LocalDate date, ServiceType type) {
int duration = type.getDurationMinutes();
return allSlots.stream()
.filter(slot -> slot.getDate().equals(date))
.filter(slot -> isStaffAvailable(slot.getStaffId(), slot, duration))
.filter(slot -> isResourceAvailable(type.getRequiredResource(), slot, duration))
.sorted(comparing(TimeSlot::getStartTime))
.collect(Collectors.toList());
}
结合短信验证码和JWT实现安全登录:
java复制@PostMapping("/login")
public ResponseEntity<LoginResult> login(@RequestBody LoginRequest request) {
User user = userService.verify(request);
String smsCode = smsService.sendCode(user.getPhone());
redisTemplate.opsForValue().set(
"login:code:" + user.getId(),
smsCode,
5, TimeUnit.MINUTES);
return ResponseEntity.ok(new LoginResult(user.getId()));
}
@PostMapping("/verify")
public ResponseEntity<AuthResponse> verify(@RequestBody VerifyRequest request) {
String storedCode = redisTemplate.opsForValue().get("login:code:" + request.userId());
if (request.code().equals(storedCode)) {
String token = jwtProvider.generate(request.userId());
return ResponseEntity.ok(new AuthResponse(token));
}
throw new InvalidCodeException();
}
使用JMeter模拟50并发用户操作时,发现宠物列表查询响应时间超过2秒。通过以下措施优化至300ms内:
java复制@Cacheable(value = "pets", key = "#ownerId")
public List<Pet> findByOwner(Long ownerId) {
return petRepository.findByOwnerId(ownerId);
}
properties复制server.compression.enabled=true
server.compression.mime-types=application/json,text/html
vue复制<el-table
:data="tableData"
v-infinite-scroll="loadMore"
:infinite-scroll-disabled="busy">
<el-table-column v-for="col in columns" :key="col.prop" v-bind="col"/>
</el-table>
Docker Compose编排文件示例:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: pet@1234
MYSQL_DATABASE: pet_manager
volumes:
- mysql_data:/var/lib/mysql
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
frontend:
build: ./frontend
ports:
- "80:80"
volumes:
mysql_data:
通过ELK栈实现集中日志管理:
xml复制<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
java复制@RestController
public class MetricsController {
@GetMapping("/metrics")
public String metrics() {
return """
# HELP pet_requests_total Total pet API calls
# TYPE pet_requests_total counter
pet_requests_total{method="GET"} 1423
""";
}
}
现象:前端报错"413 Payload Too Large"
nginx复制client_max_body_size 20M;
properties复制spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB
排查步骤:
sql复制ALTER TABLE appointment
ADD CONSTRAINT uk_appointment UNIQUE (staff_id, start_time);
通过uni-app实现多端发布:
javascript复制// 获取宠物列表
uni.request({
url: 'https://api.example.com/pets',
success: (res) => {
this.pets = res.data
}
})
基于用户行为数据分析:
python复制# 协同过滤推荐示例
from surprise import Dataset, KNNBasic
data = Dataset.load_from_df(ratings_df, reader)
algo = KNNBasic()
algo.fit(data.build_full_trainset())
algo.predict(uid=123, iid=456)
在项目开发过程中,我特别建议建立完善的宠物医疗术语字典表。比如把"犬瘟热"、"猫传腹"等专业病症名称标准化存储,这为后续的数据统计分析打下坚实基础。对于中小型宠物店,可以先从最核心的会员管理和预约功能切入,后续再逐步扩展美容师排班、商品进销存等模块