feat(DOC): 完整地审查了代码,更完整的注释说明

- 无代码修改
This commit is contained in:
tobegold574
2025-11-23 22:26:39 +08:00
parent 4e741a0523
commit c5853847ae
27 changed files with 119 additions and 110 deletions

View File

@@ -3,7 +3,7 @@ import type { ApiClient } from './types';
import type { AxiosError, AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios'; import type { AxiosError, AxiosResponse, AxiosRequestConfig, InternalAxiosRequestConfig } from 'axios';
import { ApiError } from './errors'; // 直接导入ApiError import { ApiError } from './errors'; // 直接导入ApiError
// 创建API客户端实例的工厂函数 // 创建API客户端实例的工厂函数直接用axios的requestConfig
const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClient => { const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClient => {
// 创建axios实例 // 创建axios实例
const instance = axios.create({ const instance = axios.create({
@@ -22,8 +22,9 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
// 响应拦截器数组 // 响应拦截器数组
const responseInterceptors: Array<number> = []; const responseInterceptors: Array<number> = [];
// 添加请求拦截器 // 添加请求拦截器(函数声明,非函数实体,接收两个钩子参数)
const addRequestInterceptor = ( const addRequestInterceptor = (
// InternalAxiosRequestConfig 是 axios 内部专门用来管理监听器的
onFulfilled?: (_config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>, onFulfilled?: (_config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>,
onRejected?: (_error: unknown) => unknown onRejected?: (_error: unknown) => unknown
): number => { ): number => {
@@ -46,12 +47,14 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
const removeRequestInterceptor = (handler: number): void => { const removeRequestInterceptor = (handler: number): void => {
const index = requestInterceptors.indexOf(handler); const index = requestInterceptors.indexOf(handler);
if (index !== -1) { if (index !== -1) {
// axios内部的移除逻辑
instance.interceptors.request.eject(handler); instance.interceptors.request.eject(handler);
// 封装管理的数据结构的移除有点像LRU
requestInterceptors.splice(index, 1); requestInterceptors.splice(index, 1);
} }
}; };
// 移除响应拦截器 // 移除响应拦截器(同上)
const removeResponseInterceptor = (handler: number): void => { const removeResponseInterceptor = (handler: number): void => {
const index = responseInterceptors.indexOf(handler); const index = responseInterceptors.indexOf(handler);
if (index !== -1) { if (index !== -1) {
@@ -60,19 +63,20 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
} }
}; };
// 设置默认配置 // 设置默认配置(单例的)
// defaults是最底层请求配置
const setDefaults = (_config: Partial<AxiosRequestConfig>): void => { const setDefaults = (_config: Partial<AxiosRequestConfig>): void => {
Object.assign(instance.defaults, _config); Object.assign(instance.defaults, _config);
}; };
// 更新基础URL - 专门用于Runtime层配置 // 更新基础URL,比上面那个更细
const setBaseURL = (baseURL: string): void => { const setBaseURL = (baseURL: string): void => {
instance.defaults.baseURL = baseURL; instance.defaults.baseURL = baseURL;
}; };
// 创建新实例 - 用于跨域请求等特殊场景 // 创建新实例 - 用于跨域请求等特殊场景
const createInstance = (config?: Partial<AxiosRequestConfig>): ApiClient => { const createInstance = (config?: Partial<AxiosRequestConfig>): ApiClient => {
// 创建新的axios实例 // 创建新的axios实例(工厂模式+原型模式)
const newInstance = axios.create({ const newInstance = axios.create({
...instance.defaults, ...instance.defaults,
...config ...config
@@ -83,6 +87,8 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
const newResponseInterceptors: Array<number> = []; const newResponseInterceptors: Array<number> = [];
// 复制拦截器 // 复制拦截器
// 其实逻辑有很大问题包括闭包问题、handler不会对应、双重断言等等
// 应该不复制,而是重新添加
requestInterceptors.forEach(handler => { requestInterceptors.forEach(handler => {
// 由于AxiosInterceptorManager类型定义中没有handlers属性 // 由于AxiosInterceptorManager类型定义中没有handlers属性
// 我们使用类型断言来访问内部属性 // 我们使用类型断言来访问内部属性
@@ -107,6 +113,7 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
} }
}); });
// 同上
responseInterceptors.forEach(handler => { responseInterceptors.forEach(handler => {
// 由于AxiosInterceptorManager类型定义中没有handlers属性 // 由于AxiosInterceptorManager类型定义中没有handlers属性
// 我们使用类型断言来访问内部属性 // 我们使用类型断言来访问内部属性
@@ -164,14 +171,14 @@ const createApiClientInstance = (config?: Partial<AxiosRequestConfig>): ApiClien
newInstance.defaults.baseURL = baseURL; newInstance.defaults.baseURL = baseURL;
}, },
createInstance: (newConfig?: Partial<AxiosRequestConfig>): ApiClient => { createInstance: (newConfig?: Partial<AxiosRequestConfig>): ApiClient => {
// 手动处理headers字段的类型转换 // 手动处理headers字段的类型转换这里newInstance实际上是原型
const { headers, ...otherDefaults } = newInstance.defaults; const { headers, ...otherDefaults } = newInstance.defaults;
const configWithTypedHeaders: Partial<AxiosRequestConfig> = { const configWithTypedHeaders: Partial<AxiosRequestConfig> = {
...otherDefaults, ...otherDefaults,
...newConfig ...newConfig
}; };
// 只有当headers存在时才添加 // 只有当headers存在时才添加因为defaults和axiosRequestConfig在这里类型不一样
if (headers) { if (headers) {
// 手动转换headers字段确保类型安全 // 手动转换headers字段确保类型安全
const convertedHeaders: Record<string, string | number | boolean> = {}; const convertedHeaders: Record<string, string | number | boolean> = {};
@@ -275,5 +282,6 @@ const createApiClient = (config?: Partial<AxiosRequestConfig>) => {
return createApiClientInstance(config); return createApiClientInstance(config);
}; };
// 导出API客户端和工厂函数 // 导出API客户端单例和工厂函数
// 其实单例没用因为baseurl总归不一样的但是怎么说呢单例也只需要改一个baseurl就好了
export { apiClient, createApiClient }; export { apiClient, createApiClient };

View File

@@ -13,7 +13,7 @@ export interface IApiError {
} }
/** /**
* API错误类 * API错误类(诶,就是要自定义)
*/ */
export class ApiError extends Error implements IApiError { export class ApiError extends Error implements IApiError {
public readonly code: number; public readonly code: number;
@@ -27,7 +27,7 @@ export class ApiError extends Error implements IApiError {
} }
/** /**
* 从Axios错误创建API错误 * 从Axios错误创建API错误(就是为了简化)
*/ */
static fromAxiosError(error: AxiosError): ApiError { static fromAxiosError(error: AxiosError): ApiError {
if (!error.response) { if (!error.response) {

View File

@@ -6,7 +6,7 @@ import { chatApi } from './modules/chat';
import { modelApi } from './modules/model'; import { modelApi } from './modules/model';
/** /**
* API 工厂函数,用于创建和管理 API 实例 * API 工厂函数,用于创建和管理 API 实例(模块化封装)
* 提供统一的 API 访问入口和配置管理 * 提供统一的 API 访问入口和配置管理
*/ */
export const createApi = (config?: Partial<AxiosRequestConfig>) => { export const createApi = (config?: Partial<AxiosRequestConfig>) => {
@@ -36,5 +36,6 @@ export const createApi = (config?: Partial<AxiosRequestConfig>) => {
export const api = createApi(); export const api = createApi();
// 向后兼容的导出 // 向后兼容的导出
// 单例还是输出了
export { apiClient } from './client'; export { apiClient } from './client';
export { postApi, userApi, chatApi, modelApi } from './modules'; export { postApi, userApi, chatApi, modelApi } from './modules';

View File

@@ -20,7 +20,7 @@ export const chatApi = (client: ApiClient) => ({
return client.post('/chat/sessions', data); return client.post('/chat/sessions', data);
}, },
// 更新聊天会话 // 更新聊天会话(纯充数那个)
updateSession: ({ sessionId, ...data }: UpdateChatSessionRequest): Promise<{ session: ChatSession }> => { updateSession: ({ sessionId, ...data }: UpdateChatSessionRequest): Promise<{ session: ChatSession }> => {
return client.put(`/chat/sessions/${sessionId}`, data); 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); return client.post(`/chat/sessions/${data.sessionId}/messages`, data);
}, },
// 获取聊天会话列表 // 获取聊天会话列表(不知道写了个什么玩意儿,转什么,应该不用参数的)
getSessions: (params?: GetChatSessionsRequest): Promise<GetChatSessionsResponse> => { getSessions: (params?: GetChatSessionsRequest): Promise<GetChatSessionsResponse> => {
const config: AxiosRequestConfig = params ? { params } : {}; const config: AxiosRequestConfig = params ? { params } : {};
return client.get('/chat/sessions', config); return client.get('/chat/sessions', config);

View File

@@ -4,7 +4,7 @@ import { userApi as createUserApi } from './user';
import { chatApi as createChatApi } from './chat'; import { chatApi as createChatApi } from './chat';
import { modelApi as createModelApi } from './model'; import { modelApi as createModelApi } from './model';
// 导出工厂函数 // 导出工厂函数(和实例同名导出是什么鬼啊),幸好不是从这里导出的
export { postApi as createPostApi, userApi as createUserApi, chatApi as createChatApi, modelApi as createModelApi }; export { postApi as createPostApi, userApi as createUserApi, chatApi as createChatApi, modelApi as createModelApi };
// 向后兼容的默认实例 // 向后兼容的默认实例

View File

@@ -4,8 +4,6 @@ import type {
LoginResponse, LoginResponse,
RegisterRequest, RegisterRequest,
RegisterResponse, RegisterResponse,
RefreshTokenRequest,
RefreshTokenResponse,
UserProfileUpdateRequest, UserProfileUpdateRequest,
UserProfileUpdateResponse, UserProfileUpdateResponse,
UserFollowRequest, UserFollowRequest,
@@ -28,11 +26,6 @@ export const userApi = (client: ApiClient) => ({
return client.post('/auth/register', data); return client.post('/auth/register', data);
}, },
// 刷新令牌
refreshToken: (data: RefreshTokenRequest): Promise<RefreshTokenResponse> => {
return client.post('/auth/refresh', data);
},
// 获取用户档案 // 获取用户档案
getProfile: (): Promise<UserProfileUpdateResponse> => { getProfile: (): Promise<UserProfileUpdateResponse> => {
return client.get('/user/profile'); return client.get('/user/profile');

View File

@@ -27,6 +27,7 @@ export interface ApiClient {
removeResponseInterceptor(handler: number): void; removeResponseInterceptor(handler: number): void;
} }
// 后面的除了test里用core里其他地方根本没用上过都用泛型unknown了这是不对的
// API响应接口 // API响应接口
export interface ApiResponse<T = unknown> { export interface ApiResponse<T = unknown> {
success: boolean; success: boolean;

View File

@@ -11,6 +11,7 @@ export class AuthError extends Error {
constructor(code: string, message: string, details?: unknown) { constructor(code: string, message: string, details?: unknown) {
super(message); super(message);
// 这个得删还有test文件里的也要删
this.name = 'AuthError'; this.name = 'AuthError';
this.code = code; this.code = code;
this.details = details; this.details = details;

View File

@@ -19,6 +19,7 @@ export class AuthEventManager {
this.listeners.set(event, []); this.listeners.set(event, []);
} }
const eventListeners = this.listeners.get(event); const eventListeners = this.listeners.get(event);
// 神人判断
if (eventListeners) { if (eventListeners) {
eventListeners.push(listener); eventListeners.push(listener);
} }

View File

@@ -64,6 +64,7 @@ export class DefaultSessionManager {
*/ */
clearCache(): void { clearCache(): void {
this.currentUser = null; this.currentUser = null;
// 神人判断,没用接口
// 清除存储适配器中的所有项(如果支持) // 清除存储适配器中的所有项(如果支持)
if ('clear' in this.storage && typeof this.storage.clear === 'function') { if ('clear' in this.storage && typeof this.storage.clear === 'function') {
this.storage.clear(); this.storage.clear();

View File

@@ -13,7 +13,7 @@ import type {
// 事件类型定义 // 事件类型定义
export type AuthEventType = 'login' | 'logout' | 'register' | 'session_expired' | 'session_authenticated' | 'session_logout'; export type AuthEventType = 'login' | 'logout' | 'register' | 'session_expired' | 'session_authenticated' | 'session_logout';
// 事件监听器类型 // 事件监听器类型(就是函数)
export type AuthEventListener = (...args: unknown[]) => void; export type AuthEventListener = (...args: unknown[]) => void;
// 存储适配器接口 - 仅用于非敏感数据存储不涉及session管理 // 存储适配器接口 - 仅用于非敏感数据存储不涉及session管理
@@ -77,6 +77,7 @@ export interface SessionManager {
} }
// 扩展 Axios 请求配置以支持认证选项 // 扩展 Axios 请求配置以支持认证选项
// 其实没必要,因为是服务器端处理过期
declare module 'axios' { declare module 'axios' {
interface AxiosRequestConfig { interface AxiosRequestConfig {
skipAuth?: boolean; skipAuth?: boolean;

View File

@@ -3,11 +3,12 @@
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"description": "负责准备视觉层会用到的逻辑函数、常用工具函数封装以及鉴权函数封装。负责维护统一接口。", "description": "负责准备视觉层会用到的逻辑函数、常用工具函数封装以及鉴权函数封装。负责维护统一接口。",
"main": "dist/index.js", "exports": {
"types": "dist/index.d.ts", ".": {
"files": [ "import": "./dist/index.js",
"dist" "types": "./dist/index.d.ts"
], }
},
"scripts": { "scripts": {
"type-check": "tsc --noemit", "type-check": "tsc --noemit",
"build": "tsc --project tsconfig.json", "build": "tsc --project tsconfig.json",

View File

@@ -2,21 +2,6 @@
本目录包含了对新认证架构的全面测试套件验证SessionManager、AccessTokenManager、SessionAuthService、ExtendedApiClient和兼容性处理的功能和安全性。 本目录包含了对新认证架构的全面测试套件验证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测试 ### SessionManager测试
@@ -26,7 +11,7 @@ test/
- Session清除 - Session清除
- 用户角色检查 - 用户角色检查
### AccessTokenManager测试 ### AccessTokenManager测试(已废弃)
- Token生成 - Token生成
- Token获取和缓存 - Token获取和缓存
- Token刷新 - Token刷新
@@ -45,50 +30,17 @@ test/
- Token注入 - Token注入
- 错误处理 - 错误处理
### 兼容性处理测试 ### 兼容性处理测试(已废弃)
- 认证模式切换 - 认证模式切换
- Token到Session的迁移 - Token到Session的迁移
- 混合认证模式 - 混合认证模式
### 集成测试 ### 集成测试(并没有)
- 端到端认证流程 - 端到端认证流程
- API请求流程 - 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框架配置了以下环境 测试使用Vitest框架配置了以下环境
@@ -102,16 +54,16 @@ npx ts-node scripts/run-auth-tests.ts --coverage
测试中使用了以下模拟对象: 测试中使用了以下模拟对象:
- ApiClient模拟HTTP客户端 - ApiClient模拟HTTP客户端
- SessionManager模拟Session管理器 - SessionManager模拟Session管理器
- AccessTokenManager模拟Access Token管理器 - AccessTokenManager模拟Access Token管理器(已废弃)
- AuthEventManager模拟认证事件管理器 - AuthEventManager模拟认证事件管理器
- TokenManager模拟Token管理器用于兼容性测试 - TokenManager模拟Token管理器已废弃
## 测试数据 ## 测试数据
测试使用以下模拟数据: 测试使用以下模拟数据:
- 用户信息 - 用户信息
- Session信息 - Session信息
- Access Token信息 - Access Token信息(已废弃)
- API响应数据 - API响应数据
## 断言 ## 断言
@@ -123,10 +75,3 @@ npx ts-node scripts/run-auth-tests.ts --coverage
- 数据转换 - 数据转换
- 安全性检查 - 安全性检查
## 注意事项
1. 确保在运行测试前已安装所有依赖
2. 测试使用模拟数据,不会影响实际系统
3. 测试覆盖了主要功能和边界情况
4. 测试验证了安全性和错误处理
5. 测试确保了兼容性和平滑迁移

View File

@@ -5,7 +5,7 @@
import { vi } from 'vitest'; import { vi } from 'vitest';
// 模拟浏览器API // 模拟浏览器API(其实用不到)
Object.defineProperty(window, 'localStorage', { Object.defineProperty(window, 'localStorage', {
value: { value: {
getItem: vi.fn(), getItem: vi.fn(),
@@ -26,24 +26,24 @@ Object.defineProperty(window, 'sessionStorage', {
writable: true writable: true
}); });
// 模拟IntersectionObserver // 模拟IntersectionObserver(其实用不到)
globalThis.IntersectionObserver = vi.fn().mockImplementation(() => ({ globalThis.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(), observe: vi.fn(),
unobserve: vi.fn(), unobserve: vi.fn(),
disconnect: vi.fn() disconnect: vi.fn()
})); }));
// 模拟ResizeObserver // 模拟ResizeObserver(其实用不到)
globalThis.ResizeObserver = vi.fn().mockImplementation(() => ({ globalThis.ResizeObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(), observe: vi.fn(),
unobserve: vi.fn(), unobserve: vi.fn(),
disconnect: vi.fn() disconnect: vi.fn()
})); }));
// 模拟requestAnimationFrame // 模拟requestAnimationFrame(其实用不到)
globalThis.requestAnimationFrame = vi.fn((cb: FrameRequestCallback) => setTimeout(cb, 0)); globalThis.requestAnimationFrame = vi.fn((cb: FrameRequestCallback) => setTimeout(cb, 0));
// 模拟cancelAnimationFrame // 模拟cancelAnimationFrame(其实用不到)
globalThis.cancelAnimationFrame = vi.fn((id: number) => clearTimeout(id)); globalThis.cancelAnimationFrame = vi.fn((id: number) => clearTimeout(id));
// 模拟fetch // 模拟fetch

View File

@@ -13,7 +13,8 @@
"lib": ["es2022", "dom", "dom.iterable"], "lib": ["es2022", "dom", "dom.iterable"],
// Other Outputs // Other Outputs
"sourceMap": true, // 关了,用的时候老跳找不到源文件
"sourceMap": false,
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,

View File

@@ -3,11 +3,13 @@ import type { ChatMessageType } from './enum';
// 创建聊天会话请求接口 // 创建聊天会话请求接口
export interface CreateChatSessionRequest { export interface CreateChatSessionRequest {
// 对方的ID因为当前用户的id可以在session里获取cookie自动带上)
participantId: string; participantId: string;
} }
// 更新聊天会话请求接口 // 更新聊天会话请求接口(占位)
export interface UpdateChatSessionRequest { export interface UpdateChatSessionRequest {
// 只能用于处理后台逻辑
sessionId: string; sessionId: string;
} }
@@ -57,7 +59,7 @@ export interface MarkMessagesAsReadRequest {
messageIds: string[]; messageIds: string[];
} }
// 标记消息已读响应接口 // 标记消息已读响应接口(已读状态只面向接收方)
export interface MarkMessagesAsReadResponse { export interface MarkMessagesAsReadResponse {
success: boolean; success: boolean;
markedMessageIds: string[]; // 成功标记的消息ID markedMessageIds: string[]; // 成功标记的消息ID

View File

@@ -4,6 +4,7 @@ import type { ChatMessageType, ChatMessageStatus } from './enum';
// 聊天消息接口 // 聊天消息接口
export interface ChatMessage { export interface ChatMessage {
id: string; id: string;
// 聊天会话id下面那个接口的id
sessionId: string; sessionId: string;
sender: User; sender: User;
receiver: User; receiver: User;
@@ -16,6 +17,7 @@ export interface ChatMessage {
fileName?: string; fileName?: string;
fileSize?: number; fileSize?: number;
duration?: number; duration?: number;
// 小图预览
thumbnail?: string; thumbnail?: string;
[key: string]: unknown; [key: string]: unknown;
}; };

View File

@@ -1,4 +1,4 @@
// 聊天消息类型枚举 // 聊天消息类型枚举(暂时用不到的)
export enum ChatMessageType { export enum ChatMessageType {
TEXT = 'text', TEXT = 'text',
IMAGE = 'image', IMAGE = 'image',
@@ -8,7 +8,7 @@ export enum ChatMessageType {
SYSTEM = 'system' SYSTEM = 'system'
} }
// 聊天消息状态枚举 // 聊天消息状态枚举(暂时用不到的)
export enum ChatMessageStatus { export enum ChatMessageStatus {
SENDING = 'sending', SENDING = 'sending',
SENT = 'sent', SENT = 'sent',

View File

@@ -15,6 +15,7 @@ export interface ModelComment extends BaseEntity {
author: BaseUser; // 作者信息 author: BaseUser; // 作者信息
content: string; // 评论内容 content: string; // 评论内容
parentId?: string; // 父评论ID用于嵌套评论 parentId?: string; // 父评论ID用于嵌套评论
// 其实没必要
stats: ModelCommentStats; // 统计信息 stats: ModelCommentStats; // 统计信息
} }

View File

@@ -1,4 +1,5 @@
// AI模型评论排序枚举 // AI模型评论排序枚举
// 和post的重复了而且字段还不一样
export enum CommentSortType { export enum CommentSortType {
LATEST = 'latest', // 最新 LATEST = 'latest', // 最新
HOTTEST = 'hottest', // 最热 HOTTEST = 'hottest', // 最热

View File

@@ -10,10 +10,13 @@ export interface BaseEntity {
// 用户基础信息接口 // 用户基础信息接口
export interface BaseUser { export interface BaseUser {
// 其实应该用profile的
user: User; user: User;
} }
export interface PostComment extends BaseEntity { export interface PostComment extends BaseEntity {
// 这里没带postId如果后台管理需要就要带
// 重复了,而且评论没有数据(点赞、收藏等等)
authorId: string; // 作者ID authorId: string; // 作者ID
author: BaseUser; // 作者信息 author: BaseUser; // 作者信息
content: string; // 评论内容 content: string; // 评论内容

View File

@@ -1,3 +1,4 @@
// 这里应该用type联合的现在有冗余和风格不统一的问题
// 排序方式枚举 // 排序方式枚举
export enum SortOrder { export enum SortOrder {
ASC = 'asc', ASC = 'asc',

View File

@@ -8,6 +8,7 @@ export interface GetHotAuthorsRequest {
// 获取热门作者响应接口 // 获取热门作者响应接口
export interface GetHotAuthorsResponse { export interface GetHotAuthorsResponse {
// 其实应该用profile返回的
data: User[]; // 数据列表 data: User[]; // 数据列表
total: number; // 总数 total: number; // 总数
} }
@@ -23,6 +24,7 @@ export interface GetAuthorRankingRequest {
export interface GetAuthorRankingResponse { export interface GetAuthorRankingResponse {
data: User[]; // 数据列表 data: User[]; // 数据列表
total: number; // 总数 total: number; // 总数
// 其实冗余了
period: 'day' | 'week' | 'month'; // 统计周期 period: 'day' | 'week' | 'month'; // 统计周期
type: 'posts' | 'views' | 'likes'; // 排序类型 type: 'posts' | 'views' | 'likes'; // 排序类型
} }

View File

@@ -33,16 +33,17 @@ export interface ChangePasswordRequest { // 暂时用不到
newPassword: string; newPassword: string;
} }
export interface RefreshTokenRequest { // 目前只用得到session
sessionId: string; // export interface RefreshTokenRequest {
} // sessionId: string;
// }
export interface RefreshTokenResponse { // export interface RefreshTokenResponse {
sessionId: string; // sessionId: string;
} // }
// 用户通知 // 用户通知(暂时用不到)
export interface UserNotification { // 暂时用不到 export interface UserNotification {
id: string; id: string;
userId: string; userId: string;
type: NotificationType; type: NotificationType;

View File

@@ -20,11 +20,13 @@ export interface UserProfileUpdateResponse {
// 用户关系 // 用户关系
export interface UserRelation { export interface UserRelation {
// 当前用户的id
id: string; id: string;
followerId: string[]; followerId: string[];
followingId: string[]; followingId: string[];
} }
// 当前用户的id由cookie带上
export interface UserFollowRequest { export interface UserFollowRequest {
userId: string; userId: string;
} }

View File

@@ -6,6 +6,7 @@ export interface UserSearchRequest {
} }
export interface UserSearchResponse { export interface UserSearchResponse {
// 这里逻辑是对的(可惜暂时不打算做)
users: Array<UserProfile> users: Array<UserProfile>
total: number; total: number;
} }

View File

@@ -3,6 +3,23 @@
* @param func 要节流的函数 * @param func 要节流的函数
* @param wait 等待时间(毫秒) * @param wait 等待时间(毫秒)
* @param options 选项 * @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>( export function throttle<T extends (...args: unknown[]) => unknown>(
func: T, func: T,
@@ -53,6 +70,7 @@ export const deepClone = <T>(obj: T): T => {
return new Date(obj.getTime()) as unknown as T; return new Date(obj.getTime()) as unknown as T;
} }
// 上面是边界情况判断,下面才是真的深拷贝逻辑
// 处理数组 // 处理数组
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
return obj.map(item => deepClone(item)) as unknown as T; 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; const clonedObj = {} as T;
for (const key in obj) { for (const key in obj) {
// 最安全的一集
if (Object.prototype.hasOwnProperty.call(obj, key)) { if (Object.prototype.hasOwnProperty.call(obj, key)) {
// 递归
clonedObj[key] = deepClone(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; if (keysA.length !== keysB.length) return false;
for (const key of keysA) { for (const key of keysA) {
// 递归,上面比值,下面先确认键一不一样
if (!keysB.includes(key) || !deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) { if (!keysB.includes(key) || !deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {
return false; return false;
} }
@@ -101,6 +122,8 @@ export function deepEqual(a: unknown, b: unknown): boolean {
* @param obj 源对象 * @param obj 源对象
* @param keys 要选取的属性键数组 * @param keys 要选取的属性键数组
* @returns 包含指定属性的新对象 * @returns 包含指定属性的新对象
* 对象或数组类型属性:拷贝引用
* 基本类型属性:直接赋值
*/ */
export function pick<T extends Record<string, unknown>, K extends keyof T>( export function pick<T extends Record<string, unknown>, K extends keyof T>(
obj: T, obj: T,
@@ -142,6 +165,7 @@ export function merge<T extends Record<string, unknown>>(...objects: Partial<T>[
for (const obj of objects) { for (const obj of objects) {
if (obj && typeof obj === 'object') { if (obj && typeof obj === 'object') {
// 浅拷贝
Object.assign(result, obj); Object.assign(result, obj);
} }
} }
@@ -150,7 +174,7 @@ export function merge<T extends Record<string, unknown>>(...objects: Partial<T>[
} }
/** /**
* 将对象转换为查询字符串 * 将对象转换为查询字符串(但是没用到)
* @param obj 要转换的对象 * @param obj 要转换的对象
* @returns 查询字符串 * @returns 查询字符串
*/ */
@@ -196,6 +220,7 @@ export const unique = <T, K = unknown>(
keyFn?: (item: T) => K keyFn?: (item: T) => K
): T[] => { ): T[] => {
if (!keyFn) { if (!keyFn) {
// 只适用于简单数组(也是蛮神奇的语法)
return [...new Set(array)]; return [...new Set(array)];
} }
@@ -203,9 +228,11 @@ export const unique = <T, K = unknown>(
return array.filter(item => { return array.filter(item => {
const key = keyFn(item); const key = keyFn(item);
if (seen.has(key)) { if (seen.has(key)) {
// 已存在返回false过滤该元素
return false; return false;
} }
seen.add(key); seen.add(key);
// 返回true保留该元素
return true; return true;
}); });
}; };
@@ -213,7 +240,7 @@ export const unique = <T, K = unknown>(
/** /**
* 数组分组 * 数组分组
* @param array 要分组的数组 * @param array 要分组的数组
* @param keyFn 分组键函数 * @param keyFn 分组键函数(箭头函数实现)
* @returns 分组后的对象 * @returns 分组后的对象
*/ */
export const groupBy = <T, K extends string | number | symbol>( 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 array 要排序的数组
* @param compareFn 比较函数 * @param compareFn 比较函数(箭头函数实现)
* @returns 排序后的新数组 * @returns 排序后的新数组
*/ */
export const sortBy = <T>( export const sortBy = <T>(
@@ -248,6 +275,18 @@ export const sortBy = <T>(
* @param func 要防抖的函数 * @param func 要防抖的函数
* @param wait 等待时间(毫秒) * @param wait 等待时间(毫秒)
* @param immediate 是否立即执行 * @param immediate 是否立即执行
*
* 两种类型:
* 1. 立即执行:第一次触发立即执行,之后触发只重置等待时间
* 2. 非立即执行:触发后等待时间结束才执行,期间触发会重置等待时间
*
* immediate=true 立即执行
* 第一次触发callNow为true->设定定时器(时间到了消除定时器),并立即执行
* 期间多次触发->不断重置定时器的等待时间
*
* immediate=false 非立即执行
* 第一次触发callNow为false->设定定时器(时间到了调用函数)
* 期间多次触发->不断重置定时器的等待时间
*/ */
export function debounce<T extends (...args: unknown[]) => unknown>( export function debounce<T extends (...args: unknown[]) => unknown>(
func: T, func: T,