value * * 幂等在服务层事务 + lockForUpdate 实现(仿 Goalcanvas::addGoalMilestoneLink)。 */ class TicketResource { private ConnectionInterface $db; public function __construct(DbCore $db) { $this->db = $db->getConnection(); } /** * 关联一个资源(幂等:已存在则返回 true)。 */ public function addLink(int $ticketId, string $resourceType, int $resourceId, int $userId): bool { if ($ticketId <= 0 || $resourceId <= 0 || $resourceType === '') { return false; } return $this->db->transaction(function () use ($ticketId, $resourceType, $resourceId, $userId): bool { $exists = $this->db->table('zp_entity_relationship') ->where('entityA', $ticketId) ->where('entityAType', 'Ticket') ->where('entityB', $resourceId) ->where('entityBType', $resourceType) ->where('relationship', EntityRelationshipEnum::LinkedResource->value) ->lockForUpdate() ->exists(); if ($exists) { return true; } $this->db->table('zp_entity_relationship')->insert([ 'entityA' => $ticketId, 'entityAType' => 'Ticket', 'entityB' => $resourceId, 'entityBType' => $resourceType, 'relationship' => EntityRelationshipEnum::LinkedResource->value, 'createdOn' => now(), 'createdBy' => $userId > 0 ? $userId : null, ]); return true; }); } /** * 解除单个资源关联。 */ public function removeLink(int $ticketId, string $resourceType, int $resourceId): bool { return $this->db->table('zp_entity_relationship') ->where('entityA', $ticketId) ->where('entityAType', 'Ticket') ->where('entityB', $resourceId) ->where('entityBType', $resourceType) ->where('relationship', EntityRelationshipEnum::LinkedResource->value) ->delete() > 0; } /** * 解除 ticket 的全部资源关联(删除 ticket 时级联清理)。 */ public function removeAllLinks(int $ticketId): bool { return $this->db->table('zp_entity_relationship') ->where('entityA', $ticketId) ->where('entityAType', 'Ticket') ->where('relationship', EntityRelationshipEnum::LinkedResource->value) ->delete() > 0; } /** * 某 ticket 已关联的资源(去重后的 [resourceType, resourceId] 列表)。 * * @return array */ public function getLinks(int $ticketId): array { return $this->db->table('zp_entity_relationship') ->where('entityA', $ticketId) ->where('entityAType', 'Ticket') ->where('relationship', EntityRelationshipEnum::LinkedResource->value) ->orderBy('id') ->get(['entityBType', 'entityB']) ->map(fn ($row) => ['type' => (string) $row->entityBType, 'id' => (int) $row->entityB]) ->unique(fn ($item) => $item['type'].':'.$item['id']) ->values() ->all(); } }