功能:婚姻系统第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:
2026-03-01 15:09:33 +08:00
parent 384cf8e078
commit 4f49fb7ce8
12 changed files with 929 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class MarriageManagerController extends Controller
{
//
}

View 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);
}
}

View 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,
]);
}
}