mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-14 02:50:52 +08:00
feat: multiple improvements and bug fixes
- Add gift card redemption feature - Resolve custom range selection issue in overview - Allow log page size to be modified - Add subscription path change notification - Improve dynamic node rate feature - Support markdown documentation display for plugins - Reduce power reset service logging - Fix backend version number not updating after update
This commit is contained in:
@@ -55,7 +55,7 @@ class ShadowsocksTidalabController extends Controller
|
||||
foreach ($data as $item) {
|
||||
$formatData[$item['user_id']] = [$item['u'], $item['d']];
|
||||
}
|
||||
$userService->trafficFetch($server->toArray(), 'shadowsocks', $formatData);
|
||||
$userService->trafficFetch($server, 'shadowsocks', $formatData);
|
||||
|
||||
return response([
|
||||
'ret' => 1,
|
||||
|
||||
@@ -62,7 +62,7 @@ class TrojanTidalabController extends Controller
|
||||
foreach ($data as $item) {
|
||||
$formatData[$item['user_id']] = [$item['u'], $item['d']];
|
||||
}
|
||||
$userService->trafficFetch($server->toArray(), 'trojan', $formatData);
|
||||
$userService->trafficFetch($server, 'trojan', $formatData);
|
||||
|
||||
return response([
|
||||
'ret' => 1,
|
||||
|
||||
@@ -79,7 +79,7 @@ class UniProxyController extends Controller
|
||||
);
|
||||
|
||||
$userService = new UserService();
|
||||
$userService->trafficFetch($node->toArray(), $nodeType, $data);
|
||||
$userService->trafficFetch($node, $nodeType, $data);
|
||||
return $this->success(true);
|
||||
}
|
||||
|
||||
|
||||
185
app/Http/Controllers/V1/User/GiftCardController.php
Normal file
185
app/Http/Controllers/V1/User/GiftCardController.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\V1\User;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\User\GiftCardCheckRequest;
|
||||
use App\Http\Requests\User\GiftCardRedeemRequest;
|
||||
use App\Models\GiftCardUsage;
|
||||
use App\Services\GiftCardService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class GiftCardController extends Controller
|
||||
{
|
||||
/**
|
||||
* 查询兑换码信息
|
||||
*/
|
||||
public function check(GiftCardCheckRequest $request)
|
||||
{
|
||||
try {
|
||||
$giftCardService = new GiftCardService($request->input('code'));
|
||||
$giftCardService->setUser($request->user());
|
||||
|
||||
// 1. 验证礼品卡本身是否有效 (如不存在、已过期、已禁用)
|
||||
$giftCardService->validateIsActive();
|
||||
|
||||
// 2. 检查用户是否满足使用条件,但不在此处抛出异常
|
||||
$eligibility = $giftCardService->checkUserEligibility();
|
||||
|
||||
// 3. 获取卡片信息和奖励预览
|
||||
$codeInfo = $giftCardService->getCodeInfo();
|
||||
$rewardPreview = $giftCardService->previewRewards();
|
||||
|
||||
return $this->success([
|
||||
'code_info' => $codeInfo, // 这里面已经包含 plan_info
|
||||
'reward_preview' => $rewardPreview,
|
||||
'can_redeem' => $eligibility['can_redeem'],
|
||||
'reason' => $eligibility['reason'],
|
||||
]);
|
||||
|
||||
} catch (ApiException $e) {
|
||||
// 这里只捕获 validateIsActive 抛出的异常
|
||||
return $this->fail([400, $e->getMessage()]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('礼品卡查询失败', [
|
||||
'code' => $request->input('code'),
|
||||
'user_id' => $request->user()->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return $this->fail([500, '查询失败,请稍后重试']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用兑换码
|
||||
*/
|
||||
public function redeem(GiftCardRedeemRequest $request)
|
||||
{
|
||||
try {
|
||||
$giftCardService = new GiftCardService($request->input('code'));
|
||||
$giftCardService->setUser($request->user());
|
||||
$giftCardService->validate();
|
||||
|
||||
// 使用礼品卡
|
||||
$result = $giftCardService->redeem([
|
||||
// 'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
]);
|
||||
|
||||
Log::info('礼品卡使用成功', [
|
||||
'code' => $request->input('code'),
|
||||
'user_id' => $request->user()->id,
|
||||
'rewards' => $result['rewards'],
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'message' => '兑换成功!',
|
||||
'rewards' => $result['rewards'],
|
||||
'invite_rewards' => $result['invite_rewards'],
|
||||
'template_name' => $result['template_name'],
|
||||
]);
|
||||
|
||||
} catch (ApiException $e) {
|
||||
return $this->fail([400, $e->getMessage()]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('礼品卡使用失败', [
|
||||
'code' => $request->input('code'),
|
||||
'user_id' => $request->user()->id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
return $this->fail([500, '兑换失败,请稍后重试']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户兑换记录
|
||||
*/
|
||||
public function history(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'page' => 'integer|min:1',
|
||||
'per_page' => 'integer|min:1|max:100',
|
||||
]);
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
|
||||
$usages = GiftCardUsage::with(['template', 'code'])
|
||||
->where('user_id', $request->user()->id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate($perPage);
|
||||
|
||||
$data = $usages->getCollection()->map(function ($usage) {
|
||||
return [
|
||||
'id' => $usage->id,
|
||||
'code' => substr($usage->code->code, 0, 8) . '****', // 脱敏处理
|
||||
'template_name' => $usage->template->name,
|
||||
'template_type' => $usage->template->type,
|
||||
'template_type_name' => $usage->template->type_name,
|
||||
'rewards_given' => $usage->rewards_given,
|
||||
'invite_rewards' => $usage->invite_rewards,
|
||||
'multiplier_applied' => $usage->multiplier_applied,
|
||||
'created_at' => $usage->created_at,
|
||||
];
|
||||
});
|
||||
$usages->setCollection($data);
|
||||
|
||||
return $this->paginate($usages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取兑换记录详情
|
||||
*/
|
||||
public function detail(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id' => 'required|integer|exists:v2_gift_card_usage,id',
|
||||
]);
|
||||
|
||||
$usage = GiftCardUsage::with(['template', 'code', 'inviteUser'])
|
||||
->where('user_id', $request->user()->id)
|
||||
->where('id', $request->input('id'))
|
||||
->first();
|
||||
|
||||
if (!$usage) {
|
||||
return $this->fail([404, '记录不存在']);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'id' => $usage->id,
|
||||
'code' => $usage->code->code,
|
||||
'template' => [
|
||||
'name' => $usage->template->name,
|
||||
'description' => $usage->template->description,
|
||||
'type' => $usage->template->type,
|
||||
'type_name' => $usage->template->type_name,
|
||||
'icon' => $usage->template->icon,
|
||||
'theme_color' => $usage->template->theme_color,
|
||||
],
|
||||
'rewards_given' => $usage->rewards_given,
|
||||
'invite_rewards' => $usage->invite_rewards,
|
||||
'invite_user' => $usage->inviteUser ? [
|
||||
'id' => $usage->inviteUser->id,
|
||||
'email' => substr($usage->inviteUser->email, 0, 3) . '***@***',
|
||||
] : null,
|
||||
'user_level_at_use' => $usage->user_level_at_use,
|
||||
'plan_id_at_use' => $usage->plan_id_at_use,
|
||||
'multiplier_applied' => $usage->multiplier_applied,
|
||||
// 'ip_address' => $usage->ip_address,
|
||||
'notes' => $usage->notes,
|
||||
'created_at' => $usage->created_at,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的礼品卡类型
|
||||
*/
|
||||
public function types(Request $request)
|
||||
{
|
||||
return $this->success([
|
||||
'types' => \App\Models\GiftCardTemplate::getTypeMap(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ use App\Services\MailService;
|
||||
use App\Services\TelegramService;
|
||||
use App\Services\ThemeService;
|
||||
use App\Utils\Dict;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class ConfigController extends Controller
|
||||
|
||||
@@ -46,10 +46,7 @@ class CouponController extends Controller
|
||||
$coupons = $builder
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate($pageSize, ["*"], 'page', $current);
|
||||
return response([
|
||||
'data' => $coupons->items(),
|
||||
'total' => $coupons->total()
|
||||
]);
|
||||
return $this->paginate($coupons);
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
|
||||
641
app/Http/Controllers/V2/Admin/GiftCardController.php
Normal file
641
app/Http/Controllers/V2/Admin/GiftCardController.php
Normal file
@@ -0,0 +1,641 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\V2\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\GiftCardCode;
|
||||
use App\Models\GiftCardTemplate;
|
||||
use App\Models\GiftCardUsage;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class GiftCardController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取礼品卡模板列表
|
||||
*/
|
||||
public function templates(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'type' => 'integer|min:1|max:10',
|
||||
'status' => 'integer|in:0,1',
|
||||
'page' => 'integer|min:1',
|
||||
'per_page' => 'integer|min:1|max:1000',
|
||||
]);
|
||||
|
||||
$query = GiftCardTemplate::query();
|
||||
|
||||
if ($request->has('type')) {
|
||||
$query->where('type', $request->input('type'));
|
||||
}
|
||||
|
||||
if ($request->has('status')) {
|
||||
$query->where('status', $request->input('status'));
|
||||
}
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
$templates = $query->orderBy('sort', 'asc')
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate($perPage);
|
||||
|
||||
$data = $templates->getCollection()->map(function ($template) {
|
||||
return [
|
||||
'id' => $template->id,
|
||||
'name' => $template->name,
|
||||
'description' => $template->description,
|
||||
'type' => $template->type,
|
||||
'type_name' => $template->type_name,
|
||||
'status' => $template->status,
|
||||
'conditions' => $template->conditions,
|
||||
'rewards' => $template->rewards,
|
||||
'limits' => $template->limits,
|
||||
'special_config' => $template->special_config,
|
||||
'icon' => $template->icon,
|
||||
'background_image' => $template->background_image,
|
||||
'theme_color' => $template->theme_color,
|
||||
'sort' => $template->sort,
|
||||
'admin_id' => $template->admin_id,
|
||||
'created_at' => $template->created_at,
|
||||
'updated_at' => $template->updated_at,
|
||||
// 统计信息
|
||||
'codes_count' => $template->codes()->count(),
|
||||
'used_count' => $template->usages()->count(),
|
||||
];
|
||||
});
|
||||
|
||||
$templates->setCollection($data);
|
||||
return $this->paginate($templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建礼品卡模板
|
||||
*/
|
||||
public function createTemplate(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'type' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::in(array_keys(GiftCardTemplate::getTypeMap()))
|
||||
],
|
||||
'status' => 'boolean',
|
||||
'conditions' => 'nullable|array',
|
||||
'rewards' => 'required|array',
|
||||
'limits' => 'nullable|array',
|
||||
'special_config' => 'nullable|array',
|
||||
'icon' => 'nullable|string|max:255',
|
||||
'background_image' => 'nullable|string|url|max:255',
|
||||
'theme_color' => 'nullable|string|regex:/^#[0-9A-Fa-f]{6}$/',
|
||||
'sort' => 'integer|min:0',
|
||||
], [
|
||||
'name.required' => '礼品卡名称不能为空',
|
||||
'type.required' => '礼品卡类型不能为空',
|
||||
'type.in' => '无效的礼品卡类型',
|
||||
'rewards.required' => '奖励配置不能为空',
|
||||
'theme_color.regex' => '主题色格式不正确',
|
||||
'background_image.url' => '背景图片必须是有效的URL',
|
||||
]);
|
||||
|
||||
try {
|
||||
$template = GiftCardTemplate::create([
|
||||
'name' => $request->input('name'),
|
||||
'description' => $request->input('description'),
|
||||
'type' => $request->input('type'),
|
||||
'status' => $request->input('status', true),
|
||||
'conditions' => $request->input('conditions'),
|
||||
'rewards' => $request->input('rewards'),
|
||||
'limits' => $request->input('limits'),
|
||||
'special_config' => $request->input('special_config'),
|
||||
'icon' => $request->input('icon'),
|
||||
'background_image' => $request->input('background_image'),
|
||||
'theme_color' => $request->input('theme_color', '#1890ff'),
|
||||
'sort' => $request->input('sort', 0),
|
||||
'admin_id' => $request->user()->id,
|
||||
'created_at' => time(),
|
||||
'updated_at' => time(),
|
||||
]);
|
||||
|
||||
return $this->success($template);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('创建礼品卡模板失败', [
|
||||
'admin_id' => $request->user()->id,
|
||||
'data' => $request->all(),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return $this->fail([500, '创建失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新礼品卡模板
|
||||
*/
|
||||
public function updateTemplate(Request $request)
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
'id' => 'required|integer|exists:v2_gift_card_template,id',
|
||||
'name' => 'sometimes|required|string|max:255',
|
||||
'description' => 'sometimes|nullable|string',
|
||||
'type' => [
|
||||
'sometimes',
|
||||
'required',
|
||||
'integer',
|
||||
Rule::in(array_keys(GiftCardTemplate::getTypeMap()))
|
||||
],
|
||||
'status' => 'sometimes|boolean',
|
||||
'conditions' => 'sometimes|nullable|array',
|
||||
'rewards' => 'sometimes|required|array',
|
||||
'limits' => 'sometimes|nullable|array',
|
||||
'special_config' => 'sometimes|nullable|array',
|
||||
'icon' => 'sometimes|nullable|string|max:255',
|
||||
'background_image' => 'sometimes|nullable|string|url|max:255',
|
||||
'theme_color' => 'sometimes|nullable|string|regex:/^#[0-9A-Fa-f]{6}$/',
|
||||
'sort' => 'sometimes|integer|min:0',
|
||||
]);
|
||||
|
||||
$template = GiftCardTemplate::find($validatedData['id']);
|
||||
if (!$template) {
|
||||
return $this->fail([404, '模板不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
$updateData = collect($validatedData)->except('id')->all();
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->success($template);
|
||||
}
|
||||
|
||||
$updateData['updated_at'] = time();
|
||||
|
||||
$template->update($updateData);
|
||||
|
||||
return $this->success($template->fresh());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新礼品卡模板失败', [
|
||||
'admin_id' => $request->user()->id,
|
||||
'template_id' => $template->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return $this->fail([500, '更新失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除礼品卡模板
|
||||
*/
|
||||
public function deleteTemplate(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id' => 'required|integer|exists:v2_gift_card_template,id',
|
||||
]);
|
||||
|
||||
$template = GiftCardTemplate::find($request->input('id'));
|
||||
if (!$template) {
|
||||
return $this->fail([404, '模板不存在']);
|
||||
}
|
||||
|
||||
// 检查是否有关联的兑换码
|
||||
if ($template->codes()->exists()) {
|
||||
return $this->fail([400, '该模板下存在兑换码,无法删除']);
|
||||
}
|
||||
|
||||
try {
|
||||
$template->delete();
|
||||
return $this->success(true);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('删除礼品卡模板失败', [
|
||||
'admin_id' => $request->user()->id,
|
||||
'template_id' => $template->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return $this->fail([500, '删除失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成兑换码
|
||||
*/
|
||||
public function generateCodes(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'template_id' => 'required|integer|exists:v2_gift_card_template,id',
|
||||
'count' => 'required|integer|min:1|max:10000',
|
||||
'prefix' => 'nullable|string|max:10|regex:/^[A-Z0-9]*$/',
|
||||
'expires_hours' => 'nullable|integer|min:1',
|
||||
'max_usage' => 'integer|min:1|max:1000',
|
||||
], [
|
||||
'template_id.required' => '请选择礼品卡模板',
|
||||
'count.required' => '请指定生成数量',
|
||||
'count.max' => '单次最多生成10000个兑换码',
|
||||
'prefix.regex' => '前缀只能包含大写字母和数字',
|
||||
]);
|
||||
|
||||
$template = GiftCardTemplate::find($request->input('template_id'));
|
||||
if (!$template->isAvailable()) {
|
||||
return $this->fail([400, '模板已被禁用']);
|
||||
}
|
||||
|
||||
try {
|
||||
$options = [
|
||||
'prefix' => $request->input('prefix', 'GC'),
|
||||
'max_usage' => $request->input('max_usage', 1),
|
||||
];
|
||||
|
||||
if ($request->has('expires_hours')) {
|
||||
$options['expires_at'] = time() + ($request->input('expires_hours') * 3600);
|
||||
}
|
||||
|
||||
$batchId = GiftCardCode::batchGenerate(
|
||||
$request->input('template_id'),
|
||||
$request->input('count'),
|
||||
$options
|
||||
);
|
||||
|
||||
// 查询本次生成的所有兑换码
|
||||
$codes = GiftCardCode::where('batch_id', $batchId)->get();
|
||||
|
||||
// 判断是否导出 CSV
|
||||
if ($request->input('download_csv')) {
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => 'attachment; filename="gift_codes.csv"',
|
||||
];
|
||||
$callback = function () use ($codes, $template) {
|
||||
$handle = fopen('php://output', 'w');
|
||||
// 表头
|
||||
fputcsv($handle, [
|
||||
'兑换码',
|
||||
'前缀',
|
||||
'有效期',
|
||||
'最大使用次数',
|
||||
'批次号',
|
||||
'创建时间',
|
||||
'模板名称',
|
||||
'模板类型',
|
||||
'模板奖励',
|
||||
'状态',
|
||||
'使用者',
|
||||
'使用时间',
|
||||
'备注'
|
||||
]);
|
||||
foreach ($codes as $code) {
|
||||
$expireDate = $code->expires_at ? date('Y-m-d H:i:s', $code->expires_at) : '长期有效';
|
||||
$createDate = date('Y-m-d H:i:s', $code->created_at);
|
||||
$templateName = $template->name ?? '';
|
||||
$templateType = $template->type ?? '';
|
||||
$templateRewards = $template->rewards ? json_encode($template->rewards, JSON_UNESCAPED_UNICODE) : '';
|
||||
// 状态判断
|
||||
if ($code->disabled) {
|
||||
$status = '已禁用';
|
||||
} elseif ($code->used_at) {
|
||||
$status = '已使用';
|
||||
} elseif ($code->expires_at && $code->expires_at < time()) {
|
||||
$status = '已过期';
|
||||
} else {
|
||||
$status = '未使用';
|
||||
}
|
||||
$usedBy = $code->user_id ?? '';
|
||||
$usedAt = $code->used_at ? date('Y-m-d H:i:s', $code->used_at) : '';
|
||||
$remark = $code->remark ?? '';
|
||||
fputcsv($handle, [
|
||||
$code->code,
|
||||
$code->prefix ?? '',
|
||||
$expireDate,
|
||||
$code->max_usage,
|
||||
$code->batch_id,
|
||||
$createDate,
|
||||
$templateName,
|
||||
$templateType,
|
||||
$templateRewards,
|
||||
$status,
|
||||
$usedBy,
|
||||
$usedAt,
|
||||
$remark,
|
||||
]);
|
||||
}
|
||||
fclose($handle);
|
||||
};
|
||||
return response()->streamDownload($callback, 'gift_codes.csv', $headers);
|
||||
}
|
||||
|
||||
Log::info('批量生成兑换码', [
|
||||
'admin_id' => $request->user()->id,
|
||||
'template_id' => $request->input('template_id'),
|
||||
'count' => $request->input('count'),
|
||||
'batch_id' => $batchId,
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'batch_id' => $batchId,
|
||||
'count' => $request->input('count'),
|
||||
'message' => '生成成功',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('生成兑换码失败', [
|
||||
'admin_id' => $request->user()->id,
|
||||
'data' => $request->all(),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return $this->fail([500, '生成失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取兑换码列表
|
||||
*/
|
||||
public function codes(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'template_id' => 'integer|exists:v2_gift_card_template,id',
|
||||
'batch_id' => 'string',
|
||||
'status' => 'integer|in:0,1,2,3',
|
||||
'page' => 'integer|min:1',
|
||||
'per_page' => 'integer|min:1|max:100',
|
||||
]);
|
||||
|
||||
$query = GiftCardCode::with(['template', 'user']);
|
||||
|
||||
if ($request->has('template_id')) {
|
||||
$query->where('template_id', $request->input('template_id'));
|
||||
}
|
||||
|
||||
if ($request->has('batch_id')) {
|
||||
$query->where('batch_id', $request->input('batch_id'));
|
||||
}
|
||||
|
||||
if ($request->has('status')) {
|
||||
$query->where('status', $request->input('status'));
|
||||
}
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
$codes = $query->orderBy('created_at', 'desc')->paginate($perPage);
|
||||
|
||||
$data = $codes->getCollection()->map(function ($code) {
|
||||
return [
|
||||
'id' => $code->id,
|
||||
'template_id' => $code->template_id,
|
||||
'template_name' => $code->template->name,
|
||||
'code' => $code->code,
|
||||
'batch_id' => $code->batch_id,
|
||||
'status' => $code->status,
|
||||
'status_name' => $code->status_name,
|
||||
'user_id' => $code->user_id,
|
||||
'user_email' => $code->user ? substr($code->user->email, 0, 3) . '***@***' : null,
|
||||
'used_at' => $code->used_at,
|
||||
'expires_at' => $code->expires_at,
|
||||
'usage_count' => $code->usage_count,
|
||||
'max_usage' => $code->max_usage,
|
||||
'created_at' => $code->created_at,
|
||||
];
|
||||
});
|
||||
|
||||
$codes->setCollection($data);
|
||||
return $this->paginate($codes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用/启用兑换码
|
||||
*/
|
||||
public function toggleCode(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id' => 'required|integer|exists:v2_gift_card_code,id',
|
||||
'action' => 'required|string|in:disable,enable',
|
||||
]);
|
||||
|
||||
$code = GiftCardCode::find($request->input('id'));
|
||||
if (!$code) {
|
||||
return $this->fail([404, '兑换码不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($request->input('action') === 'disable') {
|
||||
$code->markAsDisabled();
|
||||
} else {
|
||||
if ($code->status === GiftCardCode::STATUS_DISABLED) {
|
||||
$code->status = GiftCardCode::STATUS_UNUSED;
|
||||
$code->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => $request->input('action') === 'disable' ? '已禁用' : '已启用',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail([500, '操作失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出兑换码
|
||||
*/
|
||||
public function exportCodes(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'batch_id' => 'required|string|exists:v2_gift_card_code,batch_id',
|
||||
]);
|
||||
|
||||
$codes = GiftCardCode::where('batch_id', $request->input('batch_id'))
|
||||
->orderBy('created_at', 'asc')
|
||||
->get(['code']);
|
||||
|
||||
$content = $codes->pluck('code')->implode("\n");
|
||||
|
||||
return response($content)
|
||||
->header('Content-Type', 'text/plain')
|
||||
->header('Content-Disposition', 'attachment; filename="gift_cards_' . $request->input('batch_id') . '.txt"');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取使用记录
|
||||
*/
|
||||
public function usages(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'template_id' => 'integer|exists:v2_gift_card_template,id',
|
||||
'user_id' => 'integer|exists:v2_user,id',
|
||||
'page' => 'integer|min:1',
|
||||
'per_page' => 'integer|min:1|max:100',
|
||||
]);
|
||||
|
||||
$query = GiftCardUsage::with(['template', 'code', 'user', 'inviteUser']);
|
||||
|
||||
if ($request->has('template_id')) {
|
||||
$query->where('template_id', $request->input('template_id'));
|
||||
}
|
||||
|
||||
if ($request->has('user_id')) {
|
||||
$query->where('user_id', $request->input('user_id'));
|
||||
}
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
$usages = $query->orderBy('created_at', 'desc')->paginate($perPage);
|
||||
|
||||
$data = $usages->getCollection()->map(function ($usage) {
|
||||
return [
|
||||
'id' => $usage->id,
|
||||
'code' => $usage->code->code,
|
||||
'template_name' => $usage->template->name,
|
||||
'user_email' => $usage->user->email,
|
||||
'invite_user_email' => $usage->inviteUser ? substr($usage->inviteUser->email, 0, 3) . '***@***' : null,
|
||||
'rewards_given' => $usage->rewards_given,
|
||||
'invite_rewards' => $usage->invite_rewards,
|
||||
'multiplier_applied' => $usage->multiplier_applied,
|
||||
// 'ip_address' => $usage->ip_address,
|
||||
'created_at' => $usage->created_at,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'data' => $data,
|
||||
'pagination' => [
|
||||
'current_page' => $usages->currentPage(),
|
||||
'last_page' => $usages->lastPage(),
|
||||
'per_page' => $usages->perPage(),
|
||||
'total' => $usages->total(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计数据
|
||||
*/
|
||||
public function statistics(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'start_date' => 'date_format:Y-m-d',
|
||||
'end_date' => 'date_format:Y-m-d',
|
||||
]);
|
||||
|
||||
$startDate = $request->input('start_date', date('Y-m-d', strtotime('-30 days')));
|
||||
$endDate = $request->input('end_date', date('Y-m-d'));
|
||||
|
||||
// 总体统计
|
||||
$totalStats = [
|
||||
'templates_count' => GiftCardTemplate::count(),
|
||||
'active_templates_count' => GiftCardTemplate::where('status', 1)->count(),
|
||||
'codes_count' => GiftCardCode::count(),
|
||||
'used_codes_count' => GiftCardCode::where('status', GiftCardCode::STATUS_USED)->count(),
|
||||
'usages_count' => GiftCardUsage::count(),
|
||||
];
|
||||
|
||||
// 每日使用统计
|
||||
$driver = GiftCardUsage::query()->getConnection()->getDriverName();
|
||||
$dateExpression = "date(created_at, 'unixepoch')"; // Default for SQLite
|
||||
if ($driver === 'mysql') {
|
||||
$dateExpression = 'DATE(FROM_UNIXTIME(created_at))';
|
||||
} elseif ($driver === 'pgsql') {
|
||||
$dateExpression = 'date(to_timestamp(created_at))';
|
||||
}
|
||||
|
||||
$dailyUsages = GiftCardUsage::selectRaw("{$dateExpression} as date, COUNT(*) as count")
|
||||
->whereRaw("{$dateExpression} BETWEEN ? AND ?", [$startDate, $endDate])
|
||||
->groupBy('date')
|
||||
->orderBy('date')
|
||||
->get();
|
||||
|
||||
// 类型统计
|
||||
$typeStats = GiftCardUsage::with('template')
|
||||
->selectRaw('template_id, COUNT(*) as count')
|
||||
->groupBy('template_id')
|
||||
->get()
|
||||
->map(function ($item) {
|
||||
return [
|
||||
'template_name' => $item->template->name,
|
||||
'type_name' => $item->template->type_name,
|
||||
'count' => $item->count,
|
||||
];
|
||||
});
|
||||
|
||||
return $this->success([
|
||||
'total_stats' => $totalStats,
|
||||
'daily_usages' => $dailyUsages,
|
||||
'type_stats' => $typeStats,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的礼品卡类型
|
||||
*/
|
||||
public function types()
|
||||
{
|
||||
return $this->success(GiftCardTemplate::getTypeMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个兑换码
|
||||
*/
|
||||
public function updateCode(Request $request)
|
||||
{
|
||||
$validatedData = $request->validate([
|
||||
'id' => 'required|integer|exists:v2_gift_card_code,id',
|
||||
'expires_at' => 'sometimes|nullable|integer',
|
||||
'max_usage' => 'sometimes|integer|min:1|max:1000',
|
||||
'status' => 'sometimes|integer|in:0,1,2,3',
|
||||
]);
|
||||
|
||||
$code = GiftCardCode::find($validatedData['id']);
|
||||
if (!$code) {
|
||||
return $this->fail([404, '礼品卡不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
$updateData = collect($validatedData)->except('id')->all();
|
||||
|
||||
if (empty($updateData)) {
|
||||
return $this->success($code);
|
||||
}
|
||||
|
||||
$updateData['updated_at'] = time();
|
||||
$code->update($updateData);
|
||||
|
||||
return $this->success($code->fresh());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新礼品卡信息失败', [
|
||||
'admin_id' => $request->user()->id,
|
||||
'code_id' => $code->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return $this->fail([500, '更新失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除礼品卡
|
||||
*/
|
||||
public function deleteCode(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id' => 'required|integer|exists:v2_gift_card_code,id',
|
||||
]);
|
||||
|
||||
$code = GiftCardCode::find($request->input('id'));
|
||||
if (!$code) {
|
||||
return $this->fail([404, '礼品卡不存在']);
|
||||
}
|
||||
|
||||
// 检查是否已被使用
|
||||
if ($code->status === GiftCardCode::STATUS_USED) {
|
||||
return $this->fail([400, '该礼品卡已被使用,无法删除']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查是否有关联的使用记录
|
||||
if ($code->usages()->exists()) {
|
||||
return $this->fail([400, '该礼品卡存在使用记录,无法删除']);
|
||||
}
|
||||
|
||||
$code->delete();
|
||||
return $this->success(['message' => '删除成功']);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('删除礼品卡失败', [
|
||||
'admin_id' => $request->user()->id,
|
||||
'code_id' => $code->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return $this->fail([500, '删除失败']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,13 +54,13 @@ class OrderController extends Controller
|
||||
page: $current
|
||||
);
|
||||
|
||||
$paginatedResults->getCollection()->transform(function($order) {
|
||||
$paginatedResults->getCollection()->transform(function ($order) {
|
||||
$orderArray = $order->toArray();
|
||||
$orderArray['period'] = PlanService::getLegacyPeriod((string) $order->period);
|
||||
return $orderArray;
|
||||
});
|
||||
|
||||
return response()->json($paginatedResults);
|
||||
return $this->paginate($paginatedResults);
|
||||
}
|
||||
|
||||
private function applyFiltersAndSorts(Request $request, Builder $builder): void
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Services\Plugin\PluginManager;
|
||||
use App\Services\Plugin\PluginConfigService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PluginController extends Controller
|
||||
{
|
||||
@@ -44,6 +45,11 @@ class PluginController extends Controller
|
||||
$installed = isset($installedPlugins[$code]);
|
||||
// 使用配置服务获取配置
|
||||
$pluginConfig = $installed ? $this->configService->getConfig($code) : ($config['config'] ?? []);
|
||||
$readmeFile = collect(['README.md', 'readme.md'])
|
||||
->map(fn($f) => $directory . '/' . $f)
|
||||
->first(fn($path) => File::exists($path));
|
||||
$readmeContent = $readmeFile ? File::get($readmeFile) : '';
|
||||
|
||||
$plugins[] = [
|
||||
'code' => $config['code'],
|
||||
'name' => $config['name'],
|
||||
@@ -53,6 +59,7 @@ class PluginController extends Controller
|
||||
'is_installed' => $installed,
|
||||
'is_enabled' => $installed ? $installedPlugins[$code]['is_enabled'] : false,
|
||||
'config' => $pluginConfig,
|
||||
'readme' => $readmeContent,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,10 +169,7 @@ class UserController extends Controller
|
||||
return self::transformUserData($user);
|
||||
});
|
||||
|
||||
return response([
|
||||
'data' => $users->items(),
|
||||
'total' => $users->total()
|
||||
]);
|
||||
return $this->paginate($users);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user