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 三层的协作关系与数据流向:

项目分层架构与数据流向图 项目分层架构与数据流向 入口层 src/index.ts, config.ts API 接入层 client.ts, user.ts, post.ts 统一请求/响应拦截 缓存策略层 storage.ts, memory-cache.ts get/set/delete/clear 工具函数层 logger.ts, hash.ts, time.ts 日志、hash、时间处理 数据存储 内存 Map / localStorage 调用 API 接口 查询缓存 使用工具函数 写入缓存 持久化 说明:箭头方向表示数据/调用流向;优先从缓存层读取,未命中时穿透到 API 层

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"
}