作为一名经历过多次教育信息化项目落地的开发者,我深知传统作业提交方式的痛点:教师需要手动整理学生发来的各种格式文件,学生经常因错过截止时间而手忙脚乱,管理员统计作业完成情况时需要从多个平台导出数据。这个基于SpringBoot+Vue3+Android的在线学习作业提交平台,正是为了解决这些实际问题而设计的全栈解决方案。
平台采用前后端分离架构,后端使用SpringBoot 3.x提供RESTful API服务,前端分为Web端(Vue3+TypeScript)和移动端(Android原生+Kotlin)。特别针对移动学习场景,我们通过Android Studio开发了功能完整的客户端,既支持WebView嵌入Vue页面实现快速迭代,又通过原生模块保障文件上传等核心体验。数据库选用MySQL 8.0存储结构化数据,MinIO处理文件存储,Redis缓存高频访问数据,形成完整的技术闭环。
Spring Boot 3.x 作为后端核心框架,其自动配置特性大幅减少了XML配置。我们在项目中特别利用了以下特性:
数据库选型时,MySQL 8.0 相比其他关系型数据库具有明显优势:
sql复制-- 作业表设计示例(含JSON字段支持附件元数据)
CREATE TABLE `assignment_submission` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`assignment_id` BIGINT NOT NULL,
`student_id` BIGINT NOT NULL,
`attachments` JSON COMMENT '{"files":[{"name":"report.pdf","size":204800}]}',
`submitted_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`assignment_id`) REFERENCES `assignment`(`id`),
INDEX `idx_student_submission` (`student_id`, `assignment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
文件存储选用MinIO而非传统FTP方案,主要因为:
Vue3组合式API 相比Options API更适合复杂教育应用:
typescript复制// 作业列表组件逻辑封装
export function useAssignmentList(courseId: Ref<string>) {
const assignments = ref<Assignment[]>([])
const loading = ref(false)
const fetchAssignments = async () => {
loading.value = true
try {
const res = await api.get(`/courses/${courseId.value}/assignments`)
assignments.value = res.data
} finally {
loading.value = false
}
}
onMounted(fetchAssignments)
return { assignments, loading, refresh: fetchAssignments }
}
UI库选择Vant4而非Element Plus的决策点:
Android端采用混合架构,核心考量:
kotlin复制// 文件选择器原生实现
class FileChooserActivity : AppCompatActivity() {
private lateinit var resultLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val uri = it.data?.data ?: return@registerForActivityResult
// 将文件上传到MinIO后回调JS
uploadFile(uri) { url ->
webView.evaluateJavascript("onFileUploaded('$url')", null)
}
}
}
}
fun openFilePicker() {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "*/*"
addCategory(Intent.CATEGORY_OPENABLE)
}
resultLauncher.launch(intent)
}
}
采用RBAC模型实现三级权限控制:
java复制// Spring Security配置示例
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/teacher/**").hasRole("TEACHER")
.requestMatchers("/api/assignment/submit").hasRole("STUDENT")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}
前端路由动态加载方案:
typescript复制// 路由守卫实现
router.beforeEach(async (to) => {
const authStore = useAuthStore()
if (!authStore.isLoggedIn) {
return '/login'
}
// 从JWT解析用户角色
const userRole = authStore.user?.role
if (to.meta.roles && !to.meta.roles.includes(userRole)) {
return '/403'
}
})
教师端批注功能技术要点:
json复制{
"annotations": [{
"page": 1,
"type": "highlight",
"rect": [100, 200, 300, 50],
"comment": "此处引用需要注明来源"
}],
"grade": 85,
"feedback": "整体完成度较好,但..."
}
双通道消息推送方案:
java复制@Controller
public class NotificationController {
@MessageMapping("/notifications")
@SendToUser("/queue/notifications")
public Notification sendNotification(Notification notification) {
return notification;
}
}
kotlin复制class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
message.notification?.let {
showNotification(it.title, it.body)
}
}
private fun showNotification(title: String?, body: String?) {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, "submissions")
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(R.drawable.ic_notification)
.setContentIntent(pendingIntent)
.build()
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
.notify(Random.nextInt(), notification)
}
}
作业提交高峰期的优化手段:
关键安全措施实施细节:
通过实际项目对比验证的特性优势:
kotlin复制// 传统Java写法
public String getTeacherName(Assignment assignment) {
return assignment.getCourse().getTeacher().getName(); // 每步都可能NPE
}
// Kotlin写法
fun getTeacherName(assignment: Assignment): String? {
return assignment.course?.teacher?.name // 编译期检查
}
kotlin复制fun EditText.validateNonEmpty(): Boolean {
return text?.toString()?.isNotBlank() ?: false.also {
if (!it) error = "该字段不能为空"
}
}
// 使用处
if (!editText.validateNonEmpty()) return
Docker Compose编排方案:
yaml复制version: '3.8'
services:
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- minio
minio:
image: minio/minio
volumes:
- minio_data:/data
command: server /data --console-address ":9001"
redis:
image: redis:alpine
volumes:
- redis_data:/data
volumes:
minio_data:
redis_data:
Prometheus + Grafana监控指标:
在实际使用中,我们收集到教师用户的几个关键需求:
针对技术债的改进计划: