2. 项目初始化与目录结构设计
本章将完成课程实战项目的初始化工作,建立规范的分层目录结构并配置 package.json 脚本。良好的项目骨架是后续接入 API 和实现离线缓存的基石,能显著降低模块间耦合,提升可维护性。
2.1 项目初始化
使用 npm 初始化项目,生成 package.json 文件。后续所有依赖和脚本都将集中管理于此。
mkdir api-cache-course
cd api-cache-course
npm init -y
安装核心运行依赖:
npm install axios --save
# 开发依赖
npm install typescript @types/node ts-node nodemon --save-dev
2.2 分层目录结构
项目采用 src/ 作为源码根目录,内部按职责划分为 api、cache、utils 三个核心层。各层职责与典型文件见下表:
| 目录 | 职责 | 典型文件 |
|---|---|---|
src/api/ | 封装所有外部 API 请求,提供统一的请求/响应拦截器 | client.ts、user.ts、post.ts |
src/cache/ | 实现缓存策略(内存/持久化),提供 get/set/clear 接口 | storage.ts、memory-cache.ts、index.ts |
src/utils/ | 存放通用工具函数,如日志、序列化、时间处理 | logger.ts、hash.ts、time.ts |
src/ | 应用入口,协调各层工作 | index.ts、config.ts |
完整的目录结构如下:
project-root/
├── src/
│ ├── api/ # API 接入层
│ │ ├── client.ts # axios 实例与拦截器
│ │ ├── user.ts # 用户相关接口
│ │ └── post.ts # 文章相关接口
│ ├── cache/ # 缓存策略层
│ │ ├── index.ts # 缓存统一导出
│ │ ├── storage.ts # 缓存存储接口抽象
│ │ └── memory-cache.ts # 内存缓存实现
│ ├── utils/ # 工具函数层
│ │ ├── logger.ts # 日志工具
│ │ ├── hash.ts # 请求 hash 生成
│ │ └── time.ts # 时间格式化与过期判断
│ ├── config.ts # 全局配置:API 基础 URL、缓存 TTL 等
│ └── index.ts # 应用入口
├── package.json
├── tsconfig.json
└── .gitignore
2.3 核心层文件初始化
以下为各层关键文件的初始化代码骨架:
src/api/client.ts —— 创建 axios 实例,配置基础路径与超时
import axios from 'axios';
import { CONFIG } from '../config';
const apiClient = axios.create({
baseURL: CONFIG.API_BASE_URL,
timeout: 10000,
headers: { 'Content-Type': 'application/json' }
});
// 请求拦截器:注入 、记录日志
apiClient.interceptors.request.use(config => {
const = localStorage.getItem('');
if () config.headers.Authorization = `Bearer ${}`;
return config;
});
// 响应拦截器:统一错误处理
apiClient.interceptors.response.use(
response => response.data,
error => Promise.reject(error)
);
export default apiClient;
// src/cache/storage.ts —— 缓存存储接口抽象
export interface CacheStorage {
get(key: string): any | undefined;
set(key: string, value: any, ttlMs?: number): void;
delete(key: string): void;
clear(): void;
}
// src/cache/memory-cache.ts —— 内存缓存实现
import { CacheStorage } from './storage';
interface CacheItem {
value: any;
expiresAt: number | null;
}
export class MemoryCache implements CacheStorage {
private store = new Map();
get(key: string): any | undefined {
const item = this.store.get(key);
if (!item) return undefined;
if (item.expiresAt && Date.now() > item.expiresAt) {
this.store.delete(key);
return undefined;
}
return item.value;
}
set(key: string, value: any, ttlMs?: number): void {
this.store.set(key, {
value,
expiresAt: ttlMs ? Date.now() + ttlMs : null
});
}
delete(key: string): void { this.store.delete(key); }
clear(): void { this.store.clear(); }
}
// src/utils/logger.ts —— 分级日志工具
export const logger = {
info: (msg: string, data?: any) => {
console.log(`[INFO] ${new Date().toISOString()} ${msg}`, data ?? '');
},
warn: (msg: string, data?: any) => {
console.warn(`[WARN] ${msg}`, data ?? '');
},
error: (msg: string, err?: any) => {
console.error(`[ERROR] ${msg}`, err?.stack ?? err);
}
};
// src/config.ts —— 全局配置
export const CONFIG = {
API_BASE_URL: 'https://jsonplaceholder.typicode.com',
CACHE_DEFAULT_TTL_MS: 5 * 60 * 1000, // 5分钟
CACHE_MAX_ITEMS: 500
};
2.4 package.json 脚本配置
为开发、构建和运行配置便捷脚本:
{
"name": "api-cache-course",
"version": "1.0.0",
"scripts": {
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"clean": "rm -rf dist"
},
"dependencies": {
"axios": "^1.6.0"
},
"devDependencies": {
"@types/node": "^20.11.0",
"nodemon": "^3.0.0",
"ts-node": "^10.9.0",
"typescript": "^5.3.0"
}
}
2.5 分层架构关系图
下图清晰展示了 src/api、src/cache、src/utils 三层的协作关系与数据流向:
2.6 调试与验证
启动开发服务器,验证各层模块正确加载:
npm run dev
在 src/index.ts 中添加临时测试代码:
import { MemoryCache } from './cache/memory-cache';
import { logger } from './utils/logger';
const cache = new MemoryCache();
cache.set('test-key', { data: 'hello' }, 5000);
logger.info('缓存写入成功', cache.get('test-key'));
预期终端输出:
[INFO] 2025-01-15T10:30:00.000Z 缓存写入成功 { data: 'hello' }
2.7 常见错误与避坑
- 模块加载失败:
tsconfig.json未配置moduleResolution: "node"或esModuleInterop: true,导致 axios 等 CommonJS 模块无法正确导入。 - 缓存未定义:
MemoryCache实例在模块顶层创建,但被多个文件引用时,注意使用单例模式避免重复实例。 - axios 超时未生效:检查
timeout配置是否在实例创建时正确传入,而非设置在请求拦截中。
注意:
src/api/ 层不应直接依赖 src/cache/ 层;缓存逻辑由上层(如 src/index.ts)编排,保持 API 层纯粹性。违反该原则会导致循环依赖和测试困难。
2.8 优化建议
- 使用 依赖注入 模式,将
CacheStorage实例作为参数传入 API 模块,便于替换实现(如从内存换到 Redis)。 - 为
src/cache/storage.ts增加has(key)和keys()方法,增强缓存管理能力。 - 配置
nodemon.json监听src/目录变化,自动重启开发服务器:
{
"watch": ["src"],
"ext": "ts,json",
"exec": "ts-node src/index.ts"
}