1. 任务管理系统架构设计解析
在移动应用开发领域,任务管理系统是一个典型的多层架构应用。这种架构设计模式能够有效分离关注点,提高代码的可维护性和可扩展性。下面我将从客户端和服务器端两个维度,详细解析这种架构设计的核心思想和实现细节。
1.1 客户端架构:MVVM模式深度实践
现代Android开发中,MVVM(Model-View-ViewModel)模式已经成为主流架构选择。这种架构将应用分为三个主要层次:
- UI层:负责界面展示和用户交互
- ViewModel层:处理业务逻辑和状态管理
- Model层:负责数据获取和持久化
在实际项目中,我们通常会进一步细化Model层,引入UseCase和Repository的概念,形成更加清晰的分层结构。
1.1.1 UI层实现要点
UI层的核心是三个主要界面组件:
kotlin复制// 任务列表Fragment示例代码
class TaskListFragment : Fragment() {
private lateinit var binding: FragmentTaskListBinding
private val viewModel: TaskListViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentTaskListBinding.inflate(inflater, container, false)
setupRecyclerView()
observeViewModel()
return binding.root
}
private fun setupRecyclerView() {
val adapter = TaskAdapter { task ->
// 处理任务项点击
navigateToDetail(task.id)
}
binding.recyclerView.adapter = adapter
binding.recyclerView.layoutManager = LinearLayoutManager(context)
}
private fun observeViewModel() {
viewModel.tasks.observe(viewLifecycleOwner) { tasks ->
(binding.recyclerView.adapter as TaskAdapter).submitList(tasks)
}
}
}
提示:在UI层设计中,应严格遵循单一职责原则,每个界面组件只负责展示数据和响应用户交互,不包含任何业务逻辑。
1.1.2 ViewModel层设计模式
ViewModel层作为UI层和业务逻辑层的桥梁,需要精心设计:
kotlin复制class TaskListViewModel(
private val loadTasksUseCase: LoadTasksUseCase,
private val completeTaskUseCase: CompleteTaskUseCase
) : ViewModel() {
private val _tasks = MutableLiveData<List<TaskSummary>>()
val tasks: LiveData<List<TaskSummary>> = _tasks
fun loadTasks(viewType: TaskViewType, filters: TaskFilters?) {
viewModelScope.launch {
val result = loadTasksUseCase.execute(TaskQuery(viewType, filters))
if (result.isSuccess) {
_tasks.value = result.getOrNull()
}
}
}
fun completeTask(taskId: String) {
viewModelScope.launch {
completeTaskUseCase.execute(taskId)
// 完成后刷新列表
loadTasks(currentViewType, currentFilters)
}
}
}
1.2 服务器端架构:SpringBoot分层设计
服务器端采用经典的三层架构:
- Controller层:接收HTTP请求,处理参数校验和响应格式化
- Service层:实现核心业务逻辑
- Repository层:负责数据持久化
1.2.1 Controller层最佳实践
java复制@RestController
@RequestMapping("/v1/tasks")
public class TaskController {
private final TaskService taskService;
@GetMapping
public Response<Page<TaskSummaryVO>> getTasks(@ModelAttribute TaskQueryReq query) {
return Response.success(taskService.getPagedTasks(getCurrentUserId(), query));
}
@PostMapping
public Response<TaskDetailVO> createTask(@RequestBody @Valid CreateTaskReq req) {
return Response.success(taskService.createTask(getCurrentUserId(), req));
}
}
注意:Controller层应保持精简,只处理与HTTP协议相关的逻辑,业务规则应全部放在Service层。
1.2.2 Service层复杂业务处理
Service层是业务逻辑的核心所在,需要处理各种复杂场景:
java复制@Service
@RequiredArgsConstructor
public class TaskService {
private final TaskRepository taskRepository;
private final ReminderService reminderService;
private final RecurringTaskService recurringTaskService;
@Transactional
public TaskDetailVO completeTask(String taskId, Long userId) {
Task task = taskRepository.findByIdAndUserId(taskId, userId)
.orElseThrow(() -> new BusinessException("任务不存在"));
if (task.getStatus() == TaskStatus.COMPLETED) {
throw new BusinessException("任务已完成");
}
task.setStatus(TaskStatus.COMPLETED);
task.setCompletedAt(Instant.now());
// 处理周期性任务
if (task.getRecurringRule() != null) {
recurringTaskService.handleCompletion(task);
}
// 取消所有提醒
reminderService.cancelAllRemindersForTask(taskId);
return convertToDetailVO(taskRepository.save(task));
}
}
2. 核心功能实现细节
2.1 任务管理功能实现
任务管理是系统的核心功能,包括任务的创建、编辑、完成等基本操作,每个操作都涉及复杂的业务逻辑和状态管理。
2.1.1 任务创建流程详解
任务创建不仅仅是保存一条记录那么简单,它涉及多个关联操作:
- 表单验证:确保必填字段完整,时间逻辑合理
- 实体创建:构建任务实体并保存
- 提醒设置:如果设置了截止时间和提醒规则,需要创建提醒记录
- 通知触发:必要时发送创建通知
kotlin复制class CreateTaskUseCase @Inject constructor(
private val taskRepository: TaskRepository,
private val reminderService: ReminderService
) {
suspend fun execute(draft: TaskDraft): Result<TaskDetail> {
// 表单验证
if (draft.title.isBlank()) {
return Result.failure(ValidationException("标题不能为空"))
}
if (draft.dueTime != null && draft.dueTime.isBefore(Instant.now())) {
return Result.failure(ValidationException("截止时间不能早于当前时间"))
}
// 创建任务
val task = taskRepository.createTask(draft)
// 设置提醒
if (draft.reminderRules.isNotEmpty()) {
draft.reminderRules.forEach { rule ->
reminderService.scheduleReminder(task.id, rule)
}
}
return Result.success(task)
}
}
2..2 任务完成的状态流转
任务完成是一个重要的状态变更操作,需要处理多种关联逻辑:
- 状态验证:确保任务当前状态允许完成
- 状态更新:修改任务状态为已完成,记录完成时间
- 周期性任务处理:如果是周期性任务,创建下一个周期实例
- 提醒清理:取消所有未触发的提醒
- 自动归档:根据配置决定是否自动归档任务
java复制@Transactional
public void completeTask(String taskId, Long userId) {
Task task = getTaskByIdAndUser(taskId, userId);
validateTaskCanBeCompleted(task);
// 更新任务状态
task.setStatus(TaskStatus.COMPLETED);
task.setCompletedAt(Instant.now());
// 处理周期性任务
if (task.getRecurringRule() != null) {
handleRecurringTask(task);
}
// 清理提醒
reminderRepository.deleteByTaskId(taskId);
schedulingService.cancelRemindersForTask(taskId);
// 自动归档
if (userSettingsRepository.getAutoArchiveSetting(userId)) {
task.setArchived(true);
}
taskRepository.save(task);
// 发送通知
notificationService.sendTaskCompletedNotification(task);
}
2.2 任务筛选与搜索实现
高效的筛选和搜索功能可以极大提升用户体验。实现这一功能需要考虑以下几个方面:
2.2.1 客户端筛选逻辑
客户端需要将用户设置的筛选条件转换为后端可以理解的查询参数:
kotlin复制class ApplyTaskFilterUseCase @Inject constructor(
private val taskRepository: TaskRepository
) {
suspend fun execute(filter: TaskFilter): Result<PagedData<TaskSummary>> {
val query = FilteredTaskQuery(
statuses = filter.statuses,
priorities = filter.priorities,
tags = filter.tags,
dateRange = filter.dateRange,
keyword = filter.keyword
)
return try {
val result = taskRepository.getTasksWithFilter(query)
Result.success(result)
} catch (e: Exception) {
Result.failure(e)
}
}
}
2.2.2 服务器端动态查询构建
服务器端需要使用灵活的方式构建动态查询,常见的有两种方式:
- JPA Specification:类型安全,但复杂查询可读性差
- QueryDSL:更灵活,代码更易读
java复制public Page<Task> findByCriteria(Long userId, TaskSearchCriteria criteria, Pageable pageable) {
QTask task = QTask.task;
BooleanBuilder builder = new BooleanBuilder();
builder.and(task.userId.eq(userId));
if (criteria.getStatuses() != null && !criteria.getStatuses().isEmpty()) {
builder.and(task.status.in(criteria.getStatuses()));
}
if (criteria.getPriorities() != null && !criteria.getPriorities().isEmpty()) {
builder.and(task.priority.in(criteria.getPriorities()));
}
if (criteria.getStartDate() != null) {
builder.and(task.dueTime.goe(criteria.getStartDate()));
}
if (criteria.getEndDate() != null) {
builder.and(task.dueTime.loe(criteria.getEndDate()));
}
if (StringUtils.hasText(criteria.getKeyword())) {
builder.and(task.title.containsIgnoreCase(criteria.getKeyword())
.or(task.description.containsIgnoreCase(criteria.getKeyword())));
}
return taskRepository.findAll(builder, pageable);
}
3. 任务协同功能实现
3.1 任务共享机制
任务共享是协作功能的核心,需要支持两种主要方式:
- 链接分享:生成一个可共享的链接,设置权限和有效期
- 指定协作者:直接邀请特定用户协作
3.1.1 链接分享实现
java复制public ShareResult shareTaskByLink(String taskId, Long userId, ShareConfig config) {
// 验证权限
Task task = validateUserCanShareTask(taskId, userId);
// 生成唯一链接ID
String linkId = UUID.randomUUID().toString();
// 创建分享记录
ShareLink shareLink = new ShareLink();
shareLink.setLinkId(linkId);
shareLink.setTaskId(taskId);
shareLink.setPermission(config.getPermission());
shareLink.setExpiryTime(config.getExpiryTime());
shareLink.setCreatedBy(userId);
shareLink.setCreatedAt(Instant.now());
shareLink.setStatus(ShareLinkStatus.ACTIVE);
shareLinkRepository.save(shareLink);
// 构建分享结果
return ShareResult.builder()
.shareType(ShareType.LINK)
.linkId(linkId)
.url(buildShareUrl(linkId))
.expiryTime(config.getExpiryTime())
.permission(config.getPermission())
.build();
}
3.1.2 协作者邀请流程
kotlin复制class ShareTaskUseCase @Inject constructor(
private val taskCollaborationRepository: TaskCollaborationRepository,
private val notificationService: NotificationService
) {
suspend fun execute(taskId: String, config: ShareConfig): Result<ShareResult> {
return when (config.shareType) {
ShareType.LINK -> {
// 处理链接分享逻辑
val result = taskCollaborationRepository.shareByLink(taskId, config)
Result.success(result)
}
ShareType.SPECIFY_USER -> {
// 处理指定用户分享逻辑
val collaborators = config.collaborators ?: emptyList()
if (collaborators.isEmpty()) {
return Result.failure(IllegalArgumentException("必须指定至少一个协作者"))
}
val results = collaborators.map { userId ->
val invitation = taskCollaborationRepository.inviteCollaborator(taskId, userId, config.permission)
notificationService.sendCollaborationInvitation(invitation)
invitation
}
Result.success(ShareResult(ShareType.SPECIFY_USER, collaborators = results))
}
}
}
}
3.2 任务指派功能
任务指派是团队协作中的重要功能,实现时需要考虑:
- 权限验证:确保指派人有权进行指派
- 有效性检查:确保被指派人是有效的团队成员
- 状态同步:更新任务状态并通知相关方
java复制@Transactional
public void assignTask(String taskId, Long assignerId, String assigneeId) {
// 获取任务并验证权限
Task task = taskRepository.findById(taskId)
.orElseThrow(() -> new BusinessException("任务不存在"));
if (!task.getCreatedBy().equals(assignerId) && !isProjectAdmin(assignerId, task.getProjectId())) {
throw new BusinessException("无权指派此任务");
}
// 验证被指派人是否是团队成员
if (!teamMemberRepository.existsByTeamIdAndUserId(task.getTeamId(), assigneeId)) {
throw new BusinessException("被指派人不是团队成员");
}
// 更新任务负责人
task.setAssigneeId(assigneeId);
task.setStatus(TaskStatus.IN_PROGRESS); // 通常指派后状态变更为进行中
taskRepository.save(task);
// 发送通知
notificationService.sendTaskAssignedNotification(taskId, assignerId, assigneeId);
}
4. 提醒与集成功能实现
4.1 提醒管理
提醒功能是任务管理系统的重要组成部分,需要考虑多种触发方式和复杂的场景处理。
4.1.1 提醒类型与计算
系统支持两种主要提醒类型:
- 绝对时间提醒:在特定时间点触发
- 相对时间提醒:基于任务截止时间的相对偏移量计算
kotlin复制class ReminderService @Inject constructor(
private val reminderRepository: ReminderRepository,
private val schedulingService: SchedulingService
) {
fun createReminder(taskId: String, rule: ReminderRule): Reminder {
val triggerTime = when (rule.type) {
ReminderType.ABSOLUTE -> rule.time
ReminderType.RELATIVE -> calculateRelativeTime(taskId, rule.offset)
}
val reminder = Reminder(
taskId = taskId,
type = rule.type,
triggerTime = triggerTime,
status = ReminderStatus.PENDING
)
val savedReminder = reminderRepository.save(reminder)
schedulingService.scheduleReminder(savedReminder)
return savedReminder
}
private fun calculateRelativeTime(taskId: String, offset: Duration): Instant {
val task = taskRepository.findById(taskId) ?: throw IllegalArgumentException("任务不存在")
return task.dueTime.minus(offset)
}
}
4.1.2 提醒触发与处理
提醒触发后需要执行以下操作:
- 发送通知:通过多种渠道(应用内、邮件、推送等)通知用户
- 状态更新:将提醒标记为已触发
- 日志记录:记录触发历史
java复制@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void checkAndTriggerReminders() {
Instant now = Instant.now();
List<Reminder> dueReminders = reminderRepository.findByTriggerTimeBeforeAndStatus(now, ReminderStatus.PENDING);
dueReminders.forEach(reminder -> {
// 发送通知
notificationService.sendReminderNotification(
reminder.getTaskId(),
reminder.getTriggerTime()
);
// 更新状态
reminder.setStatus(ReminderStatus.TRIGGERED);
reminderRepository.save(reminder);
// 记录日志
reminderLogRepository.save(new ReminderLog(reminder));
});
}
4.2 日历集成
日历视图提供了任务的时间维度展示,实现时需要考虑多种显示策略。
4.2.1 日历事件聚合
将任务数据转换为日历事件需要考虑多种情况:
- 有开始时间和截止时间的任务:显示为时间块
- 只有截止时间的任务:根据配置显示为全天事件或固定时长事件
- 已完成的任务:通常以不同样式显示或隐藏
java复制public List<CalendarEventVO> getCalendarEvents(Long userId, CalendarQueryReq query) {
List<Task> tasks = taskRepository.findByUserIdAndTimeRange(
userId,
query.getStart(),
query.getEnd(),
query.isIncludeCompleted()
);
return tasks.stream()
.map(this::convertToCalendarEvent)
.collect(Collectors.toList());
}
private CalendarEventVO convertToCalendarEvent(Task task) {
CalendarEventVO event = new CalendarEventVO();
event.setTaskId(task.getId());
event.setTitle(task.getTitle());
// 根据任务时间设置决定如何显示
if (task.getStartTime() != null && task.getDueTime() != null) {
// 显示为时间块
event.setStartTime(task.getStartTime());
event.setEndTime(task.getDueTime());
event.setAllDay(false);
} else if (task.getDueTime() != null) {
// 只有截止时间,根据配置决定显示方式
UserCalendarSettings settings = settingsRepository.getCalendarSettings(task.getUserId());
if (settings.isShowTasksWithoutStartAsAllDay()) {
event.setAllDay(true);
event.setStartTime(task.getDueTime().truncatedTo(ChronoUnit.DAYS));
event.setEndTime(task.getDueTime().truncatedTo(ChronoUnit.DAYS).plus(1, ChronoUnit.DAYS));
} else {
event.setAllDay(false);
event.setStartTime(task.getDueTime().minus(settings.getDefaultTaskDuration()));
event.setEndTime(task.getDueTime());
}
}
return event;
}
4.2.2 拖拽更新任务时间
日历视图通常允许用户通过拖拽事件块来更新任务时间:
kotlin复制class UpdateTaskDueTimeUseCase @Inject constructor(
private val taskRepository: TaskRepository,
private val reminderService: ReminderService
) {
suspend fun execute(taskId: String, newDueTime: Instant): Result<Unit> {
return try {
// 获取当前任务
val task = taskRepository.findById(taskId) ?: return Result.failure(IllegalArgumentException("任务不存在"))
// 更新截止时间
val changes = TaskEditableFields(dueTime = newDueTime)
taskRepository.editTask(taskId, changes)
// 重新计算相对提醒
reminderService.rescheduleRelativeReminders(taskId)
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
}
5. 系统设计与实现中的关键考量
5.1 客户端状态管理
在客户端实现中,状态管理是一个复杂但至关重要的问题。我们需要考虑以下几个方面:
- 单一数据源:确保所有组件使用的数据来自同一来源
- 响应式更新:当数据变化时自动更新所有相关UI
- 离线支持:在网络不可用时仍能提供基本功能
kotlin复制class TaskListViewModel @Inject constructor(
private val loadTasksUseCase: LoadTasksUseCase,
private val syncManager: SyncManager
) : ViewModel() {
private val _uiState = MutableStateFlow<TaskListUiState>(TaskListUiState.Loading)
val uiState: StateFlow<TaskListUiState> = _uiState
private var currentViewType: TaskViewType = TaskViewType.ALL
private var currentFilters: TaskFilters? = null
init {
viewModelScope.launch {
// 监听同步状态变化
syncManager.syncState.collect { syncState ->
if (syncState == SyncState.COMPLETED) {
loadTasks(currentViewType, currentFilters)
}
}
}
}
fun loadTasks(viewType: TaskViewType, filters: TaskFilters?) {
currentViewType = viewType
currentFilters = filters
viewModelScope.launch {
_uiState.value = TaskListUiState.Loading
when (val result = loadTasksUseCase.execute(TaskQuery(viewType, filters))) {
is Result.Success -> {
_uiState.value = TaskListUiState.Success(result.data)
}
is Result.Failure -> {
_uiState.value = TaskListUiState.Error(result.exception.message)
}
}
}
}
}
5.2 服务器端事务管理
服务器端处理复杂业务逻辑时,事务管理至关重要:
- 事务边界:合理划分事务范围
- 隔离级别:根据业务需求选择合适的隔离级别
- 异常处理:确保异常时能正确回滚
java复制@Service
@RequiredArgsConstructor
public class TaskCompletionService {
private final TaskRepository taskRepository;
private final ReminderRepository reminderRepository;
private final RecurringTaskService recurringTaskService;
private final NotificationService notificationService;
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public void completeTaskWithTransaction(String taskId, Long userId) {
Task task = taskRepository.findByIdAndUserId(taskId, userId)
.orElseThrow(() -> new BusinessException("任务不存在"));
// 验证任务状态
if (task.getStatus() == TaskStatus.COMPLETED) {
throw new BusinessException("任务已完成");
}
// 更新任务状态
task.setStatus(TaskStatus.COMPLETED);
task.setCompletedAt(Instant.now());
taskRepository.save(task);
// 处理周期性任务
if (task.getRecurringRule() != null) {
recurringTaskService.handleCompletion(task);
}
// 清理提醒
reminderRepository.deleteByTaskId(taskId);
// 发送通知
notificationService.sendTaskCompletedNotification(task);
}
}
5.3 性能优化策略
在大规模应用中,性能优化是必不可少的考虑因素:
- 数据库查询优化:合理使用索引,避免N+1查询
- 缓存策略:对频繁访问的数据进行缓存
- 批量操作:减少网络往返和数据库访问次数
java复制@Service
@RequiredArgsConstructor
public class TaskBatchService {
private final TaskRepository taskRepository;
private final JdbcTemplate jdbcTemplate;
// 使用批量更新提高性能
@Transactional
public int batchUpdateStatus(List<String> taskIds, TaskStatus newStatus) {
String sql = "UPDATE task SET status = ?, updated_at = NOW() WHERE id = ?";
List<Object[]> batchArgs = taskIds.stream()
.map(id -> new Object[]{newStatus.name(), id})
.collect(Collectors.toList());
return jdbcTemplate.batchUpdate(sql, batchArgs).length;
}
// 使用JOIN和投影优化查询
public List<TaskSummaryDTO> findTaskSummariesByUser(Long userId) {
return taskRepository.findSummariesByUserId(userId);
}
}
6. 常见问题与解决方案
在实际开发过程中,我们遇到了许多典型问题,以下是其中一些常见问题及其解决方案:
6.1 数据同步问题
问题描述:在多设备场景下,如何保证数据的一致性?
解决方案:
- 乐观并发控制:使用版本号或时间戳检测冲突
- 增量同步:只同步变更部分而非全量数据
- 冲突解决策略:定义明确的冲突解决规则
kotlin复制class TaskSyncUseCase @Inject constructor(
private val taskRepository: TaskRepository,
private val syncLogRepository: SyncLogRepository
) {
suspend fun syncTasks(userId: String, lastSyncTime: Instant): SyncResult {
// 获取本地变更
val localChanges = taskRepository.getLocalChangesSince(lastSyncTime)
// 获取服务端变更
val serverChanges = taskRepository.getServerChangesSince(userId, lastSyncTime)
// 检测并解决冲突
val conflicts = detectConflicts(localChanges, serverChanges)
val resolved = resolveConflicts(conflicts)
// 应用变更
val tasksToUpdate = (serverChanges - conflicts).plus(resolved)
taskRepository.applyChanges(tasksToUpdate)
// 记录同步日志
syncLogRepository.logSync(userId, Instant.now())
return SyncResult(
updatedTasks = tasksToUpdate.size,
conflictsResolved = resolved.size
)
}
}
6.2 提醒可靠性问题
问题描述:如何确保提醒能够准时、可靠地触发?
解决方案:
- 多级触发机制:应用内提醒 + 推送通知 + 邮件提醒
- 失败重试:对失败的提醒尝试重新发送
- 心跳检测:定期检查未触发的提醒
java复制@Service
@RequiredArgsConstructor
public class ReliableReminderService {
private final ReminderRepository reminderRepository;
private final NotificationService notificationService;
private final RetryTemplate retryTemplate;
@Scheduled(fixedRate = 300000) // 每5分钟检查一次
public void checkAndRetryFailedReminders() {
List<Reminder> failedReminders = reminderRepository.findByStatusAndTriggerTimeBefore(
ReminderStatus.FAILED,
Instant.now().minus(1, ChronoUnit.HOURS)
);
failedReminders.forEach(reminder -> {
retryTemplate.execute(context -> {
notificationService.sendReminderNotification(
reminder.getTaskId(),
reminder.getTriggerTime()
);
reminder.setStatus(ReminderStatus.TRIGGERED);
reminderRepository.save(reminder);
return null;
});
});
}
}
6.3 性能瓶颈问题
问题描述:在处理大量任务时,系统响应变慢怎么办?
优化策略:
- 分页加载:避免一次性加载过多数据
- 延迟加载:只在需要时加载详细信息
- 数据库优化:合理设计索引,优化查询语句
kotlin复制class TaskPagingSource(
private val loadTasksUseCase: LoadTasksUseCase,
private val query: TaskQuery
) : PagingSource<Int, TaskSummary>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TaskSummary> {
return try {
val page = params.key ?: 0
val pageSize = params.loadSize
val result = loadTasksUseCase.execute(
query.copy(page = page, size = pageSize)
)
LoadResult.Page(
data = result.data,
prevKey = if (page == 0) null else page - 1,
nextKey = if (result.data.size < pageSize) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
7. 架构演进与扩展性设计
随着业务发展,系统架构需要不断演进以适应新的需求。以下是我们在架构设计上的一些前瞻性考虑:
7.1 微服务拆分策略
当系统规模扩大时,可以考虑按功能模块拆分为微服务:
- 任务核心服务:处理任务的CRUD操作
- 提醒服务:专门处理提醒相关逻辑
- 协作服务:处理任务共享和团队协作
- 通知服务:统一管理所有通知渠道
java复制// 微服务间通信示例 - 使用FeignClient
@FeignClient(name = "reminder-service", path = "/api/reminders")
public interface ReminderServiceClient {
@PostMapping
ReminderDTO createReminder(@RequestBody CreateReminderRequest request);
@PutMapping("/{id}/reschedule")
void rescheduleReminder(@PathVariable String id, @RequestBody RescheduleRequest request);
}
@Service
@RequiredArgsConstructor
public class TaskService {
private final ReminderServiceClient reminderServiceClient;
@Transactional
public TaskDTO createTask(CreateTaskRequest request) {
// 创建任务...
Task task = taskRepository.save(new Task(request));
// 调用提醒服务创建提醒
if (request.getReminderTime() != null) {
CreateReminderRequest reminderRequest = new CreateReminderRequest(
task.getId(),
request.getReminderTime(),
request.getReminderType()
);
reminderServiceClient.createReminder(reminderRequest);
}
return convertToDTO(task);
}
}
7.2 事件驱动架构
引入事件驱动架构可以降低系统耦合度,提高扩展性:
-
核心事件定义:
- 任务创建事件
- 任务更新事件
- 任务完成事件
- 提醒触发事件
-
事件处理器示例:
kotlin复制@Component
class TaskCompletedEventHandler(
private val analyticsService: AnalyticsService,
private val badgeService: BadgeService
) {
@TransactionalEventListener
fun handleTaskCompletedEvent(event: TaskCompletedEvent) {
// 记录分析数据
analyticsService.recordTaskCompletion(
event.userId,
event.taskId,
event.completionTime
)
// 检查并颁发成就徽章
badgeService.checkAndAwardCompletionBadges(event.userId)
}
}
7.3 前后端分离与API设计
良好的API设计是前后端高效协作的基础:
- RESTful规范:合理设计资源路径和HTTP方法
- 版本控制:通过URL或Header支持API版本管理
- 文档自动化:使用Swagger/OpenAPI生成接口文档
java复制@RestController
@RequestMapping("/api/v1/tasks")
@Tag(name = "任务管理")
public class TaskController {
@Operation(summary = "获取任务列表")
@GetMapping
public PageResponse<TaskDTO> getTasks(
@Parameter(description = "页码,从0开始") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "20") int size,
@Parameter(description = "筛选条件") TaskFilter filter
) {
return taskService.getTasks(page, size, filter);
}
@Operation(summary = "创建任务")
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public TaskDTO createTask(
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "任务创建参数")
@Valid @RequestBody CreateTaskRequest request
) {
return taskService.createTask(request);
}
}
8. 测试策略与质量保障
完善的测试体系是保证系统质量的关键。我们采用多层次测试策略:
8.1 单元测试
针对核心业务逻辑和算法进行隔离测试:
kotlin复制class CompleteTaskUseCaseTest {
@Test
fun `complete task should update status and handle recurring task`() {
// 准备测试数据
val taskId = "task-123"
val mockRepo = mockk<TaskRepository>()
val mockRecurringService = mockk<RecurringTaskService>()
every { mockRepo.findById(taskId) } returns Task(
id = taskId,
status = TaskStatus.IN_PROGRESS,
recurringRule = RecurringRule(RecurringType.DAILY, 1)
)
every { mockRepo.save(any()) } returnsArgument 0
justRun { mockRecurringService.handleCompletion(any()) }
// 执行测试
val useCase = CompleteTaskUseCase(mockRepo, mockRecurringService)
useCase.execute(taskId)
// 验证结果
verify {
mockRepo.save(withArg { task ->
task.status shouldBe TaskStatus.COMPLETED
task.completedAt shouldNotBe null
})
}
verify { mockRecurringService.handleCompletion(any()) }
}
}
8.2 集成测试
验证模块间的交互和数据流:
java复制@SpringBootTest
class TaskServiceIntegrationTest {
@Autowired
private TaskService taskService;
@Autowired
private TaskRepository taskRepository;
@Autowired
private ReminderRepository reminderRepository;
@Test
@Transactional
public void completeTask_ShouldUpdateStatusAndCleanReminders() {
// 准备测试数据
Task task = new Task();
task.setStatus(TaskStatus.IN_PROGRESS);
task = taskRepository.save(task);
Reminder reminder = new Reminder();
reminder.setTaskId(task.getId());
reminder.setStatus(ReminderStatus.PENDING);
reminderRepository.save(reminder);
// 执行测试
taskService.completeTask(task.getId(), task.getUserId());
// 验证结果
Task updatedTask = taskRepository.findById(task.getId()).get();
assertEquals(TaskStatus.COMPLETED, updatedTask.getStatus());
List<Reminder> reminders = reminderRepository.findByTaskId(task.getId());
assertTrue(reminders.isEmpty());
}
}
8.3 端到端测试
模拟真实用户场景的全流程测试:
kotlin复制@SpringBootTest
@AutoConfigureMockMvc
class TaskE2ETest {
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `create, complete and verify task`() {
// 创建任务
mockMvc.perform(
post("/api/v1/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"title": "测试任务",
"description": "这是一个测试任务",
"dueTime": "2023-12-31T00:00:00Z"
}
""".trimIndent())
).andExpect(status().isCreated)
// 获取任务列表
val result = mockMvc.perform(get("/api/v1/tasks"))
.andExpect(status().isOk)
.andReturn()
val tasks = Json.decodeFromString<List<TaskDTO>>(result.response.contentAsString)
val taskId = tasks.first().id
// 完成任务
mockMvc.perform(
post("/api/v1/tasks/$taskId/complete")
).andExpect(status().isOk)
// 验证任务状态
mockMvc.perform(get("/api/v1/tasks/$taskId"))
.andExpect(status().isOk)
.andExpect(jsonPath("$.status").value("COMPLETED"))
}
}
9. 部署与监控
完善的部署和监控体系是系统稳定运行的保障:
9.1 容器化部署
使用Docker和Kubernetes实现标准化部署:
dockerfile复制# 后端服务Dockerfile示例
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY build/libs/task-service-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
yaml复制# Kubernetes部署示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: task-service
spec:
replicas: 3
selector:
matchLabels:
app: task-service
template:
metadata:
labels:
app: task-service
spec:
containers:
- name: task-service
image: registry.example.com/task-service:1.0.0
ports:
- container