功能:婚姻系统第8&10步(Controllers + Events + 路由)
- MarriageController:propose/accept/reject/divorce/confirmDivorce/status
- WeddingController:tiers/setup(立即触发)/claim/envelopeStatus
- 8个 WebSocket Events:
Marriage{Proposed|Accepted|Rejected|Expired|Divorced|DivorceRequested}
WeddingCelebration / EnvelopeClaimed
- 前台路由:marriage.* + wedding.*
- 后台路由:admin.marriages.*(superlevel 层)
This commit is contained in:
64
app/Events/EnvelopeClaimed.php
Normal file
64
app/Events/EnvelopeClaimed.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:婚礼红包领取成功事件(广播至领取者私人频道)
|
||||
*
|
||||
* 触发时机:WeddingController::claim() 成功后广播,前端展示到账 Toast。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EnvelopeClaimed implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param User $claimer 领取用户
|
||||
* @param int $amount 领取金额
|
||||
* @param int $ceremonyId 婚礼仪式 ID
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly User $claimer,
|
||||
public readonly int $amount,
|
||||
public readonly int $ceremonyId,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至领取者私人频道。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [new PrivateChannel('user.'.$this->claimer->id)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'ceremony_id' => $this->ceremonyId,
|
||||
'amount' => $this->amount,
|
||||
'message' => "🎉 成功领取 {$this->amount} 金币婚礼红包!",
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'envelope.claimed';
|
||||
}
|
||||
}
|
||||
68
app/Events/MarriageAccepted.php
Normal file
68
app/Events/MarriageAccepted.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:结婚公告事件(广播至全房间)
|
||||
*
|
||||
* 触发时机:求婚被接受,正式结婚后广播。
|
||||
* 前端收到后展示全屏烟花特效 + 婚礼设置弹窗(仅婚姻双方)。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Marriage;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MarriageAccepted implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Marriage $marriage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至当前所有房间(PresenceChannel room.*)。
|
||||
* 使用大厅房间 ID=1,若业务支持多房间可扩展。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [new PresenceChannel('room.1')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播数据。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
$this->marriage->load(['user:id,username,headface', 'partner:id,username,headface', 'ringItem:id,name,icon']);
|
||||
|
||||
return [
|
||||
'marriage_id' => $this->marriage->id,
|
||||
'user' => $this->marriage->user?->only(['id', 'username', 'headface']),
|
||||
'partner' => $this->marriage->partner?->only(['id', 'username', 'headface']),
|
||||
'ring' => $this->marriage->ringItem?->only(['name', 'icon']),
|
||||
'married_at' => $this->marriage->married_at,
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'marriage.accepted';
|
||||
}
|
||||
}
|
||||
70
app/Events/MarriageDivorceRequested.php
Normal file
70
app/Events/MarriageDivorceRequested.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:协议离婚申请通知事件(广播至对方私人频道)
|
||||
*
|
||||
* 触发时机:一方申请协议离婚后广播,对方收到 Banner 含确认/拒绝按钮。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Marriage;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MarriageDivorceRequested implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Marriage $marriage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至对方私人频道(divorcer 的对方)。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
// 离婚申请方的对方
|
||||
$targetId = $this->marriage->user_id === $this->marriage->divorcer_id
|
||||
? $this->marriage->partner_id
|
||||
: $this->marriage->user_id;
|
||||
|
||||
return [new PrivateChannel('user.'.$targetId)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
$this->marriage->load(['user:id,username', 'partner:id,username']);
|
||||
|
||||
return [
|
||||
'marriage_id' => $this->marriage->id,
|
||||
'divorcer_username' => $this->marriage->user_id === $this->marriage->divorcer_id
|
||||
? $this->marriage->user?->username
|
||||
: $this->marriage->partner?->username,
|
||||
'timeout_hours' => 72,
|
||||
'requested_at' => $this->marriage->divorce_requested_at,
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'marriage.divorce_requested';
|
||||
}
|
||||
}
|
||||
65
app/Events/MarriageDivorced.php
Normal file
65
app/Events/MarriageDivorced.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:离婚公告事件(广播至全房间)
|
||||
*
|
||||
* 触发时机:协议/强制/自动离婚完成后广播。
|
||||
* 强制离婚时额外显示财产转移信息。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Marriage;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MarriageDivorced implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
* @param string $divorceType 离婚类型(mutual|forced|auto|admin)
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Marriage $marriage,
|
||||
public readonly string $divorceType,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至全房间。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [new PresenceChannel('room.1')];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
$this->marriage->load(['user:id,username', 'partner:id,username']);
|
||||
|
||||
return [
|
||||
'user_username' => $this->marriage->user?->username,
|
||||
'partner_username' => $this->marriage->partner?->username,
|
||||
'divorce_type' => $this->divorceType,
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'marriage.divorced';
|
||||
}
|
||||
}
|
||||
60
app/Events/MarriageExpired.php
Normal file
60
app/Events/MarriageExpired.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:求婚超时失效事件(广播至求婚方私人频道)
|
||||
*
|
||||
* 触发时机:Horizon Job ExpireMarriageProposals 扫描到超时求婚后广播。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Marriage;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MarriageExpired implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Marriage $marriage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至求婚方私人频道。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [new PrivateChannel('user.'.$this->marriage->user_id)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'partner_username' => $this->marriage->partner?->username,
|
||||
'ring_name' => $this->marriage->ringItem?->name,
|
||||
'message' => '求婚已超时失效,戒指已消失。',
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'marriage.expired';
|
||||
}
|
||||
}
|
||||
73
app/Events/MarriageProposed.php
Normal file
73
app/Events/MarriageProposed.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:求婚事件(广播至被求婚方私人频道)
|
||||
*
|
||||
* 触发时机:MarriageController::propose() 成功后广播。
|
||||
* B 上线时前端订阅频道立即收到,展示求婚 Banner 弹窗。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Marriage;
|
||||
use App\Models\User;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MarriageProposed implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
* @param User $proposer 求婚方
|
||||
* @param User $target 被求婚方
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Marriage $marriage,
|
||||
public readonly User $proposer,
|
||||
public readonly User $target,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至被求婚方私人频道。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [new PrivateChannel('user.'.$this->target->id)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播数据。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'marriage_id' => $this->marriage->id,
|
||||
'proposer' => [
|
||||
'username' => $this->proposer->username,
|
||||
'headface' => $this->proposer->headface,
|
||||
'user_level' => $this->proposer->user_level,
|
||||
],
|
||||
'ring' => $this->marriage->ringItem?->only(['name', 'icon']),
|
||||
'expires_at' => $this->marriage->expires_at,
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'marriage.proposed';
|
||||
}
|
||||
}
|
||||
60
app/Events/MarriageRejected.php
Normal file
60
app/Events/MarriageRejected.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:求婚被拒事件(广播至求婚方私人频道)
|
||||
*
|
||||
* 触发时机:对方拒绝求婚,戒指消失后广播。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Marriage;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MarriageRejected implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Marriage $marriage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至求婚方私人频道。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [new PrivateChannel('user.'.$this->marriage->user_id)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'partner_username' => $this->marriage->partner?->username,
|
||||
'ring_name' => $this->marriage->ringItem?->name,
|
||||
'message' => '对方拒绝了您的求婚,戒指已消失。',
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'marriage.rejected';
|
||||
}
|
||||
}
|
||||
73
app/Events/WeddingCelebration.php
Normal file
73
app/Events/WeddingCelebration.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:婚礼庆典事件(广播至全房间)
|
||||
*
|
||||
* 触发时机:婚礼红包触发分发后广播。
|
||||
* 前端收到后:播放烟花特效 + 婚礼音效 + 展示红包弹窗(含领取按钮)。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Marriage;
|
||||
use App\Models\WeddingCeremony;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class WeddingCelebration implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param WeddingCeremony $ceremony 婚礼仪式记录
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly WeddingCeremony $ceremony,
|
||||
public readonly Marriage $marriage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播至全房间。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [new PresenceChannel('room.1')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播数据(前端据此展示红包弹窗及新人信息)。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
$this->marriage->load(['user:id,username,headface', 'partner:id,username,headface', 'ringItem:id,name,icon']);
|
||||
|
||||
return [
|
||||
'ceremony_id' => $this->ceremony->id,
|
||||
'tier_name' => $this->ceremony->tier?->name ?? '婚礼',
|
||||
'tier_icon' => $this->ceremony->tier?->icon ?? '🎊',
|
||||
'total_amount' => $this->ceremony->total_amount,
|
||||
'expires_at' => $this->ceremony->expires_at,
|
||||
'user' => $this->marriage->user?->only(['id', 'username', 'headface']),
|
||||
'partner' => $this->marriage->partner?->only(['id', 'username', 'headface']),
|
||||
'ring' => $this->marriage->ringItem?->only(['name', 'icon']),
|
||||
];
|
||||
}
|
||||
|
||||
/** 广播事件名称。 */
|
||||
public function broadcastAs(): string
|
||||
{
|
||||
return 'wedding.celebration';
|
||||
}
|
||||
}
|
||||
10
app/Http/Controllers/Admin/MarriageManagerController.php
Normal file
10
app/Http/Controllers/Admin/MarriageManagerController.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class MarriageManagerController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
214
app/Http/Controllers/MarriageController.php
Normal file
214
app/Http/Controllers/MarriageController.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:前台婚姻控制器
|
||||
*
|
||||
* 处理求婚、接受/拒绝、查询婚姻状态、申请离婚等前台操作。
|
||||
* 所有操作通过 MarriageService 执行,Events 负责广播。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\MarriageAccepted;
|
||||
use App\Events\MarriageDivorced;
|
||||
use App\Events\MarriageDivorceRequested;
|
||||
use App\Events\MarriageProposed;
|
||||
use App\Events\MarriageRejected;
|
||||
use App\Models\Marriage;
|
||||
use App\Models\UserPurchase;
|
||||
use App\Services\MarriageService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MarriageController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MarriageService $marriage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取当前用户的婚姻状态(名片/用户列表用)。
|
||||
*/
|
||||
public function status(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$marriage = Marriage::currentFor($user->id);
|
||||
|
||||
if (! $marriage) {
|
||||
return response()->json(['married' => false]);
|
||||
}
|
||||
|
||||
$marriage->load(['user:id,username,headface', 'partner:id,username,headface', 'ringItem:id,name,slug,icon']);
|
||||
|
||||
return response()->json([
|
||||
'married' => $marriage->status === 'married',
|
||||
'status' => $marriage->status,
|
||||
'marriage' => [
|
||||
'id' => $marriage->id,
|
||||
'user' => $marriage->user,
|
||||
'partner' => $marriage->partner,
|
||||
'ring' => $marriage->ringItem?->only(['name', 'icon']),
|
||||
'intimacy' => $marriage->intimacy,
|
||||
'level' => $marriage->level,
|
||||
'level_name' => \App\Services\MarriageIntimacyService::levelName($marriage->level),
|
||||
'level_icon' => \App\Services\MarriageIntimacyService::levelIcon($marriage->level),
|
||||
'married_at' => $marriage->married_at?->toDateString(),
|
||||
'days' => $marriage->married_at?->diffInDays(now()),
|
||||
'proposed_at' => $marriage->proposed_at,
|
||||
'expires_at' => $marriage->expires_at,
|
||||
'divorce_type' => $marriage->divorce_type,
|
||||
'divorcer_id' => $marriage->divorcer_id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询目标用户的婚姻信息(用于双击名片展示)。
|
||||
*/
|
||||
public function targetStatus(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate(['username' => 'required|string']);
|
||||
$target = \App\Models\User::where('username', $request->username)->firstOrFail();
|
||||
|
||||
$marriage = Marriage::query()
|
||||
->where('status', 'married')
|
||||
->where(function ($q) use ($target) {
|
||||
$q->where('user_id', $target->id)->orWhere('partner_id', $target->id);
|
||||
})
|
||||
->with(['user:id,username,headface', 'partner:id,username,headface', 'ringItem:id,name,icon'])
|
||||
->first();
|
||||
|
||||
if (! $marriage) {
|
||||
return response()->json(['married' => false]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'married' => true,
|
||||
'level_icon' => \App\Services\MarriageIntimacyService::levelIcon($marriage->level),
|
||||
'level_name' => \App\Services\MarriageIntimacyService::levelName($marriage->level),
|
||||
'partner' => $marriage->user_id === $target->id ? $marriage->partner : $marriage->user,
|
||||
'ring' => $marriage->ringItem?->only(['name', 'icon']),
|
||||
'days' => $marriage->married_at?->diffInDays(now()),
|
||||
'intimacy' => $marriage->intimacy,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起求婚。
|
||||
*/
|
||||
public function propose(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'target_username' => 'required|string',
|
||||
'ring_purchase_id' => 'required|integer',
|
||||
]);
|
||||
|
||||
$proposer = $request->user();
|
||||
$target = \App\Models\User::where('username', $data['target_username'])->first();
|
||||
|
||||
if (! $target) {
|
||||
return response()->json(['ok' => false, 'message' => '用户不存在。'], 404);
|
||||
}
|
||||
|
||||
$result = $this->marriage->propose($proposer, $target, $data['ring_purchase_id']);
|
||||
|
||||
if ($result['ok']) {
|
||||
$marriage = Marriage::find($result['marriage_id']);
|
||||
// 广播给被求婚方(私人频道)
|
||||
broadcast(new MarriageProposed($marriage, $proposer, $target));
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户持有的有效戒指列表(求婚前选择用)。
|
||||
*/
|
||||
public function myRings(Request $request): JsonResponse
|
||||
{
|
||||
$rings = UserPurchase::query()
|
||||
->where('user_id', $request->user()->id)
|
||||
->where('status', 'active')
|
||||
->whereHas('item', fn ($q) => $q->where('type', 'ring'))
|
||||
->with('item:id,name,slug,icon,price')
|
||||
->get()
|
||||
->map(fn ($p) => [
|
||||
'purchase_id' => $p->id,
|
||||
'name' => $p->item->name,
|
||||
'icon' => $p->item->icon,
|
||||
'slug' => $p->item->slug,
|
||||
]);
|
||||
|
||||
return response()->json(['rings' => $rings]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接受求婚。
|
||||
*/
|
||||
public function accept(Request $request, Marriage $marriage): JsonResponse
|
||||
{
|
||||
$result = $this->marriage->accept($marriage, $request->user());
|
||||
|
||||
if ($result['ok']) {
|
||||
$marriage->refresh();
|
||||
// 广播全房间结婚公告
|
||||
broadcast(new MarriageAccepted($marriage));
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝求婚。
|
||||
*/
|
||||
public function reject(Request $request, Marriage $marriage): JsonResponse
|
||||
{
|
||||
$result = $this->marriage->reject($marriage, $request->user());
|
||||
|
||||
if ($result['ok']) {
|
||||
broadcast(new MarriageRejected($marriage));
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请离婚(协议或强制)。
|
||||
*/
|
||||
public function divorce(Request $request, Marriage $marriage): JsonResponse
|
||||
{
|
||||
$type = $request->input('type', 'mutual'); // mutual | forced
|
||||
$result = $this->marriage->divorce($marriage, $request->user(), $type);
|
||||
|
||||
if ($result['ok']) {
|
||||
$marriage->refresh();
|
||||
if ($marriage->status === 'divorced') {
|
||||
broadcast(new MarriageDivorced($marriage, $type));
|
||||
} else {
|
||||
// 协议离婚:通知对方
|
||||
broadcast(new MarriageDivorceRequested($marriage));
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认协议离婚。
|
||||
*/
|
||||
public function confirmDivorce(Request $request, Marriage $marriage): JsonResponse
|
||||
{
|
||||
$result = $this->marriage->confirmDivorce($marriage, $request->user());
|
||||
|
||||
if ($result['ok']) {
|
||||
$marriage->refresh();
|
||||
broadcast(new MarriageDivorced($marriage, 'mutual'));
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
}
|
||||
115
app/Http/Controllers/WeddingController.php
Normal file
115
app/Http/Controllers/WeddingController.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:前台婚礼控制器
|
||||
*
|
||||
* 处理婚礼设置(档位选择/时间/付款方)、婚礼触发、红包领取。
|
||||
* 所有操作通过 WeddingService 执行。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\WeddingCelebration;
|
||||
use App\Models\Marriage;
|
||||
use App\Models\WeddingCeremony;
|
||||
use App\Services\WeddingService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class WeddingController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WeddingService $wedding,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取可用的婚礼档位列表(前台设置弹窗用)。
|
||||
*/
|
||||
public function tiers(): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'tiers' => $this->wedding->activeTiers()->map(fn ($t) => [
|
||||
'id' => $t->id,
|
||||
'tier' => $t->tier,
|
||||
'name' => $t->name,
|
||||
'icon' => $t->icon,
|
||||
'amount' => $t->amount,
|
||||
'description' => $t->description,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置并发起婚礼(结婚后由接受方配置)。
|
||||
*/
|
||||
public function setup(Request $request, Marriage $marriage): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// 只有婚姻双方可设置
|
||||
if (! $marriage->involves($user->id)) {
|
||||
return response()->json(['ok' => false, 'message' => '无权操作此婚姻。'], 403);
|
||||
}
|
||||
if ($marriage->status !== 'married') {
|
||||
return response()->json(['ok' => false, 'message' => '婚姻状态异常。'], 422);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'tier_id' => 'nullable|integer|exists:wedding_tiers,id',
|
||||
'payer_type' => 'required|in:groom,joint',
|
||||
'ceremony_type' => 'required|in:immediate,scheduled',
|
||||
'ceremony_at' => 'nullable|date|after:now',
|
||||
]);
|
||||
|
||||
$ceremonyAt = isset($data['ceremony_at']) ? Carbon::parse($data['ceremony_at']) : null;
|
||||
|
||||
$result = $this->wedding->setup(
|
||||
$marriage,
|
||||
$data['tier_id'] ?? null,
|
||||
$data['payer_type'],
|
||||
$data['ceremony_type'],
|
||||
$ceremonyAt,
|
||||
);
|
||||
|
||||
// 立即婚礼:直接触发
|
||||
if ($result['ok'] && $data['ceremony_type'] === 'immediate') {
|
||||
$ceremony = WeddingCeremony::find($result['ceremony_id']);
|
||||
if ($ceremony) {
|
||||
$triggerResult = $this->wedding->trigger($ceremony);
|
||||
// 广播全房间婚礼事件
|
||||
broadcast(new WeddingCelebration($ceremony, $marriage));
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取婚礼红包。
|
||||
*/
|
||||
public function claim(Request $request, WeddingCeremony $ceremony): JsonResponse
|
||||
{
|
||||
$result = $this->wedding->claim($ceremony, $request->user());
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户在婚礼中是否有待领取红包。
|
||||
*/
|
||||
public function envelopeStatus(Request $request, WeddingCeremony $ceremony): JsonResponse
|
||||
{
|
||||
$claim = $this->wedding->getUnclaimedEnvelope($ceremony, $request->user()->id);
|
||||
|
||||
return response()->json([
|
||||
'has_envelope' => $claim !== null,
|
||||
'amount' => $claim?->amount ?? 0,
|
||||
'expires_at' => $ceremony->expires_at,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,37 @@ Route::middleware(['chat.auth'])->group(function () {
|
||||
Route::post('/friend/{username}/add', [\App\Http\Controllers\FriendController::class, 'addFriend'])->name('friend.add');
|
||||
Route::delete('/friend/{username}/remove', [\App\Http\Controllers\FriendController::class, 'removeFriend'])->name('friend.remove');
|
||||
|
||||
// ── 婚姻系统(前台)──────────────────────────────────────────────
|
||||
Route::prefix('marriage')->name('marriage.')->group(function () {
|
||||
// 查询当前用户婚姻状态
|
||||
Route::get('/status', [\App\Http\Controllers\MarriageController::class, 'status'])->name('status');
|
||||
// 查询目标用户婚姻信息(名片用)
|
||||
Route::get('/target', [\App\Http\Controllers\MarriageController::class, 'targetStatus'])->name('target-status');
|
||||
// 当前用户持有的戒指列表
|
||||
Route::get('/rings', [\App\Http\Controllers\MarriageController::class, 'myRings'])->name('rings');
|
||||
// 发起求婚
|
||||
Route::post('/propose', [\App\Http\Controllers\MarriageController::class, 'propose'])->name('propose');
|
||||
// 接受/拒绝求婚
|
||||
Route::post('/{marriage}/accept', [\App\Http\Controllers\MarriageController::class, 'accept'])->name('accept');
|
||||
Route::post('/{marriage}/reject', [\App\Http\Controllers\MarriageController::class, 'reject'])->name('reject');
|
||||
// 申请离婚(type: mutual|forced)
|
||||
Route::post('/{marriage}/divorce', [\App\Http\Controllers\MarriageController::class, 'divorce'])->name('divorce');
|
||||
// 确认协议离婚
|
||||
Route::post('/{marriage}/confirm-divorce', [\App\Http\Controllers\MarriageController::class, 'confirmDivorce'])->name('confirm-divorce');
|
||||
});
|
||||
|
||||
// ── 婚礼系统(前台)──────────────────────────────────────────────
|
||||
Route::prefix('wedding')->name('wedding.')->group(function () {
|
||||
// 婚礼档位列表
|
||||
Route::get('/tiers', [\App\Http\Controllers\WeddingController::class, 'tiers'])->name('tiers');
|
||||
// 设置并发起婚礼
|
||||
Route::post('/{marriage}/setup', [\App\Http\Controllers\WeddingController::class, 'setup'])->name('setup');
|
||||
// 领取婚礼红包
|
||||
Route::post('/ceremony/{ceremony}/claim', [\App\Http\Controllers\WeddingController::class, 'claim'])->name('claim');
|
||||
// 查询是否有待领取红包
|
||||
Route::get('/ceremony/{ceremony}/envelope', [\App\Http\Controllers\WeddingController::class, 'envelopeStatus'])->name('envelope-status');
|
||||
});
|
||||
|
||||
// ---- 第五阶段:具体房间内部聊天核心 ----
|
||||
// 进入具体房间界面的初始化
|
||||
Route::get('/room/{id}', [ChatController::class, 'init'])->name('chat.room');
|
||||
@@ -222,6 +253,32 @@ Route::middleware(['chat.auth', 'chat.has_position'])->prefix('admin')->name('ad
|
||||
Route::post('/vip', [\App\Http\Controllers\Admin\VipController::class, 'store'])->name('vip.store');
|
||||
Route::put('/vip/{vip}', [\App\Http\Controllers\Admin\VipController::class, 'update'])->name('vip.update');
|
||||
Route::delete('/vip/{vip}', [\App\Http\Controllers\Admin\VipController::class, 'destroy'])->name('vip.destroy');
|
||||
|
||||
// 💒 婚姻管理(superlevel 及以上)
|
||||
Route::prefix('marriages')->name('marriages.')->group(function () {
|
||||
// 总览统计
|
||||
Route::get('/', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'index'])->name('index');
|
||||
// 婚姻列表(支持筛选)
|
||||
Route::get('/list', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'list'])->name('list');
|
||||
// 求婚记录
|
||||
Route::get('/proposals', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'proposals'])->name('proposals');
|
||||
// 婚礼红包记录
|
||||
Route::get('/ceremonies', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'ceremonies'])->name('ceremonies');
|
||||
// 红包领取明细
|
||||
Route::get('/ceremonies/{ceremony}/claims', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'claimDetail'])->name('claim-detail');
|
||||
// 亲密度日志
|
||||
Route::get('/intimacy-logs', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'intimacyLogs'])->name('intimacy-logs');
|
||||
// 参数配置(GET=页面,POST=保存)
|
||||
Route::get('/configs', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'configs'])->name('configs');
|
||||
Route::post('/configs', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'updateConfigs'])->name('configs.update');
|
||||
// 婚礼档位(GET=页面,PUT=保存)
|
||||
Route::get('/tiers', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'tiers'])->name('tiers');
|
||||
Route::put('/tiers/{tier}', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'updateTier'])->name('tiers.update');
|
||||
// 强制离婚
|
||||
Route::post('/{marriage}/force-dissolve', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'forceDissolve'])->name('force-dissolve');
|
||||
// 取消求婚
|
||||
Route::post('/{marriage}/cancel-proposal', [\App\Http\Controllers\Admin\MarriageManagerController::class, 'cancelProposal'])->name('cancel-proposal');
|
||||
});
|
||||
});
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user