作为一名拥有10年全栈开发经验的工程师,我经常被问到如何选择一个既有技术深度又具备实用价值的毕业设计项目。今天要介绍的这款基于SpringBoot的宠物成长记录平台,正是我指导过2000+名学生中反响最好的项目之一。它不仅涵盖了企业级应用的核心技术栈,还解决了宠物主记录和管理爱宠成长轨迹的实际需求。
这个平台采用SpringBoot+Vue+MySQL的主流技术组合,实现了宠物档案管理、健康记录、日常活动追踪等核心功能。技术层面,我们使用Spring Security实现权限控制,通过MyBatis-Plus简化数据库操作,配合Vue.js构建响应式前端界面。项目最大的特色在于其完整的生命周期管理设计——从宠物基础信息录入,到疫苗接种提醒、体重变化曲线生成,再到医疗记录归档,形成了一套闭环的宠物健康管理解决方案。
系统采用经典的MVC模式进行分层设计,各层职责明确:
视图层(View) 使用Vue 3组合式API开发,通过Element Plus组件库快速构建UI。我特别推荐使用Vue-Router实现前端路由导航,配合Axios处理HTTP请求。一个典型的宠物信息展示组件代码如下:
vue复制<template>
<el-card class="pet-card">
<div class="pet-avatar">
<el-avatar :size="100" :src="pet.avatar" />
</div>
<div class="pet-info">
<h3>{{ pet.name }}</h3>
<p>品种:{{ pet.breed }}</p>
<p>年龄:{{ calculateAge(pet.birthday) }}岁</p>
</div>
</el-card>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
pet: Object
})
const calculateAge = (birthday) => {
// 年龄计算逻辑
}
</script>
控制层(Controller) 采用SpringBoot的RESTful风格设计。以下是一个典型的宠物控制器实现:
java复制@RestController
@RequestMapping("/api/pets")
public class PetController {
@Autowired
private PetService petService;
@GetMapping("/{id}")
public ResponseEntity<PetVO> getPetById(@PathVariable Long id) {
return ResponseEntity.ok(petService.getPetById(id));
}
@PostMapping
public ResponseEntity<Void> addPet(@Valid @RequestBody PetDTO petDTO) {
petService.addPet(petDTO);
return ResponseEntity.created().build();
}
}
服务层(Service) 包含核心业务逻辑,我们采用领域驱动设计(DDD)的思想组织代码结构:
java复制@Service
@RequiredArgsConstructor
public class PetServiceImpl implements PetService {
private final PetRepository petRepository;
private final PetMapper petMapper;
@Override
@Transactional
public void addPet(PetDTO petDTO) {
Pet pet = petMapper.toEntity(petDTO);
petRepository.save(pet);
// 初始化成长记录
initGrowthRecord(pet);
}
private void initGrowthRecord(Pet pet) {
// 初始化逻辑
}
}
数据库采用MySQL 8.0,遵循第三范式设计。以下是核心表结构设计要点:
宠物主表(pet_owner)
sql复制CREATE TABLE `pet_owner` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录账号',
`password` varchar(100) NOT NULL COMMENT '加密密码',
`phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
`email` varchar(100) DEFAULT NULL COMMENT '电子邮箱',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',
`status` tinyint DEFAULT '1' COMMENT '状态(0-禁用 1-正常)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
宠物信息表(pet_info)
sql复制CREATE TABLE `pet_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`owner_id` bigint NOT NULL COMMENT '所属主人ID',
`name` varchar(50) NOT NULL COMMENT '宠物名称',
`type` tinyint NOT NULL COMMENT '宠物类型(1-狗 2-猫 3-其他)',
`breed` varchar(50) DEFAULT NULL COMMENT '品种',
`gender` tinyint DEFAULT NULL COMMENT '性别(1-公 2-母)',
`birthday` date DEFAULT NULL COMMENT '出生日期',
`weight` decimal(5,2) DEFAULT NULL COMMENT '当前体重(kg)',
`avatar` varchar(255) DEFAULT NULL COMMENT '宠物头像',
`chip_number` varchar(100) DEFAULT NULL COMMENT '芯片编号',
`sterilization` tinyint DEFAULT '0' COMMENT '是否绝育(0-否 1-是)',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_owner_id` (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
成长记录表(pet_growth_record)
sql复制CREATE TABLE `pet_growth_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`pet_id` bigint NOT NULL COMMENT '宠物ID',
`record_date` date NOT NULL COMMENT '记录日期',
`record_type` tinyint NOT NULL COMMENT '记录类型(1-体重 2-体长 3-疫苗接种 4-驱虫 5-医疗)',
`value` varchar(100) DEFAULT NULL COMMENT '记录值',
`description` text COMMENT '详细描述',
`images` text COMMENT '图片URL,多个用逗号分隔',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_pet_id` (`pet_id`),
KEY `idx_record_date` (`record_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
数据库设计经验分享:
- 所有表都添加了create_time和update_time字段,便于后期数据追踪
- 使用utf8mb4字符集支持emoji表情存储
- 为常用查询字段建立索引,但避免过度索引影响写入性能
- 对于可能包含多值的字段(如images),采用逗号分隔的字符串存储
宠物档案是系统的核心数据,我们实现了完整的CRUD操作和高级查询功能。技术实现上有几个关键点值得注意:
java复制public String uploadImage(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFileName = UUID.randomUUID() + fileExtension;
// 上传到OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, newFileName, file.getInputStream());
return "https://" + bucketName + "." + endpoint + "/" + newFileName;
}
java复制@Data
public class PetDTO {
@NotBlank(message = "宠物名称不能为空")
@Size(max = 50, message = "宠物名称最长50个字符")
private String name;
@NotNull(message = "宠物类型不能为空")
@Range(min = 1, max = 3, message = "无效的宠物类型")
private Integer type;
@PastOrPresent(message = "出生日期不能是未来时间")
private LocalDate birthday;
}
java复制@TableLogic
private Integer deleted;
成长记录模块包含以下几个特色功能:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
public void checkVaccinationReminder() {
List<Pet> pets = petMapper.selectNeedVaccinationPets();
pets.forEach(pet -> {
String message = String.format("您的宠物%s该接种疫苗了", pet.getName());
notificationService.sendNotification(pet.getOwnerId(), message);
});
}
vue复制<template>
<div ref="chart" style="width: 100%; height: 400px;"></div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import * as echarts from 'echarts'
const chart = ref(null)
const props = defineProps(['records'])
onMounted(() => {
const myChart = echarts.init(chart.value)
const option = {
xAxis: {
type: 'category',
data: props.records.map(r => r.recordDate)
},
yAxis: {
type: 'value',
name: '体重(kg)'
},
series: [{
data: props.records.map(r => r.value),
type: 'line',
smooth: true
}]
}
myChart.setOption(option)
})
</script>
java复制public Page<GrowthRecordVO> queryRecords(GrowthRecordQuery query, Pageable pageable) {
QueryWrapper<GrowthRecord> wrapper = new QueryWrapper<>();
wrapper.eq("pet_id", query.getPetId());
if (query.getStartDate() != null) {
wrapper.ge("record_date", query.getStartDate());
}
if (query.getEndDate() != null) {
wrapper.le("record_date", query.getEndDate());
}
if (query.getRecordType() != null) {
wrapper.eq("record_type", query.getRecordType());
}
wrapper.orderByDesc("record_date");
return growthRecordMapper.selectPage(new Page<>(pageable.getPageNumber(), pageable.getPageSize()), wrapper)
.convert(this::toVO);
}
java复制@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
java复制@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
java复制@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {
private final RedisTemplate<String, String> redisTemplate;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
String key = "rate_limit:" + getRequestIP() + ":" + joinPoint.getSignature().toShortString();
long count = redisTemplate.opsForValue().increment(key, 1);
if (count == 1) {
redisTemplate.expire(key, rateLimit.time(), rateLimit.timeUnit());
}
if (count > rateLimit.count()) {
throw new BusinessException("操作过于频繁,请稍后再试");
}
return joinPoint.proceed();
}
}
java复制@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "pet")
public class PetServiceImpl implements PetService {
@Override
@Cacheable(key = "#id")
public PetVO getPetById(Long id) {
// 数据库查询逻辑
}
@Override
@CachePut(key = "#result.id")
public PetVO updatePet(PetDTO petDTO) {
// 更新逻辑
}
@Override
@CacheEvict(key = "#id")
public void deletePet(Long id) {
// 删除逻辑
}
}
sql复制ALTER TABLE pet_growth_record ADD INDEX idx_composite (pet_id, record_date, record_type);
java复制@Async
public void asyncProcessGrowthRecord(Long recordId) {
// 耗时处理逻辑
}
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
使用SpringBoot的Profile特性管理不同环境配置:
application-dev.yml
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/pet_dev?useSSL=false
username: dev_user
password: dev123
redis:
host: localhost
port: 6379
application-prod.yml
yaml复制spring:
datasource:
url: jdbc:mysql://prod-db:3306/pet_prod?useSSL=true
username: prod_user
password: ${DB_PASSWORD}
redis:
host: redis-cluster
port: 6379
通过JVM参数激活不同环境:
bash复制java -jar pet-platform.jar --spring.profiles.active=prod
使用Docker Compose编排服务:
docker-compose.yml
yaml复制version: '3.8'
services:
app:
image: pet-platform:latest
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_PASSWORD=${DB_PASSWORD}
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: pet_prod
MYSQL_USER: prod_user
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6.2
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
mysql_data:
redis_data:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
java复制@Configuration
public class MetricsConfig {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "pet-platform"
);
}
}
xml复制<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.2</version>
</dependency>
这个宠物成长记录平台还有很大的扩展空间,以下是几个值得考虑的扩展方向:
在实际开发过程中,我特别建议同学们关注以下几点:
这个项目我已经帮助超过200名学生成功完成并答辩通过,如果你在开发过程中遇到任何技术问题,或者需要完整的项目源码和文档参考,都可以联系我获取专业的技术支持。记住,好的毕业设计不在于功能有多复杂,而在于你是否真正理解每一行代码背后的设计思想和技术原理。