{"_id":"@ai-partner-x/aiko-boot-starter-cache","_rev":"4-b366b6c752aa9395d548ec1c69fee9f0","name":"@ai-partner-x/aiko-boot-starter-cache","dist-tags":{"latest":"0.1.5"},"versions":{"0.1.3":{"name":"@ai-partner-x/aiko-boot-starter-cache","version":"0.1.3","keywords":["ai-first","redis","spring-boot","cache","decorator","typescript"],"_id":"@ai-partner-x/aiko-boot-starter-cache@0.1.3","maintainers":[{"name":"moyin333","email":"158182907@qq.com"}],"dist":{"shasum":"c3bdcc69d0cec94bc1de86e26b75b1fa7a986ece","tarball":"https://registry.npmjs.org/@ai-partner-x/aiko-boot-starter-cache/-/aiko-boot-starter-cache-0.1.3.tgz","fileCount":15,"integrity":"sha512-PAoo96qH8YVFHuhU92ai/y6VyRaNmDnp7TJO65irq56oHWW6bUsrCiggQE0SkLQEXh4bvfVxdG9VhnmI4ASwwA==","signatures":[{"sig":"MEYCIQCZo24Evt+R0sPtZvnGOWNLpN8EC/UWCfZwt2jCuxGYBgIhAJFdjpqfqpxG48SzJWu0i+ZDadSK5DeHJdZxTepVyNEW","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":217488},"main":"./dist/index.js","type":"module","_from":"file:ai-partner-x-aiko-boot-starter-cache-0.1.3.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./redis":{"types":"./dist/redis.d.ts","import":"./dist/redis.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"moyin333","email":"158182907@qq.com"},"_resolved":"/tmp/42c6696d002f7f4fe025d26c3ae122ca/ai-partner-x-aiko-boot-starter-cache-0.1.3.tgz","_integrity":"sha512-PAoo96qH8YVFHuhU92ai/y6VyRaNmDnp7TJO65irq56oHWW6bUsrCiggQE0SkLQEXh4bvfVxdG9VhnmI4ASwwA==","_npmVersion":"10.8.2","description":"AI-First Framework - Cache with Spring Boot compatible decorators (Spring Cache + Spring Data Redis)","directories":{},"_nodeVersion":"20.20.1","dependencies":{"tslib":"^2.8.1","reflect-metadata":"^0.2.1","@ai-partner-x/aiko-boot":"0.1.3"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","ioredis":"^5.4.2","typescript":"^5.3.0","@types/node":"^20.11.0"},"peerDependencies":{"ioredis":">=5.0.0"},"peerDependenciesMeta":{"ioredis":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aiko-boot-starter-cache_0.1.3_1773857064589_0.5085171190971225","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@ai-partner-x/aiko-boot-starter-cache","version":"0.1.4","keywords":["ai-first","redis","spring-boot","cache","decorator","typescript"],"_id":"@ai-partner-x/aiko-boot-starter-cache@0.1.4","maintainers":[{"name":"moyin333","email":"158182907@qq.com"}],"dist":{"shasum":"eac9b49a97958b886f6bad7c3e6bd28a53bc1f1e","tarball":"https://registry.npmjs.org/@ai-partner-x/aiko-boot-starter-cache/-/aiko-boot-starter-cache-0.1.4.tgz","fileCount":15,"integrity":"sha512-rWkkVSnpjZAfWi1GaGmvBcGvwszPlp5rXFsi/e1OHC+VPP9QNyv2CRAlcHs0P3mGNVV+CWelCq10E73iKvIjdQ==","signatures":[{"sig":"MEQCH2qXipJUMzeSNC7GDSWnqJdxS6kU07FTSpnBlKaDJuACIQCDhN1B0a3Bc7MdULXqeAqrSTEMRXFAeWz88sDXLeRcvg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":217488},"main":"./dist/index.js","type":"module","_from":"file:ai-partner-x-aiko-boot-starter-cache-0.1.4.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./redis":{"types":"./dist/redis.d.ts","import":"./dist/redis.js"}},"scripts":{"dev":"tsup --watch","build":"tsup","clean":"rm -rf dist","type-check":"tsc --noEmit"},"_npmUser":{"name":"moyin333","email":"158182907@qq.com"},"_resolved":"/tmp/9f4c712b62c4cd82938dc2ccdb4e1133/ai-partner-x-aiko-boot-starter-cache-0.1.4.tgz","_integrity":"sha512-rWkkVSnpjZAfWi1GaGmvBcGvwszPlp5rXFsi/e1OHC+VPP9QNyv2CRAlcHs0P3mGNVV+CWelCq10E73iKvIjdQ==","_npmVersion":"10.8.2","description":"AI-First Framework - Cache with Spring Boot compatible decorators (Spring Cache + Spring Data Redis)","directories":{},"_nodeVersion":"20.20.1","dependencies":{"tslib":"^2.8.1","reflect-metadata":"^0.2.1","@ai-partner-x/aiko-boot":"0.1.4"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","ioredis":"^5.4.2","typescript":"^5.3.0","@types/node":"^20.11.0"},"peerDependencies":{"ioredis":">=5.0.0"},"peerDependenciesMeta":{"ioredis":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aiko-boot-starter-cache_0.1.4_1773908275573_0.6254227285822813","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@ai-partner-x/aiko-boot-starter-cache","version":"0.1.5","description":"AI-First Framework - Cache with Spring Boot compatible decorators (Spring Cache + Spring Data Redis)","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./redis":{"import":"./dist/redis.js","types":"./dist/redis.d.ts"}},"dependencies":{"reflect-metadata":"^0.2.1","tslib":"^2.8.1","@ai-partner-x/aiko-boot":"0.1.5"},"peerDependencies":{"ioredis":">=5.0.0"},"peerDependenciesMeta":{"ioredis":{"optional":true}},"devDependencies":{"@types/node":"^20.11.0","ioredis":"^5.4.2","tsup":"^8.0.0","typescript":"^5.3.0"},"keywords":["ai-first","redis","spring-boot","cache","decorator","typescript"],"publishConfig":{"registry":"https://registry.npmjs.org","access":"public"},"scripts":{"build":"tsup","dev":"tsup --watch","type-check":"tsc --noEmit","clean":"rm -rf dist"},"_id":"@ai-partner-x/aiko-boot-starter-cache@0.1.5","_integrity":"sha512-sXLk7z3zVAqNF6MWH1NiNgB14guMlqiN+Uuu2oO8l3yr2642mWsLV5Ctx1nKdKXiZj2cYIo+iGMcreCNw/Fizw==","_resolved":"/tmp/a4bd6f7d3cc9a6ce48376522906adb0c/ai-partner-x-aiko-boot-starter-cache-0.1.5.tgz","_from":"file:ai-partner-x-aiko-boot-starter-cache-0.1.5.tgz","_nodeVersion":"20.20.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-sXLk7z3zVAqNF6MWH1NiNgB14guMlqiN+Uuu2oO8l3yr2642mWsLV5Ctx1nKdKXiZj2cYIo+iGMcreCNw/Fizw==","shasum":"6eaa96f4391a39b7aa682e90b398d3e84a3ee6dc","tarball":"https://registry.npmjs.org/@ai-partner-x/aiko-boot-starter-cache/-/aiko-boot-starter-cache-0.1.5.tgz","fileCount":15,"unpackedSize":217488,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC0Syf1//caXgv0Z/IwjOASZiGKAdcHR8eXKE3qFbEkWwIhAJE7RHoMXOYHctUDnLYAvqeFg0yEBC1LT4QK92wvrxoF"}]},"_npmUser":{"name":"moyin333","email":"158182907@qq.com"},"directories":{},"maintainers":[{"name":"moyin333","email":"158182907@qq.com"},{"name":"liujin0528","email":"un0528@hotmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/aiko-boot-starter-cache_0.1.5_1774102914891_0.00974541826487929"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-18T18:04:24.471Z","modified":"2026-03-21T14:21:55.280Z","0.1.3":"2026-03-18T18:04:24.739Z","0.1.4":"2026-03-19T08:17:55.695Z","0.1.5":"2026-03-21T14:21:55.100Z"},"keywords":["ai-first","redis","spring-boot","cache","decorator","typescript"],"description":"AI-First Framework - Cache with Spring Boot compatible decorators (Spring Cache + Spring Data Redis)","maintainers":[{"name":"moyin333","email":"158182907@qq.com"},{"name":"liujin0528","email":"un0528@hotmail.com"}],"readme":"# @ai-partner-x/aiko-boot-starter-cache\n\nSpring Boot 风格的缓存抽象层，对标 **Spring Cache + Spring Data Redis**，为 AI-First Framework 应用提供声明式缓存与 Redis 数据访问能力。\n\n---\n\n## 功能概述\n\n`@ai-partner-x/aiko-boot-starter-cache` 将 Spring Boot 缓存体系完整移植到 TypeScript 生态，提供两个独立的入口点：\n\n| 入口 | 对标 Spring | 职责 |\n|---|---|---|\n| `@ai-partner-x/aiko-boot-starter-cache` | `spring-context`（Spring Cache 抽象） | 声明式缓存注解、CacheManager SPI、启动初始化 |\n| `@ai-partner-x/aiko-boot-starter-cache/redis` | `spring-data-redis`（Spring Data Redis） | Redis 连接管理、RedisTemplate、数据结构操作 |\n\n**核心能力：**\n\n- **声明式缓存**：`@Cacheable` / `@CachePut` / `@CacheEvict` 三个方法装饰器，零侵入地为业务方法添加缓存语义\n- **CacheManager SPI**：`Cache` + `CacheManager` 标准接口，任意缓存后端（Redis、Memcached、内存等）均可无缝接入\n- **通用缓存配置**：`CacheConfig` 辨别联合类型（`type: 'redis' | ...`）对标 `spring.cache.type`，支持按配置切换后端\n- **Redis 数据访问**：`RedisTemplate` / `StringRedisTemplate` + `opsForValue` / `opsForList` / `opsForHash` / `opsForSet` / `opsForZSet`，完整覆盖 Redis 五种数据结构\n- **多种 Redis 拓扑**：单机（standalone）、哨兵（sentinel）、集群（cluster）三种连接模式\n- **优雅降级**：CacheManager 未注册时，缓存装饰器自动透传原方法，不阻断业务逻辑\n\n---\n\n## 开发思路\n\n### 问题与动机\n\n传统做法将缓存实现（如 `ioredis`）直接硬编码在业务代码或启动配置中：\n\n```typescript\n// ❌ 缓存后端与业务代码强耦合\nimport Redis from 'ioredis';\nconst client = new Redis({ host: 'localhost', port: 6379 });\nconst cached = await client.get(`user:${id}`);\n```\n\n这导致：切换缓存后端（如从 Redis 切到 Memcached）需要修改所有业务代码；测试时无法轻易替换为内存实现；`createApp({ cache })` 的 `cache` 选项写死为 `RedisConfig`，框架 API 无法拓展。\n\n### 设计思路\n\n1. **分层解耦**：将「缓存语义」与「缓存实现」分离，对应 Spring 的 Cache 抽象层设计\n\n   ```\n   业务代码(@Cacheable)\n       ↓ 通过 CacheManager 接口\n   后端实现(RedisCacheManager / InMemoryCacheManager / ...)\n       ↓ 通过具体技术\n   底层驱动(ioredis / memjs / ...)\n   ```\n\n2. **SPI 扩展点**：`Cache` + `CacheManager` 两个接口定义稳定契约，新后端只需实现接口并调用 `setCacheManager()` 注册，业务代码零改动\n\n3. **CacheConfig 联合类型**：以 `type` 字段作为辨别符（类似 `spring.cache.type`），为框架 API 的 `cache` 选项提供稳定、可拓展的配置类型：\n\n   ```typescript\n   // app.config.ts 通过 cache.* 属性传入 CacheConfig，而非 Redis 专用类型\n   // { cache: { type: 'redis', host: '127.0.0.1', port: 6379 } }\n   // 未来：\n   // { cache: { type: 'memcached', host: '127.0.0.1', port: 11211 } }\n   ```\n\n4. **双入口分层**：`@ai-partner-x/aiko-boot-starter-cache` 只依赖缓存抽象（无 ioredis 直接依赖），`@ai-partner-x/aiko-boot-starter-cache/redis` 提供 Redis 专属 API，用户按需引入\n\n---\n\n## 技术实现\n\n### 入口 1：`@ai-partner-x/aiko-boot-starter-cache`（缓存抽象层）\n\n#### CacheManager SPI（`src/spi/cache.ts`）\n\n定义两个扩展接口，对应 `org.springframework.cache.Cache` 和 `CacheManager`：\n\n```typescript\ninterface Cache {\n  getName(): string;\n  get(entryKey: string): Promise<string | null>;\n  put(entryKey: string, value: string, ttlSeconds?: number): Promise<void>;\n  evict(entryKey: string): Promise<void>;\n  clear(): Promise<void>;          // 使用 SCAN 游标，非阻塞\n}\n\ninterface CacheManager {\n  getCache(name: string): Cache;   // 懒加载，按需创建命名空间\n}\n```\n\n#### 全局注册表（`src/cache-manager-registry.ts`）\n\n维护单例 `CacheManager`，装饰器通过 `getCacheManager()` 获取，与后端完全解耦：\n\n```typescript\nsetCacheManager(new RedisCacheManager(client));   // 注册（通常由 initializeCaching 自动完成）\ngetCacheManager();                                 // 装饰器内部调用\nclearCacheManager();                               // 测试/关闭时清理\n```\n\n#### 缓存注解（`src/decorators.ts`）\n\n三个方法装饰器，对应 Spring Cache 的同名注解：\n\n| 装饰器 | 对标 Spring | 行为 |\n|---|---|---|\n| `@Cacheable(options)` | `@Cacheable` | 先查缓存，命中则返回；未命中则执行方法并写入缓存 |\n| `@CachePut(options)` | `@CachePut` | 每次执行方法，并将结果写入/更新缓存 |\n| `@CacheEvict(options)` | `@CacheEvict` | 执行方法后删除缓存（`allEntries: true` 清空整个命名空间） |\n\n装饰器选项：\n\n```typescript\ninterface CacheableOptions {\n  key: string;                               // 缓存命名空间（如 'user'）\n  ttl?: number;                              // 过期时间（秒）\n  keyGenerator?: (...args: unknown[]) => string;  // 自定义条目 key\n  condition?: (...args: unknown[]) => boolean;    // 缓存条件\n}\n\ninterface CacheEvictOptions {\n  key: string;\n  keyGenerator?: (...args: unknown[]) => string;\n  allEntries?: boolean;          // true = 清空整个命名空间\n  beforeInvocation?: boolean;   // true = 方法执行前清除\n}\n```\n\n**优雅降级**：`getCacheManager()` 返回 `null` 时（即未注册任何后端），装饰器透传原方法，不抛出异常。\n\n#### 通用缓存配置（`src/spi/cache-config.ts`）\n\n`CacheConfig` 辨别联合类型，`type` 字段对标 `spring.cache.type`：\n\n```typescript\nexport type RedisCacheConfig = { type: 'redis' } & RedisConfig;\n\nexport type CacheConfig =\n  | RedisCacheConfig;\n  // 未来可扩展：\n  // | { type: 'simple' }\n  // | { type: 'memcached'; host: string; port: number }\n  // | { type: 'caffeine'; spec?: string }\n```\n\n新增后端只需：① 追加联合类型成员，② 在 `initializeCaching()` 的 `switch` 中添加一个 `case`，业务代码和注解**零改动**。\n\n#### 启动初始化（`src/enable-caching.ts`）\n\n`initializeCaching(config: CacheConfig)` 根据 `config.type` 分发到对应后端的初始化逻辑：\n\n```\nconfig.type === 'redis'\n  → 创建短生命周期客户端发送 PING（5 秒超时，连接失败立即报错）\n  → PING 成功后创建持久客户端\n  → setCacheManager(new RedisCacheManager(client))\n```\n\n连接失败抛出 `CacheInitializationError`，阻止应用启动（对应 Spring 的 `BeanCreationException`）。\n\n---\n\n### 入口 2：`@ai-partner-x/aiko-boot-starter-cache/redis`（Spring Data Redis 层）\n\n#### Redis 连接配置（`src/config.ts`）\n\n支持三种拓扑，通过 `mode` 字段区分：\n\n| mode | 类型 | 说明 |\n|---|---|---|\n| `undefined` / `'standalone'` | `RedisStandaloneConfig` | 单机模式（默认） |\n| `'sentinel'` | `RedisSentinelConfig` | 哨兵高可用模式 |\n| `'cluster'` | `RedisClusterConfig` | 集群水平扩展模式 |\n\n#### RedisTemplate（`src/redis-template.ts`）\n\nSpring `RedisTemplate<K, V>` 风格的操作模板，基于 `IORedisAdapter` 封装 ioredis：\n\n```typescript\nclass RedisTemplate<K = string, V = unknown> {\n  opsForValue(): ValueOperations<K, V>     // String 类型操作\n  opsForList(): ListOperations<K, V>       // List 类型操作\n  opsForHash<HK, HV>(): HashOperations<K, HK, HV>  // Hash 类型操作\n  opsForSet(): SetOperations<K, V>         // Set 类型操作\n  opsForZSet(): ZSetOperations<K, V>       // Sorted Set 类型操作\n  delete(keys: K | K[]): Promise<number>   // 删除 key\n}\n\nclass StringRedisTemplate extends RedisTemplate<string, string> {}  // 字符串专用\n```\n\n#### RedisCacheManager（`src/cache-managers/redis-cache-manager.ts`）\n\n实现 `CacheManager` / `Cache` SPI 接口的 Redis 后端：\n\n- 物理 key 格式：`{namespace}::{entryKey}`（entryKey 为空时退化为 `{namespace}`）\n- `clear()` 使用游标 `SCAN` 批量删除，避免 `KEYS *` 阻塞 Redis\n\n---\n\n## 快速开始\n\n### 安装\n\n```bash\npnpm add @ai-partner-x/aiko-boot-starter-cache\n```\n\n### 方式一：`app.config.ts` 自动配置（推荐）\n\n在 `app.config.ts` 中声明 `cache.*` 配置，`CacheAutoConfiguration` 在应用启动时自动完成连接验证和 CacheManager 注册：\n\n```typescript\n// app.config.ts\nimport type { AppConfig } from '@ai-partner-x/aiko-boot';\n\nexport default {\n  cache: {\n    type: 'redis',\n    host: process.env.REDIS_HOST ?? '127.0.0.1',\n    port: Number(process.env.REDIS_PORT ?? 6379),\n  },\n} satisfies AppConfig;\n\n// src/server.ts\nimport { createApp } from '@ai-partner-x/aiko-boot';\n\nconst app = await createApp({ srcDir: import.meta.dirname });\napp.run();\n```\n\nRedis Sentinel（高可用）：\n\n```typescript\n// app.config.ts\nexport default {\n  cache: {\n    type: 'redis',\n    mode: 'sentinel',\n    masterName: 'mymaster',\n    sentinels: [\n      { host: '127.0.0.1', port: 26379 },\n      { host: '127.0.0.1', port: 26380 },\n    ],\n  },\n} satisfies AppConfig;\n```\n\n> **提示**：`cache.enabled` 未设为 `true` 时，`@ConditionalOnProperty('cache.enabled', { havingValue: 'true' })` 会跳过 `CacheAutoConfiguration`，缓存装饰器自动降级，无需 Redis 即可本地开发。\n\n### 方式二：手动初始化\n\n```typescript\nimport 'reflect-metadata';\nimport { initializeCaching, CacheInitializationError } from '@ai-partner-x/aiko-boot-starter-cache';\n\ntry {\n  await initializeCaching({\n    type: 'redis',\n    host: '127.0.0.1',\n    port: 6379,\n  });\n  console.log('缓存初始化成功');\n} catch (e) {\n  if (e instanceof CacheInitializationError) {\n    console.error('Redis 连接失败，应用终止');\n    process.exit(1);\n  }\n  throw e;\n}\n```\n\n### 声明式缓存注解\n\n在 `@Service` / `@Component` 类的方法上使用缓存注解（需先完成缓存初始化）：\n\n```typescript\nimport { Service, Autowired } from '@ai-partner-x/aiko-boot';\nimport { Cacheable, CachePut, CacheEvict } from '@ai-partner-x/aiko-boot-starter-cache';\n\n@Service()\nexport class UserService {\n  @Autowired()\n  private userRepository!: UserRepository;\n\n  // 读通缓存：命中则直接返回，不访问数据库\n  @Cacheable({ key: 'user', ttl: 300 })\n  async getUserById(id: number): Promise<User | null> {\n    return this.userRepository.selectById(id);\n  }\n\n  // 写通缓存：执行方法并将结果更新到缓存\n  @CachePut({ key: 'user', ttl: 300 })\n  async updateUser(id: number, data: Partial<User>): Promise<User> {\n    return this.userRepository.updateById(id, data);\n  }\n\n  // 删除缓存：执行方法后清除对应条目\n  @CacheEvict({ key: 'user' })\n  async deleteUser(id: number): Promise<boolean> {\n    return this.userRepository.deleteById(id);\n  }\n\n  // 清空整个命名空间\n  @CacheEvict({ key: 'user', allEntries: true })\n  async clearUserCache(): Promise<void> {}\n\n  // 自定义条目 key 生成（keyGenerator 参数类型与方法参数保持一致）\n  @Cacheable({\n    key: 'user',\n    ttl: 60,\n    keyGenerator: (page: number, size: number) => `list:${page}:${size}`,\n  })\n  async getUserList(page: number, size: number): Promise<User[]> {\n    return this.userRepository.selectPage(page, size);\n  }\n}\n```\n\n### RedisTemplate 直接操作\n\n使用 `@ai-partner-x/aiko-boot-starter-cache/redis` 进行底层 Redis 数据结构操作：\n\n```typescript\nimport {\n  getRedisClient,\n  RedisTemplate,\n  StringRedisTemplate,\n} from '@ai-partner-x/aiko-boot-starter-cache/redis';\n\n// 在 createApp / initializeCaching 之后获取客户端\nconst client = getRedisClient();\n\n// 通用模板（支持 JSON 序列化）\nconst redisTemplate = new RedisTemplate<string, unknown>({ client });\n\n// String 操作\nconst valueOps = redisTemplate.opsForValue();\nawait valueOps.set('user:1', { name: '张三', age: 25 }, 3600);\nconst user = await valueOps.get('user:1');   // { name: '张三', age: 25 }\nawait valueOps.increment('counter');\n\n// Hash 操作\nconst hashOps = redisTemplate.opsForHash<string, string>();\nawait hashOps.put('user:profile:1', 'name', '张三');\nconst name = await hashOps.get('user:profile:1', 'name');\n\n// List 操作\nconst listOps = redisTemplate.opsForList();\nawait listOps.rightPush('queue', 'task1');\nconst task = await listOps.leftPop('queue');\n\n// 字符串专用模板\nconst stringTemplate = new StringRedisTemplate({ client });\nawait stringTemplate.opsForValue().set('greeting', 'hello', 60);\n\n// 删除 key\nawait redisTemplate.delete(['user:1', 'user:2']);\n```\n\n### 自定义缓存后端（SPI 扩展）\n\n实现 `Cache` + `CacheManager` 接口，可接入任意缓存后端（如 Memcached、内存缓存、测试 Mock）：\n\n```typescript\nimport { Cache, CacheManager, setCacheManager } from '@ai-partner-x/aiko-boot-starter-cache';\n\nclass MapCache implements Cache {\n  private store = new Map<string, { value: string; expiresAt?: number }>();\n  constructor(public readonly name: string) {}\n\n  getName() { return this.name; }\n\n  async get(entryKey: string): Promise<string | null> {\n    const entry = this.store.get(entryKey);\n    if (!entry) return null;\n    if (entry.expiresAt && Date.now() > entry.expiresAt) {\n      this.store.delete(entryKey);\n      return null;\n    }\n    return entry.value;\n  }\n\n  async put(entryKey: string, value: string, ttlSeconds?: number): Promise<void> {\n    this.store.set(entryKey, {\n      value,\n      expiresAt: ttlSeconds ? Date.now() + ttlSeconds * 1000 : undefined,\n    });\n  }\n\n  async evict(entryKey: string): Promise<void> { this.store.delete(entryKey); }\n  async clear(): Promise<void> { this.store.clear(); }\n}\n\nclass MapCacheManager implements CacheManager {\n  private caches = new Map<string, MapCache>();\n  getCache(name: string): Cache {\n    if (!this.caches.has(name)) this.caches.set(name, new MapCache(name));\n    return this.caches.get(name)!;\n  }\n}\n\n// 测试环境：使用内存缓存替代 Redis\nsetCacheManager(new MapCacheManager());\n```\n\n---\n\n## API 参考\n\n### `@ai-partner-x/aiko-boot-starter-cache` 导出\n\n| 导出 | 类型 | 说明 |\n|---|---|---|\n| `Cacheable(options)` | 方法装饰器 | 读通缓存 |\n| `CachePut(options)` | 方法装饰器 | 写通缓存 |\n| `CacheEvict(options)` | 方法装饰器 | 删除缓存 |\n| `initializeCaching(config)` | `async function` | 根据 `config.type` 初始化缓存后端 |\n| `CacheInitializationError` | 类 | 初始化失败异常 |\n| `CacheAutoConfiguration` | 类 | Spring Boot 风格自动配置（读取 `cache.*` 属性，自动初始化/关闭连接） |\n| `CacheProperties` | 类 | `@ConfigurationProperties('cache')` 绑定类，覆盖 standalone/sentinel/cluster 全部属性 |\n| `CacheConfig` | 类型 | 通用缓存配置联合类型（`type: 'redis' \\| ...`） |\n| `RedisCacheConfig` | 类型 | Redis 后端配置（`{ type: 'redis' } & RedisConfig`） |\n| `Cache` | 接口 | 缓存命名空间操作 SPI |\n| `CacheManager` | 接口 | 缓存管理器 SPI |\n| `setCacheManager(manager)` | 函数 | 注册 CacheManager |\n| `getCacheManager()` | 函数 | 获取当前 CacheManager |\n| `isCacheManagerInitialized()` | 函数 | 是否已注册 CacheManager |\n| `clearCacheManager()` | 函数 | 清除 CacheManager（测试/关闭时使用） |\n| `Autowired` | 装饰器 | DI 属性注入（re-export from `@ai-partner-x/aiko-boot`） |\n\n### `@ai-partner-x/aiko-boot-starter-cache/redis` 导出\n\n| 导出 | 类型 | 说明 |\n|---|---|---|\n| `RedisConfig` | 类型 | Redis 连接配置（standalone / sentinel / cluster） |\n| `createRedisConnection(config)` | 函数 | 创建并保存全局 Redis 连接 |\n| `getRedisClient()` | 函数 | 获取全局 Redis 客户端 |\n| `closeRedisConnection()` | `async function` | 关闭 Redis 连接 |\n| `RedisTemplate<K, V>` | 类 | 通用 Redis 操作模板 |\n| `StringRedisTemplate` | 类 | 字符串专用模板 |\n| `RedisCacheManager` | 类 | CacheManager SPI 的 Redis 实现 |\n\n---\n\n## 完整示例：createApp + SQLite + 声明式缓存\n\n以下示例展示如何用 `createApp` 搭建一个真实的 API 服务：底层使用 **SQLite 持久化**（`@ai-partner-x/aiko-boot-starter-orm`），上层使用 **声明式缓存注解** 降低数据库访问压力，Redis 可选接入（未配置时缓存装饰器自动降级）。完整源码见 [`app/examples/cache-crud`](../../app/examples/cache-crud)。\n\n### 目录结构\n\n```\nsrc/\n├── controller/\n│   └── user.controller.ts       # @RestController — REST CRUD 路由\n├── entity/\n│   ├── user.entity.ts           # @Entity + @TableId + @TableField\n│   └── user.repository.ts       # @Mapper + BaseMapper<User>（SQLite）\n├── service/\n│   └── user.cache.service.ts    # @Service + @Cacheable/@CachePut/@CacheEvict\n├── scripts/\n│   └── init-db.ts               # SQLite 建表 + 种子数据\n└── server.ts                    # createApp 入口\n```\n\n### 1. 实体定义（`@Entity`）\n\n```typescript\n// src/entity/user.entity.ts\nimport { Entity, TableId, TableField } from '@ai-partner-x/aiko-boot-starter-orm';\n\n@Entity({ tableName: 'cache_user' })\nexport class User {\n  @TableId({ type: 'AUTO' })\n  id!: number;\n\n  @TableField()\n  name!: string;\n\n  @TableField()\n  email!: string;\n\n  @TableField()\n  age?: number;\n}\n```\n\n### 2. Mapper 层（`@Mapper + BaseMapper`）\n\n```typescript\n// src/entity/user.repository.ts\nimport { Mapper, BaseMapper } from '@ai-partner-x/aiko-boot-starter-orm';\nimport { User } from './user.entity.js';\n\n@Mapper(User)\nexport class UserRepository extends BaseMapper<User> {\n  async findByEmail(email: string): Promise<User | null> {\n    const list = await this.selectList({ email } as Partial<User>);\n    return list.length > 0 ? list[0] : null;\n  }\n}\n```\n\n### 3. 缓存服务（`@Cacheable / @CachePut / @CacheEvict`）\n\n```typescript\n// src/service/user.cache.service.ts\nimport { Service, Autowired } from '@ai-partner-x/aiko-boot';\nimport { Cacheable, CachePut, CacheEvict } from '@ai-partner-x/aiko-boot-starter-cache';\nimport { User } from '../entity/user.entity.js';\nimport { UserRepository } from '../entity/user.repository.js';\n\n@Service({ name: 'UserCacheService' })\nexport class UserCacheService {\n  @Autowired()\n  private userRepository!: UserRepository;   // SQLite via @Mapper + BaseMapper\n\n  @Cacheable({ key: 'user', ttl: 300 })\n  async getUserById(id: number): Promise<User | null> {\n    return this.userRepository.selectById(id);   // Redis 命中时跳过 DB\n  }\n\n  @Cacheable({ key: 'user:list', ttl: 60 })\n  async getUserList(): Promise<User[]> {\n    return this.userRepository.selectList();\n  }\n\n  @CacheEvict({ key: 'user:list', allEntries: true })\n  async createUser(data: Omit<User, 'id'>): Promise<User> {\n    await this.userRepository.insert(data);\n    const list = await this.userRepository.selectList(data as Partial<User>);\n    const created = list[list.length - 1];\n    if (!created) throw new Error('Failed to create user');\n    return created;\n  }\n\n  @CachePut({ key: 'user', ttl: 300, keyGenerator: (id: unknown) => String(id) })\n  async updateUser(id: number, data: Partial<Omit<User, 'id'>>): Promise<User> {\n    const existing = await this.userRepository.selectById(id);\n    if (!existing) throw new Error(`用户 ${id} 不存在`);\n    const updated: User = { ...existing, ...data };\n    await this.userRepository.updateById(updated);\n    return updated;\n  }\n\n  @CacheEvict({ key: 'user' })\n  async deleteUser(id: number): Promise<boolean> {\n    const affected = await this.userRepository.deleteById(id);\n    return affected > 0;\n  }\n}\n```\n\n### 4. REST 控制器（`@RestController`）\n\n```typescript\n// src/controller/user.controller.ts\nimport {\n  RestController, GetMapping, PostMapping, PutMapping, DeleteMapping,\n  PathVariable, RequestBody,\n} from '@ai-partner-x/aiko-boot-starter-web';\nimport { Autowired } from '@ai-partner-x/aiko-boot';\nimport { User } from '../entity/user.entity.js';\nimport { UserCacheService } from '../service/user.cache.service.js';\n\n@RestController({ path: '/users' })\nexport class UserController {\n  @Autowired()\n  private userCacheService!: UserCacheService;\n\n  @GetMapping()\n  list(): Promise<User[]> {\n    return this.userCacheService.getUserList();          // @Cacheable user:list\n  }\n\n  @GetMapping('/:id')\n  getById(@PathVariable('id') id: string): Promise<User | null> {\n    return this.userCacheService.getUserById(Number(id)); // @Cacheable user\n  }\n\n  @PostMapping()\n  create(@RequestBody() body: Omit<User, 'id'>): Promise<User> {\n    return this.userCacheService.createUser(body);        // @CacheEvict user:list\n  }\n\n  @PutMapping('/:id')\n  update(\n    @PathVariable('id') id: string,\n    @RequestBody() body: Partial<Omit<User, 'id'>>\n  ): Promise<User> {\n    return this.userCacheService.updateUser(Number(id), body); // @CachePut user\n  }\n\n  @DeleteMapping('/:id')\n  async delete(@PathVariable('id') id: string): Promise<{ success: boolean }> {\n    return { success: await this.userCacheService.deleteUser(Number(id)) }; // @CacheEvict user\n  }\n}\n```\n\n### 5. 配置文件（`app.config.ts`）与服务器入口（`src/server.ts`）\n\n```typescript\n// app.config.ts\nimport type { AppConfig } from '@ai-partner-x/aiko-boot';\n\nconst REDIS_HOST     = process.env.REDIS_HOST;\nconst REDIS_PORT     = process.env.REDIS_PORT ? Number(process.env.REDIS_PORT) : 6379;\nconst REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;\n\nexport default {\n  server: {\n    port: Number(process.env.PORT || '3002'),\n    servlet: { contextPath: '/api' },\n    shutdown: 'graceful',\n  },\n  database: {\n    type: 'sqlite',\n    filename: './data/cache_example.db',\n  },\n  // 仅在配置了 REDIS_HOST 时才启用缓存（@ConditionalOnProperty 控制初始化）\n  ...(REDIS_HOST\n    ? { cache: { type: 'redis' as const, host: REDIS_HOST, port: REDIS_PORT, password: REDIS_PASSWORD } }\n    : {}),\n} satisfies AppConfig;\n```\n\n```typescript\n// src/server.ts\nimport 'reflect-metadata';\nimport { createApp } from '@ai-partner-x/aiko-boot';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// 配置由 app.config.ts 统一管理（server.*、database.*、cache.*）\nconst app = await createApp({ srcDir: __dirname });\n\n// 启动 HTTP 服务器（端口由 app.config.ts server.port 决定，默认 3002）\napp.run();\n```\n\n### 启动方式\n\n```bash\n# 1. 初始化数据库（只需执行一次）\npnpm init-db\n\n# 2. 启动 API 服务器（无 Redis，缓存装饰器自动降级）\npnpm server\n\n# 3. 启动 API 服务器（有 Redis，启用缓存）\nREDIS_HOST=127.0.0.1 REDIS_PORT=6379 pnpm server\n```\n\n### 接口一览\n\n| 方法 | 路径 | 缓存行为 |\n|---|---|---|\n| `GET` | `/api/users` | `@Cacheable(user:list, 60s)` |\n| `GET` | `/api/users/:id` | `@Cacheable(user, 300s)` |\n| `POST` | `/api/users` | `@CacheEvict(user:list)` |\n| `PUT` | `/api/users/:id` | `@CachePut(user, 300s)` |\n| `DELETE` | `/api/users/:id` | `@CacheEvict(user)` |\n\n> **提示**：未设置 `REDIS_HOST` 时，缓存装饰器透传原方法，每次请求直接访问 SQLite，无需 Redis 即可本地开发调试。\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md"}