作为一名有10年Java全栈开发经验的工程师,我最近完成了一个基于SpringBoot的幼儿园管理系统。这个项目不仅包含了完整的源码和文档,还特别注重系统的可维护性和扩展性。在实际开发过程中,我遇到了不少值得分享的技术问题和解决方案。
这个系统采用前后端分离架构,后端使用SpringBoot+MyBatisPlus,前端使用Vue.js,数据库选用MySQL。系统主要功能包括:用户管理、幼儿信息管理、班级管理、考勤管理、课程管理等模块。下面我将从技术选型、架构设计、核心功能实现等方面详细介绍这个项目的开发过程。
选择SpringBoot作为后端框架主要基于以下几个考虑:
java复制// 典型的SpringBoot启动类配置
@SpringBootApplication
@MapperScan("com.kindergarten.mapper")
public class KindergartenApplication {
public static void main(String[] args) {
SpringApplication.run(KindergartenApplication.class, args);
}
}
数据库访问层选用MyBatisPlus而不是原生MyBatis,主要因为:
Vue.js作为前端框架的优势:
javascript复制// 典型Vue组件示例
export default {
data() {
return {
tableData: [],
loading: false
}
},
methods: {
fetchData() {
this.loading = true
api.getStudentList().then(res => {
this.tableData = res.data
}).finally(() => {
this.loading = false
})
}
}
}
系统采用标准的MVC分层架构:
code复制└── src/main/java/com/kindergarten
├── config # 配置类
├── controller # 控制器
├── service # 服务层
│ ├── impl # 服务实现
├── mapper # MyBatis映射接口
├── entity # 实体类
└── util # 工具类
系统采用基于Token的认证方式,使用JWT实现无状态认证。关键实现点:
java复制// JWT工具类核心方法
public class JwtUtil {
private static final String SECRET = "kindergarten-secret";
public static String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getRoles())
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public static Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
幼儿信息是系统的核心数据,设计时考虑了以下要点:
数据库表设计:
sql复制CREATE TABLE `child_info` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`gender` tinyint NOT NULL COMMENT '1-男,2-女',
`birth_date` date NOT NULL,
`class_id` int NOT NULL,
`parent_id` int NOT NULL,
`health_info` text,
`photo_url` varchar(255),
`create_time` datetime NOT NULL,
`update_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_class` (`class_id`),
KEY `idx_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
考勤功能实现要点:
java复制// 考勤服务实现
@Service
public class AttendanceServiceImpl implements AttendanceService {
@Autowired
private AttendanceMapper attendanceMapper;
@Override
@Transactional
public void batchCheckIn(List<Integer> childIds, Integer classId) {
LocalDate today = LocalDate.now();
for(Integer childId : childIds) {
Attendance record = new Attendance();
record.setChildId(childId);
record.setClassId(classId);
record.setDate(today);
record.setStatus(1); // 1-正常出勤
record.setCheckInTime(LocalDateTime.now());
attendanceMapper.insert(record);
}
}
}
java复制// 密码加密处理
public class PasswordUtil {
private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
public static String encode(String rawPassword) {
return encoder.encode(rawPassword);
}
public static boolean matches(String rawPassword, String encodedPassword) {
return encoder.matches(rawPassword, encodedPassword);
}
}
数据库优化:
缓存应用:
接口优化:
java复制// 使用Redis缓存的示例
@Service
public class ClassServiceImpl implements ClassService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public List<ClassInfo> getAllClasses() {
String cacheKey = "classes:all";
List<ClassInfo> classes = (List<ClassInfo>) redisTemplate.opsForValue().get(cacheKey);
if(classes == null) {
classes = classMapper.selectList(null);
redisTemplate.opsForValue().set(cacheKey, classes, 1, TimeUnit.HOURS);
}
return classes;
}
}
幼儿园需要各种统计报表,如出勤率、幼儿成长记录等。解决方案:
sql复制-- 月度出勤率统计SQL
SELECT
c.id AS class_id,
c.name AS class_name,
COUNT(DISTINCT a.child_id) AS total_children,
SUM(CASE WHEN a.status = 1 THEN 1 ELSE 0 END) AS present_days,
ROUND(SUM(CASE WHEN a.status = 1 THEN 1 ELSE 0 END) /
(COUNT(DISTINCT a.child_id) * DAY(LAST_DAY(:monthDate))), 2) AS attendance_rate
FROM
attendance a
JOIN
child_info ci ON a.child_id = ci.id
JOIN
class_info c ON ci.class_id = c.id
WHERE
a.date BETWEEN DATE_FORMAT(:monthDate, '%Y-%m-01') AND LAST_DAY(:monthDate)
GROUP BY
c.id, c.name;
系统需要处理幼儿照片、成长档案等文件上传。实现方案:
java复制// 文件上传服务实现
@Service
public class FileUploadServiceImpl implements FileUploadService {
@Autowired
private OSS ossClient;
@Value("${aliyun.oss.bucketName}")
private String bucketName;
@Override
public String uploadFile(MultipartFile file, String directory) {
try {
String fileName = directory + "/" + UUID.randomUUID() +
getFileExtension(file.getOriginalFilename());
ossClient.putObject(bucketName, fileName, file.getInputStream());
return ossClient.generatePresignedUrl(bucketName, fileName,
Date.from(Instant.now().plus(365, ChronoUnit.DAYS))).toString();
} catch (IOException e) {
throw new RuntimeException("文件上传失败", e);
}
}
private String getFileExtension(String filename) {
return filename.substring(filename.lastIndexOf("."));
}
}
java复制// 服务层单元测试示例
@ExtendWith(MockitoExtension.class)
class ChildServiceTest {
@Mock
private ChildMapper childMapper;
@InjectMocks
private ChildServiceImpl childService;
@Test
void shouldAddChildSuccessfully() {
Child child = new Child();
child.setName("测试儿童");
when(childMapper.insert(any())).thenReturn(1);
boolean result = childService.addChild(child);
assertTrue(result);
verify(childMapper).insert(child);
}
}
采用Docker容器化部署,主要组件:
yaml复制# docker-compose.yml核心配置
version: '3'
services:
app:
image: kindergarten:latest
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: kindergarten
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6.0
ports:
- "6379:6379"
volumes:
mysql_data:
经过两个月的开发,系统已经稳定运行在多家幼儿园。开发过程中积累了一些宝贵经验:
未来改进方向:
这个项目让我对教育行业的信息化建设有了更深的理解。在实际开发中,业务需求的准确把握往往比技术实现更重要。建议后续开发者多与幼儿园老师沟通,真正理解他们的工作流程和痛点。