chore: init saasshop repo + sql migrations runner + gitee go

This commit is contained in:
萝卜
2026-03-10 11:31:02 +00:00
commit 50f15cdea8
210 changed files with 29534 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Support;
use Illuminate\Http\JsonResponse;
class ApiResponse
{
public static function success(mixed $data = null, string $message = 'ok', int $code = 0): JsonResponse
{
return response()->json([
'code' => $code,
'message' => $message,
'data' => $data,
'server_time' => now()->toDateTimeString(),
]);
}
public static function error(string $message = 'error', int $code = 1, mixed $data = null, int $httpStatus = 400): JsonResponse
{
return response()->json([
'code' => $code,
'message' => $message,
'data' => $data,
'server_time' => now()->toDateTimeString(),
], $httpStatus);
}
}

183
app/Support/CacheKeys.php Normal file
View File

@@ -0,0 +1,183 @@
<?php
namespace App\Support;
class CacheKeys
{
protected static function filtersSuffix(array $filters = []): string
{
return empty($filters) ? 'default' : md5(json_encode($filters, JSON_UNESCAPED_UNICODE));
}
public static function merchantDashboardStats(int $merchantId): string
{
return "merchant:{$merchantId}:dashboard:stats";
}
public static function merchantProductsVersion(int $merchantId): string
{
return "merchant:{$merchantId}:products:version";
}
public static function merchantProductsList(int $merchantId, int $page = 1, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::merchantProductsVersion($merchantId), 1);
return "merchant:{$merchantId}:products:v{$version}:list:page:{$page}:filters:{$suffix}";
}
public static function merchantProductsStatusStats(int $merchantId, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::merchantProductsVersion($merchantId), 1);
return "merchant:{$merchantId}:products:v{$version}:status-stats:filters:{$suffix}";
}
public static function merchantProductsSummary(int $merchantId, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::merchantProductsVersion($merchantId), 1);
return "merchant:{$merchantId}:products:v{$version}:summary:filters:{$suffix}";
}
public static function platformDashboardStats(): string
{
return 'platform:dashboard:stats';
}
public static function platformProductsVersion(): string
{
return 'platform:products:version';
}
public static function platformMerchantsList(int $page = 1): string
{
return "platform:merchants:list:page:{$page}";
}
public static function merchantOrdersVersion(int $merchantId): string
{
return "merchant:{$merchantId}:orders:version";
}
public static function merchantOrdersList(int $merchantId, int $page = 1, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::merchantOrdersVersion($merchantId), 1);
return "merchant:{$merchantId}:orders:v{$version}:list:page:{$page}:filters:{$suffix}";
}
public static function merchantOrdersStatusStats(int $merchantId, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::merchantOrdersVersion($merchantId), 1);
return "merchant:{$merchantId}:orders:v{$version}:status-stats:filters:{$suffix}";
}
public static function merchantOrdersSummary(int $merchantId, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::merchantOrdersVersion($merchantId), 1);
return "merchant:{$merchantId}:orders:v{$version}:summary:filters:{$suffix}";
}
public static function merchantOrdersTrendSummary(int $merchantId, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::merchantOrdersVersion($merchantId), 1);
return "merchant:{$merchantId}:orders:v{$version}:trend-summary:filters:{$suffix}";
}
public static function platformOrdersVersion(): string
{
return 'platform:orders:version';
}
public static function platformOrdersList(int $page = 1, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::platformOrdersVersion(), 1);
return "platform:orders:v{$version}:list:page:{$page}:filters:{$suffix}";
}
public static function platformOrdersStatusStats(array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::platformOrdersVersion(), 1);
return "platform:orders:v{$version}:status-stats:filters:{$suffix}";
}
public static function platformOrdersSummary(array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::platformOrdersVersion(), 1);
return "platform:orders:v{$version}:summary:filters:{$suffix}";
}
public static function platformOrdersTrendSummary(array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::platformOrdersVersion(), 1);
return "platform:orders:v{$version}:trend-summary:filters:{$suffix}";
}
public static function platformProductsList(int $page = 1, array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::platformProductsVersion(), 1);
return "platform:products:v{$version}:list:page:{$page}:filters:{$suffix}";
}
public static function platformProductsStatusStats(array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::platformProductsVersion(), 1);
return "platform:products:v{$version}:status-stats:filters:{$suffix}";
}
public static function platformProductsSummary(array $filters = []): string
{
$suffix = self::filtersSuffix($filters);
$version = (int) cache()->get(self::platformProductsVersion(), 1);
return "platform:products:v{$version}:summary:filters:{$suffix}";
}
public static function merchantCategoriesList(int $merchantId, int $page = 1): string
{
return "merchant:{$merchantId}:categories:list:page:{$page}";
}
public static function merchantUsersList(int $merchantId, int $page = 1): string
{
return "merchant:{$merchantId}:users:list:page:{$page}";
}
public static function platformCategoriesList(int $page = 1): string
{
return "platform:categories:list:page:{$page}";
}
public static function platformSystemConfigs(): string
{
return 'platform:settings:system';
}
public static function platformChannelsOverview(): string
{
return 'platform:settings:channels';
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Support;
use App\Models\PlatformOrder;
use App\Models\SiteSubscription;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Str;
/**
* 订阅激活服务(最小闭环)
*
* 目标:当平台订单满足“已支付 + 已生效”时,让站点订阅真正生效或续期。
*
* 约束:
* - 只负责业务层状态/时间的联动,不负责支付回执、异步通知等入口。
* - 支持两种情况:
* 1) 订单已绑定 site_subscription_id => 续期/延长该订阅
* 2) 未绑定 => 创建新订阅并回写订单 site_subscription_id
*/
class SubscriptionActivationService
{
/**
* @throws ModelNotFoundException
*/
public function activateOrder(int $orderId, ?int $adminId = null): SiteSubscription
{
/** @var PlatformOrder $order */
$order = PlatformOrder::query()->findOrFail($orderId);
// 最小校验:必须已支付 + 已生效
if ($order->payment_status !== 'paid' || $order->status !== 'activated') {
throw new \InvalidArgumentException('订单未满足生效条件:需 payment_status=paid 且 status=activated');
}
$now = now();
$months = max(1, (int) $order->period_months) * max(1, (int) ($order->quantity ?? 1));
// 幂等保护:若该订单已同步过订阅,则直接返回对应订阅(避免重复点击导致无限续期)
$activationMeta = (array) data_get($order->meta ?? [], 'subscription_activation', []);
$activatedSubscriptionId = (int) ($activationMeta['subscription_id'] ?? 0);
if ($activatedSubscriptionId > 0) {
$subscription = SiteSubscription::query()->find($activatedSubscriptionId);
if ($subscription) {
return $subscription;
}
}
// 订阅快照:优先使用订单上记录的 plan_name / billing_cycle / plan_snapshot
$snapshot = [
'from_order_id' => $order->id,
'order_no' => $order->order_no,
'order_type' => $order->order_type,
'plan_snapshot' => $order->plan_snapshot,
];
if ($order->site_subscription_id) {
/** @var SiteSubscription $subscription */
$subscription = SiteSubscription::query()->findOrFail($order->site_subscription_id);
// 以 ends_at 为基准续期:
// - 若 ends_at 为空或已过期 => 从 now 起算
// - 若仍有效 => 从 ends_at 起算
$base = $subscription->ends_at && $subscription->ends_at->greaterThan($now)
? $subscription->ends_at->copy()
: $now->copy();
$newEndsAt = $base->copy()->addMonthsNoOverflow($months);
// starts_at若为空则补齐
$startsAt = $subscription->starts_at ?: $now->copy();
$subscription->fill([
'status' => 'activated',
'plan_id' => $order->plan_id,
'plan_name' => $order->plan_name,
'billing_cycle' => $order->billing_cycle,
'period_months' => (int) $order->period_months,
'amount' => $order->paid_amount > 0 ? $order->paid_amount : $order->payable_amount,
'starts_at' => $startsAt,
'ends_at' => $newEndsAt,
'activated_at' => $subscription->activated_at ?: $now,
'snapshot' => array_merge((array) ($subscription->snapshot ?? []), $snapshot),
]);
$subscription->save();
// 写入订单 meta标记该订单已完成订阅同步幂等
$meta = (array) ($order->meta ?? []);
data_set($meta, 'subscription_activation', [
'subscription_id' => $subscription->id,
'synced_at' => $now->toDateTimeString(),
'admin_id' => $adminId,
]);
$order->meta = $meta;
$order->save();
return $subscription;
}
// 创建新订阅
$subscriptionNo = 'SUB' . $now->format('YmdHis') . Str::padLeft((string) random_int(1, 9999), 4, '0');
$subscription = SiteSubscription::query()->create([
'merchant_id' => $order->merchant_id,
'plan_id' => $order->plan_id,
'status' => 'activated',
'source' => 'platform_order',
'subscription_no' => $subscriptionNo,
'plan_name' => $order->plan_name,
'billing_cycle' => $order->billing_cycle,
'period_months' => (int) $order->period_months,
'amount' => $order->paid_amount > 0 ? $order->paid_amount : $order->payable_amount,
'starts_at' => $now,
'ends_at' => $now->copy()->addMonthsNoOverflow($months),
'activated_at' => $now,
'snapshot' => $snapshot,
'meta' => [
'activated_by_admin_id' => $adminId,
],
]);
// 回写订单 + 写入 meta 标记同步完成(幂等)
$order->site_subscription_id = $subscription->id;
$order->activated_at = $order->activated_at ?: $now;
$meta = (array) ($order->meta ?? []);
data_set($meta, 'subscription_activation', [
'subscription_id' => $subscription->id,
'synced_at' => $now->toDateTimeString(),
'admin_id' => $adminId,
]);
$order->meta = $meta;
$order->save();
return $subscription;
}
}