gongxiangp2/lib/psd-processor.ts
chenpei 383772d39f 🛡️ 服务器安全加固和FRP问题修复
主要更新:
1. FRP服务器诊断与修复
   - 发现并修复文件描述符耗尽问题(65,536 → 1,048,576)
   - 分析并解决用户上传失败问题
   - 确认SSH服务器安全未被攻破

2. 安全防护系统部署
   - 部署IP黑名单防火墙(封禁20+攻击者)
   - 设置SSH端口速率限制
   - 配置自动化定时任务(每小时更新)
   - 安装Fail2ban实时防护系统
   - 添加白名单保护机制

3. 内网访问优化
   - 改进内网检测逻辑
   - 添加网络状态API
   - 优化钉钉登录流程
   - 完善信息补充功能

4. 文档完善
   - FRP服务器诊断修复报告
   - SSH安全评估报告
   - Fail2ban使用指南
   - 文件描述符详解
   - 用户上传失败原因分析
   - 防火墙配置说明
   - 内网检测改进报告

修复的问题:
- ✅ 文件描述符耗尽导致的服务异常
- ✅ 用户上传失败(影响9.5小时)
- ✅ SSH暴力破解攻击(91,299+次)
- ✅ 第三方项目攻击影响
- ✅ 127.0.0.1误封禁问题

部署的安全措施:
- 三重防护体系(Fail2ban + 防火墙 + 强密码)
- 自动化监控和封禁
- 实时攻击拦截(秒级响应)
- 完整的管理和监控工具

测试环境:开发环境
影响范围:服务器安全、用户上传、系统稳定性
当前状态:✅ 所有问题已修复,系统运行正常
2025-11-06 13:42:36 +08:00

178 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { readPsd, initializeCanvas } from 'ag-psd'
import sharp from 'sharp'
import fs from 'fs/promises'
// 尝试初始化 canvas(如果可用)
try {
// 动态导入 canvas(可选依赖)
const canvas = require('canvas')
if (canvas && canvas.createCanvas) {
initializeCanvas(canvas.createCanvas)
console.log('[PSD] Canvas initialized successfully')
}
} catch (error) {
console.log('[PSD] Canvas not available, using fallback method')
}
interface PsdProcessResult {
success: boolean
previewPath?: string
width?: number
height?: number
error?: string
}
/**
* 从PSD文件生成预览图
* @param psdFilePath PSD文件路径
* @param outputPath 输出预览图路径
* @param maxSize 最大尺寸(宽或高),默认800px
* @param maxHeight 最大高度限制,长图裁剪,默认1200px
* @returns 处理结果
*/
export async function generatePsdPreview(
psdFilePath: string,
outputPath: string,
maxSize: number = 800,
maxHeight: number = 1200
): Promise<PsdProcessResult> {
try {
// 读取PSD文件
const psdBuffer = await fs.readFile(psdFilePath)
console.log(`[PSD] 开始解析PSD文件,大小: ${psdBuffer.length} bytes`)
// 解析PSD - 不跳过合成图像数据
const psd = readPsd(psdBuffer, {
skipLayerImageData: true, // 跳过图层数据
skipCompositeImageData: false, // 不跳过合成图像数据(关键!)
skipThumbnail: false
})
if (!psd) {
return {
success: false,
error: 'Failed to parse PSD file'
}
}
console.log(`[PSD] 解析成功,尺寸: ${psd.width}x${psd.height}`)
// 获取PSD的宽高
const width = psd.width || 0
const height = psd.height || 0
if (width === 0 || height === 0) {
return {
success: false,
error: 'Invalid PSD dimensions'
}
}
// 检查是否有合成图像数据
if (!psd.imageData || !psd.imageData.data) {
console.log('[PSD] 未找到合成图像数据')
return {
success: false,
error: 'No composite image data found in PSD file. Please manually upload a preview image.'
}
}
console.log(`[PSD] 找到合成图像数据: ${psd.imageData.width}x${psd.imageData.height}`)
const imageData = psd.imageData
// 确保有图像数据
if (!imageData.data || imageData.data.length === 0) {
return {
success: false,
error: 'Image data is empty'
}
}
console.log(`[PSD] 图像数据大小: ${imageData.data.length} bytes`)
// 将PSD图像数据转换为Buffer
const rawBuffer = Buffer.from(imageData.data)
// 创建Sharp实例
let sharpInstance = sharp(rawBuffer, {
raw: {
width: imageData.width,
height: imageData.height,
channels: 4 // RGBA
}
})
// 如果是长图(高度超过maxHeight),先裁剪顶部区域
if (imageData.height > maxHeight) {
console.log(`[PSD] 检测到长图 ${imageData.width}x${imageData.height},裁剪到顶部${maxHeight}px`)
sharpInstance = sharpInstance.extract({
left: 0,
top: 0,
width: imageData.width,
height: maxHeight
})
}
// 然后进行缩放处理
await sharpInstance
.resize(maxSize, maxSize, {
fit: 'inside',
withoutEnlargement: true
})
.jpeg({
quality: 90,
mozjpeg: true
})
.toFile(outputPath)
console.log(`[PSD] 预览图生成成功: ${outputPath}`)
return {
success: true,
previewPath: outputPath,
width,
height
}
} catch (error) {
console.error('Error generating PSD preview:', error)
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
}
}
}
/**
* 检查文件是否为PSD格式
* @param filePath 文件路径
* @returns 是否为PSD文件
*/
export async function isPsdFile(filePath: string): Promise<boolean> {
try {
const buffer = await fs.readFile(filePath, { encoding: null })
// PSD文件签名:8BPS
return buffer.length > 4 &&
buffer[0] === 0x38 && // '8'
buffer[1] === 0x42 && // 'B'
buffer[2] === 0x50 && // 'P'
buffer[3] === 0x53 // 'S'
} catch {
return false
}
}
/**
* 从文件扩展名判断是否为PSD相关格式
* @param filename 文件名
* @returns 是否为PSD相关文件(支持.psd、.tif、.tiff、.psb)
*/
export function hasPsdExtension(filename: string): boolean {
const lowerName = filename.toLowerCase()
return lowerName.endsWith('.psd') ||
lowerName.endsWith('.tif') ||
lowerName.endsWith('.tiff') ||
lowerName.endsWith('.psb')
}