宠物成长监管系统是一款基于SpringBoot+Vue技术栈的现代化Web应用,旨在为宠物主人提供全方位的宠物健康管理解决方案。系统采用前后端分离架构,后端使用SpringBoot框架构建RESTful API,前端采用Vue.js实现动态交互界面,数据库选用MySQL进行数据持久化存储。
这个系统主要解决了以下几个痛点问题:
SpringBoot作为后端核心框架,我们主要基于以下考虑:
数据库访问层我们采用了MyBatis-Plus,相比原生MyBatis具有以下优势:
java复制// MyBatis-Plus示例代码
@EntityWrapper<Pet> wrapper = new EntityWrapper<>();
wrapper.eq("owner_id", userId)
.between("birth_date", startDate, endDate)
.orderBy("weight", false);
List<Pet> pets = petService.selectList(wrapper);
Vue.js作为前端框架的选择依据:
典型的前端组件结构:
code复制src/
├── components/
│ ├── PetInfoCard.vue # 宠物信息卡片
│ ├── GrowthChart.vue # 成长曲线图表
│ └── VaccineReminder.vue # 疫苗提醒
├── views/
│ ├── Dashboard.vue # 主控台
│ └── PetDetail.vue # 宠物详情
└── store/ # Vuex状态管理
数据库设计中几个关键表结构:
sql复制CREATE TABLE `pet_health_record` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`pet_id` BIGINT NOT NULL COMMENT '关联宠物ID',
`record_date` DATETIME NOT NULL COMMENT '记录日期',
`weight` DECIMAL(5,2) COMMENT '体重(kg)',
`temperature` DECIMAL(3,1) COMMENT '体温(℃)',
`food_intake` VARCHAR(200) COMMENT '进食量',
`activity_level` TINYINT COMMENT '活动量等级1-5',
`symptom` TEXT COMMENT '异常症状',
`medical_check` TEXT COMMENT '体检记录',
PRIMARY KEY (`id`),
INDEX `idx_pet` (`pet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
体重变化趋势计算采用移动平均法:
java复制public List<GrowthData> calculateGrowthTrend(Long petId, int days) {
List<HealthRecord> records = recordMapper.selectByPetId(petId, days);
List<GrowthData> result = new ArrayList<>();
double sum = 0;
int count = 0;
for (int i = 0; i < records.size(); i++) {
sum += records.get(i).getWeight();
count++;
// 计算7日移动平均
if (i >= 6) {
double avg = sum / 7;
result.add(new GrowthData(
records.get(i).getRecordDate(),
avg
));
sum -= records.get(i-6).getWeight();
count--;
}
}
return result;
}
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
java复制public class DataMasker {
private static final String MASK = "****";
public static String maskPhone(String phone) {
if (StringUtils.isEmpty(phone) || phone.length() < 7) {
return phone;
}
return phone.substring(0, 3) + MASK + phone.substring(7);
}
}
java复制@Service
@CacheConfig(cacheNames = "petCache")
public class PetServiceImpl implements PetService {
@Cacheable(key = "#petId")
public Pet getPetById(Long petId) {
return petMapper.selectById(petId);
}
@CacheEvict(key = "#pet.id")
public void updatePet(Pet pet) {
petMapper.updateById(pet);
}
}
sql复制-- 建立复合索引提高查询效率
ALTER TABLE pet_health_record
ADD INDEX idx_pet_date (pet_id, record_date);
-- 使用覆盖索引避免回表
EXPLAIN SELECT record_date, weight
FROM pet_health_record
WHERE pet_id = 123 AND record_date > '2023-01-01';
常见问题:时区不一致导致日期显示错误
解决方案:
yaml复制# application.yml配置
spring:
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
配置调整方案:
properties复制# 调整SpringBoot默认上传限制
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
全局CORS配置示例:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
使用Profile区分环境:
code复制src/main/resources/
├── application.yml # 公共配置
├── application-dev.yml # 开发环境
└── application-prod.yml # 生产环境
启动时指定环境:
bash复制java -jar pet-care-system.jar --spring.profiles.active=prod
Dockerfile示例:
dockerfile复制FROM openjdk:11-jre
WORKDIR /app
COPY target/pet-care-system.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]
docker-compose编排:
yaml复制version: '3'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- mysql
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=pet_care
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
基于Quartz的任务调度实现:
java复制public class VaccineReminderJob implements Job {
@Autowired
private PetService petService;
@Autowired
private EmailService emailService;
@Override
public void execute(JobExecutionContext context) {
List<Pet> pets = petService.getPetsNeedVaccine();
pets.forEach(pet -> {
String content = String.format(
"亲爱的%s,您的宠物%s该接种%s疫苗了",
pet.getOwnerName(),
pet.getName(),
pet.getNextVaccine()
);
emailService.sendReminder(pet.getOwnerEmail(), content);
});
}
}
使用ECharts实现成长曲线:
javascript复制// Vue组件中
methods: {
initChart() {
const chart = echarts.init(this.$refs.chart);
chart.setOption({
title: { text: '体重变化趋势' },
tooltip: {},
xAxis: { data: this.dates },
yAxis: {},
series: [{
name: '体重',
type: 'line',
data: this.weights,
smooth: true
}]
});
}
}
java复制// 使用Lombok简化代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "pet_info")
public class Pet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Enumerated(EnumType.STRING)
private PetType type;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
}
java复制@Aspect
@Component
@Slf4j
public class LoggingAspect {
@Around("execution(* com.petcare.service.*.*(..))")
public Object logServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
log.info("Entering method: {}", methodName);
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start;
log.info("Method {} executed in {} ms", methodName, elapsed);
return result;
}
}
在实际开发过程中,有几个关键点值得特别注意: