feat: add smt module
This commit is contained in:
@@ -0,0 +1,548 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\smt\service;
|
||||
|
||||
use app\smt\model\User;
|
||||
use DateInterval;
|
||||
use think\facade\Db;
|
||||
|
||||
class QuitCheckinService
|
||||
{
|
||||
public function getProfile(array $user): array
|
||||
{
|
||||
$profile = $this->loadOrInitProfile((int) $user['id']);
|
||||
return $this->formatProfile($profile, $user);
|
||||
}
|
||||
|
||||
public function upsertProfile(array $user, array $data): array
|
||||
{
|
||||
$uid = (int) $user['id'];
|
||||
$profile = $this->loadOrInitProfile($uid);
|
||||
$updates = ['updated_at' => Support::now()->format(Support::DATETIME_LAYOUT)];
|
||||
|
||||
if (isset($data['quit_start_date']) && trim((string) $data['quit_start_date']) !== '') {
|
||||
$updates['quit_start_date'] = Support::parseDate((string) $data['quit_start_date'], 'quit_start_date')->format(Support::DATE_LAYOUT);
|
||||
}
|
||||
if (array_key_exists('pack_price_cent', $data)) {
|
||||
$updates['pack_price_cent'] = max(0, (int) $data['pack_price_cent']);
|
||||
}
|
||||
if (array_key_exists('baseline_cigs_per_day', $data)) {
|
||||
$updates['baseline_cigs_per_day'] = max(0, (int) $data['baseline_cigs_per_day']);
|
||||
}
|
||||
if (array_key_exists('motivation', $data)) {
|
||||
$updates['motivation'] = Support::truncate(trim((string) $data['motivation']), 200);
|
||||
}
|
||||
if (array_key_exists('notify_time', $data)) {
|
||||
$notifyTime = trim((string) $data['notify_time']);
|
||||
if ($notifyTime !== '') {
|
||||
$this->assertHHMM($notifyTime);
|
||||
}
|
||||
$updates['notify_time'] = $notifyTime !== '' ? $notifyTime : '21:00';
|
||||
}
|
||||
|
||||
Db::connect('mysql')->name('fa_quit_checkin_profile')->where('uid', $uid)->whereNull('deleted_at')->update($updates);
|
||||
return $this->formatProfile(array_merge($profile, $updates), $user);
|
||||
}
|
||||
|
||||
public function home(int $uid): array
|
||||
{
|
||||
$today = Support::dateOnly()->format(Support::DATE_LAYOUT);
|
||||
return [
|
||||
'daily_status' => $this->dailyStatus($uid, $today),
|
||||
'summary' => $this->summary($uid),
|
||||
'goal' => $this->activeGoal($uid),
|
||||
'badges' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function checkin(int $uid, array $data): array
|
||||
{
|
||||
$date = Support::parseDate((string) ($data['date'] ?? ''), 'date') ?: Support::dateOnly();
|
||||
$dateText = $date->format(Support::DATE_LAYOUT);
|
||||
$now = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$note = Support::truncate(trim((string) ($data['note'] ?? '')), 200);
|
||||
$db = Db::connect('mysql');
|
||||
$existing = $db->name('fa_quit_checkin_daily_status')->where('uid', $uid)->where('date', $dateText)->whereNull('deleted_at')->find();
|
||||
$payload = [
|
||||
'uid' => $uid,
|
||||
'date' => $dateText,
|
||||
'status' => 'checked_in',
|
||||
'check_in_at' => $now,
|
||||
'relapsed_at' => null,
|
||||
'relapse_num' => 0,
|
||||
'reason' => '',
|
||||
'note' => $note,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
if ($existing) {
|
||||
$db->name('fa_quit_checkin_daily_status')->where('id', (int) $existing['id'])->update($payload);
|
||||
} else {
|
||||
$payload['created_at'] = $now;
|
||||
$db->name('fa_quit_checkin_daily_status')->insert($payload);
|
||||
}
|
||||
|
||||
return ['daily_status' => $this->dailyStatus($uid, $dateText), 'summary' => $this->summary($uid)];
|
||||
}
|
||||
|
||||
public function listDreamPresets(): array
|
||||
{
|
||||
return Db::connect('mysql')->name('fa_dream_goal_preset')
|
||||
->where('is_active', 1)
|
||||
->whereNull('deleted_at')
|
||||
->order('sort_order asc,id asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function listRewardGoals(int $uid, string $status): array
|
||||
{
|
||||
$query = Db::connect('mysql')->name('fa_quit_checkin_reward_goal')->where('uid', $uid)->whereNull('deleted_at');
|
||||
if (in_array($status, ['active', 'completed', 'archived'], true)) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
$rows = $query->order('id desc')->select()->toArray();
|
||||
$saved = $this->summary($uid)['saved_money_cent'];
|
||||
$items = array_map(fn (array $row): array => $this->formatGoal($row, $saved), $rows);
|
||||
return ['items' => $items, 'total' => count($items)];
|
||||
}
|
||||
|
||||
public function createRewardGoal(int $uid, array $data): array
|
||||
{
|
||||
$title = Support::truncate(trim((string) ($data['title'] ?? '')), 64);
|
||||
$amount = (int) ($data['target_amount_cent'] ?? 0);
|
||||
if ($title === '' || $amount <= 0) {
|
||||
throw new \RuntimeException('目标名称和金额不能为空', 400);
|
||||
}
|
||||
$now = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$id = Db::connect('mysql')->name('fa_quit_checkin_reward_goal')->insertGetId([
|
||||
'uid' => $uid,
|
||||
'title' => $title,
|
||||
'target_amount_cent' => $amount,
|
||||
'cover_image' => Support::truncate(trim((string) ($data['cover_image'] ?? '')), 500),
|
||||
'status' => 'active',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
return $this->formatGoal($this->findGoal($uid, (int) $id), $this->summary($uid)['saved_money_cent']);
|
||||
}
|
||||
|
||||
public function updateRewardGoal(int $uid, int $id, array $data): array
|
||||
{
|
||||
$goal = $this->findGoal($uid, $id);
|
||||
$updates = ['updated_at' => Support::now()->format(Support::DATETIME_LAYOUT)];
|
||||
if (array_key_exists('title', $data)) {
|
||||
$title = Support::truncate(trim((string) $data['title']), 64);
|
||||
if ($title !== '') {
|
||||
$updates['title'] = $title;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('target_amount_cent', $data)) {
|
||||
$amount = (int) $data['target_amount_cent'];
|
||||
if ($amount > 0) {
|
||||
$updates['target_amount_cent'] = $amount;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('cover_image', $data)) {
|
||||
$updates['cover_image'] = Support::truncate(trim((string) $data['cover_image']), 500);
|
||||
}
|
||||
if (array_key_exists('status', $data) && in_array((string) $data['status'], ['active', 'completed', 'archived'], true)) {
|
||||
$updates['status'] = (string) $data['status'];
|
||||
$updates['completed_at'] = $updates['status'] === 'completed' ? $updates['updated_at'] : null;
|
||||
}
|
||||
Db::connect('mysql')->name('fa_quit_checkin_reward_goal')->where('id', (int) $goal['id'])->update($updates);
|
||||
return $this->formatGoal($this->findGoal($uid, $id), $this->summary($uid)['saved_money_cent']);
|
||||
}
|
||||
|
||||
public function createSupervisorInvite(int $ownerUID, int $days): array
|
||||
{
|
||||
$days = $days > 0 ? min($days, 30) : 7;
|
||||
$now = Support::now();
|
||||
$token = bin2hex(random_bytes(16));
|
||||
$expireAt = $now->add(new DateInterval('P' . $days . 'D'))->format(Support::DATETIME_LAYOUT);
|
||||
|
||||
Db::connect('mysql')->name('fa_quit_checkin_supervisor_invite')->insert([
|
||||
'owner_uid' => $ownerUID,
|
||||
'token' => $token,
|
||||
'expire_at' => $expireAt,
|
||||
'created_at' => $now->format(Support::DATETIME_LAYOUT),
|
||||
'updated_at' => $now->format(Support::DATETIME_LAYOUT),
|
||||
]);
|
||||
|
||||
return ['token' => $token, 'expire_at' => Support::formatRfc3339($expireAt)];
|
||||
}
|
||||
|
||||
public function bindSupervisorInvite(int $supervisorUID, string $token): array
|
||||
{
|
||||
$token = trim($token);
|
||||
if ($token === '') {
|
||||
throw new \RuntimeException('请求参数错误', 400);
|
||||
}
|
||||
|
||||
$db = Db::connect('mysql');
|
||||
$invite = $db->name('fa_quit_checkin_supervisor_invite')->where('token', $token)->whereNull('deleted_at')->find();
|
||||
if (!$invite) {
|
||||
throw new \RuntimeException('邀请不存在', 400);
|
||||
}
|
||||
if (!empty($invite['used_at']) || !empty($invite['used_by_uid'])) {
|
||||
throw new \RuntimeException('邀请已被使用', 400);
|
||||
}
|
||||
if (Support::toDateTime($invite['expire_at']) < Support::now()) {
|
||||
throw new \RuntimeException('邀请已过期', 400);
|
||||
}
|
||||
|
||||
$ownerUID = (int) $invite['owner_uid'];
|
||||
if ($ownerUID === $supervisorUID) {
|
||||
throw new \RuntimeException('不能绑定自己', 400);
|
||||
}
|
||||
|
||||
$exists = $db->name('fa_quit_checkin_supervisor_binding')
|
||||
->where('owner_uid', $ownerUID)
|
||||
->where('supervisor_uid', $supervisorUID)
|
||||
->where('status', 'active')
|
||||
->whereNull('deleted_at')
|
||||
->find();
|
||||
if ($exists) {
|
||||
throw new \RuntimeException('已绑定,无需重复操作', 400);
|
||||
}
|
||||
|
||||
$count = (int) $db->name('fa_quit_checkin_supervisor_binding')
|
||||
->where('owner_uid', $ownerUID)
|
||||
->where('status', 'active')
|
||||
->whereNull('deleted_at')
|
||||
->count();
|
||||
if ($count >= 3) {
|
||||
throw new \RuntimeException('对方监督人已达上限(最多 3 人)', 400);
|
||||
}
|
||||
|
||||
$now = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$db->startTrans();
|
||||
try {
|
||||
$db->name('fa_quit_checkin_supervisor_binding')->insert([
|
||||
'owner_uid' => $ownerUID,
|
||||
'supervisor_uid' => $supervisorUID,
|
||||
'status' => 'active',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$db->name('fa_quit_checkin_supervisor_invite')->where('id', (int) $invite['id'])->update([
|
||||
'used_at' => $now,
|
||||
'used_by_uid' => $supervisorUID,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$db->commit();
|
||||
} catch (\Throwable $e) {
|
||||
$db->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
public function supervisorOverview(int $supervisorUID): array
|
||||
{
|
||||
$bindings = Db::connect('mysql')->name('fa_quit_checkin_supervisor_binding')
|
||||
->where('supervisor_uid', $supervisorUID)
|
||||
->where('status', 'active')
|
||||
->whereNull('deleted_at')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$items = [];
|
||||
foreach ($bindings as $binding) {
|
||||
$ownerUID = (int) $binding['owner_uid'];
|
||||
$owner = User::findActiveById($ownerUID);
|
||||
if (!$owner) {
|
||||
continue;
|
||||
}
|
||||
$items[] = [
|
||||
'owner' => $this->userSummary($owner),
|
||||
'home' => $this->home($ownerUID),
|
||||
];
|
||||
}
|
||||
|
||||
return ['items' => $items];
|
||||
}
|
||||
|
||||
public function supervisorStatus(int $ownerUID): array
|
||||
{
|
||||
$bindings = Db::connect('mysql')->name('fa_quit_checkin_supervisor_binding')
|
||||
->where('owner_uid', $ownerUID)
|
||||
->where('status', 'active')
|
||||
->whereNull('deleted_at')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$items = [];
|
||||
foreach ($bindings as $binding) {
|
||||
$user = User::findActiveById((int) $binding['supervisor_uid']);
|
||||
if ($user) {
|
||||
$items[] = $this->userSummary($user);
|
||||
}
|
||||
}
|
||||
return ['items' => $items];
|
||||
}
|
||||
|
||||
public function revokeSupervisorBinding(int $actorUID, array $data): array
|
||||
{
|
||||
$ownerUID = (int) ($data['owner_uid'] ?? 0);
|
||||
$supervisorUID = (int) ($data['supervisor_uid'] ?? 0);
|
||||
if ($ownerUID <= 0 || $supervisorUID <= 0 || ($actorUID !== $ownerUID && $actorUID !== $supervisorUID)) {
|
||||
throw new \RuntimeException('解除失败', 400);
|
||||
}
|
||||
|
||||
Db::connect('mysql')->name('fa_quit_checkin_supervisor_binding')
|
||||
->where('owner_uid', $ownerUID)
|
||||
->where('supervisor_uid', $supervisorUID)
|
||||
->where('status', 'active')
|
||||
->update(['status' => 'revoked', 'updated_at' => Support::now()->format(Support::DATETIME_LAYOUT)]);
|
||||
|
||||
return ['ok' => true];
|
||||
}
|
||||
|
||||
public function getReminderSettings(int $ownerUID): array
|
||||
{
|
||||
$row = $this->loadOrInitReminderSetting($ownerUID);
|
||||
return [
|
||||
'enabled' => (bool) $row['enabled'],
|
||||
'notify_time' => (string) $row['notify_time'],
|
||||
'max_per_day' => (int) $row['max_per_day'],
|
||||
];
|
||||
}
|
||||
|
||||
public function updateReminderSettings(int $ownerUID, array $data): array
|
||||
{
|
||||
$this->loadOrInitReminderSetting($ownerUID);
|
||||
$updates = ['updated_at' => Support::now()->format(Support::DATETIME_LAYOUT)];
|
||||
if (array_key_exists('enabled', $data)) {
|
||||
$updates['enabled'] = (bool) $data['enabled'] ? 1 : 0;
|
||||
}
|
||||
if (array_key_exists('notify_time', $data)) {
|
||||
$notifyTime = trim((string) $data['notify_time']);
|
||||
$this->assertHHMM($notifyTime);
|
||||
$updates['notify_time'] = $notifyTime;
|
||||
}
|
||||
if (array_key_exists('max_per_day', $data)) {
|
||||
$max = (int) $data['max_per_day'];
|
||||
if ($max < 0 || $max > 10) {
|
||||
throw new \RuntimeException('保存提醒设置失败', 400);
|
||||
}
|
||||
$updates['max_per_day'] = $max;
|
||||
}
|
||||
|
||||
Db::connect('mysql')->name('fa_quit_checkin_supervisor_reminder_setting')
|
||||
->where('owner_uid', $ownerUID)
|
||||
->whereNull('deleted_at')
|
||||
->update($updates);
|
||||
|
||||
return $this->getReminderSettings($ownerUID);
|
||||
}
|
||||
|
||||
public function runReminders(int $supervisorUID): array
|
||||
{
|
||||
$db = Db::connect('mysql');
|
||||
$today = Support::dateOnly()->format(Support::DATE_LAYOUT);
|
||||
$now = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$bindings = $db->name('fa_quit_checkin_supervisor_binding')
|
||||
->where('supervisor_uid', $supervisorUID)
|
||||
->where('status', 'active')
|
||||
->whereNull('deleted_at')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$created = 0;
|
||||
$skipped = 0;
|
||||
foreach ($bindings as $binding) {
|
||||
$ownerUID = (int) $binding['owner_uid'];
|
||||
$setting = $this->loadOrInitReminderSetting($ownerUID);
|
||||
if (!(bool) $setting['enabled'] || (int) $setting['max_per_day'] <= 0 || !$this->isAfterNotifyTime((string) $setting['notify_time'])) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$status = $db->name('fa_quit_checkin_daily_status')->where('uid', $ownerUID)->where('date', $today)->whereNull('deleted_at')->find();
|
||||
if ($status && in_array((string) $status['status'], ['checked_in', 'relapsed'], true)) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$count = (int) $db->name('fa_quit_checkin_supervisor_reminder_log')
|
||||
->where('owner_uid', $ownerUID)
|
||||
->where('supervisor_uid', $supervisorUID)
|
||||
->where('reminder_date', $today)
|
||||
->whereNull('deleted_at')
|
||||
->count();
|
||||
if ($count >= (int) $setting['max_per_day']) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$db->name('fa_quit_checkin_supervisor_reminder_log')->insert([
|
||||
'owner_uid' => $ownerUID,
|
||||
'supervisor_uid' => $supervisorUID,
|
||||
'reminder_date' => $today,
|
||||
'reminder_at' => $now,
|
||||
'type' => 'missed_checkin',
|
||||
'status' => 'stubbed',
|
||||
'channel' => 'stub',
|
||||
'message' => '提醒:戒烟用户今天还没打卡',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$created++;
|
||||
}
|
||||
|
||||
return ['created' => $created, 'skipped' => $skipped];
|
||||
}
|
||||
|
||||
private function dailyStatus(int $uid, string $date): array
|
||||
{
|
||||
$row = Db::connect('mysql')->name('fa_quit_checkin_daily_status')->where('uid', $uid)->where('date', $date)->whereNull('deleted_at')->find();
|
||||
if (!$row) {
|
||||
return ['date' => $date, 'status' => 'pending', 'checkin_at' => null, 'relapsed_at' => null, 'relapse_num' => null, 'note' => null];
|
||||
}
|
||||
return [
|
||||
'date' => (string) $row['date'],
|
||||
'status' => (string) $row['status'],
|
||||
'checkin_at' => !empty($row['check_in_at']) ? Support::formatRfc3339($row['check_in_at']) : null,
|
||||
'relapsed_at' => !empty($row['relapsed_at']) ? Support::formatRfc3339($row['relapsed_at']) : null,
|
||||
'relapse_num' => isset($row['relapse_num']) ? (int) $row['relapse_num'] : null,
|
||||
'note' => $row['note'] !== null ? (string) $row['note'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function summary(int $uid): array
|
||||
{
|
||||
$profile = Db::connect('mysql')->name('fa_quit_checkin_profile')->where('uid', $uid)->whereNull('deleted_at')->find();
|
||||
$start = Support::dateOnly($profile['quit_start_date'] ?? null);
|
||||
$days = max(0, (int) $start->diff(Support::dateOnly())->days + 1);
|
||||
$baseline = (int) ($profile['baseline_cigs_per_day'] ?? 0);
|
||||
$packPrice = (int) ($profile['pack_price_cent'] ?? 0);
|
||||
$relapses = (int) Db::connect('mysql')->name('fa_quit_checkin_daily_status')->where('uid', $uid)->where('status', 'relapsed')->whereNull('deleted_at')->count();
|
||||
$streak = max(0, $days - $relapses);
|
||||
$avoided = $streak * $baseline;
|
||||
$saved = $baseline > 0 ? (int) round($avoided * ($packPrice / 20)) : 0;
|
||||
return [
|
||||
'current_streak_days' => $streak,
|
||||
'max_streak_days' => $streak,
|
||||
'milestone_days' => 7,
|
||||
'days_to_next_milestone' => max(0, 7 - $streak),
|
||||
'saved_money_cent' => $saved,
|
||||
'avoided_cigs' => $avoided,
|
||||
'avoided_cigs_mode' => 'baseline',
|
||||
'health_recovery_percent' => min(100, $streak * 2),
|
||||
'hp_current' => isset($profile['hp_current']) ? (int) $profile['hp_current'] : 100,
|
||||
'hp_change_today' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function activeGoal(int $uid): ?array
|
||||
{
|
||||
$row = Db::connect('mysql')->name('fa_quit_checkin_reward_goal')->where('uid', $uid)->where('status', 'active')->whereNull('deleted_at')->order('id desc')->find();
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
$saved = $this->summary($uid)['saved_money_cent'];
|
||||
return $this->formatGoal($row, $saved);
|
||||
}
|
||||
|
||||
private function formatGoal(array $row, int $saved): array
|
||||
{
|
||||
$target = max(1, (int) $row['target_amount_cent']);
|
||||
return [
|
||||
'id' => (int) $row['id'],
|
||||
'user_id' => (int) $row['uid'],
|
||||
'title' => (string) $row['title'],
|
||||
'target_amount_cent' => (int) $row['target_amount_cent'],
|
||||
'current_amount_cent' => min($saved, (int) $row['target_amount_cent']),
|
||||
'progress_percent' => min(100, (int) floor($saved * 100 / $target)),
|
||||
'cover_image' => (string) ($row['cover_image'] ?? ''),
|
||||
'status' => (string) $row['status'],
|
||||
'completed_at' => !empty($row['completed_at']) ? Support::formatRfc3339($row['completed_at']) : null,
|
||||
'created_at' => Support::formatRfc3339($row['created_at'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
private function findGoal(int $uid, int $id): array
|
||||
{
|
||||
$row = Db::connect('mysql')->name('fa_quit_checkin_reward_goal')->where('id', $id)->where('uid', $uid)->whereNull('deleted_at')->find();
|
||||
if (!$row) {
|
||||
throw new \RuntimeException('目标不存在', 404);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
private function loadOrInitProfile(int $uid): array
|
||||
{
|
||||
$db = Db::connect('mysql');
|
||||
$row = $db->name('fa_quit_checkin_profile')->where('uid', $uid)->whereNull('deleted_at')->find();
|
||||
if ($row) {
|
||||
return $row;
|
||||
}
|
||||
$now = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$payload = [
|
||||
'uid' => $uid,
|
||||
'quit_start_date' => Support::dateOnly()->format(Support::DATE_LAYOUT),
|
||||
'pack_price_cent' => 0,
|
||||
'baseline_cigs_per_day' => 0,
|
||||
'motivation' => '',
|
||||
'notify_time' => '21:00',
|
||||
'reset_rule' => 'any_relapse_reset',
|
||||
'hp_current' => 100,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
$payload['id'] = $db->name('fa_quit_checkin_profile')->insertGetId($payload);
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function formatProfile(array $row, array $user): array
|
||||
{
|
||||
return [
|
||||
'user_id' => (int) $user['id'],
|
||||
'nickname' => (string) ($user['nickname'] ?? ''),
|
||||
'avatar_url' => (string) ($user['avatar_url'] ?? ''),
|
||||
'quit_start_date' => (string) ($row['quit_start_date'] ?? ''),
|
||||
'pack_price_cent' => (int) ($row['pack_price_cent'] ?? 0),
|
||||
'baseline_cigs_per_day' => (int) ($row['baseline_cigs_per_day'] ?? 0),
|
||||
'motivation' => (string) ($row['motivation'] ?? ''),
|
||||
'notify_time' => (string) ($row['notify_time'] ?? '21:00'),
|
||||
'reset_rule' => (string) ($row['reset_rule'] ?? 'any_relapse_reset'),
|
||||
'created_at' => Support::formatRfc3339($row['created_at'] ?? null),
|
||||
'updated_at' => Support::formatRfc3339($row['updated_at'] ?? null),
|
||||
];
|
||||
}
|
||||
|
||||
private function loadOrInitReminderSetting(int $ownerUID): array
|
||||
{
|
||||
$db = Db::connect('mysql');
|
||||
$row = $db->name('fa_quit_checkin_supervisor_reminder_setting')->where('owner_uid', $ownerUID)->whereNull('deleted_at')->find();
|
||||
if ($row) {
|
||||
return $row;
|
||||
}
|
||||
$now = Support::now()->format(Support::DATETIME_LAYOUT);
|
||||
$payload = ['owner_uid' => $ownerUID, 'enabled' => 0, 'notify_time' => '21:00', 'max_per_day' => 1, 'channel_hint' => 'stub', 'created_at' => $now, 'updated_at' => $now];
|
||||
$payload['id'] = $db->name('fa_quit_checkin_supervisor_reminder_setting')->insertGetId($payload);
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function userSummary(User $user): array
|
||||
{
|
||||
return ['user_id' => (int) $user->id, 'nickname' => (string) $user->nick_name, 'avatar_url' => (string) $user->avatar_url];
|
||||
}
|
||||
|
||||
private function assertHHMM(string $value): void
|
||||
{
|
||||
if (!preg_match('/^\d{2}:\d{2}$/', $value)) {
|
||||
throw new \RuntimeException('时间格式错误,应为 HH:MM', 400);
|
||||
}
|
||||
[$hour, $minute] = array_map('intval', explode(':', $value));
|
||||
if ($hour < 0 || $hour > 23 || $minute < 0 || $minute > 59) {
|
||||
throw new \RuntimeException('时间格式错误,应为 HH:MM', 400);
|
||||
}
|
||||
}
|
||||
|
||||
private function isAfterNotifyTime(string $notifyTime): bool
|
||||
{
|
||||
$notifyTime = $notifyTime !== '' ? $notifyTime : '21:00';
|
||||
$this->assertHHMM($notifyTime);
|
||||
return Support::now()->format('H:i') >= $notifyTime;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user