功能:婚姻系统第4-6步(Services + Models)
Step 4 - MarriageConfigService: - 带60min Cache 的配置读取/写入 - 支持单项/分组/全量读取,管理员保存后自动清缓存 Step 5 - MarriageIntimacyService: - 亲密度增加 + 日志写入 + 等级自动更新 - Redis 每日上限计数器(各来源独立控制) - onFlowerSent/onPrivateChat/onlineTick 接入点方法 - dailyBatch 批量处理(Horizon Job 用) Step 6 - MarriageService(核心业务): - propose/accept/reject/divorce/confirmDivorce/forceDissolve - 所有金币魅力通过 UserCurrencyService 统一记账 - 冷静期检查/超时处理/强制离婚金币全转对方 Models 改良(Marriage/MarriageConfig/MarriageIntimacyLog)
This commit is contained in:
240
app/Services/MarriageIntimacyService.php
Normal file
240
app/Services/MarriageIntimacyService.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:婚姻亲密度操作服务
|
||||
*
|
||||
* 统一管理婚姻亲密度的增减逻辑:
|
||||
* - 写入 marriage_intimacy_logs 日志
|
||||
* - 更新 marriages.intimacy 和 marriages.level
|
||||
* - 通过 Redis 计数器控制每日来源上限
|
||||
* - 所有参数从 MarriageConfigService 读取,不硬编码
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\IntimacySource;
|
||||
use App\Models\Marriage;
|
||||
use App\Models\MarriageIntimacyLog;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class MarriageIntimacyService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MarriageConfigService $config,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 增加婚姻亲密度(带每日上限检查)。
|
||||
*
|
||||
* @param Marriage $marriage 婚姻记录
|
||||
* @param int $amount 增加量(必须为正整数)
|
||||
* @param IntimacySource $source 来源枚举
|
||||
* @param string $remark 备注
|
||||
* @param bool $skipCap 是否跳过每日上限(管理员调整/结婚初始时传 true)
|
||||
*/
|
||||
public function add(
|
||||
Marriage $marriage,
|
||||
int $amount,
|
||||
IntimacySource $source,
|
||||
string $remark = '',
|
||||
bool $skipCap = false,
|
||||
): void {
|
||||
if ($amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 每日上限检查(管理员调整和结婚初始加成跳过)
|
||||
if (! $skipCap) {
|
||||
$cap = $this->getDailyCap($source);
|
||||
if ($cap > 0 && ! $this->checkAndIncrRedis($marriage->id, $source, $amount, $cap)) {
|
||||
return; // 已达到每日上限,静默忽略
|
||||
}
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($marriage, $amount, $source, $remark) {
|
||||
// 原子加亲密度
|
||||
$marriage->increment('intimacy', $amount);
|
||||
$newIntimacy = $marriage->fresh()->intimacy;
|
||||
|
||||
// 更新婚姻等级
|
||||
$newLevel = self::calcLevel($newIntimacy, $this->config);
|
||||
if ($newLevel !== $marriage->level) {
|
||||
$marriage->update(['level' => $newLevel]);
|
||||
}
|
||||
|
||||
// 写入日志
|
||||
MarriageIntimacyLog::create([
|
||||
'marriage_id' => $marriage->id,
|
||||
'amount' => $amount,
|
||||
'balance_after' => $newIntimacy,
|
||||
'source' => $source->value,
|
||||
'remark' => $remark,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 每日结婚天数批量加亲密度(ProcessMarriageIntimacy Job 调用)。
|
||||
* 给所有 married 状态的婚姻对加 intimacy_daily_time 配置的积分。
|
||||
*/
|
||||
public function dailyBatch(): void
|
||||
{
|
||||
$amount = $this->config->get('intimacy_daily_time', 10);
|
||||
$remark = '每日结婚天数奖励';
|
||||
|
||||
Marriage::query()
|
||||
->where('status', 'married')
|
||||
->cursor()
|
||||
->each(function (Marriage $marriage) use ($amount, $remark) {
|
||||
$this->add($marriage, $amount, IntimacySource::DAILY_TIME, $remark, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 双方同时在线每分钟加亲密度(AutoSaveJob 调用)。
|
||||
*/
|
||||
public function onlineTick(Marriage $marriage): void
|
||||
{
|
||||
$amount = $this->config->get('intimacy_online_per_min', 1);
|
||||
$this->add($marriage, $amount, IntimacySource::ONLINE_TOGETHER, '双方同时在线');
|
||||
}
|
||||
|
||||
/**
|
||||
* 送花触发亲密度(GiftController 调用)。
|
||||
* 自动判断当前用户是送花方还是收花方,并使用对应加成。
|
||||
*
|
||||
* @param Marriage $marriage 婚姻关系
|
||||
* @param int $giverId 送花者 user.id
|
||||
* @param int $flowerQty 送花数量
|
||||
*/
|
||||
public function onFlowerSent(Marriage $marriage, int $giverId, int $flowerQty = 1): void
|
||||
{
|
||||
// 送花方:send_flower 加成
|
||||
$sendAmount = $this->config->get('intimacy_send_flower', 1) * $flowerQty;
|
||||
$this->add($marriage, $sendAmount, IntimacySource::SEND_FLOWER, "向伴侣送花×{$flowerQty}");
|
||||
|
||||
// 收花方(两人共享同一段婚姻记录,无需区分):recv_flower 加成
|
||||
$recvAmount = $this->config->get('intimacy_recv_flower', 2) * $flowerQty;
|
||||
$this->add($marriage, $recvAmount, IntimacySource::RECV_FLOWER, "伴侣送花×{$flowerQty}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 私聊消息触发亲密度(WhisperController 调用)。
|
||||
* 使用 Redis 计数,每 2 条触发 1 次加分。
|
||||
*
|
||||
* @param Marriage $marriage 婚姻关系
|
||||
*/
|
||||
public function onPrivateChat(Marriage $marriage): void
|
||||
{
|
||||
$redisKey = "marriage:{$marriage->id}:whisper_count:".now()->toDateString();
|
||||
$count = Redis::incr($redisKey);
|
||||
|
||||
// 设置 TTL(次日 00:05 过期)
|
||||
if ($count === 1) {
|
||||
Redis::expireAt($redisKey, Carbon::tomorrow()->addMinutes(5)->timestamp);
|
||||
}
|
||||
|
||||
// 每2条触发1次
|
||||
if ($count % 2 === 0) {
|
||||
$amount = $this->config->get('intimacy_private_chat', 1);
|
||||
$this->add($marriage, $amount, IntimacySource::PRIVATE_CHAT, '私聊消息');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据亲密度计算婚姻等级(1-4)。
|
||||
*
|
||||
* @param int $intimacy 当前亲密度
|
||||
* @param MarriageConfigService $config 配置服务实例
|
||||
*/
|
||||
public static function calcLevel(int $intimacy, MarriageConfigService $config): int
|
||||
{
|
||||
if ($intimacy >= $config->get('level4_threshold', 1500)) {
|
||||
return 4;
|
||||
}
|
||||
if ($intimacy >= $config->get('level3_threshold', 600)) {
|
||||
return 3;
|
||||
}
|
||||
if ($intimacy >= $config->get('level2_threshold', 200)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回等级图标(用于前端展示)。
|
||||
*/
|
||||
public static function levelIcon(int $level): string
|
||||
{
|
||||
return match ($level) {
|
||||
2 => '💕',
|
||||
3 => '💞',
|
||||
4 => '👑',
|
||||
default => '💑',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回等级名称。
|
||||
*/
|
||||
public static function levelName(int $level): string
|
||||
{
|
||||
return match ($level) {
|
||||
2 => '恩爱夫妻',
|
||||
3 => '情深意重',
|
||||
4 => '白头偕老',
|
||||
default => '新婚燕尔',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定来源的每日上限配置值(0=不限)。
|
||||
*/
|
||||
private function getDailyCap(IntimacySource $source): int
|
||||
{
|
||||
return match ($source) {
|
||||
IntimacySource::ONLINE_TOGETHER => $this->config->get('intimacy_online_daily_cap', 120),
|
||||
IntimacySource::RECV_FLOWER => $this->config->get('intimacy_recv_flower_cap', 40),
|
||||
IntimacySource::SEND_FLOWER => $this->config->get('intimacy_send_flower_cap', 20),
|
||||
IntimacySource::PRIVATE_CHAT => $this->config->get('intimacy_private_chat_cap', 10),
|
||||
default => 0, // 不限
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 Redis 每日计数器是否超限,未超限则增加计数。
|
||||
* 返回 true 表示可以继续加分,false 表示已达上限。
|
||||
*
|
||||
* @param int $marriageId 婚姻 ID
|
||||
* @param IntimacySource $source 来源
|
||||
* @param int $amount 本次加分量
|
||||
* @param int $cap 每日上限
|
||||
*/
|
||||
private function checkAndIncrRedis(int $marriageId, IntimacySource $source, int $amount, int $cap): bool
|
||||
{
|
||||
$redisKey = "marriage:{$marriageId}:intimacy:{$source->value}:".now()->toDateString();
|
||||
$current = (int) Redis::get($redisKey);
|
||||
|
||||
if ($current >= $cap) {
|
||||
return false; // 已达上限
|
||||
}
|
||||
|
||||
// 本次可加量(不超过剩余额度)
|
||||
$actualAdd = min($amount, $cap - $current);
|
||||
Redis::incrBy($redisKey, $actualAdd);
|
||||
|
||||
// 设置 TTL(次日 00:05 过期)
|
||||
if ($current === 0) {
|
||||
Redis::expireAt($redisKey, Carbon::tomorrow()->addMinutes(5)->timestamp);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user