feat(auth): 重构登录流程并增强错误处理

- 改进登录表单验证逻辑,增加非空和类型检查
- 更新认证 Cookie 名称为 Authorization 并调整安全设置
- 登录成功后返回重定向路径和成功消息
- 增强错误处理,区分网络错误、API 错误和未知错误
- 引入 ResponseError 类统一处理 API 响应错误
- 表单提交使用 enhance 函数优化用户体验
- 登录成功时显示 Toast 提示并自动跳转
- 移除独立的用户信息服务,整合至认证服务
- 删除旧的角色检查工具函数
- 新增错误处理工具函数 handleError
This commit is contained in:
chaos
2025-11-24 21:39:11 +08:00
parent f9d92e6cc9
commit 877a47807c
8 changed files with 136 additions and 64 deletions

View File

@@ -1,8 +1,8 @@
import { api } from '$lib/api/httpClient'; // 通常不需要 .ts 后缀
import type { AuthResponse, LoginPayload } from '$lib/types/auth';
import { authStore } from '$lib/stores/authStore';
import { userService } from '$lib/api/services/userService';
import { toast } from '$lib/stores/toastStore';
import { ResponseError } from '$lib/types/error.ts';
export const authService = {
/**
@@ -13,33 +13,16 @@ export const authService = {
const response = await api.post<AuthResponse>('/auth/login', payload);
if (response.code !== 200 || !response.data) {
throw new Error(response.msg || '登录失败');
throw new ResponseError(response);
}
const { token, tokenHead } = response.data;
const { token, tokenHead,userProfile } = response.data;
authStore.update(s => ({ ...s, token, tokenHead, isAuthenticated: true }));
authStore.update(s => ({ ...s, token, tokenHead, isAuthenticated: true,user: userProfile }));
try {
// 3. 获取用户信息
const userProfile = await userService.getUserProfile({tokenHead,token});
// 4. 最终确认登录状态(更新完整信息并持久化)
// 这里调用 Store 封装好的 login 方法,它会负责写入 localStorage
authStore.login({
token,
tokenHead,
user: userProfile
});
return response.data;
return response.data;
} catch (error) {
console.error('获取用户信息失败,回滚登录状态', error);
authStore.logout();
throw error; // 继续抛出错误给 UI 层处理
}
},
/**

View File

@@ -1,5 +1,18 @@
import type { ActionResult } from '@sveltejs/kit';
export interface ApiResult<T> {
code: number,
msg: string,
data: T | null;
}
export type EnhanceResult<T> = ActionResult & {
status: number;
type: "failure" | "success" | "redirect" | "error";
data: T;
}
export interface LoginFailure {
message: string;
username: string;
}

View File

@@ -1,4 +1,6 @@
import type { JsonObject } from '$lib/types/http.ts';
import type { UserProfile } from '$lib/types/user.ts';
export interface LoginPayload extends JsonObject {
username: string;
@@ -8,4 +10,6 @@ export interface LoginPayload extends JsonObject {
export interface AuthResponse {
token: string;
tokenHead: string;
}
userProfile: UserProfile;
}

View File

@@ -1,3 +1,5 @@
import type { ApiResult } from '$lib/types/api.ts';
export interface ApiError {
status: number;
details: {
@@ -5,4 +7,16 @@ export interface ApiError {
msg: string;
};
name: string;
}
}
export class ResponseError extends Error {
code: number;
msg: string;
constructor(response: ApiResult<unknown>) {
super(response.msg);
this.code = response.code;
this.msg = response.msg;
}
}

View File

@@ -1,11 +0,0 @@
import {user} from '$lib/stores/userStore';
import {get} from 'svelte/store';
export const hasRole = (role: string[]) => {
const userProfile = get(user);
if (!userProfile){
return false;
}
return role.some(r => userProfile.roles.includes(r))
};

View File

@@ -0,0 +1,14 @@
import { HttpError } from '$lib/api/httpClient.ts';
import { fail } from '@sveltejs/kit';
export const handleError = (error: Error) => {
if (error instanceof HttpError) {
return fail(error.s, {
message: error.details?.msg || '认证失败,请检查凭证'
});
if (error instanceof TypeError || (error instanceof Error && error.message.includes('fetch'))) {
return { message: '无法连接服务器,请检查网络' };
}
};