chore(release): v1.0.7
- README/CHANGELOG: add v1.0.7 entry\n- Server: JWT expiry validated server-side (401 codes)\n- Web: logout/redirect on 401; proxy forwards Authorization\n- Server: bill service uses repository consistently
This commit is contained in:
@@ -1,6 +1,32 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { auth } from '$lib/stores/auth';
|
||||
|
||||
// API 配置 - 使用相对路径,由 SvelteKit 代理到后端
|
||||
const API_BASE = '';
|
||||
|
||||
async function apiFetch(input: RequestInfo | URL, init: RequestInit = {}) {
|
||||
const headers = new Headers(init.headers);
|
||||
|
||||
if (browser) {
|
||||
const token = auth.getToken();
|
||||
if (token) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(input, { ...init, headers });
|
||||
|
||||
if (browser && response.status === 401) {
|
||||
// 由后端判断 Token 是否过期/无效,这里只负责清理和退登
|
||||
auth.logout();
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// 健康检查
|
||||
export async function checkHealth(): Promise<boolean> {
|
||||
try {
|
||||
@@ -99,7 +125,7 @@ export async function uploadBill(
|
||||
formData.append('month', options.month.toString());
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}/api/upload`, {
|
||||
const response = await apiFetch(`${API_BASE}/api/upload`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
@@ -113,7 +139,7 @@ export async function uploadBill(
|
||||
|
||||
// 获取复核记录
|
||||
export async function getReviewRecords(fileName: string): Promise<ReviewResponse> {
|
||||
const response = await fetch(`${API_BASE}/api/review?file=${encodeURIComponent(fileName)}`);
|
||||
const response = await apiFetch(`${API_BASE}/api/review?file=${encodeURIComponent(fileName)}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
@@ -124,7 +150,7 @@ export async function getReviewRecords(fileName: string): Promise<ReviewResponse
|
||||
|
||||
// 获取月度统计(全部数据,不受筛选条件影响)
|
||||
export async function fetchMonthlyStats(): Promise<MonthlyStatsResponse> {
|
||||
const response = await fetch(`${API_BASE}/api/monthly-stats`);
|
||||
const response = await apiFetch(`${API_BASE}/api/monthly-stats`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
@@ -140,7 +166,7 @@ export function getDownloadUrl(fileUrl: string): string {
|
||||
|
||||
// 解析账单内容(用于前端展示全部记录)
|
||||
export async function fetchBillContent(fileName: string): Promise<BillRecord[]> {
|
||||
const response = await fetch(`${API_BASE}/download/${fileName}`);
|
||||
const response = await apiFetch(`${API_BASE}/download/${fileName}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
@@ -264,7 +290,7 @@ export async function fetchBills(params: FetchBillsParams = {}): Promise<BillsRe
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${API_BASE}/api/bills${queryString ? '?' + queryString : ''}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
const response = await apiFetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
@@ -304,7 +330,7 @@ export interface CreateManualBillsResponse {
|
||||
|
||||
// 批量创建手动账单
|
||||
export async function createManualBills(bills: ManualBillInput[]): Promise<CreateManualBillsResponse> {
|
||||
const response = await fetch(`${API_BASE}/api/bills/manual`, {
|
||||
const response = await apiFetch(`${API_BASE}/api/bills/manual`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -321,7 +347,7 @@ export async function createManualBills(bills: ManualBillInput[]): Promise<Creat
|
||||
|
||||
// 获取待复核数据统计
|
||||
export async function fetchReviewStats(): Promise<ReviewResponse> {
|
||||
const response = await fetch(`${API_BASE}/api/review-stats`);
|
||||
const response = await apiFetch(`${API_BASE}/api/review-stats`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
@@ -332,7 +358,7 @@ export async function fetchReviewStats(): Promise<ReviewResponse> {
|
||||
|
||||
// 获取所有待复核的账单(完整数据)
|
||||
export async function fetchBillsByReviewLevel(): Promise<BillsResponse> {
|
||||
const response = await fetch(`${API_BASE}/api/bills?page=1&page_size=1000&review_level=HIGH,LOW`);
|
||||
const response = await apiFetch(`${API_BASE}/api/bills?page=1&page_size=1000&review_level=HIGH,LOW`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
@@ -67,16 +67,28 @@
|
||||
onMount(() => {
|
||||
themeMode = loadThemeFromStorage();
|
||||
applyThemeToDocument(themeMode);
|
||||
|
||||
// 检查登录状态,未登录则跳转到登录页
|
||||
const pathname = $page.url.pathname;
|
||||
if (!auth.check() && pathname !== '/login' && pathname !== '/health') {
|
||||
goto('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查服务器状态
|
||||
checkServerHealth();
|
||||
|
||||
(async () => {
|
||||
// 检查登录状态,未登录则跳转到登录页
|
||||
const pathname = $page.url.pathname;
|
||||
if (!auth.check() && pathname !== '/login' && pathname !== '/health') {
|
||||
goto('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// 由后端判断 Token 是否过期/无效
|
||||
if (auth.check() && pathname !== '/login') {
|
||||
const ok = await auth.validateToken();
|
||||
if (!ok) {
|
||||
goto('/login');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查服务器状态
|
||||
checkServerHealth();
|
||||
})();
|
||||
|
||||
// 每 30 秒检查一次
|
||||
const healthInterval = setInterval(checkServerHealth, 30000);
|
||||
|
||||
|
||||
@@ -4,11 +4,15 @@ import type { RequestHandler } from './$types';
|
||||
// 服务端使用 Docker 内部地址,默认使用 localhost
|
||||
const API_URL = env.API_URL || 'http://localhost:8080';
|
||||
|
||||
export const GET: RequestHandler = async ({ params, url, fetch }) => {
|
||||
export const GET: RequestHandler = async ({ params, url, request, fetch }) => {
|
||||
const path = params.path;
|
||||
const queryString = url.search;
|
||||
|
||||
const response = await fetch(`${API_URL}/api/${path}${queryString}`);
|
||||
const response = await fetch(`${API_URL}/api/${path}${queryString}`, {
|
||||
headers: {
|
||||
'Authorization': request.headers.get('Authorization') || '',
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
@@ -27,6 +31,7 @@ export const POST: RequestHandler = async ({ params, request, fetch }) => {
|
||||
body: await request.arrayBuffer(),
|
||||
headers: {
|
||||
'Content-Type': request.headers.get('Content-Type') || 'application/octet-stream',
|
||||
'Authorization': request.headers.get('Authorization') || '',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user