Update your .env files with required variables:
# Required
MYSQL_URL=mysql://user:pass@localhost:3306/dbname
MONGO_URL=mongodb://localhost:27017/dbname
JWT_SECRET=your-secure-random-secret-min-32-chars
# Optional (with defaults)
JWT_EXPIRES_IN_SECONDS=43200 # 12 hours
PORT=3000
NODE_ENV=production
ENCRYPTION_KEY=your-encryption-key-for-2fa
Validation: Run the app locally to verify config validation works:
pnpm dev:mgnt
If any required variables are missing, you'll see a clear error message.
Run these SQL migrations to optimize performance:
-- User role lookups
CREATE INDEX IF NOT EXISTS idx_user_role_user_id
ON sys_user_role(user_id);
CREATE INDEX IF NOT EXISTS idx_user_role_role_id
ON sys_user_role(role_id);
-- Role menu lookups
CREATE INDEX IF NOT EXISTS idx_role_menu_role_id
ON sys_role_menu(role_id);
CREATE INDEX IF NOT EXISTS idx_role_menu_menu_id
ON sys_role_menu(menu_id);
-- Menu hierarchy traversal
CREATE INDEX IF NOT EXISTS idx_menu_parent_id
ON sys_menu(parent_id);
-- Operation log queries
CREATE INDEX IF NOT EXISTS idx_operation_log_username
ON sys_operation_log(username);
CREATE INDEX IF NOT EXISTS idx_operation_log_create_time
ON sys_operation_log(create_time);
-- Login log queries
CREATE INDEX IF NOT EXISTS idx_login_log_username
ON sys_login_log(username);
CREATE INDEX IF NOT EXISTS idx_login_log_create_time
ON sys_login_log(create_time);
Old Code:
// ❌ Old response format
interface HttpResponse {
error: string;
status: 1 | 0;
data: any;
}
// Check success
if (response.status === 1) {
// success
}
New Code:
// ✅ New response format
interface ApiResponse<T> {
success: boolean;
code: string;
message: string;
data: T;
timestamp: string;
}
// Check success
if (response.success) {
// success
}
Old Code:
// ❌ All responses were HTTP 200
axios.post('/api/login', data).then((res) => {
if (res.data.status === 1) {
// success
} else {
// error - check res.data.error
}
});
New Code:
// ✅ Proper HTTP status codes
axios
.post('/api/login', data)
.then((res) => {
// HTTP 2xx - success
if (res.data.success) {
// success
}
})
.catch((err) => {
// HTTP 4xx/5xx - error
const { code, message } = err.response.data;
switch (code) {
case 'UNAUTHORIZED':
// redirect to login
break;
case 'RATE_LIMITED':
// show rate limit message
break;
case 'MFA_REQUIRED':
// redirect to MFA page
break;
default:
// show generic error
}
});
// Add correlation ID to all requests for tracing
axios.interceptors.request.use((config) => {
const correlationId = localStorage.getItem('correlationId') || uuidv4();
config.headers['x-correlation-id'] = correlationId;
return config;
});
// Extract correlation ID from responses for logging
axios.interceptors.response.use(
(response) => {
const correlationId = response.headers['x-correlation-id'];
if (correlationId) {
console.log(`[${correlationId}] Response received`);
}
return response;
},
(error) => {
const correlationId = error.response?.headers['x-correlation-id'];
if (correlationId) {
console.error(`[${correlationId}] Error:`, error.message);
}
return Promise.reject(error);
},
);
# Run existing tests (if any)
pnpm test
# Check for TypeScript errors
pnpm build:mgnt
[ ] Login Flow
success: truesuccess: false[ ] 2FA Flow
[ ] Error Responses
[ ] Correlation IDs
x-request-id headerx-request-id is preserved[ ] Configuration
JWT_SECRETMYSQL_URL# Backup database
mysqldump -u user -p dbname > backup_$(date +%Y%m%d).sql
# Tag current version
git tag -a v1.0.0-pre-refactor -m "Pre-refactor backup"
git push origin v1.0.0-pre-refactor
# Pull latest code
git pull origin main
# Install dependencies
pnpm install
# Generate Prisma clients
pnpm prisma:generate
# Build application
pnpm build:mgnt
# Update environment variables
# (copy from Pre-Deployment Steps #1)
nano .env.mgnt.dev
# Start application
pm2 restart box-mgnt-api
# OR
pnpm start:mgnt
# Watch application logs
pm2 logs box-mgnt-api
# Check for errors
tail -f logs/error.log
# Verify correlation IDs in logs
grep "x-request-id" logs/combined.log
# Health check
curl http://localhost:3000/health
# Test login (should be rate limited after 10 attempts)
for i in {1..12}; do
curl -X POST http://localhost:3000/mgnt/auth/login \
-H "Content-Type: application/json" \
-H "x-request-id: smoke-test-$i" \
-d '{"username":"testuser","password":"wrong"}'
echo "\n--- Request $i ---"
sleep 1
done
# Should see HTTP 429 on requests 11-12
Error Rates
Rate Limiting
Response Times
Authentication
# Count rate limit violations
grep "Rate limit exceeded" logs/app.log | wc -l
# Find slow requests (>1s)
grep "响应.*+[0-9]{4,}ms" logs/app.log
# Track correlation IDs for debugging
grep "550e8400-e29b-41d4-a716-446655440000" logs/app.log
If critical issues occur:
# Revert to previous version
git revert HEAD --no-edit
pnpm install
pnpm build:mgnt
pm2 restart box-mgnt-api
# Restore from backup tag
git reset --hard v1.0.0-pre-refactor
pnpm install
pnpm prisma:generate
pnpm build:mgnt
pm2 restart box-mgnt-api
# Revert frontend changes to old response format
cd frontend-app
git revert <commit-hash>
pnpm install
pnpm build
pm2 restart frontend-app
Solution: Add JWT_SECRET to your .env file with a secure random string
Solution: Edit libs/common/src/guards/rate-limit.guard.ts:
private readonly limit = 20 // Increase from 10
private readonly windowMs = 120_000 // Increase to 2 minutes
Solution: Ensure CORS is properly configured in main.ts:
app.enableCors({
origin: true,
credentials: true,
exposedHeaders: ['x-request-id', 'x-correlation-id'],
});
Solution: Update Pino config to include correlation ID:
// libs/common/src/config/pino.config.ts
serializers: {
req(req) {
return {
...req,
correlationId: req.correlationId
}
}
}
Replace in-memory rate limiter with Redis:
// Install redis client
pnpm add ioredis
// Update rate-limit.guard.ts
import Redis from 'ioredis'
const redis = new Redis(process.env.REDIS_URL)
async canActivate(context: ExecutionContext): Promise<boolean> {
const key = `ratelimit:${ip}:${endpoint}`
const count = await redis.incr(key)
if (count === 1) {
await redis.expire(key, windowSeconds)
}
if (count > this.limit) {
const ttl = await redis.ttl(key)
throw new HttpException(...)
}
return true
}
Add Redis caching for frequently accessed data:
// Cache user permissions for 5 minutes
const cacheKey = `user:${userId}:permissions`;
let permissions = await redis.get(cacheKey);
if (!permissions) {
permissions = await this.fetchPermissions(userId);
await redis.setex(cacheKey, 300, JSON.stringify(permissions));
}
return JSON.parse(permissions);