feat(diary): 数据层 + 班级系统 (Phase F1 + B3)

Flutter 数据层 (Phase F1):
- journal_entry.dart: 日记数据模型 (Mood/Weather/tags/version)
- journal_element.dart: 元素模型 (text/image/sticker/handwriting_ref/tape)
- school_class.dart: 班级模型
- user_settings.dart: 用户设置 (主题/画笔/字号)
- isar_database.dart: Isar 初始化
- api_client.dart: Dio + JWT注入 + 离线感知 + 401处理
- journal_repository.dart: 抽象接口 + InMemory实现 (乐观锁)
- sync_engine.dart: WiFi同步 + 操作队列 + 重试(5次) + 快照持久化

Rust 班级系统 (Phase B3):
- class_service.rs: 创建班级(6位码) + 加入班级 + 成员管理
- topic_service.rs: 老师布置主题 + 主题列表
- comment_service.rs: 老师点评 + 评语列表
- class_handler.rs: 5个API端点 + 权限守卫
- topic_handler.rs: 2个API端点
- comment_handler.rs: 2个API端点
- dto.rs: 新增5个DTO (ClassMemberResp/CreateTopicReq/TopicResp/CreateCommentReq/CommentResp)
- 6条新路由注册

验证: cargo check 通过, 433测试全绿, flutter analyze 1 warning
This commit is contained in:
iven
2026-06-01 00:55:51 +08:00
parent d0653614e0
commit 5e6c6fdd62
18 changed files with 2205 additions and 1 deletions

View File

@@ -0,0 +1,112 @@
// 班级数据模型 — 手写不可变类(避免 build_runner 依赖)
import 'package:uuid/uuid.dart';
/// 班级 — 老师创建,学生通过班级码加入
///
/// 班级码安全规则:
/// - 6 位字母数字混合62^6 约 568 亿种组合)
/// - 有效期控制(学期结束自动失效)
/// - 连续 5 次错误锁定 30 分钟
class SchoolClass {
final String id;
final String name;
final String schoolName;
final String teacherId;
final String classCode;
final int memberCount;
final bool isActive;
final DateTime? expiresAt;
final DateTime createdAt;
final DateTime updatedAt;
const SchoolClass({
required this.id,
required this.name,
required this.schoolName,
required this.teacherId,
required this.classCode,
this.memberCount = 0,
this.isActive = true,
this.expiresAt,
required this.createdAt,
required this.updatedAt,
});
SchoolClass copyWith({
String? id,
String? name,
String? schoolName,
String? teacherId,
String? classCode,
int? memberCount,
bool? isActive,
DateTime? expiresAt,
bool clearExpiresAt = false,
DateTime? createdAt,
DateTime? updatedAt,
}) =>
SchoolClass(
id: id ?? this.id,
name: name ?? this.name,
schoolName: schoolName ?? this.schoolName,
teacherId: teacherId ?? this.teacherId,
classCode: classCode ?? this.classCode,
memberCount: memberCount ?? this.memberCount,
isActive: isActive ?? this.isActive,
expiresAt: clearExpiresAt ? null : (expiresAt ?? this.expiresAt),
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'school_name': schoolName,
'teacher_id': teacherId,
'class_code': classCode,
'member_count': memberCount,
'is_active': isActive,
'expires_at': expiresAt?.toIso8601String(),
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
factory SchoolClass.fromJson(Map<String, dynamic> json) => SchoolClass(
id: json['id'] as String,
name: json['name'] as String,
schoolName: json['school_name'] as String,
teacherId: json['teacher_id'] as String,
classCode: json['class_code'] as String,
memberCount: (json['member_count'] as int?) ?? 0,
isActive: (json['is_active'] as bool?) ?? true,
expiresAt: json['expires_at'] != null
? DateTime.parse(json['expires_at'] as String)
: null,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
/// 创建新班级的工厂方法 — 自动生成 id 和时间戳
factory SchoolClass.create({
required String name,
required String schoolName,
required String teacherId,
required String classCode,
DateTime? expiresAt,
}) {
final now = DateTime.now();
return SchoolClass(
id: const Uuid().v4(),
name: name,
schoolName: schoolName,
teacherId: teacherId,
classCode: classCode,
memberCount: 0,
isActive: true,
expiresAt: expiresAt,
createdAt: now,
updatedAt: now,
);
}
}