where(function ($q) use ($viewer) { $q->where('user_id', $viewer)->orWhere('to_user_id', $viewer); })->firstOrFail(); } $demandId = (int) ($input['supply_demand_id'] ?? 0); $other = (int) ($input['to_user_id'] ?? 0); if ($demandId) { $query = SupplyDemand::whereKey($demandId); if ($create) $query->lockForUpdate(); $d = $query->firstOrFail(); // 接收人由需求归属确定,不能由请求伪造;发布人回复须指定已有会话。 if ((int) $d->user_id === (int) $viewer) { if (!$other || $other === (int) $viewer) throw new \InvalidArgumentException('不能给自己发信息'); $existing = Dialogue::where('supply_demand_id', $demandId)->where('user_id', $other)->where('to_user_id', $viewer)->first(); if (!$existing) throw new \InvalidArgumentException('对接记录不存在'); return $existing; } if ((int) $d->status !== 1) throw new \InvalidArgumentException('该供需信息尚未发布或已下架'); if ($d->expire_time && \Illuminate\Support\Carbon::parse($d->expire_time)->isPast()) throw new \InvalidArgumentException('该信息已过期,不能发起对接'); if ((!$d->user_id && !$d->is_proxy) || ($d->user_id && !\App\Models\User::whereKey($d->user_id)->exists())) throw new \InvalidArgumentException('发布人资料暂不可用,请联系平台'); $other = (int) $d->user_id; } elseif (!$other || $other === (int) $viewer || !\App\Models\User::whereKey($other)->exists()) { throw new \InvalidArgumentException('请选择有效的对接人,不能给自己发信息'); } $key = self::key($demandId, $viewer, $other); $q = Dialogue::where('conversation_key', $key); if (!$create) return $q->first(); return Dialogue::firstOrCreate(['conversation_key' => $key], ['user_id' => $viewer, 'to_user_id' => $other, 'supply_demand_id' => $demandId ?: null]); } public static function send($viewer, array $input) { $content = trim((string) ($input['content'] ?? '')); if ($content === '' || mb_strlen($content) > 2000) throw new \InvalidArgumentException('请输入1至2000字的对接内容'); return DB::transaction(function () use ($viewer, $input, $content) { // 同一用户并发发送时串行检查每日限额。 \App\Models\User::whereKey($viewer)->lockForUpdate()->firstOrFail(); $dialogue = self::resolve($viewer, $input, true); $dialogue = Dialogue::whereKey($dialogue->id)->lockForUpdate()->firstOrFail(); $other = (int) $dialogue->user_id === (int) $viewer ? (int) $dialogue->to_user_id : (int) $dialogue->user_id; if ($other === (int) $viewer) throw new \InvalidArgumentException('不能给自己发信息'); $d = $dialogue->supplyDemand; if ($d && $d->expire_time && \Illuminate\Support\Carbon::parse($d->expire_time)->isPast()) throw new \InvalidArgumentException('该信息已过期,不能发送对接'); $limit = \App\Models\Config::getValueByKey('message_limit'); if (is_numeric($limit) && $limit > 0 && Message::where('user_id', $viewer)->whereBetween('created_at', [now()->startOfDay(), now()->endOfDay()])->count() >= $limit) throw new \InvalidArgumentException('今天私信次数已达上限'); // 未回复前只允许一条,系统代管期间同样适用。 if ($dialogue->messages()->where('user_id', $viewer)->exists() && !$dialogue->messages()->where('user_id', $other)->exists()) throw new \InvalidArgumentException('对方回复以后才可以再次发送消息'); $first = !$dialogue->messages()->exists(); $m = Message::create(['dialogue_id' => $dialogue->id, 'user_id' => $viewer, 'to_user_id' => $other, 'supply_demand_id' => $dialogue->supply_demand_id ?: 0, 'content' => $content, 'is_read' => 0]); $dialogue->last_content = $content; $dialogue->last_datetime = now()->toDateTimeString(); $dialogue->save(); if ($first && $d) $d->increment('contact_count'); if ($d && $other && (int) $d->user_id === $other) Notices::queue($other); return ['id' => $m->id, 'dialogue_id' => $dialogue->id, 'is_managed' => $d && $d->is_proxy && !$d->user_id]; }); } public static function summary(Dialogue $d, $viewer, $private = false) { $d->loadMissing(['user', 'toUser', 'supplyDemand']); $out = $d->attributesToArray(); $out['user'] = Profiles::user($d->user, $private); $out['to_user'] = Profiles::user($d->toUser, $private); $out['supply_demand'] = $d->supplyDemand ? Profiles::demand($d->supplyDemand, $private, $viewer) : null; $out['unread_count'] = $d->messages()->where('to_user_id', $viewer)->where('is_read', 0)->count(); $out['other_profile'] = (int) $d->user_id === (int) $viewer ? $out['to_user'] : $out['user']; if (!$out['other_profile'] && $d->supplyDemand) $out['other_profile'] = Profiles::publisher($d->supplyDemand, $private); return $out; } }