fix: 处理营销账户余额导出冷启动 (#41)

首次导出未生成共享快照时回到当前页面并显示脱敏中文提示,同时覆盖冷快照和未知异常回归测试。
This commit is contained in:
WuYongJie 2026-08-13 12:21:38 +08:00
parent 5511dfb221
commit cf66c30162
3 changed files with 93 additions and 5 deletions

View File

@ -338,11 +338,29 @@ class Pages extends Controller
? app(TmallTaobaoSpreadBillRepository::class)
: app(PinduoduoSpreadBillRepository::class);
$scope = $isTaobao ? 'taobao-alipay-bills.list' : 'pinduoduo-bills.list';
$result = $this->upstreamCaches->forSource('jiansun')->remember(
$scope,
['view' => 'monthly-balances', 'filters' => $filters, 'page' => 1, 'limit' => 5000, 'export' => true],
fn () => $repository->paginateMonthlyBalances($filters['store_name'], $filters['month'], 1, 5000),
);
$redirectParameters = array_filter([
'section' => $section,
'page' => $page,
'tab' => $tab,
'store_name' => $storeName,
'month' => $month,
], static fn (mixed $value): bool => $value !== '');
try {
$result = $this->upstreamCaches->forSource('jiansun')->remember(
$scope,
['view' => 'monthly-balances', 'filters' => $filters, 'page' => 1, 'limit' => 5000, 'export' => true],
fn () => $repository->paginateMonthlyBalances($filters['store_name'], $filters['month'], 1, 5000),
);
} catch (ServiceUnavailableHttpException) {
return redirect()->route('business.pages.show', $redirectParameters)
->with('error', '首份共享快照正在准备,系统已登记刷新请求;请稍后刷新页面后再导出。');
} catch (Throwable $exception) {
report($exception);
return redirect()->route('business.pages.show', $redirectParameters)
->with('error', '余额数据导出暂时无法完成,请稍后重试。');
}
$rows = $result['rows'] ?? [];
return response()->streamDownload(function () use ($rows): void {

View File

@ -411,6 +411,10 @@
<div class="biz-alert" role="status">{{ session('success') }}</div>
@endif
@if (is_string(session('error')))
<div class="biz-alert biz-alert-error" role="alert">{{ session('error') }}</div>
@endif
@if ($errors->has('import'))
<div class="biz-alert biz-alert-error" role="alert">{{ $errors->first('import') }}</div>
@endif

View File

@ -6,6 +6,7 @@ use App\Models\Auth\Permission;
use Mockery;
use Modules\EcommerceFinance\Repositories\JdWalletBillRepository;
use Modules\EcommerceFinance\Repositories\PinduoduoSpreadBillRepository;
use Modules\EcommerceFinance\Repositories\TmallTaobaoSpreadBillRepository;
use Modules\EcommerceFinance\Services\UpstreamQueryCache;
use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
@ -156,6 +157,71 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase
], $scopes);
}
public function testMarketingAccountBalanceExportColdStartReturnsActionableRedirect(): void
{
$this->allowMarketingAccountBalancePage();
$cache = Mockery::mock(UpstreamQueryCache::class);
$cache->shouldReceive('remember')
->once()
->withArgs(function (string $scope, array $parameters, callable $resolver): bool {
return $scope === 'taobao-alipay-bills.list'
&& ($parameters['export'] ?? false) === true;
})
->andThrow(new ServiceUnavailableHttpException(30, '首份共享快照正在准备。'));
$factory = Mockery::mock(UpstreamQueryCacheFactory::class);
$factory->shouldReceive('forSource')
->once()
->with('jiansun')
->andReturn($cache);
$this->app->instance(UpstreamQueryCacheFactory::class, $factory);
$this->app->instance(TmallTaobaoSpreadBillRepository::class, Mockery::mock(TmallTaobaoSpreadBillRepository::class));
$this->loginAs()
->get(route('business.pages.export', [
'section' => 'billing-transactions',
'page' => 'marketing-account-balance',
'tab' => 'taobao-alipay',
'store_name' => '测试店铺',
'month' => '2026-08',
]))
->assertRedirect(route('business.pages.show', [
'section' => 'billing-transactions',
'page' => 'marketing-account-balance',
'tab' => 'taobao-alipay',
'store_name' => '测试店铺',
'month' => '2026-08',
]))
->assertSessionHas('error', '首份共享快照正在准备,系统已登记刷新请求;请稍后刷新页面后再导出。');
}
public function testMarketingAccountBalanceExportUnexpectedFailureReturnsRedactedRedirect(): void
{
$this->allowMarketingAccountBalancePage();
$cache = Mockery::mock(UpstreamQueryCache::class);
$cache->shouldReceive('remember')->once()->andThrow(new \RuntimeException('sensitive DSN details'));
$factory = Mockery::mock(UpstreamQueryCacheFactory::class);
$factory->shouldReceive('forSource')->once()->with('jiansun')->andReturn($cache);
$this->app->instance(UpstreamQueryCacheFactory::class, $factory);
$this->app->instance(TmallTaobaoSpreadBillRepository::class, Mockery::mock(TmallTaobaoSpreadBillRepository::class));
$this->loginAs()
->get(route('business.pages.export', [
'section' => 'billing-transactions',
'page' => 'marketing-account-balance',
'tab' => 'taobao-alipay',
]))
->assertRedirect(route('business.pages.show', [
'section' => 'billing-transactions',
'page' => 'marketing-account-balance',
'tab' => 'taobao-alipay',
]))
->assertSessionHas('error', '余额数据导出暂时无法完成,请稍后重试。');
}
private function allowMarketingAccountBillsPage(): void
{
$this->user->update(['locale' => 'zh-CN']);