Skip to content

claim

在 Flowable 中,claim 方法用于用户认领任务。当任务分配给候选用户组时,组内的用户可以使用此方法认领任务,成为任务的分配人。

语法示例

kotlin
/**
 * 认领任务(Kotlin 视角)
 * Flowable Java API 为 void;Kotlin 中等价为 Unit。
 * @param taskId 要认领的任务ID
 * @param userId 认领任务的用户ID
 */
// Kotlin 中直接调用:返回类型等价于 Unit
taskService.claim(taskId, userId)

// 如需签名说明(非真实扩展),可理解为:
fun TaskService.claim(taskId: String, userId: String): Unit

使用场景

  • 候选人任务认领:候选用户组中的用户认领任务
  • 工作负载分配:团队成员主动认领适合的任务
  • 任务分派:管理员为用户分派任务
  • 工作流协作:多人协作场景中的任务分配

真实案例

呼叫中心督办任务通常由“客服质检组”成员自行挑选处理。前端点击“认领”按钮后,需要校验该客服是否具备候选资格,并调用 Flowable 完成认领动作。

kotlin
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/support/tasks")
class SupportTaskController(
    private val supportTaskClaimService: SupportTaskClaimService
) {

    /**
     * 客服在质检工作台点击“我要处理”时触发
     * - taskId 来自路径变量
     * - userId 通过请求头传入(示例中使用 X-Operator-Id 表示当前操作用户)
     * 成功时返回 204 No Content,前端据此刷新页面或提示成功
     */
    @PostMapping("/{taskId}/claim")
    fun claim(
        @PathVariable taskId: String, // 任务 ID
        @RequestHeader("X-Operator-Id") userId: String // 当前操作用户 ID
    ): ResponseEntity<Void> {
        // 委托给领域服务进行业务校验与 Flowable 调用
        supportTaskClaimService.claimTask(taskId, userId)
        // 无响应体,仅表示操作成功
        return ResponseEntity.noContent().build()
    }
}
kotlin
import org.flowable.engine.TaskService
import org.flowable.identitylink.api.IdentityLinkType
import org.flowable.task.api.Task
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException

@Service
class SupportTaskClaimService(
    private val taskService: TaskService
) {

    @Transactional
    fun claimTask(taskId: String, userId: String) {
        // 1) 查询任务是否存在
        val task: Task = taskService.createTaskQuery()
            .taskId(taskId)
            .singleResult()
            ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "质检任务不存在")

        // 2) 幂等:若已被当前用户认领,当作成功返回
        if (task.assignee == userId) return

        // 3) 若已被其他人认领,返回 409 Conflict
        if (task.assignee != null && task.assignee != userId) {
            throw ResponseStatusException(HttpStatus.CONFLICT, "任务已被 ${task.assignee} 认领")
        }

        // 4) 取出任务的身份链接,身份链接包含候选用户/候选组等信息
        val candidateLinks = taskService.getIdentityLinksForTask(taskId)
        // 判断当前用户是否直接是候选用户
        val userIsCandidate = candidateLinks.any { link ->
            link.type == IdentityLinkType.CANDIDATE && link.userId == userId
        }
        // 判断当前用户是否属于某个候选用户组
        val groupIsCandidate = candidateLinks.any { link ->
            link.type == IdentityLinkType.CANDIDATE && link.groupId != null &&
                userBelongsToGroup(userId, link.groupId)
        }
        // 若既不是候选用户,也不在候选组,则无权认领
        if (!userIsCandidate && !groupIsCandidate) {
            throw ResponseStatusException(HttpStatus.FORBIDDEN, "当前用户无权认领该任务")
        }

        // 5) 满足条件后,调用 Flowable 的认领 API;在高并发下可能抛出“已被认领”的特定异常
        try {
            taskService.claim(taskId, userId)
        } catch (ex: RuntimeException) {
            // 注意:具体异常类名随版本可能不同(例如 FlowableTaskAlreadyClaimedException)
            val name = ex::class.simpleName ?: ""
            if (name.contains("TaskAlreadyClaimed", ignoreCase = true)) {
                throw ResponseStatusException(HttpStatus.CONFLICT, "任务已被他人认领")
            }
            throw ex
        }

        // 6) 认领成功后,可进行审计记录、埋点、异步通知等
        println("客服 $userId 认领质检任务 $taskId")
    }

    private fun userBelongsToGroup(userId: String, groupId: String): Boolean {
        // 真实场景:可对接 IAM/组织架构服务,或使用 Flowable IdentityService 校验成员关系
        // 示例中直接返回 true 表示放行
        return true
    }
}

认领策略

1. 先到先得策略

kotlin
// 最简单的认领策略:谁先点“认领”谁获得任务
// 注意:在高并发环境中仍需依赖底层引擎的并发控制与事务,避免脏写
taskService.claim(taskId, userId)

2. 能力匹配策略

kotlin
// 认领前先做能力/资质校验:用户是否具备处理该任务的必要资质
// 例如:证书、角色、业务域、优先级权限等
if (isUserQualified(userId, taskRequirements)) {
    taskService.claim(taskId, userId)
}

3. 负载均衡策略

kotlin
// 在候选集合中挑选当前负载(在办+待办数量、权重)最小的用户进行认领
// 可结合队列长度、SLA、峰值控制等维度综合计算负载
val lightestLoadUser = findUserWithLightestLoad(candidateUsers)
taskService.claim(taskId, lightestLoadUser)

重要说明

  1. 候选人验证:只有任务的候选人才能认领任务
  2. 状态检查:只有未分配的任务才能被认领
  3. 并发安全:在高并发环境下可能出现竞争条件
  4. 权限控制:应实现适当的权限验证机制
  5. 负载管理:避免单个用户认领过多任务
  6. 事务建议:对认领操作加上事务管理(@Transactional),确保一致性
  7. 幂等设计:对“已被当前用户认领”的重复请求返回成功,提升用户体验

相关方法

  • unclaim(String): 释放已认领的任务
  • setAssignee(String, String): 直接设置任务分配人
  • addCandidateUser(String, String): 添加候选用户
  • getIdentityLinksForTask(String): 获取任务的身份链接

最佳实践

  1. 权限验证:认领前验证用户是否为候选人
  2. 负载检查:避免用户过载,控制认领数量
  3. 技能匹配:实现基于技能的智能认领
  4. 并发处理:处理多用户同时认领的竞争情况
  5. 审计日志:记录认领操作用于审计和分析
  6. 错误码映射:将“任务不存在/无权限/已被认领”等映射到 404/403/409,便于前端处理

候选组校验的实现选项

示例中 userBelongsToGroup 使用了占位返回。实际工程中可以:

  • 对接外部 IAM/组织架构系统,查询用户与组关系;或
  • 使用 Flowable 的 IdentityService 进行校验(若采用 Flowable 作为身份源):
kotlin
// 伪代码示例:根据实际项目注入 identityService
val isMember = identityService.createGroupQuery()
    .groupId(groupId)
    .groupMember(userId)
    .count() > 0

方法语义对比与使用建议

  • claim(taskId, userId):将任务分配给 userId;常用于候选任务被个人接单。不会自动移除候选身份链接。
  • unclaim(taskId):释放已认领的任务,让其重新回到候选池;常用于撤销接单或转回候选。
  • setAssignee(taskId, userId):直接设置办理人,通常用于管理员强制分派或系统策略路由;绕过“候选人自行认领”的流程。