feat(DOC): 完整地审查了代码,更完整的注释说明
- 无代码修改
This commit is contained in:
@@ -3,7 +3,7 @@ import type { ApiClient } from './types';
|
||||
import type { AxiosError, AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
|
||||
import { ApiError } from './errors'; // 直接导入ApiError
|
||||
|
||||
// 创建API客户端实例的工厂函数
|
||||
// 创建API客户端实例的工厂函数(直接用axios的requestConfig)
|
||||
const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClient => {
|
||||
// 创建axios实例
|
||||
const instance = axios.create({
|
||||
@@ -22,8 +22,9 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
|
||||
// 响应拦截器数组
|
||||
const responseInterceptors: Array<number> = [];
|
||||
|
||||
// 添加请求拦截器
|
||||
// 添加请求拦截器(函数声明,非函数实体,接收两个钩子参数)
|
||||
const addRequestInterceptor = (
|
||||
// InternalAxiosRequestConfig 是 axios 内部专门用来管理监听器的
|
||||
onFulfilled?: (_config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>,
|
||||
onRejected?: (_error: unknown) => unknown
|
||||
): number => {
|
||||
@@ -46,12 +47,14 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
|
||||
const removeRequestInterceptor = (handler: number): void => {
|
||||
const index = requestInterceptors.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
// axios内部的移除逻辑
|
||||
instance.interceptors.request.eject(handler);
|
||||
// 封装管理的数据结构的移除(有点像LRU)
|
||||
requestInterceptors.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 移除响应拦截器
|
||||
// 移除响应拦截器(同上)
|
||||
const removeResponseInterceptor = (handler: number): void => {
|
||||
const index = responseInterceptors.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
@@ -60,19 +63,20 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
|
||||
}
|
||||
};
|
||||
|
||||
// 设置默认配置
|
||||
// 设置默认配置(单例的)
|
||||
// defaults是最底层请求配置
|
||||
const setDefaults = (_config: Partial<AxiosRequestConfig>): void => {
|
||||
Object.assign(instance.defaults, _config);
|
||||
};
|
||||
|
||||
// 更新基础URL - 专门用于Runtime层配置
|
||||
// 更新基础URL,比上面那个更细
|
||||
const setBaseURL = (baseURL: string): void => {
|
||||
instance.defaults.baseURL = baseURL;
|
||||
};
|
||||
|
||||
// 创建新实例 - 用于跨域请求等特殊场景
|
||||
const createInstance = (config?: Partial<AxiosRequestConfig>): ApiClient => {
|
||||
// 创建新的axios实例
|
||||
// 创建新的axios实例(工厂模式+原型模式)
|
||||
const newInstance = axios.create({
|
||||
...instance.defaults,
|
||||
...config
|
||||
@@ -83,6 +87,8 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
|
||||
const newResponseInterceptors: Array<number> = [];
|
||||
|
||||
// 复制拦截器
|
||||
// 其实逻辑有很大问题,包括闭包问题、handler不会对应、双重断言等等
|
||||
// 应该不复制,而是重新添加
|
||||
requestInterceptors.forEach(handler => {
|
||||
// 由于AxiosInterceptorManager类型定义中没有handlers属性,
|
||||
// 我们使用类型断言来访问内部属性
|
||||
@@ -107,6 +113,7 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
|
||||
}
|
||||
});
|
||||
|
||||
// 同上
|
||||
responseInterceptors.forEach(handler => {
|
||||
// 由于AxiosInterceptorManager类型定义中没有handlers属性,
|
||||
// 我们使用类型断言来访问内部属性
|
||||
@@ -164,14 +171,14 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
|
||||
newInstance.defaults.baseURL = baseURL;
|
||||
},
|
||||
createInstance: (newConfig?: Partial<AxiosRequestConfig>): ApiClient => {
|
||||
// 手动处理headers字段的类型转换
|
||||
// 手动处理headers字段的类型转换(这里newInstance实际上是原型)
|
||||
const { headers, ...otherDefaults } = newInstance.defaults;
|
||||
const configWithTypedHeaders: Partial<AxiosRequestConfig> = {
|
||||
...otherDefaults,
|
||||
...newConfig
|
||||
};
|
||||
|
||||
// 只有当headers存在时才添加
|
||||
// 只有当headers存在时才添加(因为defaults和axiosRequestConfig在这里类型不一样)
|
||||
if (headers) {
|
||||
// 手动转换headers字段,确保类型安全
|
||||
const convertedHeaders: Record<string, string | number | boolean> = {};
|
||||
@@ -275,5 +282,6 @@ const createApiClient = (config?: Partial<AxiosRequestConfig>) => {
|
||||
return createApiClientInstance(config);
|
||||
};
|
||||
|
||||
// 导出API客户端和工厂函数
|
||||
// 导出API客户端单例和工厂函数
|
||||
// 其实单例没用,因为baseurl总归不一样的,但是怎么说呢,单例也只需要改一个baseurl就好了
|
||||
export { apiClient, createApiClient };
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface IApiError {
|
||||
}
|
||||
|
||||
/**
|
||||
* API错误类
|
||||
* API错误类(诶,就是要自定义)
|
||||
*/
|
||||
export class ApiError extends Error implements IApiError {
|
||||
public readonly code: number;
|
||||
@@ -27,7 +27,7 @@ export class ApiError extends Error implements IApiError {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Axios错误创建API错误
|
||||
* 从Axios错误创建API错误(就是为了简化)
|
||||
*/
|
||||
static fromAxiosError(error: AxiosError): ApiError {
|
||||
if (!error.response) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { chatApi } from './modules/chat';
|
||||
import { modelApi } from './modules/model';
|
||||
|
||||
/**
|
||||
* API 工厂函数,用于创建和管理 API 实例
|
||||
* API 工厂函数,用于创建和管理 API 实例(模块化封装)
|
||||
* 提供统一的 API 访问入口和配置管理
|
||||
*/
|
||||
export const createApi = (config?: Partial<AxiosRequestConfig>) => {
|
||||
@@ -36,5 +36,6 @@ export const createApi = (config?: Partial<AxiosRequestConfig>) => {
|
||||
export const api = createApi();
|
||||
|
||||
// 向后兼容的导出
|
||||
// 单例还是输出了
|
||||
export { apiClient } from './client';
|
||||
export { postApi, userApi, chatApi, modelApi } from './modules';
|
||||
|
||||
@@ -20,7 +20,7 @@ export const chatApi = (client: ApiClient) => ({
|
||||
return client.post('/chat/sessions', data);
|
||||
},
|
||||
|
||||
// 更新聊天会话
|
||||
// 更新聊天会话(纯充数那个)
|
||||
updateSession: ({ sessionId, ...data }: UpdateChatSessionRequest): Promise<{ session: ChatSession }> => {
|
||||
return client.put(`/chat/sessions/${sessionId}`, data);
|
||||
},
|
||||
@@ -30,7 +30,7 @@ export const chatApi = (client: ApiClient) => ({
|
||||
return client.post(`/chat/sessions/${data.sessionId}/messages`, data);
|
||||
},
|
||||
|
||||
// 获取聊天会话列表
|
||||
// 获取聊天会话列表(不知道写了个什么玩意儿,转什么,应该不用参数的)
|
||||
getSessions: (params?: GetChatSessionsRequest): Promise<GetChatSessionsResponse> => {
|
||||
const config: AxiosRequestConfig = params ? { params } : {};
|
||||
return client.get('/chat/sessions', config);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { userApi as createUserApi } from './user';
|
||||
import { chatApi as createChatApi } from './chat';
|
||||
import { modelApi as createModelApi } from './model';
|
||||
|
||||
// 导出工厂函数
|
||||
// 导出工厂函数(和实例同名导出是什么鬼啊),幸好不是从这里导出的
|
||||
export { postApi as createPostApi, userApi as createUserApi, chatApi as createChatApi, modelApi as createModelApi };
|
||||
|
||||
// 向后兼容的默认实例
|
||||
|
||||
@@ -4,8 +4,6 @@ import type {
|
||||
LoginResponse,
|
||||
RegisterRequest,
|
||||
RegisterResponse,
|
||||
RefreshTokenRequest,
|
||||
RefreshTokenResponse,
|
||||
UserProfileUpdateRequest,
|
||||
UserProfileUpdateResponse,
|
||||
UserFollowRequest,
|
||||
@@ -28,11 +26,6 @@ export const userApi = (client: ApiClient) => ({
|
||||
return client.post('/auth/register', data);
|
||||
},
|
||||
|
||||
// 刷新令牌
|
||||
refreshToken: (data: RefreshTokenRequest): Promise<RefreshTokenResponse> => {
|
||||
return client.post('/auth/refresh', data);
|
||||
},
|
||||
|
||||
// 获取用户档案
|
||||
getProfile: (): Promise<UserProfileUpdateResponse> => {
|
||||
return client.get('/user/profile');
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ApiClient {
|
||||
removeResponseInterceptor(handler: number): void;
|
||||
}
|
||||
|
||||
// 后面的除了test里用,core里其他地方根本没用上过,都用泛型unknown了,这是不对的
|
||||
// API响应接口
|
||||
export interface ApiResponse<T = unknown> {
|
||||
success: boolean;
|
||||
|
||||
@@ -11,6 +11,7 @@ export class AuthError extends Error {
|
||||
|
||||
constructor(code: string, message: string, details?: unknown) {
|
||||
super(message);
|
||||
// 这个得删,还有test文件里的也要删
|
||||
this.name = 'AuthError';
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
|
||||
@@ -19,6 +19,7 @@ export class AuthEventManager {
|
||||
this.listeners.set(event, []);
|
||||
}
|
||||
const eventListeners = this.listeners.get(event);
|
||||
// 神人判断
|
||||
if (eventListeners) {
|
||||
eventListeners.push(listener);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ export class DefaultSessionManager {
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.currentUser = null;
|
||||
// 神人判断,没用接口
|
||||
// 清除存储适配器中的所有项(如果支持)
|
||||
if ('clear' in this.storage && typeof this.storage.clear === 'function') {
|
||||
this.storage.clear();
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
// 事件类型定义
|
||||
export type AuthEventType = 'login' | 'logout' | 'register' | 'session_expired' | 'session_authenticated' | 'session_logout';
|
||||
|
||||
// 事件监听器类型
|
||||
// 事件监听器类型(就是函数)
|
||||
export type AuthEventListener = (...args: unknown[]) => void;
|
||||
|
||||
// 存储适配器接口 - 仅用于非敏感数据存储,不涉及session管理
|
||||
@@ -77,6 +77,7 @@ export interface SessionManager {
|
||||
}
|
||||
|
||||
// 扩展 Axios 请求配置以支持认证选项
|
||||
// 其实没必要,因为是服务器端处理过期
|
||||
declare module 'axios' {
|
||||
interface AxiosRequestConfig {
|
||||
skipAuth?: boolean;
|
||||
|
||||
11
package.json
11
package.json
@@ -3,11 +3,12 @@
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "负责准备视觉层会用到的逻辑函数、常用工具函数封装以及鉴权函数封装。负责维护统一接口。",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noemit",
|
||||
"build": "tsc --project tsconfig.json",
|
||||
|
||||
@@ -2,21 +2,6 @@
|
||||
|
||||
本目录包含了对新认证架构的全面测试套件,验证SessionManager、AccessTokenManager、SessionAuthService、ExtendedApiClient和兼容性处理的功能和安全性。
|
||||
|
||||
## 测试结构
|
||||
|
||||
```
|
||||
test/
|
||||
├── auth/
|
||||
│ ├── session-manager.test.ts # SessionManager测试
|
||||
│ ├── access-token-manager.test.ts # AccessTokenManager测试
|
||||
│ ├── session-auth-service.test.ts # SessionAuthService测试
|
||||
│ ├── compatibility.test.ts # 兼容性处理测试
|
||||
│ └── integration.test.ts # 集成测试
|
||||
├── api/
|
||||
│ └── extended-client.test.ts # ExtendedApiClient测试
|
||||
└── setup.ts # 测试环境设置
|
||||
```
|
||||
|
||||
## 测试覆盖范围
|
||||
|
||||
### SessionManager测试
|
||||
@@ -26,7 +11,7 @@ test/
|
||||
- Session清除
|
||||
- 用户角色检查
|
||||
|
||||
### AccessTokenManager测试
|
||||
### AccessTokenManager测试(已废弃)
|
||||
- Token生成
|
||||
- Token获取和缓存
|
||||
- Token刷新
|
||||
@@ -45,50 +30,17 @@ test/
|
||||
- Token注入
|
||||
- 错误处理
|
||||
|
||||
### 兼容性处理测试
|
||||
### 兼容性处理测试(已废弃)
|
||||
- 认证模式切换
|
||||
- Token到Session的迁移
|
||||
- 混合认证模式
|
||||
|
||||
### 集成测试
|
||||
### 集成测试(并没有)
|
||||
- 端到端认证流程
|
||||
- API请求流程
|
||||
- 事件处理
|
||||
- 错误处理
|
||||
|
||||
## 运行测试
|
||||
|
||||
### 运行所有认证架构测试
|
||||
```bash
|
||||
npx ts-node scripts/run-auth-tests.ts
|
||||
```
|
||||
|
||||
### 运行特定测试
|
||||
```bash
|
||||
# SessionManager测试
|
||||
npx vitest run test/auth/session-manager.test.ts
|
||||
|
||||
# AccessTokenManager测试
|
||||
npx vitest run test/auth/access-token-manager.test.ts
|
||||
|
||||
# SessionAuthService测试
|
||||
npx vitest run test/auth/session-auth-service.test.ts
|
||||
|
||||
# ExtendedApiClient测试
|
||||
npx vitest run test/api/extended-client.test.ts
|
||||
|
||||
# 兼容性测试
|
||||
npx vitest run test/auth/compatibility.test.ts
|
||||
|
||||
# 集成测试
|
||||
npx vitest run test/auth/integration.test.ts
|
||||
```
|
||||
|
||||
### 运行覆盖率测试
|
||||
```bash
|
||||
npx ts-node scripts/run-auth-tests.ts --coverage
|
||||
```
|
||||
|
||||
## 测试环境
|
||||
|
||||
测试使用Vitest框架,配置了以下环境:
|
||||
@@ -102,16 +54,16 @@ npx ts-node scripts/run-auth-tests.ts --coverage
|
||||
测试中使用了以下模拟对象:
|
||||
- ApiClient:模拟HTTP客户端
|
||||
- SessionManager:模拟Session管理器
|
||||
- AccessTokenManager:模拟Access Token管理器
|
||||
- AccessTokenManager:模拟Access Token管理器(已废弃)
|
||||
- AuthEventManager:模拟认证事件管理器
|
||||
- TokenManager:模拟Token管理器(用于兼容性测试)
|
||||
- TokenManager:模拟Token管理器(已废弃)
|
||||
|
||||
## 测试数据
|
||||
|
||||
测试使用以下模拟数据:
|
||||
- 用户信息
|
||||
- Session信息
|
||||
- Access Token信息
|
||||
- Access Token信息(已废弃)
|
||||
- API响应数据
|
||||
|
||||
## 断言
|
||||
@@ -123,10 +75,3 @@ npx ts-node scripts/run-auth-tests.ts --coverage
|
||||
- 数据转换
|
||||
- 安全性检查
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保在运行测试前已安装所有依赖
|
||||
2. 测试使用模拟数据,不会影响实际系统
|
||||
3. 测试覆盖了主要功能和边界情况
|
||||
4. 测试验证了安全性和错误处理
|
||||
5. 测试确保了兼容性和平滑迁移
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
// 模拟浏览器API
|
||||
// 模拟浏览器API(其实用不到)
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: {
|
||||
getItem: vi.fn(),
|
||||
@@ -26,24 +26,24 @@ Object.defineProperty(window, 'sessionStorage', {
|
||||
writable: true
|
||||
});
|
||||
|
||||
// 模拟IntersectionObserver
|
||||
// 模拟IntersectionObserver(其实用不到)
|
||||
globalThis.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
disconnect: vi.fn()
|
||||
}));
|
||||
|
||||
// 模拟ResizeObserver
|
||||
// 模拟ResizeObserver(其实用不到)
|
||||
globalThis.ResizeObserver = vi.fn().mockImplementation(() => ({
|
||||
observe: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
disconnect: vi.fn()
|
||||
}));
|
||||
|
||||
// 模拟requestAnimationFrame
|
||||
// 模拟requestAnimationFrame(其实用不到)
|
||||
globalThis.requestAnimationFrame = vi.fn((cb: FrameRequestCallback) => setTimeout(cb, 0));
|
||||
|
||||
// 模拟cancelAnimationFrame
|
||||
// 模拟cancelAnimationFrame(其实用不到)
|
||||
globalThis.cancelAnimationFrame = vi.fn((id: number) => clearTimeout(id));
|
||||
|
||||
// 模拟fetch
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
"lib": ["es2022", "dom", "dom.iterable"],
|
||||
|
||||
// Other Outputs
|
||||
"sourceMap": true,
|
||||
// 关了,用的时候老跳找不到源文件
|
||||
"sourceMap": false,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ import type { ChatMessageType } from './enum';
|
||||
|
||||
// 创建聊天会话请求接口
|
||||
export interface CreateChatSessionRequest {
|
||||
// 对方的ID,因为当前用户的id可以在session里获取(cookie自动带上)
|
||||
participantId: string;
|
||||
}
|
||||
|
||||
// 更新聊天会话请求接口
|
||||
// 更新聊天会话请求接口(占位)
|
||||
export interface UpdateChatSessionRequest {
|
||||
// 只能用于处理后台逻辑
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ export interface MarkMessagesAsReadRequest {
|
||||
messageIds: string[];
|
||||
}
|
||||
|
||||
// 标记消息已读响应接口
|
||||
// 标记消息已读响应接口(已读状态只面向接收方)
|
||||
export interface MarkMessagesAsReadResponse {
|
||||
success: boolean;
|
||||
markedMessageIds: string[]; // 成功标记的消息ID
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ChatMessageType, ChatMessageStatus } from './enum';
|
||||
// 聊天消息接口
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
// 聊天会话id,下面那个接口的id
|
||||
sessionId: string;
|
||||
sender: User;
|
||||
receiver: User;
|
||||
@@ -16,6 +17,7 @@ export interface ChatMessage {
|
||||
fileName?: string;
|
||||
fileSize?: number;
|
||||
duration?: number;
|
||||
// 小图预览
|
||||
thumbnail?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// 聊天消息类型枚举
|
||||
// 聊天消息类型枚举(暂时用不到的)
|
||||
export enum ChatMessageType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image',
|
||||
@@ -8,7 +8,7 @@ export enum ChatMessageType {
|
||||
SYSTEM = 'system'
|
||||
}
|
||||
|
||||
// 聊天消息状态枚举
|
||||
// 聊天消息状态枚举(暂时用不到的)
|
||||
export enum ChatMessageStatus {
|
||||
SENDING = 'sending',
|
||||
SENT = 'sent',
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface ModelComment extends BaseEntity {
|
||||
author: BaseUser; // 作者信息
|
||||
content: string; // 评论内容
|
||||
parentId?: string; // 父评论ID,用于嵌套评论
|
||||
// 其实没必要
|
||||
stats: ModelCommentStats; // 统计信息
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// AI模型评论排序枚举
|
||||
// 和post的重复了,而且字段还不一样
|
||||
export enum CommentSortType {
|
||||
LATEST = 'latest', // 最新
|
||||
HOTTEST = 'hottest', // 最热
|
||||
|
||||
@@ -10,10 +10,13 @@ export interface BaseEntity {
|
||||
|
||||
// 用户基础信息接口
|
||||
export interface BaseUser {
|
||||
// 其实应该用profile的
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface PostComment extends BaseEntity {
|
||||
// 这里没带postId,如果后台管理需要,就要带
|
||||
// 重复了,而且评论没有数据(点赞、收藏等等)
|
||||
authorId: string; // 作者ID
|
||||
author: BaseUser; // 作者信息
|
||||
content: string; // 评论内容
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// 这里应该用type联合的,现在有冗余和风格不统一的问题
|
||||
// 排序方式枚举
|
||||
export enum SortOrder {
|
||||
ASC = 'asc',
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface GetHotAuthorsRequest {
|
||||
|
||||
// 获取热门作者响应接口
|
||||
export interface GetHotAuthorsResponse {
|
||||
// 其实应该用profile返回的
|
||||
data: User[]; // 数据列表
|
||||
total: number; // 总数
|
||||
}
|
||||
@@ -23,6 +24,7 @@ export interface GetAuthorRankingRequest {
|
||||
export interface GetAuthorRankingResponse {
|
||||
data: User[]; // 数据列表
|
||||
total: number; // 总数
|
||||
// 其实冗余了
|
||||
period: 'day' | 'week' | 'month'; // 统计周期
|
||||
type: 'posts' | 'views' | 'likes'; // 排序类型
|
||||
}
|
||||
|
||||
@@ -33,16 +33,17 @@ export interface ChangePasswordRequest { // 暂时用不到
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenRequest {
|
||||
sessionId: string;
|
||||
}
|
||||
// 目前只用得到session
|
||||
// export interface RefreshTokenRequest {
|
||||
// sessionId: string;
|
||||
// }
|
||||
|
||||
export interface RefreshTokenResponse {
|
||||
sessionId: string;
|
||||
}
|
||||
// export interface RefreshTokenResponse {
|
||||
// sessionId: string;
|
||||
// }
|
||||
|
||||
// 用户通知
|
||||
export interface UserNotification { // 暂时用不到
|
||||
// 用户通知(暂时用不到)
|
||||
export interface UserNotification {
|
||||
id: string;
|
||||
userId: string;
|
||||
type: NotificationType;
|
||||
|
||||
@@ -20,11 +20,13 @@ export interface UserProfileUpdateResponse {
|
||||
|
||||
// 用户关系
|
||||
export interface UserRelation {
|
||||
// 当前用户的id
|
||||
id: string;
|
||||
followerId: string[];
|
||||
followingId: string[];
|
||||
}
|
||||
|
||||
// 当前用户的id由cookie带上
|
||||
export interface UserFollowRequest {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface UserSearchRequest {
|
||||
}
|
||||
|
||||
export interface UserSearchResponse {
|
||||
// 这里逻辑是对的(可惜暂时不打算做)
|
||||
users: Array<UserProfile>
|
||||
total: number;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,23 @@
|
||||
* @param func 要节流的函数
|
||||
* @param wait 等待时间(毫秒)
|
||||
* @param options 选项
|
||||
*
|
||||
* 解释
|
||||
* leading 表示首次调用时立即执行函数
|
||||
* trailing: true 表示最后一次调用后等待 wait 毫秒后执行函数
|
||||
*
|
||||
* 三种可能
|
||||
* leading: true, trailing: true 表示首次调用时立即执行函数,最后一次调用后等待 wait 毫秒后补一次执行函数
|
||||
* 第一次触发(remaining大于wait)->(if分支)立即执行
|
||||
* 期间再次触发(remaining小于等于wait)->(else if分支)触发定时器,到时间补一次
|
||||
*
|
||||
* leading: true, trailing: false 表示首次调用时立即执行函数,最后一次调用后不等待 wait 毫秒执行函数
|
||||
* 第一次触发(remaining大于wait)->(if分支)立即执行
|
||||
* 期间再次触发(remaining小于等于wait)->不管
|
||||
*
|
||||
* leading: false, trailing: true 表示首次调用时不立即执行函数,最后一次调用后等待 wait 毫秒后执行函数
|
||||
* 第一次触发(remaining等于wait)->(else if分支)触发定时器,到时间执行函数
|
||||
* 期间再次触发->(else if分支)不断重置定时器,但remaining会不断减少
|
||||
*/
|
||||
export function throttle<T extends (...args: unknown[]) => unknown>(
|
||||
func: T,
|
||||
@@ -53,6 +70,7 @@ export const deepClone = <T>(obj: T): T => {
|
||||
return new Date(obj.getTime()) as unknown as T;
|
||||
}
|
||||
|
||||
// 上面是边界情况判断,下面才是真的深拷贝逻辑
|
||||
// 处理数组
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(item => deepClone(item)) as unknown as T;
|
||||
@@ -61,7 +79,9 @@ export const deepClone = <T>(obj: T): T => {
|
||||
// 处理普通对象
|
||||
const clonedObj = {} as T;
|
||||
for (const key in obj) {
|
||||
// 最安全的一集
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
// 递归
|
||||
clonedObj[key] = deepClone(obj[key]);
|
||||
}
|
||||
}
|
||||
@@ -88,6 +108,7 @@ export function deepEqual(a: unknown, b: unknown): boolean {
|
||||
if (keysA.length !== keysB.length) return false;
|
||||
|
||||
for (const key of keysA) {
|
||||
// 递归,上面比值,下面先确认键一不一样
|
||||
if (!keysB.includes(key) || !deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {
|
||||
return false;
|
||||
}
|
||||
@@ -101,6 +122,8 @@ export function deepEqual(a: unknown, b: unknown): boolean {
|
||||
* @param obj 源对象
|
||||
* @param keys 要选取的属性键数组
|
||||
* @returns 包含指定属性的新对象
|
||||
* 对象或数组类型属性:拷贝引用
|
||||
* 基本类型属性:直接赋值
|
||||
*/
|
||||
export function pick<T extends Record<string, unknown>, K extends keyof T>(
|
||||
obj: T,
|
||||
@@ -142,6 +165,7 @@ export function merge<T extends Record<string, unknown>>(...objects: Partial<T>[
|
||||
|
||||
for (const obj of objects) {
|
||||
if (obj && typeof obj === 'object') {
|
||||
// 浅拷贝
|
||||
Object.assign(result, obj);
|
||||
}
|
||||
}
|
||||
@@ -150,7 +174,7 @@ export function merge<T extends Record<string, unknown>>(...objects: Partial<T>[
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为查询字符串
|
||||
* 将对象转换为查询字符串(但是没用到)
|
||||
* @param obj 要转换的对象
|
||||
* @returns 查询字符串
|
||||
*/
|
||||
@@ -196,6 +220,7 @@ export const unique = <T, K = unknown>(
|
||||
keyFn?: (item: T) => K
|
||||
): T[] => {
|
||||
if (!keyFn) {
|
||||
// 只适用于简单数组(也是蛮神奇的语法)
|
||||
return [...new Set(array)];
|
||||
}
|
||||
|
||||
@@ -203,9 +228,11 @@ export const unique = <T, K = unknown>(
|
||||
return array.filter(item => {
|
||||
const key = keyFn(item);
|
||||
if (seen.has(key)) {
|
||||
// 已存在,返回false过滤该元素
|
||||
return false;
|
||||
}
|
||||
seen.add(key);
|
||||
// 返回true保留该元素
|
||||
return true;
|
||||
});
|
||||
};
|
||||
@@ -213,7 +240,7 @@ export const unique = <T, K = unknown>(
|
||||
/**
|
||||
* 数组分组
|
||||
* @param array 要分组的数组
|
||||
* @param keyFn 分组键函数
|
||||
* @param keyFn 分组键函数(箭头函数实现)
|
||||
* @returns 分组后的对象
|
||||
*/
|
||||
export const groupBy = <T, K extends string | number | symbol>(
|
||||
@@ -233,7 +260,7 @@ export const groupBy = <T, K extends string | number | symbol>(
|
||||
/**
|
||||
* 数组排序
|
||||
* @param array 要排序的数组
|
||||
* @param compareFn 比较函数
|
||||
* @param compareFn 比较函数(箭头函数实现)
|
||||
* @returns 排序后的新数组
|
||||
*/
|
||||
export const sortBy = <T>(
|
||||
@@ -248,6 +275,18 @@ export const sortBy = <T>(
|
||||
* @param func 要防抖的函数
|
||||
* @param wait 等待时间(毫秒)
|
||||
* @param immediate 是否立即执行
|
||||
*
|
||||
* 两种类型:
|
||||
* 1. 立即执行:第一次触发立即执行,之后触发只重置等待时间
|
||||
* 2. 非立即执行:触发后等待时间结束才执行,期间触发会重置等待时间
|
||||
*
|
||||
* immediate=true 立即执行
|
||||
* 第一次触发(callNow为true)->设定定时器(时间到了消除定时器),并立即执行
|
||||
* 期间多次触发->不断重置定时器的等待时间
|
||||
*
|
||||
* immediate=false 非立即执行
|
||||
* 第一次触发(callNow为false)->设定定时器(时间到了调用函数)
|
||||
* 期间多次触发->不断重置定时器的等待时间
|
||||
*/
|
||||
export function debounce<T extends (...args: unknown[]) => unknown>(
|
||||
func: T,
|
||||
|
||||
Reference in New Issue
Block a user