diff --git a/routes/web.php b/routes/web.php index e609333..a315b24 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,18 +1,6 @@ middleware(['auth', 'verified'])->name('dashboard'); - -Route::get('/sync-all-history', function () { - set_time_limit(600); - - // 🟢 1. 纯粹捞出持仓,不用多余的 with 关联 - $holdings = InvestmentHolding::all(); - $count = 0; - - foreach ($holdings as $holding) { - - // 🟢 2. 直接用持仓的 symbol 去匹配字典表,拿到真正需要的自增主键 id - $security = DB::table('securities')->where('code', $holding->symbol)->first(); - - if (!$security) { - // 如果字典表没有,顺手现场帮它开荒一笔,拿到新 id - $securityId = DB::table('securities')->insertGetId([ - 'code' => $holding->symbol, - 'name' => $holding->name ?? $holding->symbol, - 'type' => $holding->type, - 'source' => $holding->type === 'stock' ? 'twse' : 'anue', - 'latest_price' => floatval($holding->avg_cost ?? 0), - 'created_at' => now(), - 'updated_at' => now(), - ]); - } else { - $securityId = $security->id; - } - - // 找到最早的一笔买入交易作为历史起点 - $firstTx = $holding->transactions()->where('action', 'buy')->orderBy('traded_at', 'asc')->first(); - if (!$firstTx) continue; - - $startDate = Carbon::parse($firstTx->traded_at); - $endDate = Carbon::now(); - - dump("正在同步 [{$holding->type}] {$holding->symbol} - {$holding->name} 的历史资料自 {$startDate->toDateString()} 起..."); - - // ---------------------------------------- - // 股票 (Stock) 同步 - // ---------------------------------------- - if ($holding->type === 'stock') { - $current = $startDate->copy()->startOfMonth(); - - while ($current->lte($endDate)) { - $res = Http::get('https://www.twse.com.tw/rwd/zh/afterTrading/STOCK_DAY', [ - 'stockNo' => $holding->symbol, - 'date' => $current->format('Ymd'), - ]); - - $data = $res->json(); - - if (isset($data['stat']) && $data['stat'] === 'OK' && !empty($data['data'])) { - foreach ($data['data'] as $row) { - [$y, $m, $d] = explode('/', $row[0]); - $dateStr = (1911 + (int)$y) . '-' . $m . '-' . $d; - - if ($dateStr >= $startDate->toDateString()) { - $closePrice = (float) str_replace(',', '', $row[6]); - - // 🟢 成功通过 $securityId 灌入历史表 - DB::table('security_price_histories')->updateOrInsert( - [ - 'security_id' => $securityId, - 'price_date' => $dateStr, - ], - [ - 'price' => $closePrice, - 'created_at' => now(), - 'updated_at' => now(), - ] - ); - $count++; - } - } - } - $current->addMonth(); - usleep(300000); // 防黑名单 - } - } - // ---------------------------------------- - // 基金 (Fund) 同步 - // ---------------------------------------- - elseif ($holding->type === 'fund') { - $res = Http::post("https://www.anuefund.com/anuefundApi/FundDetail/Price", [ - 'priceENUM' => 'NAVHIS', - 'fundID' => $holding->symbol, - 'strDate' => $startDate->format('Y-m-d'), - 'endDate' => $endDate->format('Y-m-d'), - 'chartFomatUse' => true, - 'reload' => false - ]); - - if ($res->ok() && !empty($res->json('data'))) { - $pairs = json_decode($res->json('data'), true); - - foreach ($pairs as $item) { - if ($item[1] === 0) continue; - - $dateStr = date('Y-m-d', intval($item[0]) / 1000); - $navPrice = (float) $item[1]; - - // 🟢 成功通过 $securityId 灌入历史表 - DB::table('security_price_histories')->updateOrInsert( - [ - 'security_id' => $securityId, - 'price_date' => $dateStr, - ], - [ - 'price' => $navPrice, - 'created_at' => now(), - 'updated_at' => now(), - ] - ); - $count++; - } - } - } - dump("▶ [{$holding->symbol}] 同步完成。"); - } - - return "🎉 历史资料回归本地资料库成功!共写录 {$count} 笔历史价格点。"; -}); - - -Route::middleware('auth')->group(function () { - Route::get('/dashboard', [DashboardController::class, 'index']) - ->name('dashboard'); - Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); - Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); - Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); - Route::get('/expenses/import', [InvoiceImportController::class, 'index'])->name('expenses.import'); - Route::post('/expenses/import', [InvoiceImportController::class, 'store'])->name('expenses.import.store'); - Route::delete('/expenses/batch', [ExpenseController::class, 'destroyBatch']) - ->name('expenses.destroyBatch'); - // 注意要放在 resource 路由之前! - Route::resource('expenses', ExpenseController::class) - ->only(['index', 'store', 'update', 'destroy']); - Route::resource('categories', CategoryController::class) - ->only(['index', 'store', 'update', 'destroy']); - Route::resource('category-rules', CategoryRuleController::class) - ->only(['store', 'destroy']); -}); require __DIR__ . '/auth.php';