1. 项目概述
作为一名有10年Java全栈开发经验的工程师,我最近完成了一个基于SpringBoot+Android的计算机精品课程学习系统。这个系统不仅是我指导学生的毕业设计案例,更是一个融合了现代Web开发最佳实践的完整项目。
这个系统主要面向高校计算机专业学生,提供在线课程学习、资源下载、作业提交等功能。后端采用SpringBoot+MyBatisPlus框架,前端使用Vue.js,移动端基于Android开发,数据库选用MySQL,是一套典型的企业级全栈解决方案。
2. 系统架构设计
2.1 技术选型解析
在项目启动阶段,我经过多方考量选择了以下技术栈:
后端技术栈:
- Spring Boot 2.7.x:简化配置,快速构建微服务
- MyBatis-Plus 3.5.x:增强型ORM框架
- Shiro 1.10.x:安全认证框架
- Redis 6.x:缓存和会话管理
前端技术栈:
- Vue.js 3.x:渐进式前端框架
- Element Plus:UI组件库
- Axios:HTTP客户端
移动端:
- Android 12+
- Retrofit:网络请求库
- Room:本地数据库
数据库:
- MySQL 8.0:关系型数据库
- MongoDB 5.0(可选):非结构化数据存储
这个技术组合的选择基于以下几个考量:
- 成熟度:都是经过大量项目验证的稳定技术
- 社区支持:遇到问题容易找到解决方案
- 学习曲线:相对平缓,适合学生掌握
- 扩展性:可以方便地进行功能扩展
2.2 系统架构详解
系统采用典型的三层架构设计:
code复制┌───────────────────────────────────────┐
│ 客户端层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ Web前端 │ │ 移动端App │ │
│ └───────────┘ └───────────┘ │
└───────────────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────────────────┐
│ 服务层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ API网关 │ │ 业务服务 │ │
│ └───────────┘ └───────────┘ │
└───────────────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────────────────┐
│ 数据层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ MySQL │ │ Redis │ │
│ └───────────┘ └───────────┘ │
└───────────────────────────────────────┘
这种架构的优势在于:
- 职责分离:各层专注自己的功能
- 可扩展性:可以单独扩展某一层
- 可维护性:修改某一层不影响其他层
- 技术异构:各层可以使用最适合的技术
3. 核心功能实现
3.1 用户认证模块
用户认证是系统的门户,我采用了JWT+Shiro的方案:
java复制// JWT工具类核心代码
public class JwtUtil {
private static final String SECRET_KEY = "your-secret-key";
private static final long EXPIRATION = 86400000; // 24小时
public static String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getRoles())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public static Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
关键实现点:
- 采用HS512算法签名,保证token安全性
- 将用户角色信息存入token,减少数据库查询
- 设置合理的过期时间,平衡安全性和用户体验
3.2 课程管理模块
课程管理是系统的核心功能,采用DDD领域驱动设计:
java复制// 课程领域服务
@Service
public class CourseService {
@Autowired
private CourseRepository courseRepository;
@Transactional
public Course createCourse(CourseDTO dto) {
Course course = new Course();
course.setTitle(dto.getTitle());
course.setDescription(dto.getDescription());
course.setCoverImage(dto.getCoverImage());
course.setStatus(CourseStatus.DRAFT);
return courseRepository.save(course);
}
public Page<Course> searchCourses(String keyword, Pageable pageable) {
return courseRepository.findByTitleContainingOrDescriptionContaining(
keyword, keyword, pageable);
}
}
数据库设计:
sql复制CREATE TABLE `course` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`description` text,
`cover_image` varchar(255) DEFAULT NULL,
`status` varchar(20) DEFAULT 'DRAFT',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.3 文件上传服务
系统需要处理课程资料、作业等文件上传,我实现了分块上传和断点续传:
java复制@RestController
@RequestMapping("/api/upload")
public class UploadController {
@PostMapping("/chunk")
public ResponseEntity<?> uploadChunk(
@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("identifier") String identifier) {
// 检查文件是否已存在
String filePath = "/uploads/" + identifier;
if (chunkNumber == totalChunks) {
// 合并分块
mergeChunks(identifier, totalChunks);
return ResponseEntity.ok().build();
}
// 保存分块
try {
Files.write(Paths.get(filePath + "." + chunkNumber),
file.getBytes());
return ResponseEntity.ok().build();
} catch (IOException e) {
return ResponseEntity.status(500).build();
}
}
}
优化点:
- 使用MD5作为文件唯一标识
- 支持大文件分块上传
- 前端实现进度条显示
- 服务端做文件类型和大小限制
4. 移动端实现
4.1 Android端架构
Android端采用MVVM架构:
code复制┌───────────────────────────────────────┐
│ View层 │
│ ┌───────────┐ ┌───────────┐ │
│ │ Activity │ │ Fragment │ │
│ └───────────┘ └───────────┘ │
└───────────────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────────────────┐
│ ViewModel层 │
│ ┌───────────────────────────────┐ │
│ │ ViewModel │ │
│ └───────────────────────────────┘ │
└───────────────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────────────────┐
│ 仓库层 │
│ ┌───────────┐ ┌───────────┐ │
│ │本地数据源 │ │远程数据源 │ │
│ └───────────┘ └───────────┘ │
└───────────────────────────────────────┘
4.2 课程列表实现
kotlin复制// ViewModel
class CourseViewModel : ViewModel() {
private val repository = CourseRepository()
val courses = MutableLiveData<List<Course>>()
val isLoading = MutableLiveData<Boolean>()
val error = MutableLiveData<String>()
fun loadCourses() {
viewModelScope.launch {
isLoading.value = true
try {
val result = repository.getCourses()
courses.value = result
} catch (e: Exception) {
error.value = e.message
} finally {
isLoading.value = false
}
}
}
}
// Activity
class CourseActivity : AppCompatActivity() {
private lateinit var binding: ActivityCourseBinding
private val viewModel: CourseViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_course)
viewModel.courses.observe(this) { courses ->
// 更新UI
}
viewModel.error.observe(this) { error ->
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
}
binding.swipeRefresh.setOnRefreshListener {
viewModel.loadCourses()
}
viewModel.loadCourses()
}
}
5. 项目部署与优化
5.1 生产环境部署
我推荐使用Docker进行容器化部署:
dockerfile复制# 后端服务Dockerfile
FROM openjdk:11-jdk
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
部署架构:
code复制┌───────────────────────────────────────┐
│ Nginx │
│ ┌───────────────────────────────┐ │
│ │ 负载均衡 & 静态资源服务 │ │
│ └───────────────────────────────┘ │
└───────────────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 后端服务实例1 │ │ 后端服务实例2 │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌───────────────────────────────────────┐
│ MySQL集群 │
└───────────────────────────────────────┘
5.2 性能优化技巧
-
数据库优化:
- 添加合适的索引
- 使用连接池(HikariCP)
- 读写分离
-
缓存策略:
java复制@Cacheable(value = "courses", key = "#id") public Course getCourseById(Long id) { return courseRepository.findById(id).orElse(null); } @CacheEvict(value = "courses", key = "#course.id") public Course updateCourse(Course course) { return courseRepository.save(course); } -
前端优化:
- 组件懒加载
- 路由懒加载
- 图片懒加载
6. 常见问题与解决方案
6.1 跨域问题
解决方案:配置全局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);
}
}
6.2 接口幂等性
对于支付等关键操作,实现幂等性:
java复制@PostMapping("/pay")
public ResponseEntity<?> pay(@RequestBody PaymentRequest request,
@RequestHeader("Idempotency-Key") String idempotencyKey) {
// 检查是否已处理过该请求
if (redisTemplate.opsForValue().get(idempotencyKey) != null) {
return ResponseEntity.ok().build();
}
// 处理支付逻辑
paymentService.process(request);
// 记录已处理的请求
redisTemplate.opsForValue().set(idempotencyKey, "processed", 24, TimeUnit.HOURS);
return ResponseEntity.ok().build();
}
6.3 并发控制
使用乐观锁防止数据冲突:
java复制@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Version
private Integer version;
// 其他字段...
}
@Service
public class CourseService {
@Transactional
public void updateCourse(Long id, CourseUpdateDTO dto) {
Course course = courseRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
// 如果version不匹配,会抛出OptimisticLockingFailureException
course.setTitle(dto.getTitle());
courseRepository.save(course);
}
}
7. 项目总结与建议
这个项目从技术选型到最终实现,涵盖了现代Web开发的各个方面。在实际开发过程中,我总结了以下几点经验:
- 前后端分离:严格定义API契约,使用Swagger文档化
- 异常处理:统一的异常处理机制,返回规范的错误信息
- 日志记录:完善的日志系统,便于问题排查
- 测试覆盖:单元测试+集成测试,保证代码质量
对于想要学习这套技术栈的同学,我建议的学习路径是:
- 先掌握Java基础和Spring框架
- 学习Spring Boot自动配置原理
- 掌握MyBatis/MyBatis-Plus的使用
- 学习Vue.js基础
- 最后学习Android开发
这个项目已经成功帮助多位同学完成了毕业设计,如果你在实现过程中遇到任何问题,可以参考完整源码或与我交流。记住,编程能力的提升来自于不断的实践和总结。