refactor: 使用 lodash-es 重写 shared 工具函数并升级 cuid

- shared 包工具函数改用 lodash-es 实现 (deepClone, isEmpty, capitalize 等)
- Prisma User.id 默认值从 cuid() 改为 cuid(2) 以使用更安全的 cuid2
- 更新 CLAUDE.md 文档,补充 @CurrentUser 装饰器和 lodash 使用约定

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
charilezhou
2025-12-28 15:04:55 +08:00
parent 74ced8c0c6
commit b5e8b5e805
5 changed files with 49 additions and 20 deletions

View File

@@ -23,6 +23,7 @@ pnpm format:check # 检查格式
# 测试
pnpm test
cd apps/api && pnpm test:watch # 单个测试文件监听
cd apps/api && pnpm test:cov # 测试覆盖率报告
# 数据库
pnpm db:generate # 生成 Prisma Client
@@ -48,7 +49,7 @@ NestJS 采用模块化架构:
- **AuthModule** - JWT 认证注册、登录、token 验证)
- **UserModule** - 用户 CRUD
认证流程:使用 `@Public()` 装饰器标记公开接口,其他接口需要 JWT Bearer Token。
认证流程:使用 `@Public()` 装饰器标记公开接口,其他接口需要 JWT Bearer Token。使用 `@CurrentUser()` 装饰器获取当前登录用户信息。
### 共享包使用
@@ -57,6 +58,8 @@ import type { User, ApiResponse } from '@seclusion/shared';
import { formatDate, generateId } from '@seclusion/shared';
```
**注意**: `packages/shared` 中的工具函数应优先使用 lodash 实现。
## Key Files
- `apps/api/prisma/schema.prisma` - 数据库模型定义

View File

@@ -8,7 +8,7 @@ datasource db {
}
model User {
id String @id @default(cuid())
id String @id @default(cuid(2))
email String @unique
password String
name String?

View File

@@ -28,7 +28,11 @@
"clean": "rm -rf dist",
"lint": "eslint src --ext .ts,.tsx"
},
"dependencies": {
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@types/lodash-es": "^4.17.12",
"@seclusion/eslint-config": "workspace:*",
"@seclusion/typescript-config": "workspace:*",
"tsup": "^8.3.5",

View File

@@ -1,3 +1,12 @@
import {
cloneDeep,
isEmpty as _isEmpty,
capitalize as _capitalize,
kebabCase,
camelCase,
sampleSize,
} from 'lodash-es';
// 日期格式化
export function formatDate(date: Date | string, format = 'YYYY-MM-DD'): string {
const d = typeof date === 'string' ? new Date(date) : date;
@@ -25,28 +34,18 @@ export function delay(ms: number): Promise<void> {
// 生成随机 ID
export function generateId(length = 12): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
return sampleSize(chars.split(''), length).join('');
}
// 深拷贝
export function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
return JSON.parse(JSON.stringify(obj));
return cloneDeep(obj);
}
// 判断是否为空对象
// 判断是否为空
export function isEmpty(obj: unknown): boolean {
if (obj === null || obj === undefined) return true;
if (typeof obj === 'string') return obj.trim().length === 0;
if (Array.isArray(obj)) return obj.length === 0;
if (typeof obj === 'object') return Object.keys(obj).length === 0;
return false;
return _isEmpty(obj);
}
// 安全解析 JSON
@@ -60,16 +59,15 @@ export function safeJsonParse<T>(json: string, fallback: T): T {
// 首字母大写
export function capitalize(str: string): string {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1);
return _capitalize(str);
}
// 驼峰转短横线
export function camelToKebab(str: string): string {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
return kebabCase(str);
}
// 短横线转驼峰
export function kebabToCamel(str: string): string {
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
return camelCase(str);
}

24
pnpm-lock.yaml generated
View File

@@ -187,6 +187,10 @@ importers:
version: 8.57.1
packages/shared:
dependencies:
lodash-es:
specifier: ^4.17.21
version: 4.17.22
devDependencies:
'@seclusion/eslint-config':
specifier: workspace:*
@@ -194,6 +198,9 @@ importers:
'@seclusion/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@types/lodash-es':
specifier: ^4.17.12
version: 4.17.12
tsup:
specifier: ^8.3.5
version: 8.5.1(jiti@2.6.1)(postcss@8.4.31)(typescript@5.9.3)
@@ -1295,6 +1302,12 @@ packages:
'@types/jsonwebtoken@9.0.5':
resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==}
'@types/lodash-es@4.17.12':
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
'@types/lodash@4.17.21':
resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==}
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
@@ -3181,6 +3194,9 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
lodash-es@4.17.22:
resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
lodash.includes@4.3.0:
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
@@ -5648,6 +5664,12 @@ snapshots:
dependencies:
'@types/node': 22.19.3
'@types/lodash-es@4.17.12':
dependencies:
'@types/lodash': 4.17.21
'@types/lodash@4.17.21': {}
'@types/ms@2.1.0': {}
'@types/node@22.19.3':
@@ -8054,6 +8076,8 @@ snapshots:
dependencies:
p-locate: 5.0.0
lodash-es@4.17.22: {}
lodash.includes@4.3.0: {}
lodash.isboolean@3.0.3: {}