// 加载环境变量（必须在最开始）
require('dotenv').config();

const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const svgCaptcha = require('svg-captcha');
const multer = require('multer');
const nodemailer = require('nodemailer');
const path = require('path');
const fs = require('fs');
const zlib = require('zlib');
const { body, validationResult } = require('express-validator');
const archiver = require('archiver');
const crypto = require('crypto');
const { exec, execSync, execFile } = require('child_process');
const util = require('util');
const execAsync = util.promisify(exec);
const execFileAsync = util.promisify(execFile);

// ===== OSS 使用情况缓存 =====
// 缓存 OSS 空间统计结果，避免频繁遍历对象
const OSS_USAGE_CACHE = new Map();
const OSS_USAGE_CACHE_TTL = 5 * 60 * 1000; // 5分钟缓存

/**
 * 获取缓存的 OSS 使用情况
 * @param {number} userId - 用户ID
 * @returns {object|null} 缓存的数据或 null
 */
function getOssUsageCache(userId) {
  const cacheKey = `oss_usage_${userId}`;
  const cached = OSS_USAGE_CACHE.get(cacheKey);
  if (cached && Date.now() - cached.timestamp < OSS_USAGE_CACHE_TTL) {
    console.log(`[OSS缓存] 命中缓存: 用户 ${userId}`);
    return cached.data;
  }
  return null;
}

/**
 * 设置 OSS 使用情况缓存
 * @param {number} userId - 用户ID
 * @param {object} data - 使用情况数据
 */
function setOssUsageCache(userId, data) {
  const cacheKey = `oss_usage_${userId}`;
  OSS_USAGE_CACHE.set(cacheKey, {
    data,
    timestamp: Date.now()
  });
  console.log(`[OSS缓存] 已缓存: 用户 ${userId}, 大小: ${data.totalSize}`);
}

/**
 * 清除用户的 OSS 使用情况缓存
 * @param {number} userId - 用户ID
 */
function clearOssUsageCache(userId) {
  const cacheKey = `oss_usage_${userId}`;
  OSS_USAGE_CACHE.delete(cacheKey);
  console.log(`[OSS缓存] 已清除: 用户 ${userId}`);
}

const {
  db,
  UserDB,
  ShareDB,
  DirectLinkDB,
  SettingsDB,
  VerificationDB,
  PasswordResetTokenDB,
  DownloadTrafficReportDB,
  DownloadTrafficReservationDB,
  UploadSessionDB,
  DeviceSessionDB,
  FileHashIndexDB,
  DownloadTrafficIngestDB,
  SystemLogDB,
  TransactionDB,
  WalManager
} = require('./database');
const StorageUsageCache = require('./utils/storage-cache');
const {
  JWT_SECRET,
  generateToken,
  generateRefreshToken,
  decodeAccessToken,
  decodeRefreshToken,
  refreshAccessToken,
  authMiddleware,
  adminMiddleware,
  isJwtSecretSecure
} = require('./auth');
const { StorageInterface, LocalStorageClient, OssStorageClient, formatFileSize, formatOssError } = require('./storage');
const { encryptSecret, decryptSecret } = require('./utils/encryption');

const app = express();
const PORT = process.env.PORT || 40001;
const USERNAME_REGEX = /^[A-Za-z0-9_.\u4e00-\u9fa5-]{3,20}$/u; // 允许中英文、数字、下划线、点和短横线
const ENFORCE_HTTPS = process.env.ENFORCE_HTTPS === 'true';
const DEFAULT_LOCAL_STORAGE_QUOTA_BYTES = 1024 * 1024 * 1024; // 1GB
const DEFAULT_OSS_STORAGE_QUOTA_BYTES = 1024 * 1024 * 1024; // 1GB
const MAX_DOWNLOAD_TRAFFIC_BYTES = 10 * 1024 * 1024 * 1024 * 1024; // 10TB
const DOWNLOAD_POLICY_SWEEP_INTERVAL_MS = 30 * 60 * 1000; // 30分钟
const DOWNLOAD_RESERVATION_TTL_MS = Number(process.env.DOWNLOAD_RESERVATION_TTL_MS || (30 * 60 * 1000)); // 30分钟
const DOWNLOAD_LOG_RECONCILE_INTERVAL_MS = Number(process.env.DOWNLOAD_LOG_RECONCILE_INTERVAL_MS || (5 * 60 * 1000)); // 5分钟
const DOWNLOAD_LOG_MAX_FILES_PER_SWEEP = Number(process.env.DOWNLOAD_LOG_MAX_FILES_PER_SWEEP || 40);
const DOWNLOAD_LOG_LIST_MAX_KEYS = Number(process.env.DOWNLOAD_LOG_LIST_MAX_KEYS || 200);
const DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS = Math.max(
  10,
  Math.min(3600, Number(process.env.DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS || 30))
);
const DEFAULT_DESKTOP_VERSION = process.env.DESKTOP_LATEST_VERSION || '0.1.28';
const DEFAULT_DESKTOP_INSTALLER_URL = process.env.DESKTOP_INSTALLER_URL || '';
const DEFAULT_DESKTOP_INSTALLER_SHA256 = String(process.env.DESKTOP_INSTALLER_SHA256 || '').trim().toLowerCase();
const DEFAULT_DESKTOP_INSTALLER_SIZE = Math.max(0, Number(process.env.DESKTOP_INSTALLER_SIZE || 0));
const DEFAULT_DESKTOP_RELEASE_NOTES = process.env.DESKTOP_RELEASE_NOTES || '';
const FRONTEND_ROOT_DIR = path.resolve(__dirname, '../frontend');
const DESKTOP_INSTALLERS_DIR = path.resolve(__dirname, '../frontend/downloads');
const DESKTOP_INSTALLER_FILE_PATTERN = /^(wanwan-cloud-desktop|玩玩云)_.*_x64-setup\.exe$/i;
const DESKTOP_INSTALLER_SHA256_PATTERN = /^[a-f0-9]{64}$/;
const RESUMABLE_UPLOAD_SESSION_TTL_MS = Number(process.env.RESUMABLE_UPLOAD_SESSION_TTL_MS || (24 * 60 * 60 * 1000)); // 24小时
const RESUMABLE_UPLOAD_CHUNK_SIZE_BYTES = Number(process.env.RESUMABLE_UPLOAD_CHUNK_SIZE_BYTES || (4 * 1024 * 1024)); // 4MB
const RESUMABLE_UPLOAD_MAX_CHUNK_SIZE_BYTES = Number(process.env.RESUMABLE_UPLOAD_MAX_CHUNK_SIZE_BYTES || (32 * 1024 * 1024)); // 32MB
const GLOBAL_SEARCH_DEFAULT_LIMIT = Number(process.env.GLOBAL_SEARCH_DEFAULT_LIMIT || 80);
const GLOBAL_SEARCH_MAX_LIMIT = Number(process.env.GLOBAL_SEARCH_MAX_LIMIT || 200);
const GLOBAL_SEARCH_MAX_SCANNED_NODES = Number(process.env.GLOBAL_SEARCH_MAX_SCANNED_NODES || 4000);
const SHARE_CODE_REGEX = /^[A-Za-z0-9]{6,32}$/;
const DOWNLOAD_SECURITY_DEFAULTS = Object.freeze({
  enabled: true,
  same_ip_same_file: {
    enabled: true,
    limit_5m: 3,
    limit_1h: 10,
    limit_1d: 20
  },
  same_ip_same_user: {
    enabled: false,
    limit_1h: 80,
    limit_1d: 300
  },
  same_ip_same_file_min_interval: {
    enabled: false,
    seconds: 2
  }
});
const COOKIE_SECURE_MODE = String(process.env.COOKIE_SECURE || '').toLowerCase();
const SHOULD_USE_SECURE_COOKIES =
  COOKIE_SECURE_MODE === 'true' ||
  (process.env.NODE_ENV === 'production' && COOKIE_SECURE_MODE !== 'false');

function normalizeVersion(rawVersion, fallback = '0.0.0') {
  const value = String(rawVersion || '').trim();
  if (!value) return fallback;
  return value;
}

function compareLooseVersion(left, right) {
  const normalize = (value) => normalizeVersion(value, '0.0.0')
    .replace(/^v/i, '')
    .split('.')
    .map((part) => parseInt(part, 10))
    .map((num) => (Number.isFinite(num) ? num : 0));

  const a = normalize(left);
  const b = normalize(right);
  const size = Math.max(a.length, b.length);
  for (let i = 0; i < size; i += 1) {
    const av = a[i] || 0;
    const bv = b[i] || 0;
    if (av > bv) return 1;
    if (av < bv) return -1;
  }
  return 0;
}

function normalizeReleaseNotes(rawValue) {
  return String(rawValue || '')
    .replace(/\\r\\n/g, '\n')
    .replace(/\\n/g, '\n')
    .replace(/\\r/g, '\n')
    .trim();
}

function normalizeSha256(rawValue) {
  const digest = String(rawValue || '').trim().toLowerCase();
  return DESKTOP_INSTALLER_SHA256_PATTERN.test(digest) ? digest : '';
}

function normalizeNonNegativeInteger(rawValue, fallback = 0) {
  const value = Number(rawValue);
  if (!Number.isFinite(value) || value < 0) return Math.max(0, Number(fallback) || 0);
  return Math.floor(value);
}

function isPathInside(parent, child) {
  const rel = path.relative(parent, child);
  return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
}

function resolveDesktopInstallerLocalPath(installerUrl) {
  const raw = String(installerUrl || '').trim();
  if (!raw) return null;

  try {
    const parsed = new URL(raw, 'http://local.invalid');
    const pathname = decodeURIComponent(parsed.pathname || '');
    const normalizedPath = pathname.replace(/^\/+/, '');
    if (!normalizedPath) return null;

    const preferredPath = path.resolve(FRONTEND_ROOT_DIR, normalizedPath);
    if (isPathInside(FRONTEND_ROOT_DIR, preferredPath) && fs.existsSync(preferredPath) && fs.statSync(preferredPath).isFile()) {
      return preferredPath;
    }

    const fallbackPath = path.resolve(FRONTEND_ROOT_DIR, path.basename(normalizedPath));
    if (isPathInside(FRONTEND_ROOT_DIR, fallbackPath) && fs.existsSync(fallbackPath) && fs.statSync(fallbackPath).isFile()) {
      return fallbackPath;
    }
  } catch (error) {
    // ignore malformed installer url
  }
  return null;
}

function computeFileSha256HexSync(filePath) {
  const hash = crypto.createHash('sha256');
  const content = fs.readFileSync(filePath);
  hash.update(content);
  return hash.digest('hex');
}

function getLocalDesktopInstallerMeta(installerUrl) {
  const localPath = resolveDesktopInstallerLocalPath(installerUrl);
  if (!localPath) return null;

  try {
    const stats = fs.statSync(localPath);
    if (!stats.isFile() || stats.size <= 0) return null;
    return {
      path: localPath,
      size: stats.size,
      sha256: normalizeSha256(computeFileSha256HexSync(localPath))
    };
  } catch (error) {
    console.warn('[桌面端更新] 读取本地安装包元数据失败:', error.message);
    return null;
  }
}

function getDesktopUpdateConfig() {
  const latestVersion = normalizeVersion(
    SettingsDB.get('desktop_latest_version') || DEFAULT_DESKTOP_VERSION,
    DEFAULT_DESKTOP_VERSION
  );
  const installerUrl = String(
    SettingsDB.get('desktop_installer_url_win_x64') ||
    SettingsDB.get('desktop_installer_url') ||
    DEFAULT_DESKTOP_INSTALLER_URL ||
    ''
  ).trim();
  const releaseNotes = normalizeReleaseNotes(
    SettingsDB.get('desktop_release_notes') ||
    DEFAULT_DESKTOP_RELEASE_NOTES ||
    ''
  );
  const mandatory = SettingsDB.get('desktop_force_update') === 'true';
  let installerSha256 = normalizeSha256(
    SettingsDB.get('desktop_installer_sha256') ||
    DEFAULT_DESKTOP_INSTALLER_SHA256
  );
  let packageSize = normalizeNonNegativeInteger(
    SettingsDB.get('desktop_installer_size'),
    DEFAULT_DESKTOP_INSTALLER_SIZE
  );
  if (installerUrl && (!installerSha256 || packageSize <= 0)) {
    const localMeta = getLocalDesktopInstallerMeta(installerUrl);
    if (localMeta) {
      if (!installerSha256 && localMeta.sha256) {
        installerSha256 = localMeta.sha256;
      }
      if (packageSize <= 0 && localMeta.size > 0) {
        packageSize = localMeta.size;
      }
    }
  }
  return {
    latestVersion,
    installerUrl,
    installerSha256,
    packageSize,
    releaseNotes,
    mandatory
  };
}

function resolveDesktopInstallerFileName(installerUrl) {
  const raw = String(installerUrl || '').trim();
  if (!raw) return null;

  try {
    const parsed = new URL(raw, 'http://local.invalid');
    const pathname = decodeURIComponent(parsed.pathname || '');
    const fileName = path.basename(pathname);
    return fileName && fileName !== '.' && fileName !== '/' ? fileName : null;
  } catch (error) {
    const sanitized = raw.split('?')[0].split('#')[0];
    const fileName = path.basename(sanitized);
    return fileName && fileName !== '.' && fileName !== '/' ? fileName : null;
  }
}

function cleanupDesktopInstallerPackages(installerUrl) {
  try {
    const keepFileName = resolveDesktopInstallerFileName(installerUrl);
    if (!keepFileName || !DESKTOP_INSTALLER_FILE_PATTERN.test(keepFileName)) {
      return { executed: false, removed: 0, kept: 0, reason: 'installer_url_not_local_package' };
    }

    if (!fs.existsSync(DESKTOP_INSTALLERS_DIR)) {
      return { executed: false, removed: 0, kept: 0, reason: 'downloads_dir_missing' };
    }

    const candidates = fs.readdirSync(DESKTOP_INSTALLERS_DIR)
      .filter((name) => DESKTOP_INSTALLER_FILE_PATTERN.test(name));
    const keepFilePath = path.join(DESKTOP_INSTALLERS_DIR, keepFileName);

    if (!fs.existsSync(keepFilePath)) {
      return { executed: false, removed: 0, kept: candidates.length, reason: 'keep_package_missing' };
    }

    let removed = 0;
    for (const fileName of candidates) {
      if (fileName === keepFileName) continue;
      fs.unlinkSync(path.join(DESKTOP_INSTALLERS_DIR, fileName));
      removed += 1;
      console.log(`[桌面端更新] 已清理旧安装包: ${fileName}`);
    }

    return {
      executed: true,
      removed,
      kept: candidates.length - removed,
      keep_file: keepFileName
    };
  } catch (error) {
    console.error('[桌面端更新] 清理旧安装包失败:', error);
    return {
      executed: false,
      removed: 0,
      kept: 0,
      reason: 'cleanup_failed',
      error: error.message
    };
  }
}

function getResolvedStorageRoot() {
  const configuredRoot = process.env.STORAGE_ROOT;
  if (!configuredRoot) {
    return path.join(__dirname, 'storage');
  }
  return path.isAbsolute(configuredRoot)
    ? configuredRoot
    : path.resolve(__dirname, configuredRoot);
}

function getLocalUserStorageDir(userId) {
  return path.join(getResolvedStorageRoot(), `user_${userId}`);
}

function syncLocalStorageUsageFromDisk(userId, currentUsed = 0) {
  const userStorageDir = getLocalUserStorageDir(userId);
  if (!fs.existsSync(userStorageDir)) {
    fs.mkdirSync(userStorageDir, { recursive: true, mode: 0o755 });
  }

  const actualUsed = getUserDirectorySize(userStorageDir);
  const normalizedCurrentUsed = Number(currentUsed) || 0;

  if (actualUsed !== normalizedCurrentUsed) {
    UserDB.update(userId, { local_storage_used: actualUsed });
    console.log(`[本地存储] 已校准用户 ${userId} 本地用量: ${normalizedCurrentUsed} -> ${actualUsed}`);
  }

  return actualUsed;
}

if (process.env.NODE_ENV === 'production' && COOKIE_SECURE_MODE !== 'true') {
  console.warn('[安全警告] 生产环境建议设置 COOKIE_SECURE=true，以避免会话Cookie在HTTP下传输');
}

// ===== 安全配置：公开域名白名单（防止 Host Header 注入） =====
// 必须配置 PUBLIC_BASE_URL 环境变量，用于生成邮件链接和分享链接
// 例如: PUBLIC_BASE_URL=https://cloud.example.com
const PUBLIC_BASE_URL = process.env.PUBLIC_BASE_URL || null;
const ALLOWED_HOSTS = process.env.ALLOWED_HOSTS
  ? process.env.ALLOWED_HOSTS.split(',').map(h => h.trim().toLowerCase())
  : [];

// 获取安全的基础URL（用于生成邮件链接、分享链接等）
function getSecureBaseUrl(req) {
  // 优先使用配置的公开域名
  if (PUBLIC_BASE_URL) {
    return PUBLIC_BASE_URL.replace(/\/+$/, ''); // 移除尾部斜杠
  }

  // 如果没有配置，验证 Host 头是否在白名单中
  const host = (req.get('host') || '').toLowerCase();
  if (ALLOWED_HOSTS.length > 0 && !ALLOWED_HOSTS.includes(host)) {
    console.warn(`[安全警告] 检测到非白名单 Host 头: ${host}`);
    // 返回第一个白名单域名作为后备
    const protocol = getProtocol(req);
    return `${protocol}://${ALLOWED_HOSTS[0]}`;
  }

  // 开发环境回退（仅在没有配置时使用）
  if (process.env.NODE_ENV !== 'production') {
    return `${getProtocol(req)}://${req.get('host')}`;
  }

  // 生产环境没有配置时，记录警告并使用请求的 Host（不推荐）
  console.error('[安全警告] 生产环境未配置 PUBLIC_BASE_URL，存在 Host Header 注入风险！');
  return `${getProtocol(req)}://${req.get('host')}`;
}

// ===== 安全配置：信任代理 =====
// 默认不信任任何代理（直接暴露场景）
// 配置选项：
//   - false: 不信任代理（默认，直接暴露）
//   - true: 信任所有代理（不推荐，易被伪造）
//   - 1/2/3: 信任前N跳代理（推荐，如 Nginx 后部署用 1）
//   - 'loopback': 仅信任本地回环地址
//   - '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16': 信任指定IP/CIDR段
const TRUST_PROXY_RAW = process.env.TRUST_PROXY;
let trustProxyValue = false; // 默认不信任

if (TRUST_PROXY_RAW !== undefined && TRUST_PROXY_RAW !== '') {
  if (TRUST_PROXY_RAW === 'true') {
    trustProxyValue = true;
    console.warn('[安全警告] TRUST_PROXY=true 将信任所有代理，存在 IP/协议伪造风险！建议设置为具体跳数(1)或IP段');
  } else if (TRUST_PROXY_RAW === 'false') {
    trustProxyValue = false;
  } else if (/^\d+$/.test(TRUST_PROXY_RAW)) {
    // 数字：信任前N跳
    trustProxyValue = parseInt(TRUST_PROXY_RAW, 10);
  } else {
    // 字符串：loopback 或 IP/CIDR 列表
    trustProxyValue = TRUST_PROXY_RAW;
  }
}

app.set('trust proxy', trustProxyValue);
console.log(`[安全] trust proxy 配置: ${JSON.stringify(trustProxyValue)}`);

// 配置CORS - 严格白名单模式
const rawAllowedOrigins = process.env.ALLOWED_ORIGINS
  ? process.env.ALLOWED_ORIGINS.split(',').map(origin => origin.trim()).filter(Boolean)
  : [];
const wildcardOriginConfigured = rawAllowedOrigins.includes('*');
const allowAllOriginsForDev = wildcardOriginConfigured && process.env.NODE_ENV !== 'production';
const allowedOrigins = rawAllowedOrigins.filter(origin => origin !== '*');

if (wildcardOriginConfigured && process.env.NODE_ENV === 'production') {
  console.error('❌ 错误: 生产环境禁止 ALLOWED_ORIGINS=*。请改为明确的域名白名单。');
}

const corsOptions = {
  credentials: true,
  origin: (origin, callback) => {
    // 生产环境禁止通配符（credentials=true 时会导致任意来源携带Cookie）
    if (wildcardOriginConfigured && process.env.NODE_ENV === 'production') {
      callback(new Error('生产环境不允许 CORS 通配符配置'));
      return;
    }

    // 生产环境必须配置白名单
    if (allowedOrigins.length === 0 && !allowAllOriginsForDev && process.env.NODE_ENV === 'production') {
      console.error('❌ 错误: 生产环境必须配置 ALLOWED_ORIGINS 环境变量！');
      callback(new Error('CORS未配置'));
      return;
    }

    // 开发环境如果没有配置，允许 localhost
    if (allowedOrigins.length === 0 && !allowAllOriginsForDev) {
      const devOrigins = ['http://localhost:3000', 'http://127.0.0.1:3000'];
      if (!origin || devOrigins.some(o => origin.startsWith(o))) {
        callback(null, true);
        return;
      }
    }

    // 允许没有Origin头的同源请求和服务器请求
    if (!origin) {
      callback(null, true);
      return;
    }

    if (allowedOrigins.includes(origin) || allowAllOriginsForDev) {
      callback(null, true);
      return;
    }

    // 拒绝不在白名单中的跨域请求
    console.warn(`[CORS] 拒绝来自未授权来源的请求: ${origin}`);
    callback(new Error('CORS策略不允许来自该来源的访问'));
  }
};

function applySecurityHeaders(req, res) {
  // 防止点击劫持
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  // 防止MIME类型嗅探
  res.setHeader('X-Content-Type-Options', 'nosniff');
  // XSS保护
  res.setHeader('X-XSS-Protection', '1; mode=block');
  // HTTPS严格传输安全（仅在可信的 HTTPS 连接时设置）
  // req.secure 基于 trust proxy 配置，不会被不可信代理伪造
  if ((req && req.secure) || (!req && (ENFORCE_HTTPS || SHOULD_USE_SECURE_COOKIES))) {
    res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  }
  // 内容安全策略
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https: ws: wss:; object-src 'none'; base-uri 'self'; frame-ancestors 'self';");
  // 隐藏X-Powered-By
  res.removeHeader('X-Powered-By');
}

// 中间件
app.use(cors(corsOptions));

// 静态文件服务 - 提供前端页面
const frontendPath = path.join(__dirname, '../frontend');
console.log('[静态文件] 前端目录:', frontendPath);
app.use(express.static(frontendPath, {
  setHeaders: (res) => {
    applySecurityHeaders(null, res);
  }
}));

app.use(express.json({ limit: '10mb' })); // 限制请求体大小防止DoS
app.use(cookieParser());

// ===== CSRF 防护 =====
// 基于 Double Submit Cookie 模式的 CSRF 保护
// 对于修改数据的请求（POST/PUT/DELETE），验证请求头中的 X-CSRF-Token 与 Cookie 中的值匹配

// 生成 CSRF Token
function generateCsrfToken() {
  return crypto.randomBytes(32).toString('hex');
}

// CSRF Token Cookie 名称
const CSRF_COOKIE_NAME = 'csrf_token';

// 设置 CSRF Cookie 的中间件
app.use((req, res, next) => {
  // 如果没有 CSRF cookie，则生成一个
  if (!req.cookies[CSRF_COOKIE_NAME]) {
    const csrfToken = generateCsrfToken();
    const isSecureEnv = SHOULD_USE_SECURE_COOKIES;
    res.cookie(CSRF_COOKIE_NAME, csrfToken, {
      httpOnly: false, // 前端需要读取此值
      secure: isSecureEnv,
      sameSite: isSecureEnv ? 'strict' : 'lax',
      maxAge: 24 * 60 * 60 * 1000 // 24小时
    });
  }
  next();
});

// CSRF 验证中间件（仅用于需要保护的路由）
function csrfProtection(req, res, next) {
  // GET、HEAD、OPTIONS 请求不需要 CSRF 保护
  if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
    return next();
  }

  // 仅对基于 Cookie 的浏览器会话启用 CSRF（Bearer API 客户端不强制）
  const hasCookieAuth = !!(
    req.cookies?.token ||
    req.cookies?.refreshToken
  );

  if (!hasCookieAuth) {
    return next();
  }

  const cookieToken = req.cookies[CSRF_COOKIE_NAME];
  const headerToken = req.headers['x-csrf-token'];

  if (!cookieToken || !headerToken || cookieToken !== headerToken) {
    console.warn(`[CSRF] 验证失败: path=${req.path}, cookie=${!!cookieToken}, header=${!!headerToken}`);
    return res.status(403).json({
      success: false,
      message: 'CSRF 验证失败，请刷新页面后重试'
    });
  }

  next();
}

// CSRF 开关策略：
// - 显式配置 ENABLE_CSRF 时按配置值
// - 未配置时，生产环境默认开启
const ENABLE_CSRF = process.env.ENABLE_CSRF !== undefined
  ? process.env.ENABLE_CSRF === 'true'
  : process.env.NODE_ENV === 'production';

if (ENABLE_CSRF) {
  console.log('[安全] CSRF 保护已启用');
  app.use(csrfProtection);
}

// 强制HTTPS（可通过环境变量控制，默认关闭以兼容本地环境）
// 安全说明：使用 req.secure 判断，该值基于 trust proxy 配置，
// 只有在信任代理链中的代理才会被采信其 X-Forwarded-Proto 头
app.use((req, res, next) => {
  if (!ENFORCE_HTTPS) return next();

  // req.secure 由 Express 根据 trust proxy 配置计算：
  // - 如果 trust proxy = false，仅检查直接连接是否为 TLS
  // - 如果 trust proxy 已配置，会检查可信代理的 X-Forwarded-Proto
  if (!req.secure) {
    return res.status(400).json({
      success: false,
      message: '仅支持HTTPS访问，请使用HTTPS'
    });
  }
  return next();
});

// Session配置（用于验证码）
const isSecureCookie = SHOULD_USE_SECURE_COOKIES;
const sameSiteMode = isSecureCookie ? 'none' : 'lax'; // HTTPS下允许跨域获取验证码

// 安全检查：Session密钥配置
const SESSION_SECRET = process.env.SESSION_SECRET || 'your-session-secret-change-in-production';
const DEFAULT_SESSION_SECRETS = [
  'your-session-secret-change-in-production',
  'session-secret-change-me'
];

if (DEFAULT_SESSION_SECRETS.includes(SESSION_SECRET)) {
  const sessionWarnMsg = `
[安全警告] SESSION_SECRET 使用默认值，存在安全风险！
请在 .env 文件中设置随机生成的 SESSION_SECRET
生成命令: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
`;
  if (process.env.NODE_ENV === 'production') {
    console.error(sessionWarnMsg);
    throw new Error('生产环境必须设置 SESSION_SECRET！');
  } else {
    console.warn(sessionWarnMsg);
  }
}

app.use(session({
  secret: SESSION_SECRET,
  resave: false,
  saveUninitialized: false, // 仅在写入 session 时创建，减少无效会话
  name: 'captcha.sid', // 自定义session cookie名称
  cookie: {
    secure: isSecureCookie,
    httpOnly: true,
    sameSite: sameSiteMode,
    maxAge: 10 * 60 * 1000 // 10分钟
  }
}));

// 安全响应头中间件
app.use((req, res, next) => {
  applySecurityHeaders(req, res);
  next();
});

/**
 * XSS过滤函数 - 过滤用户输入中的潜在XSS攻击代码
 * 注意：不转义 / 因为它是文件路径的合法字符
 * @param {string} str - 需要过滤的输入字符串
 * @returns {string} 过滤后的安全字符串
 */
function sanitizeInput(str) {
  if (typeof str !== 'string') return str;

  // 1. 基础HTML实体转义（不包括 / 因为是路径分隔符，不包括 ` 因为是合法文件名字符）
  let sanitized = str
    .replace(/[&<>"']/g, (char) => {
      const map = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#x27;'
      };
      return map[char];
    });

  // 2. 过滤危险协议（javascript:, data:, vbscript:等）
  sanitized = sanitized.replace(/(?:javascript|data|vbscript|expression|on\w+)\s*:/gi, '');

  // 3. 移除空字节
  sanitized = sanitized.replace(/\x00/g, '');

  return sanitized;
}

/**
 * 将 HTML 实体解码为原始字符
 * 用于处理经过XSS过滤后的文件名/路径字段，恢复原始字符
 * 支持嵌套实体的递归解码（如 &amp;#x60; -> &#x60; -> `）
 * @param {string} str - 包含HTML实体的字符串
 * @returns {string} 解码后的原始字符串
 */
function decodeHtmlEntities(str) {
  if (typeof str !== 'string') return str;

  // 支持常见实体和数字实体（含多次嵌套，如 &amp;#x60;）
  const entityMap = {
    amp: '&',
    lt: '<',
    gt: '>',
    quot: '"',
    apos: "'",
    '#x27': "'",
    '#x2F': '/',
    '#x60': '`'
  };

  const decodeOnce = (input) =>
    input.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g, (match, code) => {
      if (code[0] === '#') {
        const isHex = code[1]?.toLowerCase() === 'x';
        const num = isHex ? parseInt(code.slice(2), 16) : parseInt(code.slice(1), 10);
        if (!Number.isNaN(num)) {
          return String.fromCharCode(num);
        }
        return match;
      }
      const mapped = entityMap[code];
      return mapped !== undefined ? mapped : match;
    });

  let output = str;
  let decoded = decodeOnce(output);
  // 处理嵌套实体（如 &amp;#x60;），直到稳定
  while (decoded !== output) {
    output = decoded;
    decoded = decodeOnce(output);
  }
  return output;
}

// HTML转义（用于模板输出）
function escapeHtml(str) {
  if (typeof str !== 'string') return str;
  return str.replace(/[&<>"']/g, char => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;'
  }[char]));
}

// 规范化并校验HTTP直链前缀，只允许http/https
function sanitizeHttpBaseUrl(raw) {
  if (!raw) return null;
  try {
    const url = new URL(raw);
    if (!['http:', 'https:'].includes(url.protocol)) {
      return null;
    }
    url.search = '';
    url.hash = '';
    // 去掉多余的结尾斜杠，保持路径稳定
    url.pathname = url.pathname.replace(/\/+$/, '');
    return url.toString();
  } catch {
    return null;
  }
}

// 构建安全的下载URL，编码路径片段并拒绝非HTTP(S)前缀
function buildHttpDownloadUrl(rawBaseUrl, filePath) {
  const baseUrl = sanitizeHttpBaseUrl(rawBaseUrl);
  if (!baseUrl || !filePath) return null;

  try {
    const url = new URL(baseUrl);
    const normalizedPath = filePath.startsWith('/') ? filePath : `/${filePath}`;
    const safeSegments = normalizedPath
      .split('/')
      .filter(Boolean)
      .map(segment => encodeURIComponent(segment));
    const safePath = safeSegments.length ? '/' + safeSegments.join('/') : '';

    const basePath = url.pathname.replace(/\/+$/, '');
    const joinedPath = `${basePath}${safePath || '/'}`;
    url.pathname = joinedPath || '/';
    url.search = '';
    url.hash = '';
    return url.toString();
  } catch (err) {
    console.warn('[安全] 生成下载URL失败:', err.message);
    return null;
  }
}

// 校验文件名/路径片段安全（禁止分隔符、控制字符、..）
function isSafePathSegment(name) {
  return (
    typeof name === 'string' &&
    name.length > 0 &&
    name.length <= 255 &&  // 限制文件名长度
    !name.includes('..') &&
    !/[/\\]/.test(name) &&
    !/[\x00-\x1F]/.test(name)
  );
}

// 危险文件扩展名黑名单（仅限可能被Web服务器解析执行的脚本文件）
// 注意：这是网盘应用，.exe等可执行文件允许上传（服务器不会执行）
const DANGEROUS_EXTENSIONS = [
  '.php', '.php3', '.php4', '.php5', '.phtml', '.phar',  // PHP
  '.jsp', '.jspx', '.jsw', '.jsv', '.jspf',             // Java Server Pages
  '.asp', '.aspx', '.asa', '.asax', '.ascx', '.ashx', '.asmx',  // ASP.NET
  '.htaccess', '.htpasswd'                              // Apache配置（可能改变服务器行为）
];

// 检查文件扩展名是否安全
function isFileExtensionSafe(filename) {
  if (!filename || typeof filename !== 'string') return false;

  const ext = path.extname(filename).toLowerCase();
  const nameLower = filename.toLowerCase();

  // 检查危险扩展名
  if (DANGEROUS_EXTENSIONS.includes(ext)) {
    return false;
  }

  // 特殊处理：检查以危险名称开头的文件（如 .htaccess, .htpasswd）
  // 因为 path.extname('.htaccess') 返回空字符串
  const dangerousFilenames = ['.htaccess', '.htpasswd'];
  if (dangerousFilenames.includes(nameLower)) {
    return false;
  }

  // 检查双扩展名攻击（如 file.php.jpg 可能被某些配置错误的服务器执行）
  for (const dangerExt of DANGEROUS_EXTENSIONS) {
    if (nameLower.includes(dangerExt + '.')) {
      return false;
    }
  }

  return true;
}

// 应用XSS过滤到所有POST/PUT请求的body
app.use((req, res, next) => {
  if ((req.method === 'POST' || req.method === 'PUT') && req.body) {
    // 递归过滤所有字符串字段
    function sanitizeObject(obj) {
      if (typeof obj === 'string') {
        return sanitizeInput(obj);
      } else if (Array.isArray(obj)) {
        return obj.map(item => sanitizeObject(item));
      } else if (obj && typeof obj === 'object') {
        const sanitized = {};
        for (const [key, value] of Object.entries(obj)) {
          sanitized[key] = sanitizeObject(value);
        }
        return sanitized;
      }
      return obj;
    }
    req.body = sanitizeObject(req.body);
  }
  next();
});

// 请求日志
app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
  next();
});

// 获取正确的协议（基于可信代理链）
// 安全说明：req.protocol 由 Express 根据 trust proxy 配置计算，
// 只有可信代理的 X-Forwarded-Proto 才会被采信
function getProtocol(req) {
  // req.protocol 会根据 trust proxy 配置：
  // - trust proxy = false: 仅检查直接连接（TLS -> 'https', 否则 'http'）
  // - trust proxy 已配置: 会检查可信代理的 X-Forwarded-Proto
  return req.protocol || (req.secure ? 'https' : 'http');
}

function normalizeOssQuota(rawQuota) {
  const parsedQuota = Number(rawQuota);
  if (!Number.isFinite(parsedQuota) || parsedQuota <= 0) {
    return DEFAULT_OSS_STORAGE_QUOTA_BYTES;
  }
  return parsedQuota;
}

function normalizeDownloadTrafficQuota(rawQuota) {
  const parsedQuota = Number(rawQuota);
  if (!Number.isFinite(parsedQuota)) {
    return 0; // 0 表示禁止下载
  }
  if (parsedQuota < 0) {
    return -1; // -1 表示不限流量
  }
  return Math.min(MAX_DOWNLOAD_TRAFFIC_BYTES, Math.floor(parsedQuota));
}

function normalizeDownloadTrafficUsed(rawUsed, quota = 0) {
  const parsedUsed = Number(rawUsed);
  const normalizedUsed = Number.isFinite(parsedUsed) && parsedUsed > 0
    ? Math.floor(parsedUsed)
    : 0;
  if (quota >= 0) {
    return Math.min(normalizedUsed, quota);
  }
  return normalizedUsed;
}

function getDownloadTrafficState(user) {
  const quota = normalizeDownloadTrafficQuota(user?.download_traffic_quota);
  const used = normalizeDownloadTrafficUsed(user?.download_traffic_used, quota);
  const isUnlimited = quota < 0;
  return {
    quota,
    used,
    isUnlimited,
    remaining: isUnlimited ? Number.POSITIVE_INFINITY : Math.max(0, quota - used)
  };
}

function getBusyDownloadMessage() {
  return '当前网络繁忙，请稍后再试';
}

function parseBooleanLike(rawValue, fallback = false) {
  if (rawValue === null || rawValue === undefined || rawValue === '') {
    return fallback;
  }
  if (typeof rawValue === 'boolean') {
    return rawValue;
  }
  const normalized = String(rawValue).trim().toLowerCase();
  if (['1', 'true', 'yes', 'on'].includes(normalized)) {
    return true;
  }
  if (['0', 'false', 'no', 'off'].includes(normalized)) {
    return false;
  }
  return fallback;
}

function clampIntegerSetting(rawValue, fallback, min, max) {
  const value = Number(rawValue);
  if (!Number.isFinite(value)) {
    return fallback;
  }
  return Math.min(max, Math.max(min, Math.floor(value)));
}

function getDownloadSecuritySettings() {
  const defaults = DOWNLOAD_SECURITY_DEFAULTS;
  return {
    enabled: parseBooleanLike(SettingsDB.get('download_security_enabled'), defaults.enabled),
    same_ip_same_file: {
      enabled: parseBooleanLike(
        SettingsDB.get('download_security_same_file_enabled'),
        defaults.same_ip_same_file.enabled
      ),
      limit_5m: clampIntegerSetting(
        SettingsDB.get('download_security_same_file_limit_5m'),
        defaults.same_ip_same_file.limit_5m,
        1,
        20000
      ),
      limit_1h: clampIntegerSetting(
        SettingsDB.get('download_security_same_file_limit_1h'),
        defaults.same_ip_same_file.limit_1h,
        1,
        50000
      ),
      limit_1d: clampIntegerSetting(
        SettingsDB.get('download_security_same_file_limit_1d'),
        defaults.same_ip_same_file.limit_1d,
        1,
        200000
      )
    },
    same_ip_same_user: {
      enabled: parseBooleanLike(
        SettingsDB.get('download_security_same_user_enabled'),
        defaults.same_ip_same_user.enabled
      ),
      limit_1h: clampIntegerSetting(
        SettingsDB.get('download_security_same_user_limit_1h'),
        defaults.same_ip_same_user.limit_1h,
        1,
        50000
      ),
      limit_1d: clampIntegerSetting(
        SettingsDB.get('download_security_same_user_limit_1d'),
        defaults.same_ip_same_user.limit_1d,
        1,
        200000
      )
    },
    same_ip_same_file_min_interval: {
      enabled: parseBooleanLike(
        SettingsDB.get('download_security_same_file_min_interval_enabled'),
        defaults.same_ip_same_file_min_interval.enabled
      ),
      seconds: clampIntegerSetting(
        SettingsDB.get('download_security_same_file_min_interval_seconds'),
        defaults.same_ip_same_file_min_interval.seconds,
        1,
        3600
      )
    }
  };
}

function normalizeDownloadSecuritySettingsPayload(rawPayload, fallbackSettings = DOWNLOAD_SECURITY_DEFAULTS) {
  const source = rawPayload && typeof rawPayload === 'object' ? rawPayload : {};
  const base = fallbackSettings && typeof fallbackSettings === 'object'
    ? fallbackSettings
    : DOWNLOAD_SECURITY_DEFAULTS;

  const sameFileRaw = source.same_ip_same_file && typeof source.same_ip_same_file === 'object'
    ? source.same_ip_same_file
    : {};
  const sameUserRaw = source.same_ip_same_user && typeof source.same_ip_same_user === 'object'
    ? source.same_ip_same_user
    : {};
  const minIntervalRaw = source.same_ip_same_file_min_interval && typeof source.same_ip_same_file_min_interval === 'object'
    ? source.same_ip_same_file_min_interval
    : {};

  return {
    enabled: parseBooleanLike(source.enabled, !!base.enabled),
    same_ip_same_file: {
      enabled: parseBooleanLike(sameFileRaw.enabled, !!base.same_ip_same_file?.enabled),
      limit_5m: clampIntegerSetting(
        sameFileRaw.limit_5m,
        Number(base.same_ip_same_file?.limit_5m || DOWNLOAD_SECURITY_DEFAULTS.same_ip_same_file.limit_5m),
        1,
        20000
      ),
      limit_1h: clampIntegerSetting(
        sameFileRaw.limit_1h,
        Number(base.same_ip_same_file?.limit_1h || DOWNLOAD_SECURITY_DEFAULTS.same_ip_same_file.limit_1h),
        1,
        50000
      ),
      limit_1d: clampIntegerSetting(
        sameFileRaw.limit_1d,
        Number(base.same_ip_same_file?.limit_1d || DOWNLOAD_SECURITY_DEFAULTS.same_ip_same_file.limit_1d),
        1,
        200000
      )
    },
    same_ip_same_user: {
      enabled: parseBooleanLike(sameUserRaw.enabled, !!base.same_ip_same_user?.enabled),
      limit_1h: clampIntegerSetting(
        sameUserRaw.limit_1h,
        Number(base.same_ip_same_user?.limit_1h || DOWNLOAD_SECURITY_DEFAULTS.same_ip_same_user.limit_1h),
        1,
        50000
      ),
      limit_1d: clampIntegerSetting(
        sameUserRaw.limit_1d,
        Number(base.same_ip_same_user?.limit_1d || DOWNLOAD_SECURITY_DEFAULTS.same_ip_same_user.limit_1d),
        1,
        200000
      )
    },
    same_ip_same_file_min_interval: {
      enabled: parseBooleanLike(
        minIntervalRaw.enabled,
        !!base.same_ip_same_file_min_interval?.enabled
      ),
      seconds: clampIntegerSetting(
        minIntervalRaw.seconds,
        Number(base.same_ip_same_file_min_interval?.seconds || DOWNLOAD_SECURITY_DEFAULTS.same_ip_same_file_min_interval.seconds),
        1,
        3600
      )
    }
  };
}

function saveDownloadSecuritySettings(settings) {
  SettingsDB.set('download_security_enabled', settings.enabled ? 'true' : 'false');
  SettingsDB.set('download_security_same_file_enabled', settings.same_ip_same_file.enabled ? 'true' : 'false');
  SettingsDB.set('download_security_same_file_limit_5m', String(settings.same_ip_same_file.limit_5m));
  SettingsDB.set('download_security_same_file_limit_1h', String(settings.same_ip_same_file.limit_1h));
  SettingsDB.set('download_security_same_file_limit_1d', String(settings.same_ip_same_file.limit_1d));
  SettingsDB.set('download_security_same_user_enabled', settings.same_ip_same_user.enabled ? 'true' : 'false');
  SettingsDB.set('download_security_same_user_limit_1h', String(settings.same_ip_same_user.limit_1h));
  SettingsDB.set('download_security_same_user_limit_1d', String(settings.same_ip_same_user.limit_1d));
  SettingsDB.set(
    'download_security_same_file_min_interval_enabled',
    settings.same_ip_same_file_min_interval.enabled ? 'true' : 'false'
  );
  SettingsDB.set(
    'download_security_same_file_min_interval_seconds',
    String(settings.same_ip_same_file_min_interval.seconds)
  );
}

class DownloadSecurityCounter {
  constructor() {
    this.windowEvents = new Map();
    this.lastSeenAt = new Map();
    this.cleanupInterval = setInterval(() => {
      this.cleanup();
    }, 10 * 60 * 1000);
  }

  getRecentEvents(key, maxWindowMs, now = Date.now()) {
    const safeWindow = Math.max(60 * 1000, Number(maxWindowMs) || (24 * 60 * 60 * 1000));
    const cutoff = now - safeWindow;
    const list = this.windowEvents.get(key) || [];
    if (list.length === 0) {
      return [];
    }

    let firstValidIndex = 0;
    while (firstValidIndex < list.length && list[firstValidIndex] <= cutoff) {
      firstValidIndex += 1;
    }
    if (firstValidIndex > 0) {
      list.splice(0, firstValidIndex);
      if (list.length === 0) {
        this.windowEvents.delete(key);
      } else {
        this.windowEvents.set(key, list);
      }
    }
    return this.windowEvents.get(key) || [];
  }

  wouldExceedWindowLimit(key, rules = [], now = Date.now()) {
    if (!key || !Array.isArray(rules) || rules.length === 0) {
      return { blocked: false };
    }

    const normalizedRules = rules
      .map((rule) => ({
        code: String(rule?.code || 'window_limit'),
        windowMs: Math.max(1000, Math.floor(Number(rule?.windowMs) || 0)),
        limit: Math.max(1, Math.floor(Number(rule?.limit) || 0))
      }))
      .filter(rule => rule.windowMs > 0 && rule.limit > 0);

    if (normalizedRules.length === 0) {
      return { blocked: false };
    }

    const maxWindowMs = normalizedRules.reduce((max, item) => Math.max(max, item.windowMs), 0);
    const events = this.getRecentEvents(key, maxWindowMs, now);

    for (const rule of normalizedRules) {
      const threshold = now - rule.windowMs;
      let count = 0;
      let limitHitTimestamp = null;

      for (let i = events.length - 1; i >= 0; i -= 1) {
        const ts = events[i];
        if (ts <= threshold) {
          break;
        }
        count += 1;
        if (count === rule.limit) {
          limitHitTimestamp = ts;
          break;
        }
      }

      if (count >= rule.limit) {
        const retryAfterMs = limitHitTimestamp
          ? Math.max(1000, (limitHitTimestamp + rule.windowMs) - now + 120)
          : rule.windowMs;
        return {
          blocked: true,
          code: rule.code,
          retryAfterSec: Math.ceil(retryAfterMs / 1000)
        };
      }
    }

    return { blocked: false };
  }

  recordWindowEvent(key, now = Date.now()) {
    if (!key) {
      return;
    }
    const list = this.windowEvents.get(key);
    if (list) {
      list.push(now);
      this.windowEvents.set(key, list);
      return;
    }
    this.windowEvents.set(key, [now]);
  }

  wouldViolateMinInterval(key, minIntervalMs, now = Date.now()) {
    const safeIntervalMs = Math.max(1000, Math.floor(Number(minIntervalMs) || 0));
    if (!key || safeIntervalMs <= 0) {
      return { blocked: false };
    }

    const lastSeen = Number(this.lastSeenAt.get(key) || 0);
    if (lastSeen > 0 && now - lastSeen < safeIntervalMs) {
      return {
        blocked: true,
        code: 'same_ip_same_file_min_interval',
        retryAfterSec: Math.ceil((safeIntervalMs - (now - lastSeen)) / 1000)
      };
    }
    return { blocked: false };
  }

  recordMinIntervalEvent(key, now = Date.now()) {
    if (!key) {
      return;
    }
    this.lastSeenAt.set(key, now);
  }

  cleanup() {
    const now = Date.now();
    const keepWindowMs = 2 * 24 * 60 * 60 * 1000;
    const staleWindowBefore = now - keepWindowMs;

    for (const [key, list] of this.windowEvents.entries()) {
      if (!Array.isArray(list) || list.length === 0) {
        this.windowEvents.delete(key);
        continue;
      }

      let firstValidIndex = 0;
      while (firstValidIndex < list.length && list[firstValidIndex] <= staleWindowBefore) {
        firstValidIndex += 1;
      }

      if (firstValidIndex > 0) {
        list.splice(0, firstValidIndex);
      }

      if (list.length === 0) {
        this.windowEvents.delete(key);
      } else {
        this.windowEvents.set(key, list);
      }
    }

    for (const [key, value] of this.lastSeenAt.entries()) {
      if (!Number.isFinite(value) || value <= staleWindowBefore) {
        this.lastSeenAt.delete(key);
      }
    }
  }
}

const downloadSecurityCounter = new DownloadSecurityCounter();

function evaluateDownloadSecurityPolicy(req, {
  ownerUserId,
  filePath,
  source = 'download'
} = {}) {
  const settings = getDownloadSecuritySettings();
  if (!settings.enabled) {
    return { allowed: true };
  }

  const ownerId = Math.floor(Number(ownerUserId) || 0);
  const normalizedPath = normalizeVirtualPath(filePath || '');
  if (ownerId <= 0 || !normalizedPath) {
    return { allowed: true };
  }

  const clientIp = String(req?.ip || req?.socket?.remoteAddress || '').trim();
  if (!clientIp) {
    return { allowed: true };
  }

  const now = Date.now();
  const eventKeys = [];
  const minIntervalKeys = [];

  if (settings.same_ip_same_file.enabled) {
    const sameFileKey = `download_sec:file:${clientIp}:${ownerId}:${normalizedPath}`;
    const sameFileRules = [
      {
        code: 'same_ip_same_file_5m',
        windowMs: 5 * 60 * 1000,
        limit: settings.same_ip_same_file.limit_5m
      },
      {
        code: 'same_ip_same_file_1h',
        windowMs: 60 * 60 * 1000,
        limit: settings.same_ip_same_file.limit_1h
      },
      {
        code: 'same_ip_same_file_1d',
        windowMs: 24 * 60 * 60 * 1000,
        limit: settings.same_ip_same_file.limit_1d
      }
    ];

    const sameFileCheck = downloadSecurityCounter.wouldExceedWindowLimit(sameFileKey, sameFileRules, now);
    if (sameFileCheck.blocked) {
      return {
        allowed: false,
        code: sameFileCheck.code,
        retryAfterSec: sameFileCheck.retryAfterSec
      };
    }
    eventKeys.push(sameFileKey);

    if (settings.same_ip_same_file_min_interval.enabled) {
      const minIntervalKey = `download_sec:min:${clientIp}:${ownerId}:${normalizedPath}`;
      const minIntervalCheck = downloadSecurityCounter.wouldViolateMinInterval(
        minIntervalKey,
        settings.same_ip_same_file_min_interval.seconds * 1000,
        now
      );
      if (minIntervalCheck.blocked) {
        return {
          allowed: false,
          code: minIntervalCheck.code,
          retryAfterSec: minIntervalCheck.retryAfterSec
        };
      }
      minIntervalKeys.push(minIntervalKey);
    }
  }

  if (settings.same_ip_same_user.enabled) {
    const sameUserKey = `download_sec:user:${clientIp}:${ownerId}`;
    const sameUserRules = [
      {
        code: 'same_ip_same_user_1h',
        windowMs: 60 * 60 * 1000,
        limit: settings.same_ip_same_user.limit_1h
      },
      {
        code: 'same_ip_same_user_1d',
        windowMs: 24 * 60 * 60 * 1000,
        limit: settings.same_ip_same_user.limit_1d
      }
    ];

    const sameUserCheck = downloadSecurityCounter.wouldExceedWindowLimit(sameUserKey, sameUserRules, now);
    if (sameUserCheck.blocked) {
      return {
        allowed: false,
        code: sameUserCheck.code,
        retryAfterSec: sameUserCheck.retryAfterSec
      };
    }
    eventKeys.push(sameUserKey);
  }

  for (const key of eventKeys) {
    downloadSecurityCounter.recordWindowEvent(key, now);
  }
  for (const key of minIntervalKeys) {
    downloadSecurityCounter.recordMinIntervalEvent(key, now);
  }

  return {
    allowed: true,
    source
  };
}

function handleDownloadSecurityBlock(req, res, blockResult, options = {}) {
  const statusCode = Number(options.statusCode || 503);
  const plainText = options.plainText === true;
  const ownerUserId = Number(options.ownerUserId || 0);
  const filePath = typeof options.filePath === 'string' ? options.filePath : '';
  const source = options.source || 'download';

  logSecurity(
    req,
    'download_security_block',
    `下载请求触发限速策略（${source}）`,
    {
      source,
      policy_code: blockResult?.code || 'download_security_blocked',
      retry_after_sec: Number(blockResult?.retryAfterSec || 0),
      owner_user_id: ownerUserId || null,
      file_path: filePath || null
    },
    'warn'
  );

  if (plainText) {
    return sendPlainTextError(res, statusCode, getBusyDownloadMessage());
  }

  return res.status(statusCode).json({
    success: false,
    message: getBusyDownloadMessage(),
    security_blocked: true
  });
}

function sendPlainTextError(res, statusCode, message) {
  return res.status(statusCode).type('text/plain; charset=utf-8').send(message);
}

function parseDateTimeValue(value) {
  if (!value || typeof value !== 'string') {
    return null;
  }

  const directDate = new Date(value);
  if (!Number.isNaN(directDate.getTime())) {
    return directDate;
  }

  // 兼容 SQLite 常见 DATETIME 格式: YYYY-MM-DD HH:mm:ss
  const normalized = value.replace(' ', 'T');
  const normalizedDate = new Date(normalized);
  if (!Number.isNaN(normalizedDate.getTime())) {
    return normalizedDate;
  }

  return null;
}

function formatDateTimeForSqlite(date = new Date()) {
  const target = date instanceof Date ? date : new Date(date);
  const year = target.getFullYear();
  const month = String(target.getMonth() + 1).padStart(2, '0');
  const day = String(target.getDate()).padStart(2, '0');
  const hours = String(target.getHours()).padStart(2, '0');
  const minutes = String(target.getMinutes()).padStart(2, '0');
  const seconds = String(target.getSeconds()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

function getDateKeyFromDate(date = new Date()) {
  const target = date instanceof Date ? date : new Date(date);
  if (Number.isNaN(target.getTime())) {
    return null;
  }
  const year = target.getFullYear();
  const month = String(target.getMonth() + 1).padStart(2, '0');
  const day = String(target.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

function getRecentDateKeys(days = 30, now = new Date()) {
  const safeDays = Math.max(1, Math.floor(Number(days) || 30));
  const keys = [];
  for (let i = safeDays - 1; i >= 0; i -= 1) {
    const date = new Date(now.getTime());
    date.setDate(date.getDate() - i);
    const key = getDateKeyFromDate(date);
    if (key) {
      keys.push(key);
    }
  }
  return keys;
}

function getNextDownloadResetTime(lastResetAt, resetCycle) {
  const baseDate = parseDateTimeValue(lastResetAt);
  if (!baseDate) {
    return null;
  }

  const next = new Date(baseDate.getTime());
  if (resetCycle === 'daily') {
    next.setDate(next.getDate() + 1);
    return next;
  }

  if (resetCycle === 'weekly') {
    next.setDate(next.getDate() + 7);
    return next;
  }

  if (resetCycle === 'monthly') {
    next.setMonth(next.getMonth() + 1);
    return next;
  }

  return null;
}

function resolveDownloadTrafficPolicyUpdates(user, now = new Date()) {
  if (!user) {
    return {
      updates: {},
      hasUpdates: false,
      expired: false,
      resetApplied: false
    };
  }

  const updates = {};
  let hasUpdates = false;
  let expired = false;
  let resetApplied = false;

  const normalizedQuota = normalizeDownloadTrafficQuota(user.download_traffic_quota);
  const normalizedUsed = normalizeDownloadTrafficUsed(user.download_traffic_used, normalizedQuota);
  if (normalizedQuota !== Number(user.download_traffic_quota || 0)) {
    updates.download_traffic_quota = normalizedQuota;
    hasUpdates = true;
  }
  if (normalizedUsed !== Number(user.download_traffic_used || 0)) {
    updates.download_traffic_used = normalizedUsed;
    hasUpdates = true;
  }

  const resetCycle = ['none', 'daily', 'weekly', 'monthly'].includes(user.download_traffic_reset_cycle)
    ? user.download_traffic_reset_cycle
    : 'none';
  if (resetCycle !== (user.download_traffic_reset_cycle || 'none')) {
    updates.download_traffic_reset_cycle = resetCycle;
    hasUpdates = true;
  }

  const expiresAt = parseDateTimeValue(user.download_traffic_quota_expires_at);
  if (normalizedQuota <= 0 && user.download_traffic_quota_expires_at) {
    updates.download_traffic_quota_expires_at = null;
    hasUpdates = true;
  } else if (normalizedQuota > 0 && expiresAt && now >= expiresAt) {
    // 到期后自动恢复为不限并重置已用量
    updates.download_traffic_quota = 0;
    updates.download_traffic_used = 0;
    updates.download_traffic_quota_expires_at = null;
    updates.download_traffic_reset_cycle = 'none';
    updates.download_traffic_last_reset_at = null;
    hasUpdates = true;
    expired = true;
  }

  if (!expired && resetCycle !== 'none') {
    const lastResetAt = user.download_traffic_last_reset_at;
    if (!lastResetAt) {
      updates.download_traffic_last_reset_at = formatDateTimeForSqlite(now);
      hasUpdates = true;
    } else {
      const nextResetAt = getNextDownloadResetTime(lastResetAt, resetCycle);
      if (nextResetAt && now >= nextResetAt) {
        updates.download_traffic_used = 0;
        updates.download_traffic_last_reset_at = formatDateTimeForSqlite(now);
        hasUpdates = true;
        resetApplied = true;
      }
    }
  } else if (resetCycle === 'none' && user.download_traffic_last_reset_at) {
    updates.download_traffic_last_reset_at = null;
    hasUpdates = true;
  }

  return {
    updates,
    hasUpdates,
    expired,
    resetApplied
  };
}

const enforceDownloadTrafficPolicyTransaction = db.transaction((userId, trigger = 'runtime') => {
  let user = UserDB.findById(userId);
  if (!user) {
    return null;
  }

  const now = new Date();
  const policyResult = resolveDownloadTrafficPolicyUpdates(user, now);
  if (policyResult.hasUpdates) {
    UserDB.update(userId, policyResult.updates);
    user = UserDB.findById(userId);
    if (policyResult.expired) {
      console.log(`[下载流量策略] 用户 ${userId} 配额已到期，已自动恢复为不限 (trigger=${trigger})`);
    } else if (policyResult.resetApplied) {
      console.log(`[下载流量策略] 用户 ${userId} 流量已按周期重置 (trigger=${trigger})`);
    }
  }

  return {
    user,
    expired: policyResult.expired,
    resetApplied: policyResult.resetApplied
  };
});

function enforceDownloadTrafficPolicy(userId, trigger = 'runtime') {
  if (!userId) {
    return null;
  }
  return enforceDownloadTrafficPolicyTransaction(userId, trigger);
}

const applyDownloadTrafficUsageTransaction = db.transaction((userId, bytesToAdd) => {
  const policyState = enforceDownloadTrafficPolicyTransaction(userId, 'usage');
  const user = policyState?.user || UserDB.findById(userId);
  if (!user) {
    return null;
  }

  const trafficState = getDownloadTrafficState(user);
  if (bytesToAdd <= 0) {
    return {
      quota: trafficState.quota,
      usedBefore: trafficState.used,
      usedAfter: trafficState.used,
      added: 0
    };
  }

  const nextUsed = trafficState.isUnlimited
    ? trafficState.used + bytesToAdd
    : Math.min(trafficState.quota, trafficState.used + bytesToAdd);

  UserDB.update(userId, { download_traffic_used: nextUsed });

  const addedBytes = Math.max(0, nextUsed - trafficState.used);
  if (addedBytes > 0) {
    DownloadTrafficReportDB.addUsage(userId, addedBytes, 1, new Date());
  }

  return {
    quota: trafficState.quota,
    usedBefore: trafficState.used,
    usedAfter: nextUsed,
    added: addedBytes
  };
});

function applyDownloadTrafficUsage(userId, bytesToAdd) {
  const parsedBytes = Number(bytesToAdd);
  if (!Number.isFinite(parsedBytes) || parsedBytes <= 0) {
    return null;
  }
  return applyDownloadTrafficUsageTransaction(userId, Math.floor(parsedBytes));
}

const reserveDirectDownloadTrafficTransaction = db.transaction((userId, bytesToReserve, reservationOptions = {}) => {
  const policyState = enforceDownloadTrafficPolicyTransaction(userId, 'direct_download_reserve');
  const user = policyState?.user || UserDB.findById(userId);
  if (!user) {
    return { ok: false, reason: 'user_not_found' };
  }

  const reserveBytes = Math.floor(Number(bytesToReserve));
  if (!Number.isFinite(reserveBytes) || reserveBytes <= 0) {
    return { ok: true, reserved: 0, isUnlimited: true };
  }

  const trafficState = getDownloadTrafficState(user);
  if (trafficState.isUnlimited) {
    return { ok: true, reserved: 0, isUnlimited: true };
  }

  const pendingReserved = Number(DownloadTrafficReservationDB.getPendingReservedBytes(userId) || 0);
  const available = Math.max(0, trafficState.remaining - pendingReserved);
  if (reserveBytes > available) {
    return {
      ok: false,
      reason: 'insufficient_available',
      quota: trafficState.quota,
      usedBefore: trafficState.used,
      pendingReserved,
      remaining: trafficState.remaining,
      available
    };
  }

  const ttlMs = Math.max(60 * 1000, Number(reservationOptions.ttlMs || DOWNLOAD_RESERVATION_TTL_MS));
  const expiresAt = new Date(Date.now() + ttlMs);
  const reservation = DownloadTrafficReservationDB.create({
    userId,
    source: reservationOptions.source || 'direct',
    objectKey: reservationOptions.objectKey || null,
    reservedBytes: reserveBytes,
    expiresAt: formatDateTimeForSqlite(expiresAt)
  });

  if (!reservation) {
    return { ok: false, reason: 'create_failed' };
  }

  return {
    ok: true,
    isUnlimited: false,
    quota: trafficState.quota,
    usedBefore: trafficState.used,
    pendingReserved,
    availableBefore: available,
    reserved: reserveBytes,
    reservation
  };
});

function reserveDirectDownloadTraffic(userId, bytesToReserve, reservationOptions = {}) {
  const parsedBytes = Number(bytesToReserve);
  if (!Number.isFinite(parsedBytes) || parsedBytes <= 0) {
    return {
      ok: true,
      reserved: 0,
      isUnlimited: true
    };
  }

  return reserveDirectDownloadTrafficTransaction(userId, Math.floor(parsedBytes), reservationOptions);
}

const applyConfirmedDownloadTrafficFromLogTransaction = db.transaction((userId, confirmedBytes, downloadCount = 0, eventDate = new Date()) => {
  const policyState = enforceDownloadTrafficPolicyTransaction(userId, 'log_confirm');
  const user = policyState?.user || UserDB.findById(userId);
  if (!user) {
    return null;
  }

  const bytes = Math.floor(Number(confirmedBytes));
  const count = Math.floor(Number(downloadCount));
  if (!Number.isFinite(bytes) || bytes <= 0) {
    return {
      userId,
      confirmed: 0,
      added: 0,
      consumedReserved: 0
    };
  }

  const trafficState = getDownloadTrafficState(user);
  const nextUsed = trafficState.isUnlimited
    ? (trafficState.used + bytes)
    : Math.min(trafficState.quota, trafficState.used + bytes);

  UserDB.update(userId, { download_traffic_used: nextUsed });
  const addedBytes = Math.max(0, nextUsed - trafficState.used);
  if (addedBytes > 0) {
    DownloadTrafficReportDB.addUsage(userId, addedBytes, count > 0 ? count : 1, eventDate);
  }
  const consumeResult = DownloadTrafficReservationDB.consumePendingBytes(userId, bytes);

  return {
    userId,
    confirmed: bytes,
    added: addedBytes,
    usedBefore: trafficState.used,
    usedAfter: nextUsed,
    consumedReserved: Number(consumeResult?.consumed || 0),
    finalizedReservations: Number(consumeResult?.finalizedCount || 0)
  };
});

function applyConfirmedDownloadTrafficFromLog(userId, confirmedBytes, downloadCount = 0, eventDate = new Date()) {
  const parsed = Number(confirmedBytes);
  if (!Number.isFinite(parsed) || parsed <= 0) {
    return null;
  }
  return applyConfirmedDownloadTrafficFromLogTransaction(
    userId,
    Math.floor(parsed),
    Math.floor(Number(downloadCount || 0)),
    eventDate
  );
}

function runDownloadTrafficPolicySweep(trigger = 'scheduled') {
  try {
    const users = UserDB.getAll();
    let affected = 0;
    for (const user of users) {
      if (
        !user.download_traffic_quota_expires_at &&
        (!user.download_traffic_reset_cycle || user.download_traffic_reset_cycle === 'none')
      ) {
        continue;
      }
      const result = enforceDownloadTrafficPolicy(user.id, trigger);
      if (result?.expired || result?.resetApplied) {
        affected += 1;
      }
    }
    if (affected > 0) {
      console.log(`[下载流量策略] 扫描完成: ${affected} 个用户发生变化 (trigger=${trigger})`);
    }
  } catch (error) {
    console.error(`[下载流量策略] 扫描失败 (trigger=${trigger}):`, error);
  }
}

function getDownloadTrafficLogIngestConfig(baseBucket = '') {
  const configuredBucket = (SettingsDB.get('download_traffic_log_bucket') || process.env.DOWNLOAD_TRAFFIC_LOG_BUCKET || '').trim();
  const configuredPrefixRaw = SettingsDB.get('download_traffic_log_prefix') || process.env.DOWNLOAD_TRAFFIC_LOG_PREFIX || '';
  const configuredPrefix = String(configuredPrefixRaw).replace(/^\/+/, '');

  return {
    bucket: configuredBucket || baseBucket || '',
    prefix: configuredPrefix
  };
}

async function readS3BodyToBuffer(body) {
  if (!body) return Buffer.alloc(0);

  if (typeof body.transformToByteArray === 'function') {
    const arr = await body.transformToByteArray();
    return Buffer.from(arr);
  }

  if (typeof body.transformToString === 'function') {
    const text = await body.transformToString();
    return Buffer.from(text, 'utf8');
  }

  if (Buffer.isBuffer(body)) {
    return body;
  }

  if (typeof body === 'string') {
    return Buffer.from(body, 'utf8');
  }

  if (typeof body[Symbol.asyncIterator] === 'function') {
    const chunks = [];
    for await (const chunk of body) {
      chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    }
    return Buffer.concat(chunks);
  }

  return Buffer.alloc(0);
}

function parseDownloadTrafficLogTime(line) {
  if (!line || typeof line !== 'string') return null;
  const match = line.match(/\[(\d{2})\/([A-Za-z]{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})\s*([+-]\d{4})?\]/);
  if (!match) return null;

  const monthMap = {
    Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5,
    Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11
  };
  const month = monthMap[match[2]];
  if (month === undefined) return null;

  const year = Number(match[3]);
  const day = Number(match[1]);
  const hour = Number(match[4]);
  const minute = Number(match[5]);
  const second = Number(match[6]);

  if ([year, day, hour, minute, second].some(v => !Number.isFinite(v))) {
    return null;
  }

  // 先按 UTC 构造，再应用时区偏移（若存在）
  let utcMillis = Date.UTC(year, month, day, hour, minute, second);
  const tzRaw = match[7];
  if (tzRaw && /^[+-]\d{4}$/.test(tzRaw)) {
    const sign = tzRaw[0] === '+' ? 1 : -1;
    const tzHour = Number(tzRaw.slice(1, 3));
    const tzMin = Number(tzRaw.slice(3, 5));
    const offsetMinutes = sign * (tzHour * 60 + tzMin);
    utcMillis -= offsetMinutes * 60 * 1000;
  }

  const parsed = new Date(utcMillis);
  return Number.isNaN(parsed.getTime()) ? null : parsed;
}

function parseDownloadTrafficLine(line) {
  if (!line || typeof line !== 'string') {
    return null;
  }

  const trimmed = line.trim();
  if (!trimmed) return null;

  // 仅处理 GET 请求（HEAD/PUT/POST 等不计下载流量）
  if (!/\bGET\b/i.test(trimmed)) {
    return null;
  }

  let statusCode = 0;
  let bytesSent = 0;
  const statusMatch = trimmed.match(/"\s*(\d{3})\s+(\d+|-)\b/);
  if (statusMatch) {
    statusCode = Number(statusMatch[1]);
    bytesSent = statusMatch[2] === '-' ? 0 : Number(statusMatch[2]);
  }

  if (![200, 206].includes(statusCode) || !Number.isFinite(bytesSent) || bytesSent <= 0) {
    return null;
  }

  // 尝试从请求路径提取 object key
  let objectKey = null;
  const requestMatch = trimmed.match(/"(?:GET|HEAD)\s+([^" ]+)\s+HTTP\//i);
  if (requestMatch && requestMatch[1]) {
    let requestPath = requestMatch[1];
    const qIndex = requestPath.indexOf('?');
    if (qIndex >= 0) {
      requestPath = requestPath.slice(0, qIndex);
    }
    requestPath = requestPath.replace(/^https?:\/\/[^/]+/i, '');
    requestPath = requestPath.replace(/^\/+/, '');
    try {
      requestPath = decodeURIComponent(requestPath);
    } catch {
      // ignore decode error
    }
    objectKey = requestPath || null;
  }

  if (!objectKey) {
    const keyMatch = trimmed.match(/\buser_(\d+)\/[^\s"]+/);
    if (keyMatch && keyMatch[0]) {
      objectKey = keyMatch[0];
    }
  }

  if (!objectKey) {
    return null;
  }

  const userMatch = objectKey.match(/(?:^|\/)user_(\d+)\//);
  if (!userMatch) {
    return null;
  }

  const userId = Number(userMatch[1]);
  if (!Number.isFinite(userId) || userId <= 0) {
    return null;
  }

  return {
    userId,
    bytes: Math.floor(bytesSent),
    objectKey,
    eventAt: parseDownloadTrafficLogTime(trimmed) || new Date()
  };
}

function extractLogLinesFromBuffer(buffer, logKey = '') {
  let content = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer || '');
  if ((logKey || '').toLowerCase().endsWith('.gz')) {
    try {
      content = zlib.gunzipSync(content);
    } catch (error) {
      console.warn(`[下载流量日志] 解压失败: ${logKey}`, error.message);
    }
  }

  const text = content.toString('utf8');
  return text.split(/\r?\n/);
}

async function runDownloadTrafficLogReconcile(trigger = 'interval') {
  try {
    const expiredResult = DownloadTrafficReservationDB.expirePendingReservations();
    if ((expiredResult?.changes || 0) > 0) {
      console.log(`[下载流量预扣] 已释放过期保留额度: ${expiredResult.changes} 条 (trigger=${trigger})`);
    }

    if (!SettingsDB.hasUnifiedOssConfig()) {
      return;
    }

    const serviceUser = {
      id: 0,
      has_oss_config: 0,
      current_storage_type: 'oss'
    };
    const { client, bucket: defaultBucket } = createS3ClientContextForUser(serviceUser);
    const ingestConfig = getDownloadTrafficLogIngestConfig(defaultBucket);

    if (!ingestConfig.bucket) {
      return;
    }

    const { ListObjectsV2Command, GetObjectCommand } = require('@aws-sdk/client-s3');
    let continuationToken = null;
    let listed = 0;
    const candidates = [];

    do {
      const listResp = await client.send(new ListObjectsV2Command({
        Bucket: ingestConfig.bucket,
        Prefix: ingestConfig.prefix || undefined,
        ContinuationToken: continuationToken || undefined,
        MaxKeys: DOWNLOAD_LOG_LIST_MAX_KEYS
      }));

      const contents = Array.isArray(listResp?.Contents) ? listResp.Contents : [];
      for (const item of contents) {
        listed += 1;
        const key = item?.Key;
        if (!key) continue;

        const size = Number(item?.Size || 0);
        if (!Number.isFinite(size) || size <= 0) continue;

        const etag = (item?.ETag || '').replace(/"/g, '');
        const processed = DownloadTrafficIngestDB.isProcessed(ingestConfig.bucket, key, etag);
        if (processed) continue;

        // 兼容无后缀日志（阿里云 OSS 默认日志文件通常没有 .log 后缀）
        if (key.endsWith('/')) {
          continue;
        }
        const lowerKey = key.toLowerCase();
        const hasKnownLogExt = lowerKey.endsWith('.log') || lowerKey.endsWith('.txt') || lowerKey.endsWith('.gz');
        const hasLogLikeToken = lowerKey.includes('traffic') || lowerKey.includes('access') || lowerKey.includes('log');
        // 当前缀未配置时，避免误扫整桶普通业务对象
        if (!ingestConfig.prefix && !hasKnownLogExt && !hasLogLikeToken) {
          continue;
        }

        candidates.push({ key, etag, size });
        if (candidates.length >= DOWNLOAD_LOG_MAX_FILES_PER_SWEEP) {
          break;
        }
      }

      if (candidates.length >= DOWNLOAD_LOG_MAX_FILES_PER_SWEEP) {
        break;
      }

      continuationToken = listResp?.IsTruncated ? listResp?.NextContinuationToken : null;
    } while (continuationToken);

    if (candidates.length === 0) {
      return;
    }

    let processedFiles = 0;
    let processedLines = 0;
    let confirmedBytes = 0;

    for (const candidate of candidates) {
      const { key, etag, size } = candidate;

      try {
        const getResp = await client.send(new GetObjectCommand({
          Bucket: ingestConfig.bucket,
          Key: key
        }));
        const bodyBuffer = await readS3BodyToBuffer(getResp?.Body);
        const lines = extractLogLinesFromBuffer(bodyBuffer, key);
        const aggregateByUser = new Map();
        let parsedLineCount = 0;

        for (const line of lines) {
          const parsed = parseDownloadTrafficLine(line);
          if (!parsed) continue;
          parsedLineCount += 1;
          const existing = aggregateByUser.get(parsed.userId) || {
            bytes: 0,
            count: 0,
            eventAt: parsed.eventAt
          };
          existing.bytes += parsed.bytes;
          existing.count += 1;
          if (parsed.eventAt && existing.eventAt && parsed.eventAt < existing.eventAt) {
            existing.eventAt = parsed.eventAt;
          }
          aggregateByUser.set(parsed.userId, existing);
        }

        let fileBytes = 0;
        for (const [uid, stat] of aggregateByUser.entries()) {
          if (!stat || !Number.isFinite(stat.bytes) || stat.bytes <= 0) continue;
          const result = applyConfirmedDownloadTrafficFromLog(uid, stat.bytes, stat.count, stat.eventAt || new Date());
          if (result) {
            fileBytes += stat.bytes;
          }
        }

        processedFiles += 1;
        processedLines += parsedLineCount;
        confirmedBytes += fileBytes;

        DownloadTrafficIngestDB.markProcessed({
          bucket: ingestConfig.bucket,
          logKey: key,
          etag,
          fileSize: size,
          lineCount: parsedLineCount,
          bytesCount: fileBytes,
          status: 'success',
          errorMessage: null
        });
      } catch (error) {
        console.error(`[下载流量日志] 处理失败: ${key}`, error);
        DownloadTrafficIngestDB.markProcessed({
          bucket: ingestConfig.bucket,
          logKey: key,
          etag,
          fileSize: size,
          lineCount: 0,
          bytesCount: 0,
          status: 'failed',
          errorMessage: String(error?.message || error).slice(0, 500)
        });
      }
    }

    if (processedFiles > 0) {
      console.log(
        `[下载流量日志] 扫描完成 (trigger=${trigger}) ` +
        `listed=${listed}, files=${processedFiles}, lines=${processedLines}, bytes=${confirmedBytes}`
      );
    }
  } catch (error) {
    console.error(`[下载流量日志] 扫描失败 (trigger=${trigger}):`, error);
  }
}

const downloadPolicySweepTimer = setInterval(() => {
  runDownloadTrafficPolicySweep('interval');
}, DOWNLOAD_POLICY_SWEEP_INTERVAL_MS);

if (downloadPolicySweepTimer && typeof downloadPolicySweepTimer.unref === 'function') {
  downloadPolicySweepTimer.unref();
}

setTimeout(() => {
  runDownloadTrafficPolicySweep('startup');
}, 10 * 1000);

const downloadTrafficLogReconcileTimer = setInterval(() => {
  runDownloadTrafficLogReconcile('interval');
}, DOWNLOAD_LOG_RECONCILE_INTERVAL_MS);

if (downloadTrafficLogReconcileTimer && typeof downloadTrafficLogReconcileTimer.unref === 'function') {
  downloadTrafficLogReconcileTimer.unref();
}

setTimeout(() => {
  runDownloadTrafficLogReconcile('startup');
}, 30 * 1000);

function cleanupExpiredUploadSessions(trigger = 'interval') {
  try {
    const expiredRows = UploadSessionDB.listExpiredActive(300);
    if (!Array.isArray(expiredRows) || expiredRows.length === 0) {
      return;
    }

    let cleaned = 0;
    for (const session of expiredRows) {
      const tempFilePath = typeof session.temp_file_path === 'string' ? session.temp_file_path : '';
      if (tempFilePath) {
        safeUnlink(tempFilePath);
      }
      UploadSessionDB.expireSession(session.session_id);
      cleaned += 1;
    }

    if (cleaned > 0) {
      console.log(`[断点上传] 已清理过期会话 ${cleaned} 个 (trigger=${trigger})`);
    }
  } catch (error) {
    console.error(`[断点上传] 清理过期会话失败 (trigger=${trigger}):`, error);
  }
}

const uploadSessionSweepTimer = setInterval(() => {
  cleanupExpiredUploadSessions('interval');
}, 5 * 60 * 1000);

if (uploadSessionSweepTimer && typeof uploadSessionSweepTimer.unref === 'function') {
  uploadSessionSweepTimer.unref();
}

setTimeout(() => {
  cleanupExpiredUploadSessions('startup');
}, 20 * 1000);

const deviceSessionSweepTimer = setInterval(() => {
  try {
    const result = DeviceSessionDB.cleanupExpired(30);
    const cleaned = Number(result?.changes || 0);
    if (cleaned > 0) {
      console.log(`[在线设备] 已清理过期会话 ${cleaned} 条`);
    }
  } catch (error) {
    console.error('[在线设备] 清理过期会话失败:', error);
  }
}, 6 * 60 * 60 * 1000);

if (deviceSessionSweepTimer && typeof deviceSessionSweepTimer.unref === 'function') {
  deviceSessionSweepTimer.unref();
}

setTimeout(() => {
  try {
    const result = DeviceSessionDB.cleanupExpired(30);
    const cleaned = Number(result?.changes || 0);
    if (cleaned > 0) {
      console.log(`[在线设备] 启动清理过期会话 ${cleaned} 条`);
    }
  } catch (error) {
    console.error('[在线设备] 启动清理过期会话失败:', error);
  }
}, 25 * 1000);

// 构建用于存储客户端的用户对象（自动尝试解密 OSS Secret）
function buildStorageUserContext(user, overrides = {}) {
  if (!user) {
    return user;
  }

  const storageUser = {
    ...user,
    ...overrides
  };

  if (typeof storageUser.oss_access_key_secret === 'string' && storageUser.oss_access_key_secret) {
    try {
      storageUser.oss_access_key_secret = decryptSecret(storageUser.oss_access_key_secret);
    } catch {
      // 已是明文或不是加密串，保持原值
    }
  }

  return storageUser;
}

// 为指定用户创建 OSS 客户端（兼容加密存储的密钥）
function createOssClientForUser(user, overrides = {}) {
  const storageUser = buildStorageUserContext(user, {
    current_storage_type: 'oss',
    ...overrides
  });
  return new OssStorageClient(storageUser);
}

// 为指定用户创建可直接用于签名的 S3 客户端上下文
function createS3ClientContextForUser(user, overrides = {}) {
  const { S3Client } = require('@aws-sdk/client-s3');
  const ossClient = createOssClientForUser(user, overrides);
  const effectiveConfig = ossClient.getEffectiveConfig();

  // 修复：未 connect() 时 getBucket() 会回退到用户个人 bucket，
  // 在启用统一 OSS 配置时可能拿错 bucket（如误用 user_2）。
  // 这里显式绑定当前有效配置，确保签名下载与上传使用同一 bucket。
  ossClient.currentConfig = effectiveConfig;

  return {
    client: new S3Client(ossClient.buildConfig(effectiveConfig)),
    bucket: effectiveConfig.oss_bucket,
    ossClient
  };
}

const EPHEMERAL_TOKEN_SECRET = JWT_SECRET;

function signEphemeralToken(payload, expiresInSeconds = 900) {
  const tokenPayload = {
    ...payload,
    exp: Math.floor(Date.now() / 1000) + expiresInSeconds
  };

  const encodedPayload = Buffer.from(JSON.stringify(tokenPayload)).toString('base64url');
  const signature = crypto
    .createHmac('sha256', EPHEMERAL_TOKEN_SECRET)
    .update(encodedPayload)
    .digest('base64url');

  return `${encodedPayload}.${signature}`;
}

function verifyEphemeralToken(token, expectedType) {
  if (!token || typeof token !== 'string') {
    return { valid: false, error: 'token_missing' };
  }

  const parts = token.split('.');
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
    return { valid: false, error: 'token_format_invalid' };
  }

  const [encodedPayload, receivedSignature] = parts;
  const expectedSignature = crypto
    .createHmac('sha256', EPHEMERAL_TOKEN_SECRET)
    .update(encodedPayload)
    .digest('base64url');

  const receivedBuffer = Buffer.from(receivedSignature);
  const expectedBuffer = Buffer.from(expectedSignature);
  if (
    receivedBuffer.length !== expectedBuffer.length ||
    !crypto.timingSafeEqual(receivedBuffer, expectedBuffer)
  ) {
    return { valid: false, error: 'token_signature_invalid' };
  }

  try {
    const payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));
    if (!payload || typeof payload !== 'object') {
      return { valid: false, error: 'token_payload_invalid' };
    }

    if (expectedType && payload.type !== expectedType) {
      return { valid: false, error: 'token_type_mismatch' };
    }

    if (!payload.exp || Math.floor(Date.now() / 1000) > payload.exp) {
      return { valid: false, error: 'token_expired' };
    }

    return { valid: true, payload };
  } catch {
    return { valid: false, error: 'token_parse_failed' };
  }
}

// ===== 系统日志工具函数 =====

// 从请求中提取日志信息
function getLogInfoFromReq(req) {
  return {
    ipAddress: req.ip || req.socket?.remoteAddress || 'unknown',
    userAgent: req.get('User-Agent') || 'unknown',
    userId: req.user?.id || null,
    username: req.user?.username || null
  };
}

// 记录认证日志
function logAuth(req, action, message, details = null, level = 'info') {
  const info = getLogInfoFromReq(req);
  SystemLogDB.log({
    level,
    category: 'auth',
    action,
    message,
    ...info,
    details
  });
}

// 记录用户管理日志
function logUser(req, action, message, details = null, level = 'info') {
  const info = getLogInfoFromReq(req);
  SystemLogDB.log({
    level,
    category: 'user',
    action,
    message,
    ...info,
    details
  });
}

// 记录文件操作日志
function logFile(req, action, message, details = null, level = 'info') {
  const info = getLogInfoFromReq(req);
  SystemLogDB.log({
    level,
    category: 'file',
    action,
    message,
    ...info,
    details
  });
}

// 记录分享操作日志
function logShare(req, action, message, details = null, level = 'info') {
  const info = getLogInfoFromReq(req);
  SystemLogDB.log({
    level,
    category: 'share',
    action,
    message,
    ...info,
    details
  });
}

// 记录系统操作日志
function logSystem(req, action, message, details = null, level = 'info') {
  const info = req ? getLogInfoFromReq(req) : {};
  SystemLogDB.log({
    level,
    category: 'system',
    action,
    message,
    ...info,
    details
  });
}

// 记录安全事件日志
function logSecurity(req, action, message, details = null, level = 'warn') {
  const info = getLogInfoFromReq(req);
  SystemLogDB.log({
    level,
    category: 'security',
    action,
    message,
    ...info,
    details
  });
}

// 文件上传配置（临时存储）
const MULTER_UPLOAD_MAX_BYTES = Math.max(
  1 * 1024 * 1024,
  Number(process.env.MULTER_UPLOAD_MAX_BYTES || (50 * 1024 * 1024 * 1024))
);
const upload = multer({
  dest: path.join(__dirname, 'uploads'),
  limits: { fileSize: MULTER_UPLOAD_MAX_BYTES }
});

// ===== TTL缓存类 =====

// 带过期时间的缓存类
class TTLCache {
  constructor(defaultTTL = 3600000) { // 默认1小时
    this.cache = new Map();
    this.defaultTTL = defaultTTL;
    
    // 每10分钟清理一次过期缓存
    this.cleanupInterval = setInterval(() => {
      this.cleanup();
    }, 10 * 60 * 1000);
  }

  set(key, value, ttl = this.defaultTTL) {
    const expiresAt = Date.now() + ttl;
    this.cache.set(key, { value, expiresAt });
  }

  get(key) {
    const item = this.cache.get(key);
    if (!item) {
      return undefined;
    }

    // 检查是否过期
    if (Date.now() > item.expiresAt) {
      this.cache.delete(key);
      return undefined;
    }

    return item.value;
  }

  has(key) {
    const item = this.cache.get(key);
    if (!item) {
      return false;
    }

    // 检查是否过期
    if (Date.now() > item.expiresAt) {
      this.cache.delete(key);
      return false;
    }

    return true;
  }

  delete(key) {
    return this.cache.delete(key);
  }

  // 清理过期缓存
  cleanup() {
    const now = Date.now();
    let cleaned = 0;

    for (const [key, item] of this.cache.entries()) {
      if (now > item.expiresAt) {
        this.cache.delete(key);
        cleaned++;
      }
    }

    if (cleaned > 0) {
      console.log(`[缓存清理] 已清理 ${cleaned} 个过期的分享缓存`);
    }
  }

  // 获取缓存大小
  size() {
    return this.cache.size;
  }

  // 停止清理定时器
  destroy() {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
    }
  }
}

// 分享文件信息缓存（内存缓存，1小时TTL）
const shareFileCache = new TTLCache(60 * 60 * 1000);

// ===== 防爆破限流器 =====

// 防爆破限流器类
class RateLimiter {
  constructor(options = {}) {
    this.maxAttempts = options.maxAttempts || 5;
    this.windowMs = options.windowMs || 15 * 60 * 1000;
    this.blockDuration = options.blockDuration || 30 * 60 * 1000;
    this.attempts = new Map();
    this.blockedKeys = new Map();

    // 每5分钟清理一次过期记录
    this.cleanupInterval = setInterval(() => {
      this.cleanup();
    }, 5 * 60 * 1000);
  }

  // 获取客户端IP（基于可信代理链）
  // 安全说明：req.ip 由 Express 根据 trust proxy 配置计算，
  // 只有可信代理的 X-Forwarded-For 才会被采信
  getClientKey(req) {
    // req.ip 会根据 trust proxy 配置：
    // - trust proxy = false: 使用直接连接的 IP（socket 地址）
    // - trust proxy = 1: 取 X-Forwarded-For 的最后 1 个 IP
    // - trust proxy = true: 取 X-Forwarded-For 的第 1 个 IP（不推荐）
    return req.ip || req.socket?.remoteAddress || 'unknown';
  }

  // 检查是否被封锁
  isBlocked(key) {
    const blockInfo = this.blockedKeys.get(key);
    if (!blockInfo) {
      return false;
    }

    // 检查封锁是否过期
    if (Date.now() > blockInfo.expiresAt) {
      this.blockedKeys.delete(key);
      this.attempts.delete(key);
      return false;
    }

    return true;
  }

  // 记录失败尝试
  recordFailure(key) {
    const now = Date.now();

    // 如果已被封锁，返回封锁信息
    if (this.isBlocked(key)) {
      const blockInfo = this.blockedKeys.get(key);
      return {
        blocked: true,
        remainingAttempts: 0,
        resetTime: blockInfo.expiresAt,
        waitMinutes: Math.ceil((blockInfo.expiresAt - now) / 60000),
        needCaptcha: true
      };
    }

    // 获取或创建尝试记录
    let attemptInfo = this.attempts.get(key);
    if (!attemptInfo || now > attemptInfo.windowEnd) {
      attemptInfo = {
        count: 0,
        windowEnd: now + this.windowMs,
        firstAttempt: now
      };
    }

    attemptInfo.count++;
    this.attempts.set(key, attemptInfo);

    // 检查是否达到封锁阈值
    if (attemptInfo.count >= this.maxAttempts) {
      const blockExpiresAt = now + this.blockDuration;
      this.blockedKeys.set(key, {
        expiresAt: blockExpiresAt,
        blockedAt: now
      });
      console.warn(`[防爆破] 封锁Key: ${key}, 失败次数: ${attemptInfo.count}, 封锁时长: ${Math.ceil(this.blockDuration / 60000)}分钟`);
      return {
        blocked: true,
        remainingAttempts: 0,
        resetTime: blockExpiresAt,
        waitMinutes: Math.ceil(this.blockDuration / 60000),
        needCaptcha: true
      };
    }

    return {
      blocked: false,
      remainingAttempts: this.maxAttempts - attemptInfo.count,
      resetTime: attemptInfo.windowEnd,
      waitMinutes: 0,
      needCaptcha: attemptInfo.count >= 2 // 失败2次后需要验证码
    };
  }

  // 获取失败次数
  getFailureCount(key) {
    const attemptInfo = this.attempts.get(key);
    if (!attemptInfo || Date.now() > attemptInfo.windowEnd) {
      return 0;
    }
    return attemptInfo.count;
  }

  // 记录成功（清除失败记录）
  recordSuccess(key) {
    this.attempts.delete(key);
    this.blockedKeys.delete(key);
  }

  // 清理过期记录
  cleanup() {
    const now = Date.now();
    let cleanedAttempts = 0;
    let cleanedBlocks = 0;

    // 清理过期的尝试记录
    for (const [key, info] of this.attempts.entries()) {
      if (now > info.windowEnd) {
        this.attempts.delete(key);
        cleanedAttempts++;
      }
    }

    // 清理过期的封锁记录
    for (const [key, info] of this.blockedKeys.entries()) {
      if (now > info.expiresAt) {
        this.blockedKeys.delete(key);
        cleanedBlocks++;
      }
    }

    if (cleanedAttempts > 0 || cleanedBlocks > 0) {
      console.log(`[防爆破清理] 已清理 ${cleanedAttempts} 个过期尝试记录, ${cleanedBlocks} 个过期封锁记录`);
    }
  }

  // 获取统计信息
  getStats() {
    return {
      activeAttempts: this.attempts.size,
      blockedKeys: this.blockedKeys.size
    };
  }

  // 停止清理定时器
  destroy() {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
    }
  }
}

// 创建登录限流器（5次失败/15分钟，封锁30分钟）
const loginLimiter = new RateLimiter({
  maxAttempts: 5,
  windowMs: 15 * 60 * 1000,
  blockDuration: 30 * 60 * 1000
});

// 创建分享密码限流器（10次失败/10分钟，封锁20分钟）
const shareLimiter = new RateLimiter({
  maxAttempts: 10,
  windowMs: 10 * 60 * 1000,
  blockDuration: 20 * 60 * 1000
});

// 邮件发送限流（防刷）
// 半小时最多3次，超过封30分钟；全天最多10次，超过封24小时
const mailLimiter30Min = new RateLimiter({
  maxAttempts: 3,
  windowMs: 30 * 60 * 1000,
  blockDuration: 30 * 60 * 1000
});
const mailLimiterDay = new RateLimiter({
  maxAttempts: 10,
  windowMs: 24 * 60 * 60 * 1000,
  blockDuration: 24 * 60 * 60 * 1000
});

// 创建验证码获取限流器（30次请求/10分钟，封锁30分钟）
const captchaLimiter = new RateLimiter({
  maxAttempts: 30,
  windowMs: 10 * 60 * 1000,
  blockDuration: 30 * 60 * 1000
});

// 创建API密钥验证限流器（防止暴力枚举API密钥，5次失败/小时，封锁24小时）
const apiKeyLimiter = new RateLimiter({
  maxAttempts: 5,
  windowMs: 60 * 60 * 1000,        // 1小时窗口
  blockDuration: 24 * 60 * 60 * 1000  // 封锁24小时
});

// 创建文件上传限流器（每用户每小时最多100次上传）
const uploadLimiter = new RateLimiter({
  maxAttempts: 100,
  windowMs: 60 * 60 * 1000,
  blockDuration: 60 * 60 * 1000
});

// 创建文件列表查询限流器（每用户每分钟最多60次）
const fileListLimiter = new RateLimiter({
  maxAttempts: 60,
  windowMs: 60 * 1000,
  blockDuration: 5 * 60 * 1000
});

// 验证码最小请求间隔控制
const CAPTCHA_MIN_INTERVAL = 1000; // 1秒
const captchaLastRequest = new TTLCache(15 * 60 * 1000); // 15分钟自动清理

// 验证码防刷中间件
function captchaRateLimitMiddleware(req, res, next) {
  const clientKey = `captcha:${captchaLimiter.getClientKey(req)}`;
  const now = Date.now();

  // 最小时间间隔限制
  const lastRequest = captchaLastRequest.get(clientKey);
  if (lastRequest && (now - lastRequest) < CAPTCHA_MIN_INTERVAL) {
    return res.status(429).json({
      success: false,
      message: '验证码请求过于频繁，请稍后再试'
    });
  }
  captchaLastRequest.set(clientKey, now, 15 * 60 * 1000);

  // 窗口内总次数限流
  const result = captchaLimiter.recordFailure(clientKey);
  if (result.blocked) {
    return res.status(429).json({
      success: false,
      message: `验证码请求过多，请在 ${result.waitMinutes} 分钟后再试`,
      blocked: true,
      resetTime: result.resetTime
    });
  }

  next();
}

// 登录防爆破中间件
function loginRateLimitMiddleware(req, res, next) {
  const clientIP = loginLimiter.getClientKey(req);
  const { username } = req.body;
  const ipKey = `login:ip:${clientIP}`;

  // 检查IP是否被封锁
  if (loginLimiter.isBlocked(ipKey)) {
    const result = loginLimiter.recordFailure(ipKey);
    console.warn(`[防爆破] 拦截登录尝试 - IP: ${clientIP}, 原因: IP被封锁`);
    return res.status(429).json({
      success: false,
      message: `登录尝试过多，请在 ${result.waitMinutes} 分钟后重试`,
      blocked: true,
      resetTime: result.resetTime
    });
  }

  // 检查用户名是否被封锁
  if (username) {
    const usernameKey = `login:username:${username}`;
    if (loginLimiter.isBlocked(usernameKey)) {
      const result = loginLimiter.recordFailure(usernameKey);
      console.warn(`[防爆破] 拦截登录尝试 - 用户名: ${username}, 原因: 用户名被封锁`);
      return res.status(429).json({
        success: false,
        message: `该账号登录尝试过多，请在 ${result.waitMinutes} 分钟后重试`,
        blocked: true,
        resetTime: result.resetTime
      });
    }
  }

  // 将限流key附加到请求对象，供后续使用
  req.rateLimitKeys = {
    ipKey,
    usernameKey: username ? `login:username:${username}` : null
  };
  next();
}

// 分享密码防爆破中间件
function shareRateLimitMiddleware(req, res, next) {
  const clientIP = shareLimiter.getClientKey(req);
  const { code } = req.params;

  if (!isValidShareCode(code)) {
    return res.status(400).json({
      success: false,
      message: '无效的分享码'
    });
  }

  const key = `share:${code}:${clientIP}`;

  // 检查是否被封锁
  if (shareLimiter.isBlocked(key)) {
    const result = shareLimiter.recordFailure(key);
    console.warn(`[防爆破] 拦截分享密码尝试 - 分享码: ${code}, IP: ${clientIP}`);
    return res.status(429).json({
      success: false,
      message: `密码尝试过多，请在 ${result.waitMinutes} 分钟后重试`,
      blocked: true,
      resetTime: result.resetTime
    });
  }

  req.shareRateLimitKey = key;
  next();
}




// ===== 工具函数 =====

/**
 * 安全的错误响应处理
 * 在生产环境中隐藏敏感的错误详情，仅在开发环境显示详细信息
 * @param {Error} error - 原始错误对象
 * @param {string} userMessage - 给用户显示的友好消息
 * @param {string} logContext - 日志上下文标识
 * @returns {string} 返回给客户端的错误消息
 */
function getSafeErrorMessage(error, userMessage, logContext = '') {
  // 记录完整错误日志
  if (logContext) {
    console.error(`[${logContext}]`, error);
  } else {
    console.error(error);
  }

  // 生产环境返回通用消息，开发环境返回详细信息
  if (process.env.NODE_ENV === 'production') {
    return userMessage;
  }
  // 开发环境下，返回详细错误信息便于调试
  return `${userMessage}: ${error.message}`;
}

// 安全删除文件（不抛出异常）
function safeDeleteFile(filePath) {
  try {
    if (fs.existsSync(filePath)) {
      fs.unlinkSync(filePath);
      console.log(`[清理] 已删除临时文件: ${filePath}`);
      return true;
    }
  } catch (error) {
    console.error(`[清理] 删除临时文件失败: ${filePath}`, error.message);
    return false;
  }
}


// 规范化虚拟文件路径（统一用于分享路径校验）
function normalizeVirtualPath(rawPath) {
  if (typeof rawPath !== 'string') {
    return null;
  }

  let decoded = rawPath;
  try {
    decoded = decodeURIComponent(rawPath);
  } catch {
    // 忽略解码失败，使用原始输入继续校验
  }

  if (decoded.includes('\x00') || decoded.includes('%00')) {
    return null;
  }

  const unifiedPath = decoded.replace(/\\/g, '/');

  // 严格拦截路径遍历片段（在 normalize 前先检查）
  if (/(^|\/)\.\.(\/|$)/.test(unifiedPath)) {
    return null;
  }

  let normalized = path.posix.normalize(unifiedPath);
  if (normalized === '' || normalized === '.') {
    normalized = '/';
  }

  if (!normalized.startsWith('/')) {
    normalized = `/${normalized}`;
  }

  normalized = normalized.replace(/\/+$/g, '');
  return normalized || '/';
}

function isValidShareCode(code) {
  return typeof code === 'string' && SHARE_CODE_REGEX.test(code);
}

// 验证请求路径是否在分享范围内（防止越权访问）
function isPathWithinShare(requestPath, share) {
  if (!requestPath || !share) {
    return false;
  }

  const normalizedRequest = normalizeVirtualPath(requestPath);
  const normalizedShare = normalizeVirtualPath(share.share_path);

  if (!normalizedRequest || !normalizedShare) {
    return false;
  }

  if (share.share_type === 'file') {
    // 单文件分享：只允许下载该文件
    return normalizedRequest === normalizedShare;
  }

  // 目录分享：只允许下载该目录及其子目录下的文件
  const sharePrefix = normalizedShare.endsWith('/') ? normalizedShare : `${normalizedShare}/`;
  return normalizedRequest === normalizedShare || normalizedRequest.startsWith(sharePrefix);
}

function normalizeClientIp(rawIp) {
  const ip = String(rawIp || '').trim();
  if (!ip) return '';
  if (ip.startsWith('::ffff:')) {
    return ip.slice(7);
  }
  if (ip === '::1') {
    return '127.0.0.1';
  }
  return ip;
}

function getClientIp(req) {
  return normalizeClientIp(
    req?.ip
      || req?.socket?.remoteAddress
      || req?.connection?.remoteAddress
      || ''
  );
}

function parseShareIpWhitelist(rawValue) {
  if (typeof rawValue !== 'string') return [];
  return rawValue
    .split(/[\s,;]+/)
    .map(item => item.trim())
    .filter(Boolean)
    .slice(0, 100);
}

function isShareIpAllowed(clientIp, whitelist = []) {
  if (!clientIp || !Array.isArray(whitelist) || whitelist.length === 0) {
    return true;
  }

  for (const rule of whitelist) {
    const normalizedRule = String(rule || '').trim();
    if (!normalizedRule) continue;

    if (normalizedRule === clientIp) {
      return true;
    }

    if (normalizedRule.endsWith('*')) {
      const prefix = normalizedRule.slice(0, -1);
      if (prefix && clientIp.startsWith(prefix)) {
        return true;
      }
    }
  }

  return false;
}

function detectDeviceTypeFromUserAgent(userAgent = '') {
  const ua = String(userAgent || '');
  const mobilePattern = /(Mobile|Android|iPhone|iPad|iPod|Windows Phone|HarmonyOS|Mobi)/i;
  return mobilePattern.test(ua) ? 'mobile' : 'desktop';
}

function inferPlatformFromUserAgent(userAgent = '') {
  const ua = String(userAgent || '');
  if (!ua) return '未知平台';
  if (/windows/i.test(ua)) return 'Windows';
  if (/macintosh|mac os x/i.test(ua)) return 'macOS';
  if (/android/i.test(ua)) return 'Android';
  if (/iphone|ipad|ios/i.test(ua)) return 'iOS';
  if (/linux/i.test(ua)) return 'Linux';
  return '未知平台';
}

function normalizeClientType(value = '') {
  const normalized = String(value || '').trim().toLowerCase();
  if (['web', 'desktop', 'mobile', 'api'].includes(normalized)) {
    return normalized;
  }
  return '';
}

function resolveClientType(clientType, userAgent = '') {
  const normalized = normalizeClientType(clientType);
  if (normalized) return normalized;
  const ua = String(userAgent || '').toLowerCase();
  if (ua.includes('tauri') || ua.includes('electron') || ua.includes('wanwan-cloud-desktop') || ua.includes('玩玩云')) {
    return 'desktop';
  }
  return detectDeviceTypeFromUserAgent(ua) === 'mobile' ? 'mobile' : 'web';
}

function sanitizeDeviceText(value, maxLength = 120) {
  if (typeof value !== 'string') return '';
  return value.trim().slice(0, maxLength);
}

function buildDeviceName({ clientType, deviceName, platform }) {
  const preferred = sanitizeDeviceText(deviceName, 120);
  if (preferred) return preferred;
  const platformText = sanitizeDeviceText(platform, 80) || '未知平台';
  if (clientType === 'desktop') return `桌面客户端 · ${platformText}`;
  if (clientType === 'mobile') return `移动端浏览器 · ${platformText}`;
  if (clientType === 'api') return `API 客户端 · ${platformText}`;
  return `网页端浏览器 · ${platformText}`;
}

function buildDeviceSessionContext(req, payload = {}) {
  const userAgent = sanitizeDeviceText(req.get('user-agent') || req.headers?.['user-agent'] || '', 1024);
  const clientType = resolveClientType(payload.client_type || req.headers?.['x-client-type'], userAgent);
  const platform = sanitizeDeviceText(payload.platform, 80) || inferPlatformFromUserAgent(userAgent);
  return {
    clientType,
    deviceId: sanitizeDeviceText(payload.device_id, 128) || null,
    deviceName: buildDeviceName({
      clientType,
      deviceName: payload.device_name,
      platform
    }),
    platform,
    ipAddress: sanitizeDeviceText(getClientIp(req), 80) || null,
    userAgent
  };
}

function formatOnlineDeviceRecord(row, currentSessionId = '') {
  const sid = String(row?.session_id || '');
  const isCurrent = !!currentSessionId && sid === currentSessionId;
  return {
    session_id: sid,
    client_type: String(row?.client_type || 'web'),
    device_name: String(row?.device_name || '未知设备'),
    platform: String(row?.platform || ''),
    ip_address: String(row?.ip_address || ''),
    last_active_at: row?.last_active_at || row?.updated_at || row?.created_at || null,
    created_at: row?.created_at || null,
    expires_at: row?.expires_at || null,
    is_current: isCurrent,
    is_local: isCurrent
  };
}

function buildDeviceDedupKey(row = {}) {
  const sessionId = String(row?.session_id || '').trim();
  const clientType = String(row?.client_type || 'web').trim().toLowerCase() || 'web';
  const deviceId = String(row?.device_id || '').trim();
  if (deviceId) {
    return `${clientType}:id:${deviceId}`;
  }

  if (clientType === 'desktop') {
    const deviceName = String(row?.device_name || '').trim().toLowerCase();
    const platform = String(row?.platform || '').trim().toLowerCase();
    return `${clientType}:fallback:${deviceName}|${platform}`;
  }

  return `session:${sessionId}`;
}

function dedupeOnlineDeviceRows(rows = [], currentSessionId = '') {
  if (!Array.isArray(rows) || rows.length <= 1) return Array.isArray(rows) ? rows : [];
  const currentSid = String(currentSessionId || '').trim();
  const deduped = new Map();
  for (const row of rows) {
    const key = buildDeviceDedupKey(row);
    const sid = String(row?.session_id || '').trim();
    if (!deduped.has(key)) {
      deduped.set(key, row);
      continue;
    }

    const currentPicked = deduped.get(key);
    const pickedSid = String(currentPicked?.session_id || '').trim();
    const incomingIsCurrent = !!currentSid && sid === currentSid;
    const pickedIsCurrent = !!currentSid && pickedSid === currentSid;
    if (incomingIsCurrent && !pickedIsCurrent) {
      deduped.set(key, row);
    }
  }
  return Array.from(deduped.values());
}

function normalizeTimeHHmm(value) {
  if (typeof value !== 'string') return null;
  const trimmed = value.trim();
  const match = trimmed.match(/^(\d{2}):(\d{2})$/);
  if (!match) return null;
  const hours = Number(match[1]);
  const minutes = Number(match[2]);
  if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return null;
  if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null;
  return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
}

function toMinutesOfDay(hhmm) {
  const normalized = normalizeTimeHHmm(hhmm);
  if (!normalized) return null;
  const [hh, mm] = normalized.split(':').map(Number);
  return hh * 60 + mm;
}

function isCurrentTimeInWindow(startTime, endTime, now = new Date()) {
  const start = toMinutesOfDay(startTime);
  const end = toMinutesOfDay(endTime);
  if (start === null || end === null) {
    return true;
  }

  const nowMinutes = now.getHours() * 60 + now.getMinutes();
  if (start === end) {
    return true;
  }
  if (start < end) {
    return nowMinutes >= start && nowMinutes < end;
  }
  // 跨天窗口：如 22:00 - 06:00
  return nowMinutes >= start || nowMinutes < end;
}

function getSharePolicySummary(share) {
  const maxDownloads = Number(share?.max_downloads);
  const whitelist = parseShareIpWhitelist(share?.ip_whitelist || '');
  const deviceLimit = ['all', 'mobile', 'desktop'].includes(share?.device_limit)
    ? share.device_limit
    : 'all';
  const accessTimeStart = normalizeTimeHHmm(share?.access_time_start || '');
  const accessTimeEnd = normalizeTimeHHmm(share?.access_time_end || '');

  return {
    max_downloads: Number.isFinite(maxDownloads) && maxDownloads > 0 ? Math.floor(maxDownloads) : null,
    ip_whitelist_count: whitelist.length,
    device_limit: deviceLimit,
    access_time_start: accessTimeStart,
    access_time_end: accessTimeEnd
  };
}

function evaluateShareSecurityPolicy(share, req, options = {}) {
  const action = options.action || 'view';
  const enforceDownloadLimit = options.enforceDownloadLimit === true;

  if (!share) {
    return { allowed: false, code: 'share_not_found', message: '分享不存在' };
  }

  const clientIp = normalizeClientIp(req?.ip || req?.socket?.remoteAddress || '');
  const whitelist = parseShareIpWhitelist(share.ip_whitelist || '');
  if (whitelist.length > 0 && !isShareIpAllowed(clientIp, whitelist)) {
    return {
      allowed: false,
      code: 'ip_not_allowed',
      message: '当前访问环境受限，请稍后再试'
    };
  }

  const deviceLimit = ['all', 'mobile', 'desktop'].includes(share.device_limit)
    ? share.device_limit
    : 'all';
  if (deviceLimit !== 'all') {
    const deviceType = detectDeviceTypeFromUserAgent(req?.get('user-agent') || req?.headers?.['user-agent'] || '');
    if (deviceType !== deviceLimit) {
      return {
        allowed: false,
        code: 'device_not_allowed',
        message: '当前访问环境受限，请稍后再试'
      };
    }
  }

  const accessTimeStart = normalizeTimeHHmm(share.access_time_start || '');
  const accessTimeEnd = normalizeTimeHHmm(share.access_time_end || '');
  if (accessTimeStart && accessTimeEnd && !isCurrentTimeInWindow(accessTimeStart, accessTimeEnd, new Date())) {
    return {
      allowed: false,
      code: 'time_not_allowed',
      message: '当前访问环境受限，请稍后再试'
    };
  }

  if (enforceDownloadLimit && action === 'download') {
    const maxDownloads = Number(share.max_downloads);
    const currentDownloads = Number(share.download_count || 0);
    if (Number.isFinite(maxDownloads) && maxDownloads > 0 && currentDownloads >= maxDownloads) {
      return {
        allowed: false,
        code: 'download_limit_reached',
        message: '下载次数已达到上限'
      };
    }
  }

  return { allowed: true, code: 'ok', message: '' };
}

function normalizeFileHash(rawHash) {
  if (typeof rawHash !== 'string') return null;
  const trimmed = rawHash.trim();
  if (!trimmed) return null;
  if (trimmed.length < 16 || trimmed.length > 128) return null;
  if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) return null;
  return trimmed;
}

function getResumableUploadTempRoot() {
  return path.join(__dirname, 'uploads', 'resumable');
}

function ensureResumableUploadTempRoot() {
  const tempRoot = getResumableUploadTempRoot();
  if (!fs.existsSync(tempRoot)) {
    fs.mkdirSync(tempRoot, { recursive: true, mode: 0o755 });
  }
  return tempRoot;
}

function buildResumableUploadExpiresAt(ttlMs = RESUMABLE_UPLOAD_SESSION_TTL_MS) {
  const safeTtl = Math.max(10 * 60 * 1000, Number(ttlMs) || RESUMABLE_UPLOAD_SESSION_TTL_MS);
  return formatDateTimeForSqlite(new Date(Date.now() + safeTtl));
}

function safeUnlink(filePath) {
  if (!filePath) return;
  try {
    if (fs.existsSync(filePath)) {
      fs.unlinkSync(filePath);
    }
  } catch (error) {
    console.error('[清理] 删除文件失败:', filePath, error.message);
  }
}

async function searchFilesRecursively(storage, startPath, keyword, options = {}) {
  const queue = [startPath || '/'];
  const visited = new Set();
  const results = [];
  const normalizedKeyword = String(keyword || '').toLowerCase();
  const limit = Math.max(1, Math.min(GLOBAL_SEARCH_MAX_LIMIT, Math.floor(Number(options.limit) || GLOBAL_SEARCH_DEFAULT_LIMIT)));
  const maxNodes = Math.max(limit, Math.floor(Number(options.maxNodes) || GLOBAL_SEARCH_MAX_SCANNED_NODES));
  const type = ['all', 'file', 'directory'].includes(options.type) ? options.type : 'all';

  let scannedNodes = 0;
  let scannedDirs = 0;
  let truncated = false;

  while (queue.length > 0 && results.length < limit && scannedNodes < maxNodes) {
    const currentPath = queue.shift() || '/';
    if (visited.has(currentPath)) continue;
    visited.add(currentPath);

    let list;
    try {
      list = await storage.list(currentPath);
    } catch (error) {
      // 某个目录无法访问时跳过，不中断全局搜索
      continue;
    }
    scannedDirs += 1;

    for (const item of (Array.isArray(list) ? list : [])) {
      const name = String(item?.name || '');
      if (!name) continue;
      const isDirectory = item?.type === 'd';
      const absolutePath = currentPath === '/'
        ? `/${name}`
        : `${currentPath}/${name}`;

      scannedNodes += 1;
      if (scannedNodes > maxNodes) {
        truncated = true;
        break;
      }

      if (isDirectory) {
        queue.push(absolutePath);
      }

      if (normalizedKeyword && !name.toLowerCase().includes(normalizedKeyword)) {
        continue;
      }

      if (type === 'file' && isDirectory) {
        continue;
      }
      if (type === 'directory' && !isDirectory) {
        continue;
      }

      results.push({
        name,
        path: absolutePath,
        parent_path: currentPath,
        isDirectory,
        type: isDirectory ? 'directory' : 'file',
        size: Number(item?.size || 0),
        sizeFormatted: formatFileSize(Number(item?.size || 0)),
        modifiedAt: new Date(item?.modifyTime || Date.now())
      });

      if (results.length >= limit) {
        truncated = queue.length > 0 || scannedNodes >= maxNodes;
        break;
      }
    }
  }

  return {
    results,
    meta: {
      keyword: keyword || '',
      limit,
      scanned_nodes: scannedNodes,
      scanned_dirs: scannedDirs,
      truncated
    }
  };
}

function normalizeUploadPath(rawPath) {
  const safeRaw = typeof rawPath === 'string' ? rawPath : '/';
  if (safeRaw.includes('..') || safeRaw.includes('\x00')) {
    return null;
  }
  const normalized = path.posix.normalize(safeRaw || '/');
  if (normalized.includes('..')) {
    return null;
  }
  return normalized === '.' ? '/' : normalized;
}

function buildVirtualFilePath(basePath, filename) {
  const normalizedBasePath = normalizeUploadPath(basePath || '/');
  if (!normalizedBasePath) return null;
  const safeName = sanitizeFilename(filename || '');
  return normalizedBasePath === '/'
    ? `/${safeName}`
    : `${normalizedBasePath}/${safeName}`;
}

function isTrackableFileHash(fileHash, fileSize) {
  return !!normalizeFileHash(fileHash) && Number.isFinite(Number(fileSize)) && Number(fileSize) > 0;
}

async function trackFileHashIndexForUpload({
  userId,
  storageType,
  fileHash,
  fileSize,
  filePath,
  objectKey = null
}) {
  const normalizedHash = normalizeFileHash(fileHash);
  const size = Math.floor(Number(fileSize) || 0);
  if (!normalizedHash || !filePath || size <= 0) {
    return;
  }
  FileHashIndexDB.upsert({
    userId,
    storageType,
    fileHash: normalizedHash,
    fileSize: size,
    filePath,
    objectKey
  });
}

async function tryInstantUploadByHash(user, {
  storageType,
  fileHash,
  fileSize,
  targetPath
} = {}) {
  const uid = Number(user?.id);
  const normalizedStorageType = storageType === 'oss' ? 'oss' : 'local';
  const normalizedHash = normalizeFileHash(fileHash);
  const normalizedTargetPath = normalizeVirtualPath(targetPath || '');
  const normalizedSize = Math.floor(Number(fileSize) || 0);

  if (!Number.isFinite(uid) || uid <= 0 || !normalizedHash || !normalizedTargetPath || normalizedSize <= 0) {
    return { instant: false };
  }

  const sourceEntry = FileHashIndexDB.findLatestByHash(uid, normalizedStorageType, normalizedHash, normalizedSize);
  if (!sourceEntry) {
    return { instant: false };
  }

  if (normalizedStorageType === 'local') {
    const latestUser = UserDB.findById(uid);
    if (!latestUser) {
      return { instant: false };
    }
    const localUser = buildStorageUserContext(latestUser, { current_storage_type: 'local' });
    const localStorage = new LocalStorageClient(localUser);
    await localStorage.init();

    const sourcePath = normalizeVirtualPath(sourceEntry.file_path || '');
    if (!sourcePath) {
      return { instant: false };
    }

    if (sourcePath === normalizedTargetPath) {
      const sourceFullPath = localStorage.getFullPath(sourcePath);
      if (!fs.existsSync(sourceFullPath)) {
        FileHashIndexDB.deleteByPath(uid, 'local', sourcePath);
        return { instant: false };
      }
      return { instant: true, alreadyExists: true, path: normalizedTargetPath };
    }

    const sourceFullPath = localStorage.getFullPath(sourcePath);
    if (!fs.existsSync(sourceFullPath)) {
      FileHashIndexDB.deleteByPath(uid, 'local', sourcePath);
      return { instant: false };
    }

    await localStorage.put(sourceFullPath, normalizedTargetPath);
    await trackFileHashIndexForUpload({
      userId: uid,
      storageType: 'local',
      fileHash: normalizedHash,
      fileSize: normalizedSize,
      filePath: normalizedTargetPath
    });
    return { instant: true, alreadyExists: false, path: normalizedTargetPath };
  }

  // OSS 秒传：服务端 CopyObject，不走客户端上传
  const latestUser = UserDB.findById(uid);
  if (!latestUser) {
    return { instant: false };
  }

  const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
  if (!latestUser.has_oss_config && !hasUnifiedConfig) {
    return { instant: false };
  }

  const { HeadObjectCommand, CopyObjectCommand } = require('@aws-sdk/client-s3');
  const { client, bucket, ossClient } = createS3ClientContextForUser(latestUser);
  const sourcePath = normalizeVirtualPath(sourceEntry.file_path || '');
  const sourceObjectKey = sourceEntry.object_key || (sourcePath ? ossClient.getObjectKey(sourcePath) : null);
  const targetObjectKey = ossClient.getObjectKey(normalizedTargetPath);

  if (!sourceObjectKey || !targetObjectKey) {
    return { instant: false };
  }

  if (sourceObjectKey === targetObjectKey) {
    try {
      await client.send(new HeadObjectCommand({ Bucket: bucket, Key: sourceObjectKey }));
      return { instant: true, alreadyExists: true, path: normalizedTargetPath };
    } catch (error) {
      FileHashIndexDB.deleteByPath(uid, 'oss', sourcePath || normalizedTargetPath);
      return { instant: false };
    }
  }

  let sourceSize = 0;
  try {
    const headSource = await client.send(new HeadObjectCommand({
      Bucket: bucket,
      Key: sourceObjectKey
    }));
    sourceSize = Number(headSource?.ContentLength || 0);
  } catch (error) {
    const statusCode = error?.$metadata?.httpStatusCode;
    if (error?.name === 'NotFound' || error?.name === 'NoSuchKey' || statusCode === 404) {
      if (sourcePath) {
        FileHashIndexDB.deleteByPath(uid, 'oss', sourcePath);
      }
      return { instant: false };
    }
    throw error;
  }

  if (!Number.isFinite(sourceSize) || sourceSize <= 0) {
    return { instant: false };
  }

  let previousTargetSize = 0;
  try {
    const headTarget = await client.send(new HeadObjectCommand({
      Bucket: bucket,
      Key: targetObjectKey
    }));
    previousTargetSize = Number(headTarget?.ContentLength || 0);
  } catch (error) {
    const statusCode = error?.$metadata?.httpStatusCode;
    if (error?.name !== 'NotFound' && error?.name !== 'NoSuchKey' && statusCode !== 404) {
      throw error;
    }
  }

  const latestUsageUser = UserDB.findById(uid) || latestUser;
  const ossQuota = normalizeOssQuota(latestUsageUser?.oss_storage_quota);
  const currentUsage = Number(latestUsageUser?.storage_used || 0);
  const projectedUsage = Math.max(0, currentUsage + (sourceSize - previousTargetSize));
  if (projectedUsage > ossQuota) {
    return {
      instant: false,
      blocked: true,
      message: `OSS 配额不足：剩余 ${formatFileSize(Math.max(0, ossQuota - currentUsage))}`
    };
  }

  const encodedSourceKey = sourceObjectKey
    .split('/')
    .map(segment => encodeURIComponent(segment))
    .join('/');
  const copySource = `${bucket}/${encodedSourceKey}`;
  await client.send(new CopyObjectCommand({
    Bucket: bucket,
    Key: targetObjectKey,
    CopySource: copySource
  }));

  const deltaSize = sourceSize - previousTargetSize;
  if (deltaSize !== 0) {
    await StorageUsageCache.updateUsage(uid, deltaSize);
  }
  clearOssUsageCache(uid);

  await trackFileHashIndexForUpload({
    userId: uid,
    storageType: 'oss',
    fileHash: normalizedHash,
    fileSize: sourceSize,
    filePath: normalizedTargetPath,
    objectKey: targetObjectKey
  });

  return { instant: true, alreadyExists: false, path: normalizedTargetPath };
}
// 清理旧的临时文件（启动时执行一次）
function cleanupOldTempFiles() {
  const uploadsDir = path.join(__dirname, 'uploads');
  if (!fs.existsSync(uploadsDir)) {
    return;
  }

  try {
    const files = fs.readdirSync(uploadsDir);
    const now = Date.now();
    const maxAge = 24 * 60 * 60 * 1000; // 24小时

    let cleaned = 0;
    files.forEach(file => {
      const filePath = path.join(uploadsDir, file);
      try {
        const stats = fs.statSync(filePath);
        if (now - stats.mtimeMs > maxAge) {
          fs.unlinkSync(filePath);
          cleaned++;
        }
      } catch (err) {
        console.error(`[清理] 检查文件失败: ${filePath}`, err.message);
      }
    });

    if (cleaned > 0) {
      console.log(`[清理] 已清理 ${cleaned} 个超过24小时的临时文件`);
    }
  } catch (error) {
    console.error('[清理] 清理临时文件目录失败:', error.message);
  }
}

// formatFileSize 已在文件顶部导入

// 生成随机Token（crypto 已在文件顶部导入）
function generateRandomToken(length = 48) {
  return crypto.randomBytes(length).toString('hex');
}

// 获取SMTP配置
function getSmtpConfig() {
  const host = SettingsDB.get('smtp_host');
  const port = SettingsDB.get('smtp_port');
  const secure = SettingsDB.get('smtp_secure');
  const user = SettingsDB.get('smtp_user');
  const pass = SettingsDB.get('smtp_password');
  const from = SettingsDB.get('smtp_from') || user;

  if (!host || !port || !user || !pass) {
    return null;
  }

  return {
    host,
    port: parseInt(port, 10) || 465,
    secure: secure === 'true' || secure === true || port === '465',
    auth: { user, pass },
    from
  };
}

// 创建邮件传输器
function createTransport() {
  const config = getSmtpConfig();
  if (!config) return null;

  return nodemailer.createTransport({
    host: config.host,
    port: config.port,
    secure: config.secure,
    auth: config.auth
  });
}

// 发送邮件
async function sendMail(to, subject, html) {
  const config = getSmtpConfig();
  const transporter = createTransport();
  if (!config || !transporter) {
    throw new Error('SMTP未配置');
  }

  const from = (config.from && config.from.trim()) ? config.from.trim() : config.auth.user;

  await transporter.sendMail({
    from,
    to,
    subject,
    html
  });
}

// 检查邮件发送限流
function checkMailRateLimit(req, type = 'mail') {
  // 使用 req.ip，基于 trust proxy 配置获取可信的客户端 IP
  const clientKey = `${type}:${req.ip || req.socket?.remoteAddress || 'unknown'}`;

  const res30 = mailLimiter30Min.recordFailure(clientKey);
  if (res30.blocked) {
    const err = new Error(`请求过于频繁，30分钟内最多3次，请在 ${res30.waitMinutes} 分钟后再试`);
    err.status = 429;
    throw err;
  }

  const resDay = mailLimiterDay.recordFailure(clientKey);
  if (resDay.blocked) {
    const err = new Error(`今天的次数已用完（最多10次），请稍后再试`);
    err.status = 429;
    throw err;
  }
}

// ===== 验证码验证辅助函数 =====

/**
 * 验证验证码
 * @param {Object} req - 请求对象
 * @param {string} captcha - 用户输入的验证码
 * @returns {{valid: boolean, message?: string}} 验证结果
 */
function verifyCaptcha(req, captcha) {
  if (!captcha) {
    return { valid: false, message: '请输入验证码' };
  }

  const sessionCaptcha = req.session.captcha;
  const captchaTime = req.session.captchaTime;

  // 调试日志
  console.log('[验证码验证] SessionID:', req.sessionID, '输入:', captcha?.toLowerCase(), 'Session中:', sessionCaptcha);

  if (!sessionCaptcha || !captchaTime) {
    console.log('[验证码验证] 失败: session中无验证码');
    return { valid: false, message: '验证码已过期，请刷新验证码' };
  }

  // 验证码有效期5分钟
  if (Date.now() - captchaTime > 5 * 60 * 1000) {
    console.log('[验证码验证] 失败: 验证码已超时');
    return { valid: false, message: '验证码已过期，请刷新验证码' };
  }

  if (captcha.toLowerCase() !== sessionCaptcha) {
    console.log('[验证码验证] 失败: 验证码不匹配');
    return { valid: false, message: '验证码错误' };
  }

  console.log('[验证码验证] 成功');
  // 验证通过后清除session中的验证码
  delete req.session.captcha;
  delete req.session.captchaTime;

  return { valid: true };
}

// ===== 公开API =====

// 健康检查
app.get('/api/health', (req, res) => {
  res.json({ success: true, message: 'Server is running' });
});

// 获取公开的系统配置（不需要登录）
app.get('/api/config', (req, res) => {
  const maxUploadSize = parseInt(SettingsDB.get('max_upload_size') || '10737418240');
  res.json({
    success: true,
    config: {
      max_upload_size: maxUploadSize
    }
  });
});

// 桌面客户端更新信息（无需登录）
app.get('/api/client/desktop-update', (req, res) => {
  try {
    const currentVersion = normalizeVersion(req.query.currentVersion || '0.0.0', '0.0.0');
    const platform = String(req.query.platform || 'windows-x64').trim();
    const channel = String(req.query.channel || 'stable').trim();
    const config = getDesktopUpdateConfig();
    const hasDownload = Boolean(config.installerUrl);
    const updateAvailable = hasDownload && compareLooseVersion(currentVersion, config.latestVersion) < 0;

    res.json({
      success: true,
      currentVersion,
      latestVersion: config.latestVersion,
      updateAvailable,
      downloadUrl: config.installerUrl,
      sha256: config.installerSha256,
      packageSize: config.packageSize,
      releaseNotes: config.releaseNotes,
      mandatory: config.mandatory && updateAvailable,
      platform,
      channel,
      message: hasDownload
        ? (updateAvailable ? '检测到新版本' : '当前已是最新版本')
        : '服务器暂未配置桌面端升级包地址'
    });
  } catch (error) {
    console.error('[客户端更新] 获取升级信息失败:', error);
    res.status(500).json({
      success: false,
      message: '获取客户端更新信息失败'
    });
  }
});

// 生成验证码API
app.get('/api/captcha', captchaRateLimitMiddleware, (req, res) => {
  try {
    const captcha = svgCaptcha.create({
      size: 6, // 验证码长度
      noise: 3, // 干扰线条数
      color: true, // 使用彩色
      background: '#f7f7f7', // 背景色
      width: 140,
      height: 44,
      fontSize: 52,
      charPreset: 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' // 去掉易混淆字符，字母+数字
    });

    // 将验证码文本存储在session中
    req.session.captcha = captcha.text.toLowerCase();
    req.session.captchaTime = Date.now();

    // 保存session后再返回响应（修复：确保session保存成功）
    req.session.save((err) => {
      if (err) {
        console.error('[验证码] Session保存失败:', err);
        return res.status(500).json({
          success: false,
          message: '验证码生成失败'
        });
      }
      console.log('[验证码] 生成成功, SessionID:', req.sessionID);
      res.type('svg');
      res.send(captcha.data);
    });
  } catch (error) {
    console.error('生成验证码失败:', error);
    res.status(500).json({
      success: false,
      message: '生成验证码失败'
    });
  }
});

// 获取 CSRF Token（用于前端初始化）
app.get('/api/csrf-token', (req, res) => {
  let csrfToken = req.cookies[CSRF_COOKIE_NAME];

  // 如果没有 token，生成一个新的
  if (!csrfToken) {
    csrfToken = generateCsrfToken();
    const isSecureEnv = SHOULD_USE_SECURE_COOKIES;
    res.cookie(CSRF_COOKIE_NAME, csrfToken, {
      httpOnly: false,
      secure: isSecureEnv,
      sameSite: isSecureEnv ? 'strict' : 'lax',
      maxAge: 24 * 60 * 60 * 1000
    });
  }

  res.json({
    success: true,
    csrfToken: csrfToken
  });
});

// 密码强度验证函数
function validatePasswordStrength(password) {
  if (!password || password.length < 8) {
    return { valid: false, message: '密码至少8个字符' };
  }
  if (password.length > 128) {
    return { valid: false, message: '密码不能超过128个字符' };
  }

  // 检查是否包含至少两种字符类型（字母、数字、特殊字符）
  const hasLetter = /[a-zA-Z]/.test(password);
  const hasNumber = /[0-9]/.test(password);
  const hasSpecial = /[!@#$%^&*(),.?":{}|<>_\-+=\[\]\\\/`~]/.test(password);

  const typeCount = [hasLetter, hasNumber, hasSpecial].filter(Boolean).length;

  if (typeCount < 2) {
    return { valid: false, message: '密码必须包含字母、数字、特殊字符中的至少两种' };
  }

  // 检查常见弱密码
  const commonWeakPasswords = [
    'password', '12345678', '123456789', 'qwerty123', 'admin123',
    'letmein', 'welcome', 'monkey', 'dragon', 'master'
  ];
  if (commonWeakPasswords.includes(password.toLowerCase())) {
    return { valid: false, message: '密码过于简单，请使用更复杂的密码' };
  }

  return { valid: true };
}

// 用户注册（简化版）
app.post('/api/register',
  [
    body('username')
      .isLength({ min: 3, max: 20 }).withMessage('用户名长度3-20个字符')
      .matches(USERNAME_REGEX).withMessage('用户名仅允许中英文、数字、下划线、点和短横线'),
    body('email').isEmail().withMessage('邮箱格式不正确'),
    body('password')
      .isLength({ min: 8, max: 128 }).withMessage('密码长度8-128个字符')
      .custom((value) => {
        const result = validatePasswordStrength(value);
        if (!result.valid) {
          throw new Error(result.message);
        }
        return true;
      }),
    body('captcha').notEmpty().withMessage('请输入验证码')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      // 验证验证码
      const { captcha } = req.body;
      const captchaResult = verifyCaptcha(req, captcha);
      if (!captchaResult.valid) {
        return res.status(400).json({
          success: false,
          message: captchaResult.message
        });
      }

      checkMailRateLimit(req, 'verify');
      const { username, email, password } = req.body;

      // 检查用户名是否存在
      if (UserDB.findByUsername(username)) {
        return res.status(400).json({
          success: false,
          message: '用户名已存在'
        });
      }

      // 检查邮箱是否存在
      if (UserDB.findByEmail(email)) {
        return res.status(400).json({
          success: false,
          message: '邮箱已被使用'
        });
      }

      // 检查SMTP配置
      const smtpConfig = getSmtpConfig();
      if (!smtpConfig) {
        return res.status(400).json({
          success: false,
          message: '管理员尚未配置SMTP，暂时无法注册'
        });
      }

      const verifyToken = generateRandomToken(24);
      const expiresAtMs = Date.now() + 30 * 60 * 1000; // 30分钟
      const safeUsernameForMail = escapeHtml(username);

      // 创建用户（不需要FTP配置），标记未验证
      const userId = UserDB.create({
        username,
        email,
        password,
        is_verified: 0,
        verification_token: verifyToken,
        verification_expires_at: expiresAtMs
      });

      const verifyLink = `${getSecureBaseUrl(req)}/app.html?verifyToken=${verifyToken}`;

      try {
        await sendMail(
          email,
          '邮箱验证 - 玩玩云',
          `<p>您好，${safeUsernameForMail}：</p>
           <p>请点击下面的链接验证您的邮箱，30分钟内有效：</p>
           <p><a href="${verifyLink}" target="_blank">${verifyLink}</a></p>
           <p>如果不是您本人操作，请忽略此邮件。</p>`
        );
      } catch (mailErr) {
        console.error('发送验证邮件失败:', mailErr);
        return res.status(500).json({
          success: false,
          message: '注册成功，但发送验证邮件失败，请稍后重试或联系管理员',
          needVerify: true
        });
      }

      // 记录注册日志
      logAuth(req, 'register', `新用户注册: ${username}`, { userId, email });

      res.json({
        success: true,
        message: '注册成功，请查收邮箱完成验证',
        user_id: userId
      });
    } catch (error) {
      console.error('注册失败:', error);
      logAuth(req, 'register_failed', `用户注册失败: ${req.body.username || 'unknown'}`, { error: error.message }, 'error');
      // 安全修复：不向客户端泄露具体错误信息
      const safeMessage = error.message?.includes('UNIQUE constraint')
        ? '用户名或邮箱已被注册'
        : '注册失败，请稍后重试';
      res.status(500).json({
        success: false,
        message: safeMessage
      });
    }
  }
);

// 重新发送邮箱验证邮件
app.post('/api/resend-verification', [
  body('email').optional({ checkFalsy: true }).isEmail().withMessage('邮箱格式不正确'),
  body('username')
    .optional({ checkFalsy: true })
    .isLength({ min: 3 }).withMessage('用户名格式不正确')
    .matches(USERNAME_REGEX).withMessage('用户名仅允许中英文、数字、下划线、点和短横线'),
  body('captcha').notEmpty().withMessage('请输入验证码')
], async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ success: false, errors: errors.array() });
  }

  try {
    // 验证验证码
    const { captcha } = req.body;
    const captchaResult = verifyCaptcha(req, captcha);
    if (!captchaResult.valid) {
      return res.status(400).json({
        success: false,
        message: captchaResult.message
      });
    }

    checkMailRateLimit(req, 'verify');

    const { email, username } = req.body;
    const user = email ? UserDB.findByEmail(email) : UserDB.findByUsername(username);

    if (!user) {
      return res.status(400).json({ success: false, message: '用户不存在' });
    }
    if (user.is_verified) {
      return res.status(400).json({ success: false, message: '该邮箱已验证，无需重复验证' });
    }

    const smtpConfig = getSmtpConfig();
    if (!smtpConfig) {
      return res.status(400).json({ success: false, message: 'SMTP未配置，无法发送邮件' });
    }

    const verifyToken = generateRandomToken(24);
    const expiresAtMs = Date.now() + 30 * 60 * 1000;
    VerificationDB.setVerification(user.id, verifyToken, expiresAtMs);

    const verifyLink = `${getSecureBaseUrl(req)}/app.html?verifyToken=${verifyToken}`;
    const safeUsernameForMail = escapeHtml(user.username);
    await sendMail(
      user.email,
      '邮箱验证 - 玩玩云',
      `<p>您好，${safeUsernameForMail}：</p>
       <p>请点击下面的链接验证您的邮箱，30分钟内有效：</p>
       <p><a href="${verifyLink}" target="_blank">${verifyLink}</a></p>
       <p>如果不是您本人操作，请忽略此邮件。</p>`
    );

    res.json({ success: true, message: '验证邮件已发送，请查收' });
  } catch (error) {
    const status = error.status || 500;
    console.error('重发验证邮件失败:', error);
    res.status(status).json({ success: false, message: error.message || '发送失败' });
  }
});

// 验证邮箱
app.get('/api/verify-email', async (req, res) => {
  const { token } = req.query;

  // 参数验证：token 不能为空且长度合理（48字符的hex字符串）
  if (!token || typeof token !== 'string') {
    return res.status(400).json({ success: false, message: '缺少token' });
  }

  // token 格式验证：应该是 hex 字符串，长度合理
  if (!/^[a-f0-9]{32,96}$/i.test(token)) {
    return res.status(400).json({ success: false, message: '无效的token格式' });
  }

  try {
    const user = VerificationDB.consumeVerificationToken(token);
    if (!user) {
      return res.status(400).json({ success: false, message: '无效或已过期的验证链接' });
    }

    // 记录验证成功日志
    logAuth(req, 'email_verified', `邮箱验证成功: ${user.email || user.username}`, { userId: user.id });

    res.json({ success: true, message: '邮箱验证成功，请登录' });
  } catch (error) {
    console.error('邮箱验证失败:', error);
    res.status(500).json({ success: false, message: '邮箱验证失败' });
  }
});

// 发起密码重置（邮件）
app.post('/api/password/forgot', [
  body('email').isEmail().withMessage('邮箱格式不正确'),
  body('captcha').notEmpty().withMessage('请输入验证码')
], async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ success: false, errors: errors.array() });
  }

  const { email, captcha } = req.body;
  try {
    // 验证验证码
    const captchaResult = verifyCaptcha(req, captcha);
    if (!captchaResult.valid) {
      return res.status(400).json({
        success: false,
        message: captchaResult.message
      });
    }

    checkMailRateLimit(req, 'pwd_forgot');

    const smtpConfig = getSmtpConfig();
    if (!smtpConfig) {
      return res.status(400).json({ success: false, message: 'SMTP未配置，无法发送邮件' });
    }

    // 安全修复：无论邮箱是否存在，都返回相同的成功消息（防止邮箱枚举）
    const user = UserDB.findByEmail(email);

    // 只有当用户存在、已验证、未封禁时才发送邮件
    if (user && user.is_verified && user.is_active && !user.is_banned) {
      const token = generateRandomToken(24);
      const expiresAtMs = Date.now() + 30 * 60 * 1000;
      PasswordResetTokenDB.create(user.id, token, expiresAtMs);

      const resetLink = `${getSecureBaseUrl(req)}/app.html?resetToken=${token}`;
      const safeUsernameForMail = escapeHtml(user.username);

      // 异步发送邮件，不等待结果（避免通过响应时间判断邮箱是否存在）
      sendMail(
        email,
        '密码重置 - 玩玩云',
        `<p>您好，${safeUsernameForMail}：</p>
         <p>请点击下面的链接重置密码，30分钟内有效：</p>
         <p><a href="${resetLink}" target="_blank">${resetLink}</a></p>
         <p>如果不是您本人操作，请忽略此邮件。</p>`
      ).catch(err => {
        console.error('发送密码重置邮件失败:', err.message);
      });
    } else {
      // 记录但不暴露邮箱是否存在
      console.log('[密码重置] 邮箱不存在或账号不可用:', email);
    }

    // 无论邮箱是否存在，都返回相同的成功消息
    res.json({ success: true, message: '如果该邮箱已注册，您将收到密码重置链接' });
  } catch (error) {
    const status = error.status || 500;
    console.error('密码重置请求失败:', error);
    res.status(status).json({ success: false, message: error.message || '发送失败' });
  }
});

// 使用邮件Token重置密码
app.post('/api/password/reset', [
  body('token').notEmpty().withMessage('缺少token'),
  body('new_password')
    .isLength({ min: 8, max: 128 }).withMessage('密码长度8-128个字符')
    .custom((value) => {
      const result = validatePasswordStrength(value);
      if (!result.valid) {
        throw new Error(result.message);
      }
      return true;
    })
], async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ success: false, errors: errors.array() });
  }

  const { token, new_password } = req.body;
  try {
    const tokenRow = PasswordResetTokenDB.use(token);
    if (!tokenRow) {
      return res.status(400).json({ success: false, message: '无效或已过期的重置链接' });
    }

    const user = UserDB.findById(tokenRow.user_id);
    if (!user) {
      return res.status(404).json({ success: false, message: '用户不存在' });
    }
    if (user.is_banned || !user.is_active) {
      return res.status(403).json({ success: false, message: '账号不可用，无法重置密码' });
    }
    if (!user.is_verified) {
      return res.status(400).json({ success: false, message: '邮箱未验证，无法重置密码' });
    }

    // 更新密码
    const hashed = require('bcryptjs').hashSync(new_password, 10);
    db.prepare('UPDATE users SET password = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
      .run(hashed, tokenRow.user_id);

    res.json({ success: true, message: '密码重置成功，请重新登录' });
  } catch (error) {
    console.error('密码重置失败:', error);
    res.status(500).json({ success: false, message: '密码重置失败' });
  }
});

// 用户登录
app.post('/api/login',
  loginRateLimitMiddleware,
  [
    body('username').notEmpty().withMessage('用户名不能为空'),
    body('password').notEmpty().withMessage('密码不能为空')
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    const { username, password, captcha, client_type, device_id, device_name, platform } = req.body;

    try {
      // 检查是否需要验证码
      const ipKey = req.rateLimitKeys?.ipKey;
      const usernameKey = req.rateLimitKeys?.usernameKey;
      const ipFailures = ipKey ? loginLimiter.getFailureCount(ipKey) : 0;
      const usernameFailures = usernameKey ? loginLimiter.getFailureCount(usernameKey) : 0;
      const needCaptcha = ipFailures >= 2 || usernameFailures >= 2;

      // 如果需要验证码，则验证验证码
      if (needCaptcha) {
        console.log('[登录验证] 需要验证码, IP失败次数:', ipFailures, '用户名失败次数:', usernameFailures);

        if (!captcha) {
          return res.status(400).json({
            success: false,
            message: '请输入验证码',
            needCaptcha: true
          });
        }

        // 验证验证码
        const sessionCaptcha = req.session.captcha;
        const captchaTime = req.session.captchaTime;

        // 安全：不记录验证码明文
        console.log('[登录验证] 正在验证验证码...');

        if (!sessionCaptcha || !captchaTime) {
          console.log('[登录验证] 验证码不存在于Session中');
          return res.status(400).json({
            success: false,
            message: '验证码已过期，请刷新验证码',
            needCaptcha: true
          });
        }

        // 验证码有效期5分钟
        if (Date.now() - captchaTime > 5 * 60 * 1000) {
          console.log('[登录验证] 验证码已超过5分钟');
          return res.status(400).json({
            success: false,
            message: '验证码已过期，请刷新验证码',
            needCaptcha: true
          });
        }

        if (captcha.toLowerCase() !== sessionCaptcha) {
          console.log('[登录验证] 验证码不匹配');
          return res.status(400).json({
            success: false,
            message: '验证码错误',
            needCaptcha: true
          });
        }

        console.log('[登录验证] 验证码验证通过');
        // 验证通过后清除session中的验证码
        delete req.session.captcha;
        delete req.session.captchaTime;
      }

      let user = UserDB.findByUsername(username);

      if (!user) {
        // 记录失败尝试
        if (req.rateLimitKeys) {
          const result = loginLimiter.recordFailure(req.rateLimitKeys.ipKey);
          if (req.rateLimitKeys.usernameKey) {
            loginLimiter.recordFailure(req.rateLimitKeys.usernameKey);
          }
          return res.status(401).json({
            success: false,
            message: '用户名或密码错误',
            needCaptcha: result.needCaptcha
          });
        }
        return res.status(401).json({
          success: false,
          message: '用户名或密码错误'
        });
      }

      if (user.is_banned) {
        return res.status(403).json({
          success: false,
          message: '账号已被封禁'
        });
      }

      if (!user.is_verified) {
        return res.status(403).json({
          success: false,
          message: '邮箱未验证，请查收邮件或重新发送验证邮件',
          needVerify: true,
          email: user.email
        });
      }

      if (!UserDB.verifyPassword(password, user.password)) {
        // 记录登录失败安全日志
        logSecurity(req, 'login_failed', `登录失败(密码错误): ${username}`, { userId: user.id });

        // 记录失败尝试
        if (req.rateLimitKeys) {
          const result = loginLimiter.recordFailure(req.rateLimitKeys.ipKey);
          if (req.rateLimitKeys.usernameKey) {
            loginLimiter.recordFailure(req.rateLimitKeys.usernameKey);
          }
          return res.status(401).json({
            success: false,
            message: '用户名或密码错误',
            needCaptcha: result.needCaptcha
          });
        }
        return res.status(401).json({
          success: false,
          message: '用户名或密码错误'
        });
      }

      // 本地存储用量校准：防止目录被外部清理后仍显示旧用量
      if ((user.current_storage_type || 'oss') === 'local') {
        user.local_storage_used = syncLocalStorageUsageFromDisk(user.id, user.local_storage_used);
      }

      // 下载流量策略应用：处理到期恢复/周期重置
      const loginPolicyState = enforceDownloadTrafficPolicy(user.id, 'login');
      if (loginPolicyState?.user) {
        user = loginPolicyState.user;
      }

      const deviceContext = buildDeviceSessionContext(req, {
        client_type,
        device_id,
        device_name,
        platform
      });
      let sessionId = null;
      try {
        let reusedSessionId = '';
        const shouldReuseDeviceSession = Boolean(deviceContext.deviceId) || deviceContext.clientType === 'desktop';
        if (shouldReuseDeviceSession) {
          const matchedDeviceSessions = DeviceSessionDB.listActiveByDevice(user.id, {
            clientType: deviceContext.clientType,
            deviceId: deviceContext.deviceId,
            deviceName: deviceContext.deviceName,
            platform: deviceContext.platform,
            limit: 20
          });
          if (matchedDeviceSessions.length > 0) {
            reusedSessionId = String(matchedDeviceSessions[0]?.session_id || '').trim();
            let revokedCount = 0;
            for (const matched of matchedDeviceSessions) {
              const duplicateSid = String(matched?.session_id || '').trim();
              if (!duplicateSid || duplicateSid === reusedSessionId) continue;
              const revokeResult = DeviceSessionDB.revoke(duplicateSid, user.id, 'dedupe_login_same_device');
              if (Number(revokeResult?.changes || 0) > 0) {
                revokedCount += 1;
              }
            }
            if (revokedCount > 0) {
              console.log(`[在线设备] 登录会话去重 user=${user.id}, revoked=${revokedCount}`);
            }
          }
        }

        const createdSession = DeviceSessionDB.create({
          sessionId: reusedSessionId || crypto.randomBytes(24).toString('hex'),
          userId: user.id,
          clientType: deviceContext.clientType,
          deviceId: deviceContext.deviceId,
          deviceName: deviceContext.deviceName,
          platform: deviceContext.platform,
          ipAddress: deviceContext.ipAddress,
          userAgent: deviceContext.userAgent,
          ttlDays: 7
        });
        sessionId = createdSession?.session_id || null;
      } catch (sessionError) {
        console.error('[在线设备] 创建登录会话失败:', sessionError.message);
      }

      const token = generateToken(user, sessionId);
      const refreshToken = generateRefreshToken(user, sessionId);

      // 清除失败记录
      if (req.rateLimitKeys) {
        loginLimiter.recordSuccess(req.rateLimitKeys.ipKey);
        if (req.rateLimitKeys.usernameKey) {
          loginLimiter.recordSuccess(req.rateLimitKeys.usernameKey);
        }
      }

      // 增强Cookie安全设置
      const isSecureEnv = SHOULD_USE_SECURE_COOKIES;
      const cookieOptions = {
        httpOnly: true,
        secure: isSecureEnv,
        sameSite: isSecureEnv ? 'strict' : 'lax',
        path: '/'
      };

      // 设置 access token Cookie（2小时有效）
      res.cookie('token', token, {
        ...cookieOptions,
        maxAge: 2 * 60 * 60 * 1000
      });

      // 设置 refresh token Cookie（7天有效）
      res.cookie('refreshToken', refreshToken, {
        ...cookieOptions,
        maxAge: 7 * 24 * 60 * 60 * 1000
      });

      // 记录登录成功日志
      logAuth(req, 'login', `用户登录成功: ${user.username}`, { userId: user.id, isAdmin: user.is_admin });

      res.json({
        success: true,
        message: '登录成功',
        expiresIn: 2 * 60 * 60 * 1000,  // 告知前端access token有效期（毫秒）
        user: {
          id: user.id,
          username: user.username,
          email: user.email,
          is_admin: user.is_admin,
          has_oss_config: user.has_oss_config,
          // 存储相关字段
          storage_permission: user.storage_permission || 'oss_only',
          current_storage_type: user.current_storage_type || 'oss',
          local_storage_quota: user.local_storage_quota || DEFAULT_LOCAL_STORAGE_QUOTA_BYTES,
          local_storage_used: user.local_storage_used || 0,
          oss_storage_quota: normalizeOssQuota(user.oss_storage_quota),
          storage_used: user.storage_used || 0,
          download_traffic_quota: normalizeDownloadTrafficQuota(user.download_traffic_quota),
          download_traffic_used: normalizeDownloadTrafficUsed(
            user.download_traffic_used,
            normalizeDownloadTrafficQuota(user.download_traffic_quota)
          ),
          download_traffic_quota_expires_at: user.download_traffic_quota_expires_at || null,
          download_traffic_reset_cycle: user.download_traffic_reset_cycle || 'none',
          download_traffic_last_reset_at: user.download_traffic_last_reset_at || null,
          // OSS配置来源（重要：用于前端判断是否使用OSS直连上传）
          oss_config_source: SettingsDB.hasUnifiedOssConfig() ? 'unified' : (user.has_oss_config ? 'personal' : 'none')
        }
      });
    } catch (error) {
      console.error('登录失败:', error);
      logAuth(req, 'login_error', `登录异常: ${req.body.username || 'unknown'}`, { error: error.message }, 'error');
      // 安全修复：不向客户端泄露具体错误信息
      res.status(500).json({
        success: false,
        message: '登录失败，请稍后重试'
      });
    }
  }
);

// 刷新Access Token（从 HttpOnly Cookie 读取 refreshToken）
app.post('/api/refresh-token', (req, res) => {
  // 优先从 Cookie 读取，兼容从请求体读取（向后兼容）
  const refreshToken = req.cookies?.refreshToken || req.body?.refreshToken;

  if (!refreshToken) {
    return res.status(400).json({
      success: false,
      message: '缺少刷新令牌'
    });
  }

  const result = refreshAccessToken(refreshToken, {
    ipAddress: getClientIp(req),
    userAgent: req.get('user-agent') || ''
  });

  if (!result.success) {
    return res.status(401).json({
      success: false,
      message: result.message
    });
  }

  // 更新Cookie中的token
  const isSecureEnv = SHOULD_USE_SECURE_COOKIES;
  res.cookie('token', result.token, {
    httpOnly: true,
    secure: isSecureEnv,
    sameSite: isSecureEnv ? 'strict' : 'lax',
    maxAge: 2 * 60 * 60 * 1000,
    path: '/'
  });

  res.json({
    success: true,
    expiresIn: 2 * 60 * 60 * 1000
  });
});

// 登出（清除Cookie）
app.post('/api/logout', (req, res) => {
  const accessToken = req.headers.authorization?.replace('Bearer ', '') || req.cookies?.token || '';
  const refreshToken = req.cookies?.refreshToken || '';
  const accessPayload = decodeAccessToken(accessToken);
  const refreshPayload = decodeRefreshToken(refreshToken);
  const sessionId = String(accessPayload?.sid || refreshPayload?.sid || '').trim();

  if (sessionId) {
    try {
      DeviceSessionDB.revoke(sessionId, null, 'logout');
    } catch (error) {
      console.error('[在线设备] 登出时撤销会话失败:', error.message);
    }
  }

  // 清除所有认证Cookie
  res.clearCookie('token', { path: '/' });
  res.clearCookie('refreshToken', { path: '/' });
  res.json({ success: true, message: '已登出' });
});

// ===== 需要认证的API =====

// 获取当前用户信息
app.get('/api/user/profile', authMiddleware, (req, res) => {
  const userPayload = { ...req.user };

  const profilePolicyState = enforceDownloadTrafficPolicy(userPayload.id, 'profile');
  if (profilePolicyState?.user) {
    const policyUser = profilePolicyState.user;
    userPayload.download_traffic_quota = normalizeDownloadTrafficQuota(policyUser.download_traffic_quota);
    userPayload.download_traffic_used = normalizeDownloadTrafficUsed(
      policyUser.download_traffic_used,
      userPayload.download_traffic_quota
    );
    userPayload.download_traffic_quota_expires_at = policyUser.download_traffic_quota_expires_at || null;
    userPayload.download_traffic_reset_cycle = policyUser.download_traffic_reset_cycle || 'none';
    userPayload.download_traffic_last_reset_at = policyUser.download_traffic_last_reset_at || null;
  }

  // 本地存储用量校准：避免“有占用但目录为空”的显示错乱
  if ((userPayload.current_storage_type || 'oss') === 'local') {
    userPayload.local_storage_used = syncLocalStorageUsageFromDisk(userPayload.id, userPayload.local_storage_used);
  }

  // 不返回敏感信息（密码和 OSS 密钥）
  const { password, oss_access_key_secret, ...safeUser } = userPayload;

  // 检查是否使用统一 OSS 配置
  const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();

  res.json({
    success: true,
    user: {
      ...safeUser,
      // 添加配置来源信息
      oss_config_source: hasUnifiedConfig ? 'unified' : (safeUser.has_oss_config ? 'personal' : 'none')
    }
  });
});

// 在线设备列表
app.get('/api/user/online-devices', authMiddleware, (req, res) => {
  try {
    const currentSessionId = String(req.authSessionId || '').trim();
    const rawRows = DeviceSessionDB.listActiveByUser(req.user.id, 80);
    const dedupedRows = dedupeOnlineDeviceRows(rawRows, currentSessionId);
    const devices = dedupedRows.map((row) => formatOnlineDeviceRecord(row, currentSessionId));

    res.json({
      success: true,
      devices,
      current_session_id: currentSessionId || null
    });
  } catch (error) {
    console.error('获取在线设备失败:', error);
    res.status(500).json({
      success: false,
      message: '获取在线设备失败'
    });
  }
});

// 强制踢下线（可踢其它设备，也可踢当前设备）
app.post('/api/user/online-devices/:sessionId/kick', authMiddleware, (req, res) => {
  try {
    const sessionId = String(req.params.sessionId || '').trim();
    if (!sessionId || sessionId.length < 16 || sessionId.length > 128) {
      return res.status(400).json({
        success: false,
        message: '会话标识无效'
      });
    }

    const target = DeviceSessionDB.findBySessionId(sessionId);
    if (!target || Number(target.user_id) !== Number(req.user.id)) {
      return res.status(404).json({
        success: false,
        message: '目标设备不存在'
      });
    }

    if (target.revoked_at) {
      return res.status(409).json({
        success: false,
        message: '设备已离线'
      });
    }

    const revokeResult = DeviceSessionDB.revoke(sessionId, req.user.id, 'kicked_by_user');
    if (!(Number(revokeResult?.changes || 0) > 0)) {
      return res.status(409).json({
        success: false,
        message: '设备已离线'
      });
    }

    const kickedCurrent = String(req.authSessionId || '') === sessionId;
    if (kickedCurrent) {
      res.clearCookie('token', { path: '/' });
      res.clearCookie('refreshToken', { path: '/' });
    }

    logAuth(req, 'device_kick', `用户踢下线设备: ${sessionId}`, {
      userId: req.user.id,
      kickedCurrent
    });

    res.json({
      success: true,
      kicked_current: kickedCurrent,
      message: kickedCurrent ? '当前设备已下线' : '设备已下线'
    });
  } catch (error) {
    console.error('踢设备下线失败:', error);
    res.status(500).json({
      success: false,
      message: '踢设备下线失败'
    });
  }
});

// 获取用户下载流量额度与报表
app.get('/api/user/download-traffic-report', authMiddleware, (req, res) => {
  try {
    const days = DownloadTrafficReportDB.normalizeDays(req.query.days, 30, 365);

    const policyState = enforceDownloadTrafficPolicy(req.user.id, 'traffic_report');
    const latestUser = policyState?.user || UserDB.findById(req.user.id);
    if (!latestUser) {
      return res.status(404).json({
        success: false,
        message: '用户不存在'
      });
    }

    const trafficState = getDownloadTrafficState(latestUser);
    const dailyRows = DownloadTrafficReportDB.getDailyUsage(latestUser.id, days);
    const dailyRowMap = new Map(
      dailyRows.map(row => [row.date_key, row])
    );
    const dateKeys = getRecentDateKeys(days, new Date());
    const dailySeries = dateKeys.map(dateKey => {
      const row = dailyRowMap.get(dateKey);
      const bytesUsed = Number(row?.bytes_used || 0);
      const downloadCount = Number(row?.download_count || 0);
      return {
        date: dateKey,
        bytes_used: Number.isFinite(bytesUsed) && bytesUsed > 0 ? Math.floor(bytesUsed) : 0,
        download_count: Number.isFinite(downloadCount) && downloadCount > 0 ? Math.floor(downloadCount) : 0
      };
    });

    const selectedSummary = DownloadTrafficReportDB.getPeriodSummary(latestUser.id, days);
    const todaySummary = DownloadTrafficReportDB.getPeriodSummary(latestUser.id, 1);
    const sevenDaySummary = DownloadTrafficReportDB.getPeriodSummary(latestUser.id, 7);
    const thirtyDaySummary = DownloadTrafficReportDB.getPeriodSummary(latestUser.id, 30);
    const allSummary = DownloadTrafficReportDB.getPeriodSummary(latestUser.id, null);

    const peakDay = dailySeries.reduce((peak, current) => {
      if (!peak || current.bytes_used > peak.bytes_used) {
        return current;
      }
      return peak;
    }, null);

    const safeQuota = trafficState.isUnlimited ? 0 : Math.max(0, trafficState.quota);
    const remainingBytes = trafficState.isUnlimited ? null : Math.max(0, safeQuota - trafficState.used);
    const usagePercentage = trafficState.isUnlimited
      ? null
      : (safeQuota > 0
        ? Math.min(100, Math.round((trafficState.used / safeQuota) * 100))
        : 100);

    res.json({
      success: true,
      quota: {
        quota: safeQuota,
        used: trafficState.used,
        remaining: remainingBytes,
        usage_percentage: usagePercentage,
        is_unlimited: trafficState.isUnlimited,
        reset_cycle: latestUser.download_traffic_reset_cycle || 'none',
        expires_at: latestUser.download_traffic_quota_expires_at || null,
        last_reset_at: latestUser.download_traffic_last_reset_at || null
      },
      report: {
        days,
        daily: dailySeries,
        summary: {
          today: {
            bytes_used: Number(todaySummary?.bytes_used || 0),
            download_count: Number(todaySummary?.download_count || 0)
          },
          last_7_days: {
            bytes_used: Number(sevenDaySummary?.bytes_used || 0),
            download_count: Number(sevenDaySummary?.download_count || 0)
          },
          last_30_days: {
            bytes_used: Number(thirtyDaySummary?.bytes_used || 0),
            download_count: Number(thirtyDaySummary?.download_count || 0)
          },
          selected_range: {
            bytes_used: Number(selectedSummary?.bytes_used || 0),
            download_count: Number(selectedSummary?.download_count || 0),
            average_daily_bytes: Math.round(Number(selectedSummary?.bytes_used || 0) / Math.max(1, days))
          },
          all_time: {
            bytes_used: Number(allSummary?.bytes_used || 0),
            download_count: Number(allSummary?.download_count || 0)
          },
          peak_day: peakDay
            ? {
                date: peakDay.date,
                bytes_used: peakDay.bytes_used,
                download_count: peakDay.download_count
              }
            : null
        }
      }
    });
  } catch (error) {
    console.error('获取下载流量报表失败:', error);
    res.status(500).json({
      success: false,
      message: '获取下载流量报表失败'
    });
  }
});

// 获取用户主题偏好（包含全局默认主题）
app.get('/api/user/theme', authMiddleware, (req, res) => {
  try {
    const globalTheme = SettingsDB.get('global_theme') || 'dark';
    const userTheme = req.user.theme_preference; // null表示跟随全局

    res.json({
      success: true,
      theme: {
        global: globalTheme,
        user: userTheme,
        effective: userTheme || globalTheme // 用户设置优先，否则使用全局
      }
    });
  } catch (error) {
    res.status(500).json({ success: false, message: '获取主题失败' });
  }
});

// 设置用户主题偏好
app.post('/api/user/theme', authMiddleware, (req, res) => {
  try {
    const { theme } = req.body;
    const validThemes = ['dark', 'light', null]; // null表示跟随全局

    if (!validThemes.includes(theme)) {
      return res.status(400).json({
        success: false,
        message: '无效的主题设置，可选: dark, light, null(跟随全局)'
      });
    }

    UserDB.update(req.user.id, { theme_preference: theme });

    const globalTheme = SettingsDB.get('global_theme') || 'dark';
    res.json({
      success: true,
      message: '主题偏好已更新',
      theme: {
        global: globalTheme,
        user: theme,
        effective: theme || globalTheme
      }
    });
  } catch (error) {
    console.error('更新主题失败:', error);
    res.status(500).json({ success: false, message: '更新主题失败' });
  }
});

// 更新OSS配置
app.post('/api/user/update-oss',
  authMiddleware,
  [
    body('oss_provider').isIn(['aliyun', 'tencent', 'aws']).withMessage('无效的OSS服务商'),
    body('oss_region').notEmpty().withMessage('地域不能为空'),
    body('oss_access_key_id').notEmpty().withMessage('Access Key ID不能为空'),
    body('oss_access_key_secret').notEmpty().withMessage('Access Key Secret不能为空'),
    body('oss_bucket').notEmpty().withMessage('存储桶名称不能为空'),
    body('oss_endpoint').optional({ checkFalsy: true }).isURL().withMessage('Endpoint必须是有效的URL')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      // 检查是否已配置系统级统一 OSS
      const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
      if (hasUnifiedConfig && !req.user.is_admin) {
        return res.status(403).json({
          success: false,
          message: '系统已配置统一 OSS，普通用户无法配置个人 OSS。如需修改，请联系管理员'
        });
      }

      const { oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_bucket, oss_endpoint } = req.body;

      // 如果用户已配置OSS且密钥为空，使用现有密钥
      let actualSecret = oss_access_key_secret;
      if (!oss_access_key_secret && req.user.has_oss_config && req.user.oss_access_key_secret) {
        actualSecret = req.user.oss_access_key_secret;
      } else if (!oss_access_key_secret) {
        return res.status(400).json({
          success: false,
          message: 'Access Key Secret不能为空'
        });
      }

      // 验证OSS连接
      try {
        // OssStorageClient 已在文件顶部导入
        const testUser = {
          id: req.user.id,
          has_oss_config: 1, // 标记为已配置，允许使用个人配置
          oss_provider,
          oss_region,
          oss_access_key_id,
          oss_access_key_secret: actualSecret,
          oss_bucket,
          oss_endpoint
        };
        const ossClient = new OssStorageClient(testUser);
        await ossClient.connect();

        // 尝试列出 bucket 内容（验证配置是否正确）
        await ossClient.list('/');
        await ossClient.end();
      } catch (error) {
        return res.status(400).json({
          success: false,
          message: 'OSS连接失败，请检查配置: ' + error.message
        });
      }

      // 安全修复：加密存储 OSS Access Key Secret
      let encryptedSecret;
      try {
        encryptedSecret = encryptSecret(actualSecret);
      } catch (error) {
        console.error('[安全] 加密 OSS 密钥失败:', error);
        return res.status(500).json({
          success: false,
          message: '加密配置失败'
        });
      }

      // 更新用户配置（存储加密后的密钥）
      UserDB.update(req.user.id, {
        oss_provider,
        oss_region,
        oss_access_key_id,
        oss_access_key_secret: encryptedSecret,
        oss_bucket,
        oss_endpoint: oss_endpoint || null,
        has_oss_config: 1
      });

      res.json({
        success: true,
        message: 'OSS配置已更新'
      });
    } catch (error) {
      console.error('更新OSS配置失败:', error);
      res.status(500).json({
        success: false,
        message: '更新配置失败: ' + error.message
      });
    }
  }
);

// 测试 OSS 连接（不保存配置，仅验证）
app.post('/api/user/test-oss',
  authMiddleware,
  [
    body('oss_provider').isIn(['aliyun', 'tencent', 'aws']).withMessage('无效的OSS服务商'),
    body('oss_region').notEmpty().withMessage('地域不能为空'),
    body('oss_access_key_id').notEmpty().withMessage('Access Key ID不能为空'),
    body('oss_bucket').notEmpty().withMessage('存储桶名称不能为空')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { oss_provider, oss_region, oss_access_key_id, oss_access_key_secret, oss_bucket, oss_endpoint } = req.body;

      // 如果密钥为空且用户已配置OSS，使用现有密钥
      let actualSecret = oss_access_key_secret;
      if (!oss_access_key_secret && req.user.has_oss_config && req.user.oss_access_key_secret) {
        actualSecret = req.user.oss_access_key_secret;
      } else if (!oss_access_key_secret) {
        return res.status(400).json({
          success: false,
          message: 'Access Key Secret不能为空'
        });
      }

      // 验证 OSS 连接
      // OssStorageClient 已在文件顶部导入
      const testUser = {
        id: req.user.id,
        has_oss_config: 1, // 标记为已配置，允许使用个人配置
        oss_provider,
        oss_region,
        oss_access_key_id,
        oss_access_key_secret: actualSecret,
        oss_bucket,
        oss_endpoint
      };
      const ossClient = new OssStorageClient(testUser);
      await ossClient.connect();

      // 尝试列出 bucket 内容（验证配置是否正确）
      await ossClient.list('/');
      await ossClient.end();

      res.json({
        success: true,
        message: 'OSS 连接测试成功'
      });
    } catch (error) {
      console.error('[OSS测试] 连接失败:', error);
      res.status(400).json({
        success: false,
        message: 'OSS 连接失败: ' + error.message
      });
    }
  }
);

// 获取 OSS 存储空间使用情况（带缓存）
// ===== P0 性能优化：优先使用数据库缓存，避免全量统计 =====
app.get('/api/user/oss-usage', authMiddleware, async (req, res) => {
  try {
    // 检查是否有可用的OSS配置（个人配置或系统级统一配置）
    const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
    if (!req.user.has_oss_config && !hasUnifiedConfig) {
      return res.status(400).json({
        success: false,
        message: '未配置OSS服务'
      });
    }

    // ===== P0 优化：优先使用数据库缓存 =====
    // 从数据库 storage_used 字段读取（上传/删除时增量更新）
    const user = UserDB.findById(req.user.id);
    const storageUsed = Number(user.storage_used || 0);
    const ossQuota = normalizeOssQuota(user.oss_storage_quota);
    const usagePercentage = Math.min(100, Math.round((storageUsed / ossQuota) * 100));
    const remainingSize = Math.max(ossQuota - storageUsed, 0);

    return res.json({
      success: true,
      usage: {
        totalSize: storageUsed,
        totalSizeFormatted: formatFileSize(storageUsed),
        fileCount: null, // 缓存模式不提供文件数
        cached: true,
        quota: ossQuota,
        quotaFormatted: formatFileSize(ossQuota),
        quotaLimited: true,
        remainingSize,
        remainingSizeFormatted: formatFileSize(remainingSize),
        usagePercentage
      },
      cached: true
    });

  } catch (error) {
    console.error('[OSS统计] 获取失败:', error);
    res.status(500).json({
      success: false,
      message: '获取存储使用情况失败: ' + error.message
    });
  }
});

// 获取 OSS 存储空间详细统计（全量统计，仅限管理员或需要精确统计时使用）
// ===== P0 性能优化：此接口较慢，建议只在必要时调用 =====
app.get('/api/user/oss-usage-full', authMiddleware, async (req, res) => {
  let ossClient;

  try {
    // 检查是否有可用的OSS配置（个人配置或系统级统一配置）
    const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
    if (!req.user.has_oss_config && !hasUnifiedConfig) {
      return res.status(400).json({
        success: false,
        message: '未配置OSS服务'
      });
    }

    // 先检查内存缓存（5分钟 TTL）
    const cached = getOssUsageCache(req.user.id);
    if (cached) {
      return res.json({
        success: true,
        usage: cached,
        cached: true
      });
    }

    // 执行全量统计（较慢，仅在缓存未命中时执行）
    ossClient = createOssClientForUser(req.user);
    await ossClient.connect();

    let totalSize = 0;
    let fileCount = 0;
    let continuationToken = null;

    do {
      const { ListObjectsV2Command } = require('@aws-sdk/client-s3');
      const command = new ListObjectsV2Command({
        Bucket: ossClient.getBucket(),
        Prefix: `user_${req.user.id}/`,
        ContinuationToken: continuationToken
      });

      const response = await ossClient.s3Client.send(command);

      if (response.Contents) {
        for (const obj of response.Contents) {
          totalSize += obj.Size || 0;
          fileCount++;
        }
      }

      continuationToken = response.NextContinuationToken;
    } while (continuationToken);

    const usageData = {
      totalSize,
      totalSizeFormatted: formatFileSize(totalSize),
      fileCount,
      dirCount: 0 // OSS 没有目录概念
    };

    // 存入内存缓存
    setOssUsageCache(req.user.id, usageData);

    res.json({
      success: true,
      usage: usageData,
      cached: false
    });

  } catch (error) {
    console.error('[OSS统计] 全量统计失败:', error);
    res.status(500).json({
      success: false,
      message: '获取OSS空间使用情况失败: ' + error.message
    });
  } finally {
    if (ossClient) {
      await ossClient.end();
    }
  }
});

// 修改管理员账号信息（仅管理员可修改用户名）
app.post('/api/admin/update-profile',
  authMiddleware,
  adminMiddleware,
  [
    body('username')
      .isLength({ min: 3, max: 20 }).withMessage('用户名长度3-20个字符')
      .matches(USERNAME_REGEX).withMessage('用户名仅允许中英文、数字、下划线、点和短横线')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { username } = req.body;

      // 检查用户名是否被占用（排除自己）
      if (username !== req.user.username) {
        const existingUser = UserDB.findByUsername(username);
        if (existingUser && existingUser.id !== req.user.id) {
          return res.status(400).json({
            success: false,
            message: '用户名已被使用'
          });
        }

        // 更新用户名
        UserDB.update(req.user.id, { username });

        // 获取更新后的用户信息
        const updatedUser = UserDB.findById(req.user.id);

        // 生成新的token（因为用户名变了）
        const newToken = generateToken(updatedUser);

        res.json({
          success: true,
          message: '用户名已更新',
          token: newToken,
          user: {
            id: updatedUser.id,
            username: updatedUser.username,
            email: updatedUser.email,
            is_admin: updatedUser.is_admin
          }
        });
      } else {
        res.json({
          success: true,
          message: '没有需要更新的信息'
        });
      }
    } catch (error) {
      console.error('更新账号信息失败:', error);
      // 安全修复：不向客户端泄露具体错误信息
      res.status(500).json({
        success: false,
        message: '更新失败，请稍后重试'
      });
    }
  }
);

// 修改当前用户密码（需要验证当前密码）
app.post('/api/user/change-password',
  authMiddleware,
  [
    body('current_password').notEmpty().withMessage('当前密码不能为空'),
    body('new_password')
      .isLength({ min: 8, max: 128 }).withMessage('密码长度8-128个字符')
      .custom((value) => {
        const result = validatePasswordStrength(value);
        if (!result.valid) {
          throw new Error(result.message);
        }
        return true;
      })
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { current_password, new_password } = req.body;

      // 获取当前用户信息
      const user = UserDB.findById(req.user.id);
      if (!user) {
        return res.status(404).json({
          success: false,
          message: '用户不存在'
        });
      }

      // 验证当前密码
      if (!UserDB.verifyPassword(current_password, user.password)) {
        return res.status(401).json({
          success: false,
          message: '当前密码错误'
        });
      }

      // 更新密码
      UserDB.update(req.user.id, { password: new_password });

      res.json({
        success: true,
        message: '密码修改成功'
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        message: getSafeErrorMessage(error, '修改密码失败，请稍后重试', '修改密码失败')
      });
    }
  }
);

// 更新当前用户资料（目前支持邮箱）
app.post('/api/user/update-profile',
  authMiddleware,
  [
    body('email').isEmail().withMessage('邮箱格式不正确')
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { email } = req.body;

      const existingUser = UserDB.findByEmail(email);
      if (existingUser && existingUser.id !== req.user.id) {
        return res.status(400).json({
          success: false,
          message: '邮箱已被使用'
        });
      }

      UserDB.update(req.user.id, { email });

      const updatedUser = UserDB.findById(req.user.id);
      res.json({
        success: true,
        message: '资料更新成功',
        user: {
          id: updatedUser.id,
          username: updatedUser.username,
          email: updatedUser.email,
          is_admin: updatedUser.is_admin
        }
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        message: getSafeErrorMessage(error, '更新资料失败，请稍后重试', '更新资料失败')
      });
    }
  }
);

// 修改当前用户名
app.post('/api/user/update-username',
  authMiddleware,
  [
    body('username')
      .isLength({ min: 3 }).withMessage('用户名至少3个字符')
      .matches(USERNAME_REGEX).withMessage('用户名仅允许中英文、数字、下划线、点和短横线')
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { username } = req.body;

      // 检查用户名是否已存在
      const existingUser = UserDB.findByUsername(username);
      if (existingUser && existingUser.id !== req.user.id) {
        return res.status(400).json({
          success: false,
          message: '用户名已存在'
        });
      }

      // 更新用户名
      UserDB.update(req.user.id, { username });

      res.json({
        success: true,
        message: '用户名修改成功'
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        message: getSafeErrorMessage(error, '修改用户名失败，请稍后重试', '修改用户名失败')
      });
    }
  }
);

// 切换存储方式
app.post('/api/user/switch-storage',
  authMiddleware,
  [
    body('storage_type').isIn(['local', 'oss']).withMessage('无效的存储类型')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { storage_type } = req.body;

      // 检查权限
      if (req.user.storage_permission === 'local_only' && storage_type !== 'local') {
        return res.status(403).json({
          success: false,
          message: '您只能使用本地存储'
        });
      }

      if (req.user.storage_permission === 'oss_only' && storage_type !== 'oss') {
        return res.status(403).json({
          success: false,
          message: '您只能使用OSS存储'
        });
      }

      // 检查OSS配置（包括个人配置和系统级统一配置）
      if (storage_type === 'oss') {
        const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
        if (!req.user.has_oss_config && !hasUnifiedConfig) {
          return res.status(400).json({
            success: false,
            message: 'OSS服务未配置，请联系管理员配置系统级OSS服务'
          });
        }
      }

      // 更新存储类型（切到本地时同步校准本地用量）
      const updates = { current_storage_type: storage_type };
      if (storage_type === 'local') {
        const latestUser = UserDB.findById(req.user.id);
        if (latestUser) {
          updates.local_storage_used = syncLocalStorageUsageFromDisk(
            latestUser.id,
            latestUser.local_storage_used
          );
        }
      }

      UserDB.update(req.user.id, updates);

      res.json({
        success: true,
        message: '存储方式已切换',
        storage_type
      });
    } catch (error) {
      console.error('切换存储失败:', error);
      res.status(500).json({
        success: false,
        message: '切换存储失败: ' + error.message
      });
    }
  }
);

// 获取文件列表（添加速率限制）
app.get('/api/files', authMiddleware, async (req, res) => {
  // 速率限制检查
  const rateLimitKey = `file_list:${req.user.id}`;
  const rateLimitResult = fileListLimiter.recordFailure(rateLimitKey);
  if (rateLimitResult.blocked) {
    return res.status(429).json({
      success: false,
      message: `请求过于频繁，请在 ${rateLimitResult.waitMinutes} 分钟后重试`
    });
  }

  const rawPath = req.query.path || '/';

  // 路径安全验证：在 API 层提前拒绝包含 .. 或空字节的路径
  if (rawPath.includes('..') || rawPath.includes('\x00') || rawPath.includes('%00')) {
    return res.status(400).json({
      success: false,
      message: '路径包含非法字符'
    });
  }

  // 规范化路径
  const dirPath = path.posix.normalize(rawPath);
  let storage;

  try {
    // 使用统一存储接口
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    const list = await storage.list(dirPath);

    const storageType = req.user.current_storage_type || 'oss';

    const formattedList = list.map(item => {
      return {
        name: item.name,
        displayName: decodeHtmlEntities(item.name || ''),
        type: item.type === 'd' ? 'directory' : 'file',
        size: item.size,
        sizeFormatted: formatFileSize(item.size),
        modifiedAt: new Date(item.modifyTime),
        isDirectory: item.type === 'd',
        httpDownloadUrl: null // OSS 使用 API 下载，不需要 httpDownloadUrl
      };
    });

    formattedList.sort((a, b) => {
      if (a.isDirectory && !b.isDirectory) return -1;
      if (!a.isDirectory && b.isDirectory) return 1;
      return a.name.localeCompare(b.name);
    });

    res.json({
      success: true,
      path: dirPath,
      items: formattedList,
      storageType: storageType,
      storagePermission: req.user.storage_permission || 'oss_only'
    });
  } catch (error) {
    console.error('获取文件列表失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '获取文件列表失败，请稍后重试', '获取文件列表')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 全局文件搜索（递归搜索当前存储空间）
app.get('/api/files/search', authMiddleware, async (req, res) => {
  const keyword = typeof req.query?.keyword === 'string' ? req.query.keyword.trim() : '';
  const searchType = typeof req.query?.type === 'string' ? req.query.type.trim() : 'all';
  const rawStartPath = typeof req.query?.path === 'string' ? req.query.path : '/';
  const startPath = normalizeVirtualPath(rawStartPath);
  const limit = Math.max(1, Math.min(GLOBAL_SEARCH_MAX_LIMIT, parseInt(req.query?.limit, 10) || GLOBAL_SEARCH_DEFAULT_LIMIT));
  let storage;

  if (!keyword || keyword.length < 1) {
    return res.status(400).json({
      success: false,
      message: '搜索关键词不能为空'
    });
  }

  if (keyword.length > 80) {
    return res.status(400).json({
      success: false,
      message: '搜索关键词过长（最多80个字符）'
    });
  }

  if (!startPath) {
    return res.status(400).json({
      success: false,
      message: '搜索路径非法'
    });
  }

  if (!['all', 'file', 'directory'].includes(searchType)) {
    return res.status(400).json({
      success: false,
      message: '搜索类型无效'
    });
  }

  try {
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    const searchResult = await searchFilesRecursively(storage, startPath, keyword, {
      type: searchType,
      limit,
      maxNodes: GLOBAL_SEARCH_MAX_SCANNED_NODES
    });

    res.json({
      success: true,
      keyword,
      path: startPath,
      type: searchType,
      storageType: req.user.current_storage_type || 'oss',
      items: searchResult.results,
      meta: searchResult.meta
    });
  } catch (error) {
    console.error('全局搜索失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '全局搜索失败，请稍后重试', '全局搜索')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 重命名文件
app.post('/api/files/rename', authMiddleware, async (req, res) => {
  const oldName = decodeHtmlEntities(req.body.oldName);
  const newName = decodeHtmlEntities(req.body.newName);
  const path = decodeHtmlEntities(req.body.path) || '/';
  let storage;

  if (!oldName || !newName) {
    return res.status(400).json({
      success: false,
      message: '缺少文件名参数'
    });
  }

  try {
    // 使用统一存储接口
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    const oldPath = path === '/' ? `/${oldName}` : `${path}/${oldName}`;
    const newPath = path === '/' ? `/${newName}` : `${path}/${newName}`;

    await storage.rename(oldPath, newPath);

    const normalizedStorageType = req.user.current_storage_type === 'oss' ? 'oss' : 'local';
    const normalizedOldPath = normalizeVirtualPath(oldPath);
    const normalizedNewPath = normalizeVirtualPath(newPath);
    if (normalizedOldPath && normalizedNewPath) {
      const oldHashRow = FileHashIndexDB.getByPath(req.user.id, normalizedStorageType, normalizedOldPath);
      if (oldHashRow) {
        FileHashIndexDB.upsert({
          userId: req.user.id,
          storageType: normalizedStorageType,
          fileHash: oldHashRow.file_hash,
          fileSize: oldHashRow.file_size,
          filePath: normalizedNewPath,
          objectKey: oldHashRow.object_key || null
        });
      }
      FileHashIndexDB.deleteByPath(req.user.id, normalizedStorageType, normalizedOldPath);
    }

    // 清除 OSS 使用情况缓存（如果用户使用 OSS）
    if (req.user.current_storage_type === 'oss') {
      clearOssUsageCache(req.user.id);
    }

    res.json({
      success: true,
      message: '文件重命名成功'
    });
  } catch (error) {
    console.error('重命名文件失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '重命名文件失败，请稍后重试', '重命名文件')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 创建文件夹（支持本地存储和OSS）
app.post('/api/files/mkdir', authMiddleware, async (req, res) => {
  const path = decodeHtmlEntities(req.body.path) || '/';
  const folderName = decodeHtmlEntities(req.body.folderName);
  let storage;

  // 参数验证
  if (!folderName || folderName.trim() === '') {
    return res.status(400).json({
      success: false,
      message: '文件夹名称不能为空'
    });
  }

  // 文件名长度检查
  if (folderName.length > 255) {
    return res.status(400).json({
      success: false,
      message: '文件夹名称过长（最大255个字符）'
    });
  }

  // 文件名安全检查 - 防止路径遍历攻击
  if (folderName.includes('/') || folderName.includes('\\') || folderName.includes('..') || folderName.includes(':')) {
    return res.status(400).json({
      success: false,
      message: '文件夹名称不能包含特殊字符 (/ \\ .. :)'
    });
  }

  try {
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    // 构造文件夹路径
    const basePath = path || '/';
    const folderPath = basePath === '/' ? `/${folderName}` : `${basePath}/${folderName}`;

    // 根据存储类型创建文件夹
    if (req.user.current_storage_type === 'local') {
      // 本地存储：使用 fs.mkdirSync
      const fullPath = storage.getFullPath(folderPath);

      // 检查是否已存在
      if (fs.existsSync(fullPath)) {
        return res.status(400).json({
          success: false,
          message: '文件夹已存在'
        });
      }

      // 创建文件夹 (不使用recursive,只创建当前层级)
      fs.mkdirSync(fullPath, { mode: 0o755 });

      console.log(`[创建文件夹成功] 本地存储 - 用户${req.user.id}: ${folderPath}`);
    } else {
      // OSS 存储：使用 storage.mkdir() 创建空对象模拟文件夹
      await storage.mkdir(folderPath);
      console.log(`[创建文件夹成功] OSS存储 - 用户${req.user.id}: ${folderPath}`);

      // 清除 OSS 使用情况缓存
      clearOssUsageCache(req.user.id);
    }

    res.json({
      success: true,
      message: '文件夹创建成功'
    });
  } catch (error) {
    console.error('[创建文件夹失败]', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '创建文件夹失败，请稍后重试', '创建文件夹')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 获取文件夹详情(大小统计) - 支持本地存储和OSS
app.post('/api/files/folder-info', authMiddleware, async (req, res) => {
  const dirPath = decodeHtmlEntities(req.body.path) || '/';
  const folderName = decodeHtmlEntities(req.body.folderName);
  let storage;

  if (!folderName) {
    return res.status(400).json({
      success: false,
      message: '缺少文件夹名称参数'
    });
  }

  try {
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    // 构造文件夹路径
    const basePath = dirPath || '/';
    const folderPath = basePath === '/' ? `/${folderName}` : `${basePath}/${folderName}`;

    if (req.user.current_storage_type === 'local') {
      // 本地存储实现
      const fullPath = storage.getFullPath(folderPath);

      // 检查是否存在且是文件夹
      if (!fs.existsSync(fullPath)) {
        return res.status(404).json({
          success: false,
          message: '文件夹不存在'
        });
      }

      const stats = fs.statSync(fullPath);
      if (!stats.isDirectory()) {
        return res.status(400).json({
          success: false,
          message: '指定路径不是文件夹'
        });
      }

      // 计算文件夹大小
      const folderSize = storage.calculateFolderSize(fullPath);

      // 计算文件数量
      function countFiles(countDirPath) {
        let fileCount = 0;
        let folderCount = 0;

        const items = fs.readdirSync(countDirPath, { withFileTypes: true });

        for (const item of items) {
          const itemPath = path.join(countDirPath, item.name);

          if (item.isDirectory()) {
            folderCount++;
            const subCounts = countFiles(itemPath);
            fileCount += subCounts.fileCount;
            folderCount += subCounts.folderCount;
          } else {
            fileCount++;
          }
        }

        return { fileCount, folderCount };
      }

      const counts = countFiles(fullPath);

      res.json({
        success: true,
        data: {
          name: folderName,
          path: folderPath,
          size: folderSize,
          fileCount: counts.fileCount,
          folderCount: counts.folderCount
        }
      });
    } else {
      // OSS 存储实现
      const { ListObjectsV2Command } = require('@aws-sdk/client-s3');
      const folderKey = `user_${req.user.id}${folderPath}`;
      // 确保前缀以斜杠结尾
      const prefix = folderKey.endsWith('/') ? folderKey : `${folderKey}/`;

      let totalSize = 0;
      let fileCount = 0;
      let continuationToken = null;

      do {
        const command = new ListObjectsV2Command({
          Bucket: storage.getBucket(),
          Prefix: prefix,
          ContinuationToken: continuationToken
        });

        const response = await storage.s3Client.send(command);

        if (response.Contents) {
          for (const obj of response.Contents) {
            // 跳过文件夹标记对象（以斜杠结尾且大小为0）
            if (obj.Key.endsWith('/') && obj.Size === 0) {
              continue;
            }
            totalSize += obj.Size || 0;
            fileCount++;
          }
        }

        continuationToken = response.NextContinuationToken;
      } while (continuationToken);

      res.json({
        success: true,
        data: {
          name: folderName,
          path: folderPath,
          size: totalSize,
          fileCount: fileCount,
          folderCount: 0 // OSS 没有真正的文件夹概念
        }
      });
    }
  } catch (error) {
    console.error('[获取文件夹详情失败]', error);
    res.status(500).json({
      success: false,
      message: '获取文件夹详情失败: ' + error.message
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 删除文件
// ===== P0 性能优化：更新存储使用量缓存 =====
app.post('/api/files/delete', authMiddleware, async (req, res) => {
  const rawFileName = req.body.fileName;
  const rawPath = req.body.path;
  const fileName = decodeHtmlEntities(rawFileName);
  const path = decodeHtmlEntities(rawPath) || '/';
  let storage;
  let deletedSize = 0;
  let deletedTargetPath = null;

  if (!fileName) {
    return res.status(400).json({
      success: false,
      message: '缺少文件名参数'
    });
  }

  try {
    // 使用统一存储接口
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    const tried = new Set();
    const candidates = [fileName];

    // 兼容被二次编码的实体（如 &amp;#x60; -> &#x60;）
    if (typeof rawFileName === 'string') {
      const entityName = rawFileName.replace(/&amp;/g, '&');
      if (entityName && !candidates.includes(entityName)) {
        candidates.push(entityName);
      }
      if (rawFileName && !candidates.includes(rawFileName)) {
        candidates.push(rawFileName);
      }
    }

    const pathsToDelete = candidates.map(name => (path === '/' ? `/${name}` : `${path}/${name}`));

    try {
      for (const targetPath of pathsToDelete) {
        if (tried.has(targetPath)) continue;
        tried.add(targetPath);
        try {
          // 删除文件并获取文件大小（用于更新缓存）
          const deleteResult = await storage.delete(targetPath);

          // 如果返回了文件大小，记录下来
          if (deleteResult && deleteResult.size !== undefined) {
            deletedSize = deleteResult.size;
          }
          deletedTargetPath = normalizeVirtualPath(targetPath);

          break;
        } catch (err) {
          if (err.code === 'ENOENT') {
            // 尝试下一个候选路径
            if (targetPath === pathsToDelete[pathsToDelete.length - 1]) throw err;
          } else {
            throw err;
          }
        }
      }
    } catch (err) {
      throw err;
    }

    // ===== P0 性能优化：更新存储使用量 =====
    if (req.user.current_storage_type === 'oss' && deletedSize > 0) {
      // 减少存储使用量
      await StorageUsageCache.updateUsage(req.user.id, -deletedSize);

      // 同时更新旧的内存缓存（保持兼容性）
      clearOssUsageCache(req.user.id);

      console.log(`[删除文件] 用户 ${req.user.id} 释放空间: ${deletedSize} 字节`);
    } else if (req.user.current_storage_type === 'oss') {
      // 如果没有获取到文件大小，清除缓存（下次查询时会重新统计）
      clearOssUsageCache(req.user.id);
    }

    if (deletedTargetPath) {
      FileHashIndexDB.deleteByPath(
        req.user.id,
        req.user.current_storage_type === 'oss' ? 'oss' : 'local',
        deletedTargetPath
      );
    }

    res.json({
      success: true,
      message: '删除成功'
    });
  } catch (error) {
    console.error('删除文件失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '删除文件失败，请稍后重试', '删除文件')
    });
  } finally {
    if (storage) await storage.end();
  }
});

function calculateResumableUploadedBytes(chunks = [], totalChunks = 0, chunkSize = 0, fileSize = 0) {
  if (!Array.isArray(chunks) || chunks.length === 0) return 0;
  const safeTotalChunks = Math.max(1, Math.floor(Number(totalChunks) || 1));
  const safeChunkSize = Math.max(1, Math.floor(Number(chunkSize) || 1));
  const safeFileSize = Math.max(0, Math.floor(Number(fileSize) || 0));
  let total = 0;

  for (const chunkIndexRaw of chunks) {
    const chunkIndex = Math.floor(Number(chunkIndexRaw));
    if (!Number.isFinite(chunkIndex) || chunkIndex < 0 || chunkIndex >= safeTotalChunks) {
      continue;
    }
    const start = chunkIndex * safeChunkSize;
    const end = Math.min(safeFileSize, start + safeChunkSize);
    if (end > start) {
      total += (end - start);
    }
  }

  return total;
}

// 秒传预检查：命中哈希后由服务器直接复制，不走客户端上传流量
app.post('/api/files/instant-upload/check', authMiddleware, async (req, res) => {
  try {
    const filename = typeof req.body?.filename === 'string' ? req.body.filename : '';
    const fileHash = normalizeFileHash(req.body?.file_hash);
    const fileSize = Math.floor(Number(req.body?.size) || 0);
    const uploadPath = normalizeUploadPath(req.body?.path || '/');
    const storageType = req.user.current_storage_type === 'oss' ? 'oss' : 'local';

    if (!filename || !isSafePathSegment(filename)) {
      return res.status(400).json({
        success: false,
        message: '文件名无效'
      });
    }

    if (!uploadPath) {
      return res.status(400).json({
        success: false,
        message: '上传路径非法'
      });
    }

    if (!fileHash || fileSize <= 0) {
      return res.json({
        success: true,
        instant: false,
        message: '未命中秒传'
      });
    }

    const targetPath = buildVirtualFilePath(uploadPath, filename);
    if (!targetPath) {
      return res.status(400).json({
        success: false,
        message: '目标路径非法'
      });
    }

    const instantResult = await tryInstantUploadByHash(req.user, {
      storageType,
      fileHash,
      fileSize,
      targetPath
    });

    if (instantResult?.blocked) {
      return res.status(400).json({
        success: false,
        message: instantResult.message || '当前无法执行秒传'
      });
    }

    if (instantResult?.instant) {
      return res.json({
        success: true,
        instant: true,
        already_exists: !!instantResult.alreadyExists,
        path: instantResult.path || targetPath,
        message: instantResult.alreadyExists ? '文件已存在，无需上传' : '秒传成功'
      });
    }

    res.json({
      success: true,
      instant: false,
      message: '未命中秒传'
    });
  } catch (error) {
    console.error('秒传检查失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '秒传检查失败，请稍后重试', '秒传检查')
    });
  }
});

// 本地分片上传初始化（断点续传）
app.post('/api/upload/resumable/init', authMiddleware, async (req, res) => {
  try {
    const uploadStorageType = (req.user.current_storage_type || 'oss') === 'local' ? 'local' : 'oss';
    if (uploadStorageType === 'oss') {
      const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
      if (!req.user.has_oss_config && !hasUnifiedConfig) {
        return res.status(400).json({
          success: false,
          message: '未配置 OSS 服务，无法上传文件'
        });
      }
    }

    const filename = typeof req.body?.filename === 'string' ? req.body.filename.trim() : '';
    const uploadPath = normalizeUploadPath(req.body?.path || '/');
    const fileSize = Math.floor(Number(req.body?.size) || 0);
    const maxUploadSize = parseInt(SettingsDB.get('max_upload_size') || '10737418240', 10);
    const requestedChunkSize = Math.floor(Number(req.body?.chunk_size) || RESUMABLE_UPLOAD_CHUNK_SIZE_BYTES);
    const chunkSize = Math.min(
      RESUMABLE_UPLOAD_MAX_CHUNK_SIZE_BYTES,
      Math.max(256 * 1024, requestedChunkSize)
    );
    const fileHash = normalizeFileHash(req.body?.file_hash);

    if (!filename || !isSafePathSegment(filename)) {
      return res.status(400).json({
        success: false,
        message: '文件名无效'
      });
    }

    if (!isFileExtensionSafe(filename)) {
      return res.status(400).json({
        success: false,
        message: '不允许上传此类型的文件（安全限制）'
      });
    }

    if (!uploadPath) {
      return res.status(400).json({
        success: false,
        message: '上传路径非法'
      });
    }

    if (!Number.isFinite(fileSize) || fileSize <= 0) {
      return res.status(400).json({
        success: false,
        message: '文件大小无效'
      });
    }

    if (Number.isFinite(maxUploadSize) && maxUploadSize > 0 && fileSize > maxUploadSize) {
      return res.status(400).json({
        success: false,
        message: `文件过大，最大允许 ${formatFileSize(maxUploadSize)}`
      });
    }

    const targetPath = buildVirtualFilePath(uploadPath, filename);
    if (!targetPath) {
      return res.status(400).json({
        success: false,
        message: '目标路径非法'
      });
    }

    const totalChunks = Math.ceil(fileSize / chunkSize);
    const existingSession = UploadSessionDB.findActiveForResume(
      req.user.id,
      uploadStorageType,
      targetPath,
      fileSize,
      fileHash || null
    );

    if (existingSession) {
      if (existingSession.temp_file_path && fs.existsSync(existingSession.temp_file_path)) {
        const uploadedChunks = UploadSessionDB.parseUploadedChunks(existingSession.uploaded_chunks);
        const uploadedBytes = calculateResumableUploadedBytes(
          uploadedChunks,
          existingSession.total_chunks,
          existingSession.chunk_size,
          existingSession.file_size
        );

        UploadSessionDB.updateProgress(
          existingSession.session_id,
          uploadedChunks,
          uploadedBytes,
          buildResumableUploadExpiresAt()
        );

        return res.json({
          success: true,
          resumed: true,
          storage_type: existingSession.storage_type || uploadStorageType,
          session_id: existingSession.session_id,
          target_path: existingSession.target_path,
          file_name: existingSession.file_name,
          file_size: existingSession.file_size,
          chunk_size: existingSession.chunk_size,
          total_chunks: existingSession.total_chunks,
          uploaded_chunks: uploadedChunks,
          uploaded_bytes: uploadedBytes
        });
      }

      // 会话存在但临时文件丢失，标记过期并重建
      UploadSessionDB.expireSession(existingSession.session_id);
    }

    const tempRoot = ensureResumableUploadTempRoot();
    const sessionId = crypto.randomBytes(20).toString('hex');
    const tempFilePath = path.join(tempRoot, `${sessionId}.part`);
    fs.closeSync(fs.openSync(tempFilePath, 'w'));

    const createdSession = UploadSessionDB.create({
      sessionId,
      userId: req.user.id,
      storageType: uploadStorageType,
      targetPath,
      fileName: filename,
      fileSize,
      chunkSize,
      totalChunks,
      uploadedChunks: [],
      uploadedBytes: 0,
      tempFilePath,
      fileHash: fileHash || null,
      expiresAt: buildResumableUploadExpiresAt()
    });

    if (!createdSession) {
      safeUnlink(tempFilePath);
      return res.status(500).json({
        success: false,
        message: '创建分片上传会话失败'
      });
    }

    res.json({
      success: true,
      resumed: false,
      storage_type: createdSession.storage_type || uploadStorageType,
      session_id: createdSession.session_id,
      target_path: createdSession.target_path,
      file_name: createdSession.file_name,
      file_size: createdSession.file_size,
      chunk_size: createdSession.chunk_size,
      total_chunks: createdSession.total_chunks,
      uploaded_chunks: []
    });
  } catch (error) {
    console.error('初始化分片上传失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '初始化分片上传失败，请稍后重试', '分片上传初始化')
    });
  }
});

// 查询分片上传会话状态
app.get('/api/upload/resumable/status', authMiddleware, (req, res) => {
  try {
    const sessionId = typeof req.query?.session_id === 'string' ? req.query.session_id.trim() : '';
    if (!sessionId) {
      return res.status(400).json({
        success: false,
        message: '缺少会话ID'
      });
    }

    const session = UploadSessionDB.findBySessionId(sessionId);
    if (!session || Number(session.user_id) !== Number(req.user.id)) {
      return res.status(404).json({
        success: false,
        message: '上传会话不存在'
      });
    }

    const uploadedChunks = UploadSessionDB.parseUploadedChunks(session.uploaded_chunks);
    const uploadedBytes = calculateResumableUploadedBytes(
      uploadedChunks,
      session.total_chunks,
      session.chunk_size,
      session.file_size
    );

    res.json({
      success: true,
      session_id: session.session_id,
      status: session.status,
      target_path: session.target_path,
      file_name: session.file_name,
      file_size: session.file_size,
      chunk_size: session.chunk_size,
      total_chunks: session.total_chunks,
      uploaded_chunks: uploadedChunks,
      uploaded_bytes: uploadedBytes
    });
  } catch (error) {
    console.error('获取分片上传状态失败:', error);
    res.status(500).json({
      success: false,
      message: '获取上传状态失败'
    });
  }
});

// 上传分片
app.post('/api/upload/resumable/chunk', authMiddleware, upload.single('chunk'), async (req, res) => {
  const tempChunkPath = req.file?.path;
  try {
    const sessionId = typeof req.body?.session_id === 'string' ? req.body.session_id.trim() : '';
    const chunkIndex = Math.floor(Number(req.body?.chunk_index));
    if (!sessionId) {
      return res.status(400).json({
        success: false,
        message: '缺少会话ID'
      });
    }
    if (!Number.isFinite(chunkIndex) || chunkIndex < 0) {
      return res.status(400).json({
        success: false,
        message: '分片索引无效'
      });
    }
    if (!req.file) {
      return res.status(400).json({
        success: false,
        message: '缺少分片文件'
      });
    }

    const session = UploadSessionDB.findBySessionId(sessionId);
    if (!session || Number(session.user_id) !== Number(req.user.id)) {
      return res.status(404).json({
        success: false,
        message: '上传会话不存在'
      });
    }

    if (session.status !== 'active') {
      return res.status(409).json({
        success: false,
        message: '上传会话已结束，请重新开始上传'
      });
    }

    const sessionExpireAt = parseDateTimeValue(session.expires_at);
    if (sessionExpireAt && Date.now() >= sessionExpireAt.getTime()) {
      UploadSessionDB.expireSession(session.session_id);
      return res.status(409).json({
        success: false,
        message: '上传会话已过期，请重新开始上传'
      });
    }

    const totalChunks = Math.max(1, Math.floor(Number(session.total_chunks) || 1));
    const chunkSize = Math.max(1, Math.floor(Number(session.chunk_size) || 1));
    const fileSize = Math.max(0, Math.floor(Number(session.file_size) || 0));

    if (chunkIndex >= totalChunks) {
      return res.status(400).json({
        success: false,
        message: '分片索引超出范围'
      });
    }

    const expectedChunkSize = chunkIndex === totalChunks - 1
      ? Math.max(0, fileSize - (chunkIndex * chunkSize))
      : chunkSize;
    const receivedChunkSize = Math.max(0, Number(req.file.size || 0));
    if (receivedChunkSize <= 0 || receivedChunkSize > chunkSize || (expectedChunkSize > 0 && receivedChunkSize > expectedChunkSize)) {
      return res.status(400).json({
        success: false,
        message: '分片大小无效'
      });
    }

    if (!session.temp_file_path || !fs.existsSync(session.temp_file_path)) {
      return res.status(409).json({
        success: false,
        message: '上传会话文件已丢失，请重新开始上传'
      });
    }

    const chunkBuffer = await fs.promises.readFile(req.file.path);
    const offset = chunkIndex * chunkSize;
    const fd = await fs.promises.open(session.temp_file_path, 'r+');
    try {
      await fd.write(chunkBuffer, 0, chunkBuffer.length, offset);
    } finally {
      await fd.close();
    }

    const uploadedChunks = UploadSessionDB.parseUploadedChunks(session.uploaded_chunks);
    if (!uploadedChunks.includes(chunkIndex)) {
      uploadedChunks.push(chunkIndex);
      uploadedChunks.sort((a, b) => a - b);
    }

    const uploadedBytes = calculateResumableUploadedBytes(
      uploadedChunks,
      totalChunks,
      chunkSize,
      fileSize
    );

    UploadSessionDB.updateProgress(
      session.session_id,
      uploadedChunks,
      uploadedBytes,
      buildResumableUploadExpiresAt()
    );

    res.json({
      success: true,
      session_id: session.session_id,
      chunk_index: chunkIndex,
      uploaded_chunks_count: uploadedChunks.length,
      total_chunks: totalChunks,
      uploaded_bytes: uploadedBytes,
      progress: fileSize > 0 ? Math.min(100, Math.round((uploadedBytes / fileSize) * 100)) : 0
    });
  } catch (error) {
    console.error('上传分片失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '上传分片失败，请稍后重试', '上传分片')
    });
  } finally {
    if (tempChunkPath) {
      safeDeleteFile(tempChunkPath);
    }
  }
});

// 完成分片上传（写入当前存储：本地或 OSS）
app.post('/api/upload/resumable/complete', authMiddleware, async (req, res) => {
  let storage = null;
  try {
    const sessionId = typeof req.body?.session_id === 'string' ? req.body.session_id.trim() : '';
    if (!sessionId) {
      return res.status(400).json({
        success: false,
        message: '缺少会话ID'
      });
    }

    const session = UploadSessionDB.findBySessionId(sessionId);
    if (!session || Number(session.user_id) !== Number(req.user.id)) {
      return res.status(404).json({
        success: false,
        message: '上传会话不存在'
      });
    }

    if (session.status !== 'active') {
      return res.status(409).json({
        success: false,
        message: '上传会话已结束，请重新上传'
      });
    }

    const uploadedChunks = UploadSessionDB.parseUploadedChunks(session.uploaded_chunks);
    const totalChunks = Math.max(1, Math.floor(Number(session.total_chunks) || 1));
    if (uploadedChunks.length < totalChunks) {
      return res.status(409).json({
        success: false,
        message: `分片未上传完成（${uploadedChunks.length}/${totalChunks}）`
      });
    }

    if (!session.temp_file_path || !fs.existsSync(session.temp_file_path)) {
      return res.status(409).json({
        success: false,
        message: '上传临时文件不存在，请重新上传'
      });
    }

    const tempFileStats = fs.statSync(session.temp_file_path);
    const fileSize = Math.max(0, Math.floor(Number(session.file_size) || 0));
    if (tempFileStats.size < fileSize) {
      return res.status(409).json({
        success: false,
        message: '分片数据不完整，请重试'
      });
    }

    const sessionStorageType = session.storage_type === 'local' ? 'local' : 'oss';

    const latestUser = UserDB.findById(req.user.id);
    if (!latestUser) {
      return res.status(404).json({
        success: false,
        message: '用户不存在'
      });
    }

    if (sessionStorageType === 'oss') {
      const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
      if (!latestUser.has_oss_config && !hasUnifiedConfig) {
        return res.status(400).json({
          success: false,
          message: '未配置 OSS 服务，无法完成分片上传'
        });
      }
    }

    const storageUserContext = buildStorageUserContext(latestUser, {
      current_storage_type: sessionStorageType
    });
    const storageInterface = new StorageInterface(storageUserContext);
    storage = await storageInterface.connect();
    await storage.put(session.temp_file_path, session.target_path);
    if (sessionStorageType === 'oss') {
      clearOssUsageCache(req.user.id);
    }

    UploadSessionDB.setStatus(session.session_id, 'completed', {
      completed: true,
      expiresAt: buildResumableUploadExpiresAt(60 * 1000)
    });
    safeUnlink(session.temp_file_path);

    await trackFileHashIndexForUpload({
      userId: req.user.id,
      storageType: sessionStorageType,
      fileHash: session.file_hash,
      fileSize,
      filePath: session.target_path
    });

    res.json({
      success: true,
      message: '分片上传完成',
      path: session.target_path,
      file_name: session.file_name,
      file_size: fileSize
    });
  } catch (error) {
    console.error('完成分片上传失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '完成分片上传失败，请稍后重试', '完成分片上传')
    });
  } finally {
    if (storage) {
      await storage.end().catch(() => {});
    }
  }
});

// ========== OSS 直连相关接口（Presigned URL）==========

// 生成 OSS 上传签名 URL（用户直连 OSS 上传，不经过后端）
app.get('/api/files/upload-signature', authMiddleware, async (req, res) => {
  const filename = req.query.filename;
  const uploadPath = req.query.path || '/'; // 上传目标路径
  const contentType = req.query.contentType || 'application/octet-stream';
  const fileSize = Number(req.query.size);
  const fileHash = normalizeFileHash(req.query.fileHash);

  if (!filename) {
    return res.status(400).json({
      success: false,
      message: '缺少文件名参数'
    });
  }

  // 文件名长度限制（最大255字符）
  if (filename.length > 255) {
    return res.status(400).json({
      success: false,
      message: '文件名过长，最大支持255个字符'
    });
  }

  // 文件名安全校验
  if (!isSafePathSegment(filename)) {
    return res.status(400).json({
      success: false,
      message: '文件名包含非法字符'
    });
  }

  // 文件扩展名安全检查（防止上传危险文件）
  if (!isFileExtensionSafe(filename)) {
    console.warn(`[安全] 拒绝上传危险文件: ${filename}, 用户: ${req.user.username}`);
    return res.status(400).json({
      success: false,
      message: '不允许上传此类型的文件（安全限制）'
    });
  }

  // 文件大小参数校验（用于 OSS 配额校验）
  if (!Number.isFinite(fileSize) || fileSize <= 0) {
    return res.status(400).json({
      success: false,
      message: '缺少或无效的文件大小参数'
    });
  }

  const maxUploadSize = parseInt(SettingsDB.get('max_upload_size') || '10737418240', 10);
  if (Number.isFinite(maxUploadSize) && maxUploadSize > 0 && fileSize > maxUploadSize) {
    return res.status(400).json({
      success: false,
      message: `文件过大，最大允许 ${formatFileSize(maxUploadSize)}`
    });
  }

  // 路径安全验证：防止目录遍历攻击
  if (uploadPath.includes('..') || uploadPath.includes('\x00')) {
    return res.status(400).json({
      success: false,
      message: '上传路径非法'
    });
  }

  // 检查用户是否配置了 OSS（包括个人配置和系统级统一配置）
  const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
  if (!req.user.has_oss_config && !hasUnifiedConfig) {
    return res.status(400).json({
      success: false,
      message: '未配置 OSS 服务'
    });
  }

  try {
    const { PutObjectCommand, HeadObjectCommand } = require('@aws-sdk/client-s3');
    const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

    const { client, bucket } = createS3ClientContextForUser(req.user);

    // 构建对象 Key（与 OssStorageClient.getObjectKey 格式一致）
    // 格式：user_${id}/${path}/${filename}
    const sanitizedFilename = sanitizeFilename(filename);
    let normalizedPath = uploadPath.replace(/\\/g, '/').replace(/\/+/g, '/');
    // 移除开头的斜杠
    normalizedPath = normalizedPath.replace(/^\/+/, '');
    // 移除结尾的斜杠
    normalizedPath = normalizedPath.replace(/\/+$/, '');

    // 构建完整的 objectKey
    let objectKey;
    if (normalizedPath === '' || normalizedPath === '.') {
      // 根目录上传
      objectKey = `user_${req.user.id}/${sanitizedFilename}`;
    } else {
      // 子目录上传
      objectKey = `user_${req.user.id}/${normalizedPath}/${sanitizedFilename}`;
    }

    let previousSize = 0;
    try {
      const headResponse = await client.send(new HeadObjectCommand({
        Bucket: bucket,
        Key: objectKey
      }));
      previousSize = Number(headResponse.ContentLength || 0);
      if (!Number.isFinite(previousSize) || previousSize < 0) {
        previousSize = 0;
      }
    } catch (headError) {
      const statusCode = headError?.$metadata?.httpStatusCode;
      if (headError?.name !== 'NotFound' && headError?.name !== 'NoSuchKey' && statusCode !== 404) {
        throw headError;
      }
    }

    // OSS 配额校验（未配置时默认 1GB）
    const latestUser = UserDB.findById(req.user.id);
    const ossQuota = normalizeOssQuota(latestUser?.oss_storage_quota);
    const currentUsage = Number(latestUser?.storage_used || 0);
    const baseUsage = Math.max(0, currentUsage - previousSize);
    const projectedUsage = baseUsage + fileSize;

    if (projectedUsage > ossQuota) {
      const remainingBytes = Math.max(ossQuota - baseUsage, 0);
      return res.status(400).json({
        success: false,
        message: `OSS 配额不足：文件 ${formatFileSize(fileSize)}，剩余 ${formatFileSize(remainingBytes)}（总配额 ${formatFileSize(ossQuota)}）`
      });
    }

    const completionToken = signEphemeralToken({
      type: 'upload_complete',
      userId: req.user.id,
      objectKey,
      previousSize,
      expectedSize: fileSize,
      fileHash: fileHash || null
    }, 30 * 60);

    // 创建 PutObject 命令
    const command = new PutObjectCommand({
      Bucket: bucket,
      Key: objectKey,
      ContentType: contentType
    });

    // 生成签名 URL（15分钟有效）
    const signedUrl = await getSignedUrl(client, command, { expiresIn: 900 });

    res.json({
      success: true,
      uploadUrl: signedUrl,
      objectKey: objectKey,
      previousSize,
      completionToken,
      expiresIn: 900
    });
  } catch (error) {
    console.error('[OSS签名] 生成上传签名失败:', error);
    res.status(500).json({
      success: false,
      message: '生成上传签名失败: ' + error.message
    });
  }
});

// OSS 上传完成通知（用于更新缓存和数据库）
// ===== P0 性能优化：使用增量更新替代全量统计 =====
app.post('/api/files/upload-complete', authMiddleware, async (req, res) => {
  const { objectKey, size, completionToken } = req.body;

  const normalizedObjectKey = typeof objectKey === 'string'
    ? objectKey.replace(/\\/g, '/').replace(/^\/+/, '')
    : '';
  const reportedSize = Number(size);

  if (!normalizedObjectKey) {
    return res.status(400).json({
      success: false,
      message: '缺少对象Key参数'
    });
  }

  if (!Number.isFinite(reportedSize) || reportedSize < 0) {
    return res.status(400).json({
      success: false,
      message: '文件大小参数无效'
    });
  }

  const expectedPrefix = `user_${req.user.id}/`;
  if (
    !normalizedObjectKey.startsWith(expectedPrefix) ||
    normalizedObjectKey.includes('..') ||
    normalizedObjectKey.includes('\x00')
  ) {
    return res.status(403).json({
      success: false,
      message: '对象Key不属于当前用户或格式非法'
    });
  }

  // 安全检查：验证用户是否配置了OSS（个人配置或系统级统一配置）
  const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
  if (!req.user.has_oss_config && !hasUnifiedConfig) {
    return res.status(400).json({
      success: false,
      message: '未配置OSS服务，无法完成上传'
    });
  }

  const completionTokenResult = verifyEphemeralToken(completionToken, 'upload_complete');
  if (!completionTokenResult.valid) {
    return res.status(403).json({
      success: false,
      message: '上传完成凭证无效或已过期'
    });
  }

  const completionPayload = completionTokenResult.payload || {};
  if (
    Number(completionPayload.userId) !== Number(req.user.id) ||
    completionPayload.objectKey !== normalizedObjectKey
  ) {
    return res.status(403).json({
      success: false,
      message: '上传完成凭证与对象不匹配'
    });
  }

  const previousObjectSize = Number.isFinite(Number(completionPayload.previousSize)) && Number(completionPayload.previousSize) >= 0
    ? Number(completionPayload.previousSize)
    : 0;
  const completionFileHash = normalizeFileHash(completionPayload.fileHash);
  const virtualFilePath = `/${normalizedObjectKey.slice(expectedPrefix.length)}`;

  let ossClient;

  try {
    const { HeadObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3');

    ossClient = createOssClientForUser(req.user);
    await ossClient.connect();

    const headResponse = await ossClient.s3Client.send(new HeadObjectCommand({
      Bucket: ossClient.getBucket(),
      Key: normalizedObjectKey
    }));

    const verifiedSize = Number(headResponse.ContentLength || 0);

    if (!Number.isFinite(verifiedSize) || verifiedSize < 0) {
      throw new Error('无法确认上传对象大小');
    }

    if (verifiedSize !== reportedSize) {
      console.warn(`[上传完成] 用户 ${req.user.id} 上报大小(${reportedSize})与实际大小(${verifiedSize})不一致，已使用实际大小`);
    }

    const deltaSize = verifiedSize - previousObjectSize;

    // 二次校验 OSS 配额（防止签名后并发上传导致超限）
    const latestUser = UserDB.findById(req.user.id);
    const ossQuota = normalizeOssQuota(latestUser?.oss_storage_quota);
    const currentUsage = Number(latestUser?.storage_used || 0);
    const projectedUsage = Math.max(0, currentUsage + deltaSize);

    if (projectedUsage > ossQuota) {
      // 回滚：删除刚上传的对象，避免超配额文件残留
      await ossClient.s3Client.send(new DeleteObjectCommand({
        Bucket: ossClient.getBucket(),
        Key: normalizedObjectKey
      }));

      // 若为覆盖上传，旧对象已被替换，删除后需扣除旧对象体积
      if (previousObjectSize > 0) {
        await StorageUsageCache.updateUsage(req.user.id, -previousObjectSize);
      }

      clearOssUsageCache(req.user.id);

      return res.status(400).json({
        success: false,
        message: `OSS 配额不足：已用 ${formatFileSize(currentUsage)}，配额 ${formatFileSize(ossQuota)}，上传后将超限`
      });
    }

    // 更新存储使用量缓存（增量更新，覆盖上传只记录差值）
    if (deltaSize !== 0) {
      await StorageUsageCache.updateUsage(req.user.id, deltaSize);
    }

    // 同时更新旧的内存缓存（保持兼容性）
    clearOssUsageCache(req.user.id);

    await trackFileHashIndexForUpload({
      userId: req.user.id,
      storageType: 'oss',
      fileHash: completionFileHash,
      fileSize: verifiedSize,
      filePath: normalizeVirtualPath(virtualFilePath),
      objectKey: normalizedObjectKey
    });

    console.log(`[上传完成] 用户 ${req.user.id} 文件大小: ${verifiedSize} 字节, 增量: ${deltaSize} 字节`);
    res.json({
      success: true,
      message: '上传完成已记录',
      recordedSize: verifiedSize,
      deltaSize
    });
  } catch (error) {
    console.error('[OSS上传] 记录上传完成失败:', error);
    res.status(500).json({
      success: false,
      message: '记录上传完成失败: ' + error.message
    });
  } finally {
    if (ossClient) {
      await ossClient.end();
    }
  }
});

// 生成 OSS 下载签名 URL（用户直连 OSS 下载，不经过后端）
app.get('/api/files/download-url', authMiddleware, async (req, res) => {
  const filePath = req.query.path;
  const mode = String(req.query.mode || 'download').toLowerCase();
  const isPreviewMode = mode === 'preview';

  if (!filePath) {
    return res.status(400).json({
      success: false,
      message: '缺少文件路径参数'
    });
  }

  // 路径安全验证：防止目录遍历攻击
  const normalizedPath = path.posix.normalize(filePath);
  if (normalizedPath.includes('..') || filePath.includes('\x00')) {
    return res.status(400).json({
      success: false,
      message: '文件路径非法'
    });
  }

  const policyState = enforceDownloadTrafficPolicy(req.user.id, 'download_url');
  const latestUser = policyState?.user || UserDB.findById(req.user.id);
  if (!latestUser) {
    return res.status(401).json({
      success: false,
      message: '用户不存在'
    });
  }

  if (!isPreviewMode) {
    const securityResult = evaluateDownloadSecurityPolicy(req, {
      ownerUserId: latestUser.id,
      filePath: normalizedPath,
      source: 'user_download_url'
    });
    if (!securityResult.allowed) {
      return handleDownloadSecurityBlock(req, res, securityResult, {
        statusCode: 503,
        ownerUserId: latestUser.id,
        filePath: normalizedPath,
        source: 'user_download_url'
      });
    }
  }

  const trafficState = getDownloadTrafficState(latestUser);

  // 检查用户是否配置了 OSS（包括个人配置和系统级统一配置）
  const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
  if (!req.user.has_oss_config && !hasUnifiedConfig) {
    return res.status(400).json({
      success: false,
      message: '未配置 OSS 服务'
    });
  }

  try {
    const { GetObjectCommand, HeadObjectCommand } = require('@aws-sdk/client-s3');
    const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

    const { client, bucket, ossClient } = createS3ClientContextForUser(latestUser);
    const objectKey = ossClient.getObjectKey(normalizedPath);
    let fileSize = 0;

    // 启用下载流量限制时，签发前先获取文件大小（用于预扣保留额度）
    if (!trafficState.isUnlimited) {
      let headResponse;
      try {
        headResponse = await client.send(new HeadObjectCommand({
          Bucket: bucket,
          Key: objectKey
        }));
      } catch (headError) {
        const statusCode = headError?.$metadata?.httpStatusCode;
        if (headError?.name === 'NotFound' || headError?.name === 'NoSuchKey' || statusCode === 404) {
          return res.status(404).json({
            success: false,
            message: '文件不存在'
          });
        }
        throw headError;
      }

      const contentLength = Number(headResponse?.ContentLength || 0);
      fileSize = Number.isFinite(contentLength) && contentLength > 0
        ? Math.floor(contentLength)
        : 0;
      if (fileSize <= 0) {
        return res.status(503).json({
          success: false,
          message: getBusyDownloadMessage()
        });
      }
    }

    // 创建 GetObject 命令（预览模式使用 inline，下载模式使用 attachment）
    const fileName = normalizedPath.split('/').pop() || 'download.bin';
    const commandInput = {
      Bucket: bucket,
      Key: objectKey,
      ResponseContentDisposition: isPreviewMode
        ? `inline; filename*=UTF-8''${encodeURIComponent(fileName)}`
        : `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`
    };
    const command = new GetObjectCommand(commandInput);

    // 生成签名 URL（短时有效，默认30秒）
    const signedUrl = await getSignedUrl(client, command, {
      expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS
    });

    // 直连模式：先预扣保留额度（不写入已用），实际用量由 OSS 日志异步确认入账
    if (!trafficState.isUnlimited && fileSize > 0) {
      const reserveResult = reserveDirectDownloadTraffic(latestUser.id, fileSize, {
        source: 'direct',
        objectKey,
        ttlMs: DOWNLOAD_RESERVATION_TTL_MS
      });
      if (!reserveResult?.ok) {
        return res.status(503).json({
          success: false,
          message: getBusyDownloadMessage()
        });
      }
    }

    res.json({
      success: true,
      downloadUrl: signedUrl,
      expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS,
      direct: true,
      quotaLimited: !trafficState.isUnlimited
    });
  } catch (error) {
    console.error('[OSS签名] 生成下载签名失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '生成下载签名失败，请稍后重试', '生成下载签名')
    });
  }
});

// 辅助函数：清理文件名（增强版安全处理）
function sanitizeFilename(filename) {
  if (!filename || typeof filename !== 'string') {
    return 'unnamed_file';
  }

  let sanitized = filename;

  // 1. 移除空字节和控制字符
  sanitized = sanitized.replace(/[\x00-\x1f\x7f]/g, '');

  // 2. 移除或替换危险字符（Windows/Linux 文件系统不允许的字符）
  sanitized = sanitized.replace(/[<>:"/\\|?*]/g, '_');

  // 3. 移除前导/尾随的点和空格（防止隐藏文件和路径混淆）
  sanitized = sanitized.replace(/^[\s.]+|[\s.]+$/g, '');

  // 4. 限制文件名长度（防止过长文件名攻击）
  if (sanitized.length > 200) {
    const ext = path.extname(sanitized);
    const base = path.basename(sanitized, ext);
    sanitized = base.substring(0, 200 - ext.length) + ext;
  }

  // 5. 如果处理后为空，使用默认名称
  if (!sanitized || sanitized.length === 0) {
    sanitized = 'unnamed_file';
  }

  return sanitized;
}

// ========== 本地存储上传接口（保留用于本地存储模式）==========

// 上传文件（添加速率限制）- 仅用于本地存储
app.post('/api/upload', authMiddleware, upload.single('file'), async (req, res) => {
  // 速率限制检查
  const rateLimitKey = `upload:${req.user.id}`;
  const rateLimitResult = uploadLimiter.recordFailure(rateLimitKey);
  if (rateLimitResult.blocked) {
    // 清理已上传的临时文件
    if (req.file && fs.existsSync(req.file.path)) {
      safeDeleteFile(req.file.path);
    }
    return res.status(429).json({
      success: false,
      message: `上传过于频繁，请在 ${rateLimitResult.waitMinutes} 分钟后重试`
    });
  }

  if (!req.file) {
    return res.status(400).json({
      success: false,
      message: '没有上传文件'
    });
  }

  // 检查文件大小限制
  const maxUploadSize = parseInt(SettingsDB.get('max_upload_size') || '10737418240');
  if (req.file.size > maxUploadSize) {
    // 删除已上传的临时文件
    if (fs.existsSync(req.file.path)) {
      safeDeleteFile(req.file.path);
    }

    return res.status(413).json({
      success: false,
      message: '文件超过上传限制',
      maxSize: maxUploadSize,
      fileSize: req.file.size
    });
  }

  const remotePath = req.body.path || '/';
  const fileHash = normalizeFileHash(req.body?.file_hash || req.body?.fileHash);
  // 修复中文文件名：multer将UTF-8转为了Latin1，需要转回来
  const originalFilename = Buffer.from(req.file.originalname, 'latin1').toString('utf8');

  // 文件名长度限制（最大255字符）
  if (originalFilename.length > 255) {
    safeDeleteFile(req.file.path);
    return res.status(400).json({
      success: false,
      message: '文件名过长，最大支持255个字符'
    });
  }

  // 文件名安全校验
  if (!isSafePathSegment(originalFilename)) {
    safeDeleteFile(req.file.path);
    return res.status(400).json({
      success: false,
      message: '文件名包含非法字符'
    });
  }

  // 文件扩展名安全检查（防止上传危险文件）
  if (!isFileExtensionSafe(originalFilename)) {
    console.warn(`[安全] 拒绝上传危险文件: ${originalFilename}, 用户: ${req.user.username}`);
    safeDeleteFile(req.file.path);
    return res.status(400).json({
      success: false,
      message: '不允许上传此类型的文件（安全限制）'
    });
  }

  // 路径安全校验
  const normalizedPath = path.posix.normalize(remotePath || '/');
  if (normalizedPath.includes('..')) {
    return res.status(400).json({
      success: false,
      message: '上传路径非法'
    });
  }
  const safePath = normalizedPath === '.' ? '/' : normalizedPath;

  const remoteFilePath = safePath === '/'
    ? `/${originalFilename}`
    : `${safePath}/${originalFilename}`;

  let storage;

  try {
    // 使用统一存储接口
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    // storage.put() 内部已经实现了临时文件+重命名逻辑
    await storage.put(req.file.path, remoteFilePath);
    console.log(`[上传] 文件上传成功: ${remoteFilePath}`);

    await trackFileHashIndexForUpload({
      userId: req.user.id,
      storageType: 'local',
      fileHash,
      fileSize: req.file.size,
      filePath: normalizeVirtualPath(remoteFilePath)
    });

    // 清除 OSS 使用情况缓存（如果用户使用 OSS）
    if (req.user.current_storage_type === 'oss') {
      clearOssUsageCache(req.user.id);
    }

    // 删除本地临时文件
    safeDeleteFile(req.file.path);

    res.json({
      success: true,
      message: '文件上传成功',
      filename: originalFilename,
      path: remoteFilePath
    });
  } catch (error) {
    console.error('文件上传失败:', error);

    // 删除临时文件
    if (fs.existsSync(req.file.path)) {
      safeDeleteFile(req.file.path);
    }

    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '文件上传失败，请稍后重试', '文件上传')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 下载预检（避免前端直接下载到 JSON 错误响应）
app.get('/api/files/download-check', authMiddleware, async (req, res) => {
  const filePath = req.query.path;
  let storage;

  if (!filePath) {
    return res.status(400).json({
      success: false,
      message: '缺少文件路径参数'
    });
  }

  const normalizedPath = path.posix.normalize(filePath);
  if (normalizedPath.includes('..') || filePath.includes('\x00')) {
    return res.status(400).json({
      success: false,
      message: '文件路径非法'
    });
  }

  try {
    const policyState = enforceDownloadTrafficPolicy(req.user.id, 'download_check');
    const latestUser = policyState?.user || UserDB.findById(req.user.id);
    if (!latestUser) {
      return res.status(401).json({
        success: false,
        message: '用户不存在'
      });
    }
    const trafficState = getDownloadTrafficState(latestUser);

    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    const fileStats = await storage.stat(normalizedPath);
    const fileSize = Math.max(0, Number(fileStats?.size) || 0);
    const fileName = normalizedPath.split('/').pop() || 'download.bin';

    if (!trafficState.isUnlimited && fileSize > trafficState.remaining) {
      return res.status(403).json({
        success: false,
        message: `下载流量不足：文件 ${formatFileSize(fileSize)}，剩余 ${formatFileSize(trafficState.remaining)}`
      });
    }

    res.json({
      success: true,
      fileName,
      fileSize
    });
  } catch (error) {
    console.error('下载预检失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '下载检查失败，请稍后重试', '下载预检')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 下载文件
app.get('/api/files/download', authMiddleware, async (req, res) => {
  const filePath = req.query.path;
  let storage;
  let storageEnded = false; // 防止重复关闭
  let transferFinalized = false; // 防止重复结算
  let downloadedBytes = 0;
  let responseBodyStartSocketBytes = 0;

  // Express 会将 HEAD 映射到 GET 处理器，这里显式拒绝，避免误触发下载计量
  if (req.method === 'HEAD') {
    res.setHeader('Allow', 'GET');
    return res.status(405).end();
  }

  // 安全关闭存储连接的辅助函数
  const safeEndStorage = async () => {
    if (storage && !storageEnded) {
      storageEnded = true;
      try {
        await storage.end();
      } catch (err) {
        console.error('关闭存储连接失败:', err);
      }
    }
  };

  const finalizeTransfer = async (reason = '') => {
    if (transferFinalized) {
      return;
    }
    transferFinalized = true;

    try {
      const socketBytesWritten = Number(res.socket?.bytesWritten);
      const socketBodyBytes = Number.isFinite(socketBytesWritten) && socketBytesWritten > responseBodyStartSocketBytes
        ? Math.floor(socketBytesWritten - responseBodyStartSocketBytes)
        : 0;
      const billableBytes = socketBodyBytes > 0
        ? Math.min(downloadedBytes, socketBodyBytes)
        : downloadedBytes;

      if (billableBytes > 0) {
        const usageResult = applyDownloadTrafficUsage(req.user.id, billableBytes);
        if (usageResult) {
          const quotaText = usageResult.quota > 0 ? formatFileSize(usageResult.quota) : '不限';
          console.log(
            `[下载流量] 用户 ${req.user.id} 新增 ${formatFileSize(usageResult.added)}，` +
            `累计 ${formatFileSize(usageResult.usedAfter)} / ${quotaText} ` +
            `(reason=${reason || 'unknown'}, streamed=${downloadedBytes}, socket=${socketBodyBytes})`
          );
        }
      }
    } catch (error) {
      console.error(`[下载流量] 结算失败: user=${req.user.id}, bytes=${downloadedBytes}`, error);
    }

    await safeEndStorage();
  };

  if (!filePath) {
    return res.status(400).json({
      success: false,
      message: '缺少文件路径参数'
    });
  }

  // 路径安全验证：防止目录遍历攻击
  const normalizedPath = path.posix.normalize(filePath);
  if (normalizedPath.includes('..') || filePath.includes('\x00')) {
    return res.status(400).json({
      success: false,
      message: '文件路径非法'
    });
  }

  try {
    const policyState = enforceDownloadTrafficPolicy(req.user.id, 'download');
    const latestUser = policyState?.user || UserDB.findById(req.user.id);
    if (!latestUser) {
      return res.status(401).json({
        success: false,
        message: '用户不存在'
      });
    }

    const securityResult = evaluateDownloadSecurityPolicy(req, {
      ownerUserId: latestUser.id,
      filePath: normalizedPath,
      source: 'user_download_stream'
    });
    if (!securityResult.allowed) {
      return handleDownloadSecurityBlock(req, res, securityResult, {
        statusCode: 503,
        ownerUserId: latestUser.id,
        filePath: normalizedPath,
        source: 'user_download_stream'
      });
    }

    const trafficState = getDownloadTrafficState(latestUser);

    // 使用统一存储接口
    const { StorageInterface } = require('./storage');
    const storageInterface = new StorageInterface(req.user);
    storage = await storageInterface.connect();

    // 获取文件名
    const fileName = normalizedPath.split('/').pop() || 'download.bin';

    // 先获取文件信息（获取文件大小）
    const fileStats = await storage.stat(normalizedPath);
    const fileSize = Math.max(0, Number(fileStats?.size) || 0);
    console.log('[下载] 文件: ' + fileName + ', 大小: ' + fileSize + ' 字节');

    if (!trafficState.isUnlimited && fileSize > trafficState.remaining) {
      await safeEndStorage();
      return res.status(403).json({
        success: false,
        message: `下载流量不足：文件 ${formatFileSize(fileSize)}，剩余 ${formatFileSize(trafficState.remaining)}`
      });
    }

    // 设置响应头（包含文件大小，浏览器可显示下载进度）
    res.setHeader('Content-Type', 'application/octet-stream');
    res.setHeader('Content-Length', fileSize);
    // 关闭 Nginx 代理缓冲，避免上游提前读完整文件导致流量计量失真
    res.setHeader('X-Accel-Buffering', 'no');
    res.setHeader('Content-Disposition', 'attachment; filename="' + encodeURIComponent(fileName) + '"; filename*=UTF-8\'\'' + encodeURIComponent(fileName));
    if (typeof res.flushHeaders === 'function') {
      res.flushHeaders();
    }
    responseBodyStartSocketBytes = Number(res.socket?.bytesWritten) || 0;

    // 创建文件流并传输（流式下载，服务器不保存临时文件）
    const stream = await storage.createReadStream(normalizedPath);

    stream.on('data', (chunk) => {
      if (!chunk) return;
      downloadedBytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
    });

    res.on('finish', () => {
      finalizeTransfer('finish').catch(err => {
        console.error('下载完成后资源释放失败:', err);
      });
    });

    res.on('close', () => {
      finalizeTransfer('close').catch(err => {
        console.error('下载连接关闭后资源释放失败:', err);
      });
    });

    stream.on('error', (error) => {
      console.error('文件流错误:', error);
      if (!res.headersSent) {
        res.status(500).json({
          success: false,
          message: getSafeErrorMessage(error, '下载文件失败，请稍后重试', '下载文件')
        });
      }
      finalizeTransfer('stream_error').catch(err => {
        console.error('流错误后资源释放失败:', err);
      });
    });

    stream.pipe(res);

  } catch (error) {
    console.error('下载文件失败:', error);

    // 如果stream还未创建或发生错误，关闭storage连接
    await finalizeTransfer('catch_error');
    if (!res.headersSent) {
      res.status(500).json({
        success: false,
        message: getSafeErrorMessage(error, '下载文件失败，请稍后重试', '下载文件')
      });
    }
  }
});

// 生成上传工具（生成新密钥并创建配置文件）
app.post('/api/upload/generate-tool', authMiddleware, async (req, res) => {
  try {
    // 生成新的API密钥（32位随机字符串）
    const newApiKey = crypto.randomBytes(16).toString('hex');

    // 更新用户的upload_api_key
    UserDB.update(req.user.id, { upload_api_key: newApiKey });

    // 创建配置文件内容
    const config = {
      username: req.user.username,
      api_key: newApiKey,
      api_base_url: getSecureBaseUrl(req)
    };

    res.json({
      success: true,
      message: '上传工具已生成',
      config: config
    });
  } catch (error) {
    console.error('生成上传工具失败:', error);
    res.status(500).json({
      success: false,
      message: '生成上传工具失败: ' + error.message
    });
  }
});

// 下载上传工具（zip包含exe+config.json+README.txt）
app.get('/api/upload/download-tool', authMiddleware, async (req, res) => {
  let tempZipPath = null;

  try {
    console.log(`[上传工具] 用户 ${req.user.username} 请求下载上传工具`);

    // 生成新的API密钥
    const newApiKey = crypto.randomBytes(16).toString('hex');

    // 更新用户的upload_api_key
    UserDB.update(req.user.id, { upload_api_key: newApiKey });

    // 创建配置文件内容
    const config = {
      username: req.user.username,
      api_key: newApiKey,
      api_base_url: getSecureBaseUrl(req)
    };
    console.log("[上传工具配置]", JSON.stringify(config, null, 2));

    // 检查exe文件是否存在
    const toolDir = path.join(__dirname, '..', 'upload-tool');
    const exePath = path.join(toolDir, 'dist', '玩玩云上传工具.exe');
    const readmePath = path.join(toolDir, 'README.txt');

    if (!fs.existsSync(exePath)) {
      console.error('[上传工具] exe文件不存在:', exePath);
      return res.status(500).json({
        success: false,
        message: '上传工具尚未打包，请联系管理员运行 upload-tool/build.bat'
      });
    }

    // 创建临时zip文件路径
    const uploadsDir = path.join(__dirname, 'uploads');
    if (!fs.existsSync(uploadsDir)) {
      fs.mkdirSync(uploadsDir, { recursive: true });
    }
    tempZipPath = path.join(uploadsDir, `tool_${req.user.username}_${Date.now()}.zip`);

    console.log('[上传工具] 开始创建zip包到临时文件:', tempZipPath);

    // 创建文件写入流
    const output = fs.createWriteStream(tempZipPath);
    const archive = archiver('zip', {
      store: true  // 使用STORE模式，不压缩，速度最快
    });

    // 等待zip文件创建完成
    await new Promise((resolve, reject) => {
      output.on('close', () => {
        console.log(`[上传工具] zip创建完成，大小: ${archive.pointer()} 字节`);
        resolve();
      });

      archive.on('error', (err) => {
        console.error('[上传工具] archiver错误:', err);
        reject(err);
      });

      // 连接archive到文件流
      archive.pipe(output);

      // 添加exe文件
      console.log('[上传工具] 添加exe文件...');
      archive.file(exePath, { name: '玩玩云上传工具.exe' });

      // 添加config.json
      console.log('[上传工具] 添加config.json...');
      archive.append(JSON.stringify(config, null, 2), { name: 'config.json' });

      // 添加README.txt
      if (fs.existsSync(readmePath)) {
        console.log('[上传工具] 添加README.txt...');
        archive.file(readmePath, { name: 'README.txt' });
      }

      // 完成打包
      console.log('[上传工具] 执行finalize...');
      archive.finalize();
    });

    // 获取文件大小
    const stats = fs.statSync(tempZipPath);
    const fileSize = stats.size;
    console.log(`[上传工具] 准备发送文件，大小: ${fileSize} 字节`);

    // 设置响应头（包含Content-Length）
    const filename = `玩玩云上传工具_${req.user.username}.zip`;
    res.setHeader('Content-Type', 'application/zip');
    res.setHeader('Content-Length', fileSize);
    res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"; filename*=UTF-8''${encodeURIComponent(filename)}`);

    // 创建文件读取流并发送
    const fileStream = fs.createReadStream(tempZipPath);

    fileStream.on('end', () => {
      console.log(`[上传工具] 用户 ${req.user.username} 下载完成`);
      // 删除临时文件
      if (tempZipPath && fs.existsSync(tempZipPath)) {
        fs.unlinkSync(tempZipPath);
        console.log('[上传工具] 临时文件已删除');
      }
    });

    fileStream.on('error', (err) => {
      console.error('[上传工具] 文件流错误:', err);
      // 删除临时文件
      if (tempZipPath && fs.existsSync(tempZipPath)) {
        fs.unlinkSync(tempZipPath);
      }
    });

    fileStream.pipe(res);

  } catch (error) {
    console.error('[上传工具] 异常:', error);

    // 删除临时文件
    if (tempZipPath && fs.existsSync(tempZipPath)) {
      fs.unlinkSync(tempZipPath);
      console.log('[上传工具] 临时文件已删除（异常）');
    }

    if (!res.headersSent) {
      res.status(500).json({
        success: false,
        message: '下载失败: ' + error.message
      });
    }
  }
});

// 通过API密钥获取OSS配置（供上传工具调用）
// 添加速率限制防止暴力枚举
app.post('/api/upload/get-config', async (req, res) => {
  // 获取客户端IP用于速率限制
  const clientIP = apiKeyLimiter.getClientKey(req);
  const rateLimitKey = `api_key:${clientIP}`;

  // 检查是否被封锁
  if (apiKeyLimiter.isBlocked(rateLimitKey)) {
    const result = apiKeyLimiter.recordFailure(rateLimitKey);
    console.warn(`[安全] API密钥暴力枚举检测 - IP: ${clientIP}`);
    return res.status(429).json({
      success: false,
      message: `请求过于频繁，请在 ${result.waitMinutes} 分钟后重试`,
      blocked: true
    });
  }

  try {
    const { api_key } = req.body;

    if (!api_key) {
      return res.status(400).json({
        success: false,
        message: 'API密钥不能为空'
      });
    }

    // 查找拥有此API密钥的用户
    const user = db.prepare('SELECT * FROM users WHERE upload_api_key = ?').get(api_key);

    if (!user) {
      // 记录失败尝试
      const result = apiKeyLimiter.recordFailure(rateLimitKey);
      console.warn(`[安全] API密钥验证失败 - IP: ${clientIP}, 剩余尝试: ${result.remainingAttempts}`);
      return res.status(401).json({
        success: false,
        message: 'API密钥无效或已过期'
      });
    }

    // 验证成功，清除失败记录
    apiKeyLimiter.recordSuccess(rateLimitKey);

    if (user.is_banned) {
      return res.status(403).json({
        success: false,
        message: '账号已被封禁'
      });
    }

    // 确定用户的存储类型
    const storageType = user.current_storage_type || 'local';

    // 返回配置信息（新版上传工具 v3.0 使用服务器 API 上传，无需返回 OSS 凭证）
    res.json({
      success: true,
      config: {
        storage_type: storageType,
        username: user.username,
        // OSS 配置（仅用于显示）
        oss_provider: user.oss_provider,
        oss_bucket: user.oss_bucket
      }
    });
  } catch (error) {
    console.error('获取OSS配置失败:', error);
    res.status(500).json({
      success: false,
      message: '服务器内部错误，请稍后重试'
    });
  }
});

// 创建分享链接
app.post('/api/share/create', authMiddleware, (req, res) => {
  try {
    const {
      share_type,
      file_path,
      file_name,
      password,
      expiry_days,
      max_downloads,
      ip_whitelist,
      device_limit,
      access_time_start,
      access_time_end
    } = req.body;

    // 参数验证：share_type 只能是 'file' 或 'directory'
    const validShareTypes = ['file', 'directory'];
    const actualShareType = share_type || 'file';
    if (!validShareTypes.includes(actualShareType)) {
      return res.status(400).json({
        success: false,
        message: '无效的分享类型，只能是 file 或 directory'
      });
    }

    // 参数验证：file_path 不能为空
    if (!file_path) {
      return res.status(400).json({
        success: false,
        message: actualShareType === 'file' ? '文件路径不能为空' : '目录路径不能为空'
      });
    }

    // 参数验证：expiry_days 必须为正整数或 null
    if (expiry_days !== undefined && expiry_days !== null) {
      const days = parseInt(expiry_days, 10);
      if (isNaN(days) || days <= 0 || days > 365) {
        return res.status(400).json({
          success: false,
          message: '有效期必须是1-365之间的整数'
        });
      }
    }

    const hasPasswordField = Object.prototype.hasOwnProperty.call(req.body || {}, 'password');
    let normalizedPassword = null;

    if (hasPasswordField) {
      if (password !== null && password !== undefined && typeof password !== 'string') {
        return res.status(400).json({
          success: false,
          message: '密码格式无效'
        });
      }

      if (typeof password === 'string') {
        normalizedPassword = password.trim();
      }

      if (password !== null && password !== undefined && !normalizedPassword) {
        return res.status(400).json({
          success: false,
          message: '已启用密码保护时，访问密码不能为空'
        });
      }

      if (normalizedPassword && normalizedPassword.length > 32) {
        return res.status(400).json({
          success: false,
          message: '密码长度不能超过32个字符'
        });
      }
    }

    let normalizedMaxDownloads = null;
    if (max_downloads !== undefined && max_downloads !== null && max_downloads !== '') {
      const parsedMaxDownloads = parseInt(max_downloads, 10);
      if (!Number.isFinite(parsedMaxDownloads) || parsedMaxDownloads < 1 || parsedMaxDownloads > 1000000) {
        return res.status(400).json({
          success: false,
          message: '下载次数上限必须是 1 到 1000000 的整数'
        });
      }
      normalizedMaxDownloads = parsedMaxDownloads;
    }

    let normalizedWhitelist = null;
    if (ip_whitelist !== undefined && ip_whitelist !== null && ip_whitelist !== '') {
      const rawWhitelist = Array.isArray(ip_whitelist)
        ? ip_whitelist.join(',')
        : String(ip_whitelist);
      const whitelistItems = parseShareIpWhitelist(rawWhitelist);
      if (whitelistItems.length === 0) {
        return res.status(400).json({
          success: false,
          message: 'IP 白名单格式无效'
        });
      }
      normalizedWhitelist = whitelistItems.join(',');
    }

    const allowedDeviceLimit = new Set(['all', 'mobile', 'desktop']);
    const normalizedDeviceLimit = typeof device_limit === 'string' && device_limit.trim()
      ? device_limit.trim()
      : 'all';
    if (!allowedDeviceLimit.has(normalizedDeviceLimit)) {
      return res.status(400).json({
        success: false,
        message: '设备限制参数无效'
      });
    }

    const normalizedAccessTimeStart = normalizeTimeHHmm(access_time_start || '');
    const normalizedAccessTimeEnd = normalizeTimeHHmm(access_time_end || '');
    if ((normalizedAccessTimeStart && !normalizedAccessTimeEnd) || (!normalizedAccessTimeStart && normalizedAccessTimeEnd)) {
      return res.status(400).json({
        success: false,
        message: '访问时间限制需同时提供开始和结束时间'
      });
    }
    if ((access_time_start && !normalizedAccessTimeStart) || (access_time_end && !normalizedAccessTimeEnd)) {
      return res.status(400).json({
        success: false,
        message: '访问时间格式必须为 HH:mm'
      });
    }

    const normalizedSharePath = normalizeVirtualPath(file_path);
    if (!normalizedSharePath) {
      return res.status(400).json({
        success: false,
        message: '路径包含非法字符'
      });
    }

    const storageType = req.user.current_storage_type || 'oss';
    const existingShare = ShareDB.findExistingByTarget(req.user.id, {
      share_type: actualShareType,
      file_path: normalizedSharePath,
      storage_type: storageType
    });

    if (existingShare) {
      const shareUrl = `${getSecureBaseUrl(req)}/s/${existingShare.share_code}`;
      const securityPolicy = getSharePolicySummary(existingShare);

      logShare(
        req,
        'reuse_share',
        `用户复用现有分享: ${actualShareType === 'file' ? '文件' : '目录'} ${normalizedSharePath}`,
        {
          shareId: existingShare.id,
          shareCode: existingShare.share_code,
          sharePath: normalizedSharePath,
          shareType: existingShare.share_type
        }
      );

      return res.json({
        success: true,
        message: '已复用现有分享链接',
        reused: true,
        share_id: existingShare.id,
        share_code: existingShare.share_code,
        share_url: shareUrl,
        share_type: existingShare.share_type,
        expires_at: existingShare.expires_at,
        has_password: !!existingShare.share_password,
        security_policy: securityPolicy
      });
    }

    SystemLogDB.log({
      level: 'info',
      category: 'share',
      action: 'create_share',
      message: '创建分享请求',
      details: {
        share_type: actualShareType,
        file_path: normalizedSharePath,
        file_name,
        expiry_days,
        max_downloads: normalizedMaxDownloads,
        ip_whitelist_count: normalizedWhitelist ? parseShareIpWhitelist(normalizedWhitelist).length : 0,
        device_limit: normalizedDeviceLimit,
        access_time_start: normalizedAccessTimeStart,
        access_time_end: normalizedAccessTimeEnd
      }
    });

    const result = ShareDB.create(req.user.id, {
      share_type: actualShareType,
      file_path: normalizedSharePath,
      file_name: file_name || '',
      password: normalizedPassword || null,
      expiry_days: expiry_days || null,
      max_downloads: normalizedMaxDownloads,
      ip_whitelist: normalizedWhitelist,
      device_limit: normalizedDeviceLimit,
      access_time_start: normalizedAccessTimeStart,
      access_time_end: normalizedAccessTimeEnd
    });

    // 更新分享的存储类型
    db.prepare('UPDATE shares SET storage_type = ? WHERE id = ?')
      .run(storageType, result.id);

    const shareUrl = `${getSecureBaseUrl(req)}/s/${result.share_code}`;

    // 记录分享创建日志
    logShare(req, 'create_share',
      `用户创建分享: ${actualShareType === 'file' ? '文件' : '目录'} ${normalizedSharePath}`,
      {
        shareCode: result.share_code,
        sharePath: normalizedSharePath,
        shareType: actualShareType,
        hasPassword: !!normalizedPassword,
        policy: getSharePolicySummary({
          ...result,
          max_downloads: normalizedMaxDownloads,
          ip_whitelist: normalizedWhitelist,
          device_limit: normalizedDeviceLimit,
          access_time_start: normalizedAccessTimeStart,
          access_time_end: normalizedAccessTimeEnd
        })
      }
    );

    const securityPolicy = getSharePolicySummary({
      ...result,
      max_downloads: normalizedMaxDownloads,
      ip_whitelist: normalizedWhitelist,
      device_limit: normalizedDeviceLimit,
      access_time_start: normalizedAccessTimeStart,
      access_time_end: normalizedAccessTimeEnd
    });

    res.json({
      success: true,
      message: '分享链接创建成功',
      reused: false,
      share_code: result.share_code,
      share_url: shareUrl,
      share_type: result.share_type,
      expires_at: result.expires_at,
      has_password: !!normalizedPassword,
      security_policy: securityPolicy
    });
  } catch (error) {
    console.error('创建分享链接失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '创建分享链接失败，请稍后重试', '创建分享')
    });
  }
});

// 获取我的分享列表
app.get('/api/share/my', authMiddleware, (req, res) => {
  try {
    const shares = ShareDB.getUserShares(req.user.id);

    res.json({
      success: true,
      shares: shares.map(share => {
        const hasPassword = !!share.share_password;
        const { share_password, ...safeShare } = share;

        return {
          ...safeShare,
          has_password: hasPassword,
          share_url: `${getSecureBaseUrl(req)}/s/${share.share_code}`
        };
      })
    });
  } catch (error) {
    console.error('获取分享列表失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '获取分享列表失败，请稍后重试', '获取分享列表')
    });
  }
});

// 删除分享（增强IDOR防护）
app.delete('/api/share/:id', authMiddleware, (req, res) => {
  try {
    const shareId = parseInt(req.params.id, 10);

    // 验证ID格式
    if (isNaN(shareId) || shareId <= 0) {
      return res.status(400).json({
        success: false,
        message: '无效的分享ID'
      });
    }

    // 先获取分享信息以获得share_code
    const share = ShareDB.findById(shareId);

    if (!share) {
      return res.status(404).json({
        success: false,
        message: '分享不存在'
      });
    }

    // 严格的权限检查
    if (share.user_id !== req.user.id) {
      // 记录可疑的越权尝试
      console.warn(`[安全] IDOR尝试 - 用户 ${req.user.id}(${req.user.username}) 试图删除用户 ${share.user_id} 的分享 ${shareId}`);
      return res.status(403).json({
        success: false,
        message: '无权限删除此分享'
      });
    }

    // 删除缓存
    if (shareFileCache.has(share.share_code)) {
      shareFileCache.delete(share.share_code);
      console.log(`[缓存清除] 分享码: ${share.share_code}`);
    }

    // 删除数据库记录
    ShareDB.delete(shareId, req.user.id);

    res.json({
      success: true,
      message: '分享已删除'
    });
  } catch (error) {
    console.error('删除分享失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '删除分享失败，请稍后重试', '删除分享')
    });
  }
});

// ===== 直链管理（与分享链接独立） =====

// 创建文件直链
app.post('/api/direct-link/create',
  authMiddleware,
  [
    body('file_path').isString().notEmpty().withMessage('文件路径不能为空'),
    body('file_name').optional({ nullable: true }).isString().withMessage('文件名格式无效'),
    body('expiry_days').optional({ nullable: true }).custom((value) => {
      if (value === null || value === undefined || value === '') {
        return true;
      }
      const days = parseInt(value, 10);
      if (Number.isNaN(days) || days < 1 || days > 365) {
        throw new Error('有效期必须是1-365之间的整数');
      }
      return true;
    })
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    const normalizedPath = normalizeVirtualPath(req.body?.file_path || '');
    if (!normalizedPath) {
      return res.status(400).json({
        success: false,
        message: '文件路径非法'
      });
    }

    const expiryDays = req.body?.expiry_days === null || req.body?.expiry_days === undefined || req.body?.expiry_days === ''
      ? null
      : parseInt(req.body.expiry_days, 10);

    let storage;
    try {
      // 创建前校验文件是否存在且为文件
      const { StorageInterface } = require('./storage');
      const storageInterface = new StorageInterface(req.user);
      storage = await storageInterface.connect();

      const fileStats = await storage.stat(normalizedPath);
      if (fileStats?.isDirectory) {
        return res.status(400).json({
          success: false,
          message: '直链仅支持文件，不支持目录'
        });
      }

      const resolvedFileName = (typeof req.body?.file_name === 'string' && req.body.file_name.trim())
        ? req.body.file_name.trim()
        : (normalizedPath.split('/').pop() || 'download.bin');

      const storageType = req.user.current_storage_type || 'oss';
      const existingLink = DirectLinkDB.findExistingByTarget(req.user.id, {
        file_path: normalizedPath,
        storage_type: storageType
      });

      if (existingLink) {
        const directUrl = `${getSecureBaseUrl(req)}/d/${existingLink.link_code}`;

        logShare(
          req,
          'reuse_direct_link',
          `用户复用直链: ${normalizedPath}`,
          {
            linkId: existingLink.id,
            linkCode: existingLink.link_code,
            filePath: normalizedPath,
            storageType
          }
        );

        return res.json({
          success: true,
          message: '已复用现有直链',
          reused: true,
          link_id: existingLink.id,
          link_code: existingLink.link_code,
          file_path: normalizedPath,
          file_name: existingLink.file_name || resolvedFileName,
          storage_type: storageType,
          expires_at: existingLink.expires_at || null,
          direct_url: directUrl
        });
      }

      const directLink = DirectLinkDB.create(req.user.id, {
        file_path: normalizedPath,
        file_name: resolvedFileName,
        storage_type: storageType,
        expiry_days: expiryDays
      });

      const directUrl = `${getSecureBaseUrl(req)}/d/${directLink.link_code}`;

      logShare(
        req,
        'create_direct_link',
        `用户创建直链: ${normalizedPath}`,
        {
          linkId: directLink.id,
          linkCode: directLink.link_code,
          filePath: normalizedPath,
          storageType,
          expiresAt: directLink.expires_at || null
        }
      );

      res.json({
        success: true,
        message: '直链创建成功',
        reused: false,
        link_id: directLink.id,
        link_code: directLink.link_code,
        file_path: normalizedPath,
        file_name: resolvedFileName,
        storage_type: storageType,
        expires_at: directLink.expires_at || null,
        direct_url: directUrl
      });
    } catch (error) {
      console.error('创建直链失败:', error);

      if (String(error?.message || '').includes('不存在')) {
        return res.status(404).json({
          success: false,
          message: '文件不存在'
        });
      }

      res.status(500).json({
        success: false,
        message: getSafeErrorMessage(error, '创建直链失败，请稍后重试', '创建直链')
      });
    } finally {
      if (storage) await storage.end();
    }
  }
);

// 获取我的直链列表
app.get('/api/direct-link/my', authMiddleware, (req, res) => {
  try {
    const links = DirectLinkDB.getUserLinks(req.user.id);

    res.json({
      success: true,
      links: links.map((link) => ({
        ...link,
        direct_url: `${getSecureBaseUrl(req)}/d/${link.link_code}`
      }))
    });
  } catch (error) {
    console.error('获取直链列表失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '获取直链列表失败，请稍后重试', '获取直链列表')
    });
  }
});

// 删除直链
app.delete('/api/direct-link/:id', authMiddleware, (req, res) => {
  try {
    const linkId = parseInt(req.params.id, 10);
    if (Number.isNaN(linkId) || linkId <= 0) {
      return res.status(400).json({
        success: false,
        message: '无效的直链ID'
      });
    }

    const link = DirectLinkDB.findById(linkId);
    if (!link) {
      return res.status(404).json({
        success: false,
        message: '直链不存在'
      });
    }

    if (link.user_id !== req.user.id) {
      return res.status(403).json({
        success: false,
        message: '无权限删除此直链'
      });
    }

    DirectLinkDB.delete(linkId, req.user.id);

    logShare(
      req,
      'delete_direct_link',
      `用户删除直链: ${link.file_path}`,
      {
        linkId: link.id,
        linkCode: link.link_code,
        filePath: link.file_path
      }
    );

    res.json({
      success: true,
      message: '直链已删除'
    });
  } catch (error) {
    console.error('删除直链失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '删除直链失败，请稍后重试', '删除直链')
    });
  }
});

// ===== 分享链接访问（公开） =====

// 获取公共主题设置（用于分享页面，无需认证）
app.get('/api/public/theme', (req, res) => {
  try {
    const globalTheme = SettingsDB.get('global_theme') || 'dark';
    res.json({
      success: true,
      theme: globalTheme
    });
  } catch (error) {
    res.json({ success: true, theme: 'dark' }); // 出错默认暗色
  }
});

// 获取分享页面主题（基于分享者偏好或全局设置）
app.get('/api/share/:code/theme', (req, res) => {
  try {
    const { code } = req.params;
    const globalTheme = SettingsDB.get('global_theme') || 'dark';

    if (!isValidShareCode(code)) {
      return res.json({
        success: true,
        theme: globalTheme
      });
    }

    const share = ShareDB.findByCode(code);

    if (!share) {
      return res.json({
        success: true,
        theme: globalTheme
      });
    }

    // 优先使用分享者的主题偏好，否则使用全局主题
    const effectiveTheme = share.theme_preference || globalTheme;
    res.json({
      success: true,
      theme: effectiveTheme
    });
  } catch (error) {
    res.json({ success: true, theme: 'dark' });
  }
});

// 访问分享链接 - 验证密码（支持本地存储和OSS）
app.post('/api/share/:code/verify', shareRateLimitMiddleware, async (req, res) => {
  const { code } = req.params;
  const { password } = req.body;
  let storage;

  try {

    const share = ShareDB.findByCode(code);

    if (!share) {
      return res.status(404).json({
        success: false,
        message: '分享不存在'
      });
    }

    // 如果设置了密码，验证密码
    if (share.share_password) {
      if (!password) {
        return res.status(401).json({
          success: false,
          message: '需要密码',
          needPassword: true
        });
      }

      if (!ShareDB.verifyPassword(password, share.share_password)) {
        // 记录密码错误
        if (req.shareRateLimitKey) {
          shareLimiter.recordFailure(req.shareRateLimitKey);
        }
        return res.status(401).json({
          success: false,
          message: '密码错误',
          needPassword: true
        });
      }
    }

    // 清除失败记录（密码验证成功）
    if (req.shareRateLimitKey) {
      shareLimiter.recordSuccess(req.shareRateLimitKey);
    }

    const accessPolicy = evaluateShareSecurityPolicy(share, req, {
      action: 'view',
      enforceDownloadLimit: false
    });
    if (!accessPolicy.allowed) {
      return res.status(403).json({
        success: false,
        message: accessPolicy.message || '当前访问环境受限，请稍后再试',
        policy_code: accessPolicy.code || 'policy_blocked'
      });
    }

    // 增加查看次数
    ShareDB.incrementViewCount(code);

    // 构建返回数据
    const responseData = {
      success: true,
      share: {
        share_path: share.share_path,
        share_type: share.share_type,
        username: share.username,
        created_at: share.created_at,
        expires_at: share.expires_at,  // 添加到期时间
        security_policy: getSharePolicySummary(share)
      }
    };

    // 如果是单文件分享，查询存储获取文件信息（带缓存）
    if (share.share_type === 'file') {
      const filePath = share.share_path;
      const lastSlashIndex = filePath.lastIndexOf('/');
      const dirPath = lastSlashIndex > 0 ? filePath.substring(0, lastSlashIndex) : '/';
      const fileName = lastSlashIndex >= 0 ? filePath.substring(lastSlashIndex + 1) : filePath;

      // 检查缓存
      if (shareFileCache.has(code)) {
        responseData.file = shareFileCache.get(code);
      } else {
        // 缓存未命中，查询存储
        try {
          // 获取分享者的用户信息
          const shareOwner = UserDB.findById(share.user_id);
          if (!shareOwner) {
            throw new Error('分享者不存在');
          }
          // 使用分享创建时记录的存储类型，而非用户当前的存储类型
          // 这样即使用户切换了存储，分享链接仍然有效
          const storageType = share.storage_type || 'oss';

          // 使用统一存储接口
          const { StorageInterface } = require('./storage');
          const userForStorage = buildStorageUserContext(shareOwner, {
            current_storage_type: storageType
          });

          const storageInterface = new StorageInterface(userForStorage);
          storage = await storageInterface.connect();

          const list = await storage.list(dirPath);
          const fileInfo = list.find(item => item.name === fileName);

          // 检查文件是否存在
          if (!fileInfo) {
            shareFileCache.delete(code);
            throw new Error("分享的文件已被删除或不存在");
          }

          if (fileInfo) {
            const fileData = {
              name: fileName,
              type: 'file',
              isDirectory: false,
              httpDownloadUrl: null, // OSS 使用 API 下载
              size: fileInfo.size,
              sizeFormatted: formatFileSize(fileInfo.size),
              modifiedAt: new Date(fileInfo.modifyTime)
            };

            // 存入缓存
            shareFileCache.set(code, fileData);
            console.log(`[缓存存储] 分享码: ${code}，文件: ${fileName}`);

            responseData.file = fileData;
          }
        } catch (storageError) {
          console.error('获取文件信息失败:', storageError);

          // 如果是文件不存在的错误，重新抛出
          if (storageError.message && storageError.message.includes("分享的文件已被删除或不存在")) {
            throw storageError;
          }
          // 存储失败时仍返回基本信息，只是没有大小
          responseData.file = {
            name: fileName,
            type: 'file',
            isDirectory: false,
            httpDownloadUrl: null,
            size: 0,
            sizeFormatted: '-'
          };
        }
      }
    }

    res.json(responseData);
  } catch (error) {
    console.error('验证分享失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '验证失败，请稍后重试', '分享验证')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 获取分享的文件列表（支持本地存储和OSS）
app.post('/api/share/:code/list', shareRateLimitMiddleware, async (req, res) => {
  const { code } = req.params;
  const { password, path: subPath } = req.body;

  let storage;

  try {

    // ===== 调试日志: 获取分享文件列表 =====
    console.log('[获取文件列表]', {
      timestamp: new Date().toISOString(),
      shareCode: code,
      subPath: subPath,
      hasPassword: !!password,
      requestIP: req.ip
    });

    const share = ShareDB.findByCode(code);


    if (!share) {
      return res.status(404).json({
        success: false,
        message: '分享不存在'
      });
    }

    // 验证密码
    if (share.share_password) {
      const normalizedPassword = typeof password === 'string' ? password : '';

      if (!normalizedPassword || !ShareDB.verifyPassword(normalizedPassword, share.share_password)) {
        // 记录密码错误
        if (req.shareRateLimitKey) {
          shareLimiter.recordFailure(req.shareRateLimitKey);
        }
        return res.status(401).json({
          success: false,
          message: '密码错误或未提供密码',
          needPassword: true
        });
      }
    }

    // 清除失败记录（密码验证成功或无密码）
    if (req.shareRateLimitKey && share.share_password) {
      shareLimiter.recordSuccess(req.shareRateLimitKey);
    }

    const accessPolicy = evaluateShareSecurityPolicy(share, req, {
      action: 'view',
      enforceDownloadLimit: false
    });
    if (!accessPolicy.allowed) {
      return res.status(403).json({
        success: false,
        message: accessPolicy.message || '当前访问环境受限，请稍后再试',
        policy_code: accessPolicy.code || 'policy_blocked'
      });
    }

    // 获取分享者的用户信息（查看列表不触发下载流量策略）
    // 仅在实际下载接口中校验和消耗下载流量，避免“可见性”受配额影响
    const shareOwner = UserDB.findById(share.user_id);
    if (!shareOwner) {
      return res.status(404).json({
        success: false,
        message: '分享者不存在'
      });
    }

    // 构造安全的请求路径，防止越权遍历
    const baseSharePath = normalizeVirtualPath(share.share_path || '/');
    if (!baseSharePath) {
      return res.status(400).json({
        success: false,
        message: '分享路径非法'
      });
    }

    const rawSubPath = typeof subPath === 'string' ? subPath : '';
    const requestedPath = rawSubPath
      ? normalizeVirtualPath(`${baseSharePath}/${rawSubPath}`)
      : baseSharePath;

    if (!requestedPath) {
      return res.status(400).json({
        success: false,
        message: '请求路径非法'
      });
    }

    // 校验请求路径是否在分享范围内
    if (!isPathWithinShare(requestedPath, share)) {
      return res.status(403).json({
        success: false,
        message: '无权访问该路径'
      });
    }

    // 使用统一存储接口，根据分享的storage_type选择存储后端
    const { StorageInterface } = require('./storage');
    const storageType = share.storage_type || 'oss';
    console.log(`[分享列表] 存储类型: ${storageType}, 分享路径: ${share.share_path}`);

    // 临时构造用户对象以使用存储接口
    const userForStorage = buildStorageUserContext(shareOwner, {
      current_storage_type: storageType
    });

    const storageInterface = new StorageInterface(userForStorage);
    storage = await storageInterface.connect();

    let formattedList = [];

    // 如果是单文件分享
    if (share.share_type === 'file') {
      // share_path 就是文件路径
      const filePath = share.share_path;

      // 提取父目录和文件名
      const lastSlashIndex = filePath.lastIndexOf('/');
      const dirPath = lastSlashIndex > 0 ? filePath.substring(0, lastSlashIndex) : '/';
      const fileName = lastSlashIndex >= 0 ? filePath.substring(lastSlashIndex + 1) : filePath;

      // 列出父目录
      const list = await storage.list(dirPath);

      // 只返回这个文件
      const fileInfo = list.find(item => item.name === fileName);

      if (fileInfo) {
        formattedList = [{
          name: fileInfo.name,
          type: 'file',
          size: fileInfo.size,
          sizeFormatted: formatFileSize(fileInfo.size),
          modifiedAt: new Date(fileInfo.modifyTime),
          isDirectory: false,
          httpDownloadUrl: null // OSS 使用 API 下载
        }];
      }
    }
    // 如果是目录分享（分享所有文件）
    else {
      const list = await storage.list(requestedPath);

      formattedList = list.map(item => {
        return {
          name: item.name,
          type: item.type === 'd' ? 'directory' : 'file',
          size: item.size,
          sizeFormatted: formatFileSize(item.size),
          modifiedAt: new Date(item.modifyTime),
          isDirectory: item.type === 'd',
          httpDownloadUrl: null // OSS 使用 API 下载
        };
      });

      formattedList.sort((a, b) => {
        if (a.isDirectory && !b.isDirectory) return -1;
        if (!a.isDirectory && b.isDirectory) return 1;
        return a.name.localeCompare(b.name);
      });
    }

    res.json({
      success: true,
      path: share.share_path,
      items: formattedList
    });
  } catch (error) {
    console.error('获取分享文件列表失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '获取文件列表失败，请稍后重试', '分享列表')
    });
  } finally {
    if (storage) await storage.end();
  }
});

// 记录下载次数（添加限流保护防止滥用）
app.post('/api/share/:code/download', shareRateLimitMiddleware, (req, res) => {
  const { code } = req.params;

  // 参数验证：code 不能为空
  if (!code || typeof code !== 'string' || code.length < 1 || code.length > 32) {
    return res.status(400).json({
      success: false,
      message: '无效的分享码'
    });
  }

  try {
    const share = ShareDB.findByCode(code);

    if (!share) {
      return res.status(404).json({
        success: false,
        message: '分享不存在'
      });
    }

    const accessPolicy = evaluateShareSecurityPolicy(share, req, {
      action: 'download',
      enforceDownloadLimit: true
    });
    if (!accessPolicy.allowed) {
      return res.status(403).json({
        success: false,
        message: accessPolicy.message || '下载已受限',
        policy_code: accessPolicy.code || 'policy_blocked'
      });
    }

    // 增加下载次数
    ShareDB.incrementDownloadCount(code);

    res.json({
      success: true,
      message: '下载统计已记录'
    });
  } catch (error) {
    console.error('记录下载失败:', error);
    res.status(500).json({
      success: false,
      message: '记录下载失败: ' + error.message
    });
  }
});

// 生成分享文件下载签名 URL（OSS 直连下载，公开 API，添加限流保护）
app.post('/api/share/:code/download-url', shareRateLimitMiddleware, async (req, res) => {
  const { code } = req.params;
  const { path: filePath, password } = req.body || {};

  // 参数验证：code 不能为空
  if (!code || typeof code !== 'string' || code.length < 1 || code.length > 32) {
    return res.status(400).json({
      success: false,
      message: '无效的分享码'
    });
  }

  const normalizedFilePath = normalizeVirtualPath(filePath);
  if (!normalizedFilePath) {
    return res.status(400).json({
      success: false,
      message: '文件路径非法'
    });
  }

  try {
    const share = ShareDB.findByCode(code);

    if (!share) {
      return res.status(404).json({
        success: false,
        message: '分享不存在'
      });
    }

    // 验证密码（如果需要）
    if (share.share_password) {
      if (!password || !ShareDB.verifyPassword(password, share.share_password)) {
        if (req.shareRateLimitKey) {
          shareLimiter.recordFailure(req.shareRateLimitKey);
        }
        return res.status(401).json({
          success: false,
          message: '密码错误或未提供密码',
          needPassword: true
        });
      }

      if (req.shareRateLimitKey) {
        shareLimiter.recordSuccess(req.shareRateLimitKey);
      }
    }

    // 安全验证：检查请求路径是否在分享范围内
    if (!isPathWithinShare(normalizedFilePath, share)) {
      return res.status(403).json({
        success: false,
        message: '无权访问该文件'
      });
    }

    const accessPolicy = evaluateShareSecurityPolicy(share, req, {
      action: 'download',
      enforceDownloadLimit: true
    });
    if (!accessPolicy.allowed) {
      return res.status(403).json({
        success: false,
        message: accessPolicy.message || '下载已受限',
        policy_code: accessPolicy.code || 'policy_blocked'
      });
    }

    // 获取分享者的用户信息
    const ownerPolicyState = enforceDownloadTrafficPolicy(share.user_id, 'share_download');
    const shareOwner = ownerPolicyState?.user || UserDB.findById(share.user_id);
    if (!shareOwner) {
      return res.status(404).json({
        success: false,
        message: '分享者不存在'
      });
    }

    const storageType = share.storage_type || 'oss';
    const ownerTrafficState = getDownloadTrafficState(shareOwner);

    if (storageType === 'oss') {
      const securityResult = evaluateDownloadSecurityPolicy(req, {
        ownerUserId: shareOwner.id,
        filePath: normalizedFilePath,
        source: 'share_download_url_oss'
      });
      if (!securityResult.allowed) {
        return handleDownloadSecurityBlock(req, res, securityResult, {
          statusCode: 503,
          ownerUserId: shareOwner.id,
          filePath: normalizedFilePath,
          source: 'share_download_url_oss'
        });
      }
    }

    // 本地存储：继续走后端下载
    if (storageType !== 'oss') {
      if (!ownerTrafficState.isUnlimited) {
        const { StorageInterface } = require('./storage');
        const userForStorage = buildStorageUserContext(shareOwner, {
          current_storage_type: storageType
        });
        const storageInterface = new StorageInterface(userForStorage);
        let storage = null;
        try {
          storage = await storageInterface.connect();
          const fileStats = await storage.stat(normalizedFilePath);
          const fileSize = Number(fileStats?.size || 0);
          if (!Number.isFinite(fileSize) || fileSize <= 0) {
            return res.status(503).json({
              success: false,
              message: getBusyDownloadMessage()
            });
          }
          if (fileSize > ownerTrafficState.remaining) {
            return res.status(503).json({
              success: false,
              message: getBusyDownloadMessage()
            });
          }
        } catch (statError) {
          if (statError && (statError.code === 'ENOENT' || String(statError.message || '').includes('不存在'))) {
            return res.status(404).json({
              success: false,
              message: '文件不存在'
            });
          }
          throw statError;
        } finally {
          if (storage) {
            await storage.end();
          }
        }
      }

      let downloadUrl = `${getSecureBaseUrl(req)}/api/share/${code}/download-file?path=${encodeURIComponent(normalizedFilePath)}`;

      if (share.share_password) {
        const downloadToken = signEphemeralToken({
          type: 'share_download',
          code,
          path: normalizedFilePath
        }, 15 * 60);
        downloadUrl += `&token=${encodeURIComponent(downloadToken)}`;
      }

      return res.json({
        success: true,
        downloadUrl,
        direct: false,
        quotaLimited: !ownerTrafficState.isUnlimited
      });
    }

    // OSS 模式：检查配置并生成签名 URL
    const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
    if (!shareOwner.has_oss_config && !hasUnifiedConfig) {
      return res.status(400).json({
        success: false,
        message: '分享者未配置 OSS 服务'
      });
    }

    const { GetObjectCommand, HeadObjectCommand } = require('@aws-sdk/client-s3');
    const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

    const { client, bucket, ossClient } = createS3ClientContextForUser(shareOwner);
    const objectKey = ossClient.getObjectKey(normalizedFilePath);
    let fileSize = 0;

    if (!ownerTrafficState.isUnlimited) {
      let headResponse;
      try {
        headResponse = await client.send(new HeadObjectCommand({
          Bucket: bucket,
          Key: objectKey
        }));
      } catch (headError) {
        const statusCode = headError?.$metadata?.httpStatusCode;
        if (headError?.name === 'NotFound' || headError?.name === 'NoSuchKey' || statusCode === 404) {
          return res.status(404).json({
            success: false,
            message: '文件不存在'
          });
        }
        throw headError;
      }

      const contentLength = Number(headResponse?.ContentLength || 0);
      fileSize = Number.isFinite(contentLength) && contentLength > 0
        ? Math.floor(contentLength)
        : 0;
      if (fileSize <= 0) {
        return res.status(503).json({
          success: false,
          message: getBusyDownloadMessage()
        });
      }
    }

    // 创建 GetObject 命令
    const command = new GetObjectCommand({
      Bucket: bucket,
      Key: objectKey,
      ResponseContentDisposition: `attachment; filename="${encodeURIComponent(normalizedFilePath.split('/').pop())}"`
    });

    // 生成签名 URL（短时有效，默认30秒）
    const signedUrl = await getSignedUrl(client, command, {
      expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS
    });

    if (!ownerTrafficState.isUnlimited && fileSize > 0) {
      const reserveResult = reserveDirectDownloadTraffic(shareOwner.id, fileSize, {
        source: 'share_direct',
        objectKey,
        ttlMs: DOWNLOAD_RESERVATION_TTL_MS
      });
      if (!reserveResult?.ok) {
        return res.status(503).json({
          success: false,
          message: getBusyDownloadMessage()
        });
      }
    }

    res.json({
      success: true,
      downloadUrl: signedUrl,
      direct: true,
      quotaLimited: !ownerTrafficState.isUnlimited,
      expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS
    });

  } catch (error) {
    console.error('[分享签名] 生成下载签名失败:', error);
    res.status(500).json({
      success: false,
      message: getSafeErrorMessage(error, '生成下载签名失败，请稍后重试', '生成下载签名')
    });
  }
});

// 分享文件下载（支持本地存储和 OSS，公开 API，需要分享码和密码验证）
app.get('/api/share/:code/download-file', shareRateLimitMiddleware, async (req, res) => {
  const { code } = req.params;
  const rawFilePath = typeof req.query?.path === 'string' ? req.query.path : '';
  const { password, token } = req.query;
  const filePath = normalizeVirtualPath(rawFilePath);
  let storage;
  let storageEnded = false; // 防止重复关闭
  let transferFinalized = false; // 防止重复结算
  let downloadedBytes = 0;
  let responseBodyStartSocketBytes = 0;
  let shareOwnerId = null;

  // Express 会将 HEAD 映射到 GET 处理器，这里显式拒绝，避免误触发下载计量
  if (req.method === 'HEAD') {
    res.setHeader('Allow', 'GET');
    return res.status(405).end();
  }

  // 安全关闭存储连接的辅助函数
  const safeEndStorage = async () => {
    if (storage && !storageEnded) {
      storageEnded = true;
      try {
        await storage.end();
      } catch (err) {
        console.error('关闭存储连接失败:', err);
      }
    }
  };

  const finalizeTransfer = async (reason = '') => {
    if (transferFinalized) {
      return;
    }
    transferFinalized = true;

    try {
      if (shareOwnerId && downloadedBytes > 0) {
        const socketBytesWritten = Number(res.socket?.bytesWritten);
        const socketBodyBytes = Number.isFinite(socketBytesWritten) && socketBytesWritten > responseBodyStartSocketBytes
          ? Math.floor(socketBytesWritten - responseBodyStartSocketBytes)
          : 0;
        const billableBytes = socketBodyBytes > 0
          ? Math.min(downloadedBytes, socketBodyBytes)
          : downloadedBytes;

        const usageResult = applyDownloadTrafficUsage(shareOwnerId, billableBytes);
        if (usageResult) {
          const quotaText = usageResult.quota > 0 ? formatFileSize(usageResult.quota) : '不限';
          console.log(
            `[分享下载流量] 用户 ${shareOwnerId} 新增 ${formatFileSize(usageResult.added)}，` +
            `累计 ${formatFileSize(usageResult.usedAfter)} / ${quotaText} ` +
            `(reason=${reason || 'unknown'}, streamed=${downloadedBytes}, socket=${socketBodyBytes})`
          );
        }
      }
    } catch (error) {
      console.error(`[分享下载流量] 结算失败: user=${shareOwnerId}, bytes=${downloadedBytes}`, error);
    }

    await safeEndStorage();
  };

  if (!filePath) {
    return res.status(400).json({
      success: false,
      message: '文件路径非法'
    });
  }

  try {
    const share = ShareDB.findByCode(code);

    if (!share) {
      return res.status(404).json({
        success: false,
        message: '分享不存在'
      });
    }

    // 验证密码（如果需要），支持短期下载 token（避免密码出现在 URL）
    if (share.share_password) {
      let verifiedByToken = false;

      if (token) {
        const tokenResult = verifyEphemeralToken(token, 'share_download');
        if (tokenResult.valid) {
          const tokenPayload = tokenResult.payload || {};
          if (tokenPayload.code === code && tokenPayload.path === filePath) {
            verifiedByToken = true;
          }
        }
      }

      if (!verifiedByToken) {
        if (!password || !ShareDB.verifyPassword(password, share.share_password)) {
          // 只在密码错误时记录失败
          if (req.shareRateLimitKey) {
            shareLimiter.recordFailure(req.shareRateLimitKey);
          }
          return res.status(401).json({
            success: false,
            message: '密码错误或未提供密码',
            needPassword: true
          });
        }
      }

      // 密码验证成功，清除失败记录
      if (req.shareRateLimitKey) {
        shareLimiter.recordSuccess(req.shareRateLimitKey);
      }
    }

    // 安全验证：检查请求路径是否在分享范围内（防止越权访问）
    if (!isPathWithinShare(filePath, share)) {
      console.warn(`[安全] 检测到越权访问尝试 - 分享码: ${code}, 请求路径: ${filePath}, 分享路径: ${share.share_path}`);
      return res.status(403).json({
        success: false,
        message: '无权访问该文件'
      });
    }

    const accessPolicy = evaluateShareSecurityPolicy(share, req, {
      action: 'download',
      enforceDownloadLimit: true
    });
    if (!accessPolicy.allowed) {
      return res.status(403).json({
        success: false,
        message: accessPolicy.message || '下载已受限',
        policy_code: accessPolicy.code || 'policy_blocked'
      });
    }

    // 获取分享者的用户信息
    const shareOwner = UserDB.findById(share.user_id);
    if (!shareOwner) {
      return res.status(404).json({
        success: false,
        message: '分享者不存在'
      });
    }
    shareOwnerId = shareOwner.id;

    const securityResult = evaluateDownloadSecurityPolicy(req, {
      ownerUserId: shareOwner.id,
      filePath,
      source: 'share_download_stream'
    });
    if (!securityResult.allowed) {
      return handleDownloadSecurityBlock(req, res, securityResult, {
        statusCode: 503,
        ownerUserId: shareOwner.id,
        filePath,
        source: 'share_download_stream'
      });
    }

    const ownerTrafficState = getDownloadTrafficState(shareOwner);

    // 使用统一存储接口，根据分享的storage_type选择存储后端
    // 注意：必须使用分享创建时记录的 storage_type，而不是分享者当前的存储类型
    // 这样即使用户后来切换了存储类型，之前创建的分享仍然可以正常工作
    const { StorageInterface } = require('./storage');
    const storageType = share.storage_type || 'oss';
    console.log(`[分享下载] 存储类型: ${storageType} (分享记录), 文件路径: ${filePath}`);

    // 临时构造用户对象以使用存储接口
    const userForStorage = buildStorageUserContext(shareOwner, {
      current_storage_type: storageType
    });

    const storageInterface = new StorageInterface(userForStorage);
    storage = await storageInterface.connect();

    // 获取文件名
    const fileName = filePath.split('/').pop();

    // 获取文件信息（获取文件大小）
    const fileStats = await storage.stat(filePath);
    const fileSize = fileStats.size;
    console.log(`[分享下载] 文件: ${fileName}, 大小: ${fileSize} 字节`);

    if (!ownerTrafficState.isUnlimited && fileSize > ownerTrafficState.remaining) {
      await safeEndStorage();
      return res.status(403).json({
        success: false,
        message: `分享者下载流量不足：文件 ${formatFileSize(fileSize)}，剩余 ${formatFileSize(ownerTrafficState.remaining)}`
      });
    }

    // 增加下载次数
    ShareDB.incrementDownloadCount(code);

    // 设置响应头（包含文件大小，浏览器可显示下载进度）
    res.setHeader('Content-Type', 'application/octet-stream');
    res.setHeader('Content-Length', fileSize);
    // 关闭 Nginx 代理缓冲，减少代理预读导致的计量偏差
    res.setHeader('X-Accel-Buffering', 'no');
    res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"; filename*=UTF-8''${encodeURIComponent(fileName)}`);
    if (typeof res.flushHeaders === 'function') {
      res.flushHeaders();
    }
    responseBodyStartSocketBytes = Number(res.socket?.bytesWritten) || 0;

    // 创建文件流并传输（流式下载，服务器不保存临时文件）
    const stream = await storage.createReadStream(filePath);

    stream.on('data', (chunk) => {
      if (!chunk) return;
      downloadedBytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
    });

    res.on('finish', () => {
      finalizeTransfer('finish').catch(err => {
        console.error('分享下载完成后资源释放失败:', err);
      });
    });

    res.on('close', () => {
      finalizeTransfer('close').catch(err => {
        console.error('分享下载连接关闭后资源释放失败:', err);
      });
    });

    stream.on('error', (error) => {
      console.error('文件流错误:', error);
      if (!res.headersSent) {
        res.status(500).json({
          success: false,
          message: getSafeErrorMessage(error, '下载文件失败，请稍后重试', '分享下载')
        });
      }
      finalizeTransfer('stream_error').catch(err => {
        console.error('分享下载流错误后资源释放失败:', err);
      });
    });

    stream.pipe(res);

  } catch (error) {
    console.error('分享下载文件失败:', error);
    if (!res.headersSent) {
      res.status(500).json({
        success: false,
        message: getSafeErrorMessage(error, '下载文件失败，请稍后重试', '分享下载')
      });
    }
    // 如果发生错误，结算并关闭存储连接
    await finalizeTransfer('catch_error');
  }
});

// ===== 管理员API =====

// 获取系统设置
app.get('/api/admin/settings', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const maxUploadSize = parseInt(SettingsDB.get('max_upload_size') || '10737418240');
    const smtpHost = SettingsDB.get('smtp_host');
    const smtpPort = SettingsDB.get('smtp_port');
    const smtpSecure = SettingsDB.get('smtp_secure') === 'true';
    const smtpUser = SettingsDB.get('smtp_user');
    const smtpFrom = SettingsDB.get('smtp_from') || smtpUser;
    const smtpHasPassword = !!SettingsDB.get('smtp_password');
    const globalTheme = SettingsDB.get('global_theme') || 'dark';
    const downloadSecurity = getDownloadSecuritySettings();
    const desktopUpdate = getDesktopUpdateConfig();

    res.json({
      success: true,
      settings: {
        max_upload_size: maxUploadSize,
        global_theme: globalTheme,
        download_security: downloadSecurity,
        desktop_update: {
          latest_version: desktopUpdate.latestVersion,
          installer_url: desktopUpdate.installerUrl,
          installer_sha256: desktopUpdate.installerSha256,
          installer_size: desktopUpdate.packageSize,
          release_notes: desktopUpdate.releaseNotes,
          force_update: desktopUpdate.mandatory
        },
        smtp: {
          host: smtpHost || '',
          port: smtpPort ? parseInt(smtpPort, 10) : 465,
          secure: smtpSecure,
          user: smtpUser || '',
          from: smtpFrom || '',
          has_password: smtpHasPassword
        }
      }
    });
  } catch (error) {
    console.error('获取系统设置失败:', error);
    res.status(500).json({
      success: false,
      message: '获取系统设置失败: ' + error.message
    });
  }
});

// 更新系统设置
// 注意：已移除 requirePasswordConfirmation 中间件，依赖管理员登录认证
app.post('/api/admin/settings',
  authMiddleware,
  adminMiddleware,
  (req, res) => {
  try {
    const {
      max_upload_size,
      smtp,
      global_theme,
      download_security,
      desktop_update
    } = req.body;
    let desktopInstallerCleanup = null;

    if (max_upload_size !== undefined) {
      const size = parseInt(max_upload_size);
      if (isNaN(size) || size < 0) {
        return res.status(400).json({
          success: false,
          message: '无效的文件大小'
        });
      }
      SettingsDB.set('max_upload_size', size.toString());
    }

    // 更新全局主题
    if (global_theme !== undefined) {
      const validThemes = ['dark', 'light'];
      if (!validThemes.includes(global_theme)) {
        return res.status(400).json({
          success: false,
          message: '无效的主题设置'
        });
      }
      SettingsDB.set('global_theme', global_theme);
    }

    if (download_security !== undefined) {
      if (!download_security || typeof download_security !== 'object') {
        return res.status(400).json({
          success: false,
          message: '下载安全策略配置格式错误'
        });
      }

      const currentSecuritySettings = getDownloadSecuritySettings();
      const normalizedSecuritySettings = normalizeDownloadSecuritySettingsPayload(
        download_security,
        currentSecuritySettings
      );
      saveDownloadSecuritySettings(normalizedSecuritySettings);
    }

    if (smtp) {
      if (!smtp.host || !smtp.port || !smtp.user) {
        return res.status(400).json({ success: false, message: 'SMTP配置不完整' });
      }
      SettingsDB.set('smtp_host', smtp.host);
      SettingsDB.set('smtp_port', smtp.port);
      SettingsDB.set('smtp_secure', smtp.secure ? 'true' : 'false');
      SettingsDB.set('smtp_user', smtp.user);
      SettingsDB.set('smtp_from', smtp.from || smtp.user);
      if (smtp.password) {
        SettingsDB.set('smtp_password', smtp.password);
      }
    }

    if (desktop_update !== undefined) {
      if (!desktop_update || typeof desktop_update !== 'object') {
        return res.status(400).json({
          success: false,
          message: '桌面端更新配置格式错误'
        });
      }

      if (desktop_update.latest_version !== undefined) {
        SettingsDB.set('desktop_latest_version', normalizeVersion(desktop_update.latest_version, DEFAULT_DESKTOP_VERSION));
      }
      if (desktop_update.installer_url !== undefined) {
        const normalizedInstallerUrl = String(desktop_update.installer_url || '').trim();
        SettingsDB.set('desktop_installer_url', normalizedInstallerUrl);
        SettingsDB.set('desktop_installer_url_win_x64', normalizedInstallerUrl);
        if (!normalizedInstallerUrl) {
          SettingsDB.set('desktop_installer_sha256', '');
          SettingsDB.set('desktop_installer_size', '0');
        } else {
          const localMeta = getLocalDesktopInstallerMeta(normalizedInstallerUrl);
          if (localMeta?.sha256) {
            SettingsDB.set('desktop_installer_sha256', localMeta.sha256);
          }
          if (localMeta?.size) {
            SettingsDB.set('desktop_installer_size', String(localMeta.size));
          }
        }
        desktopInstallerCleanup = cleanupDesktopInstallerPackages(normalizedInstallerUrl);
      }
      if (desktop_update.installer_sha256 !== undefined) {
        const rawDigest = String(desktop_update.installer_sha256 || '').trim().toLowerCase();
        const normalizedDigest = normalizeSha256(rawDigest);
        if (rawDigest && !normalizedDigest) {
          return res.status(400).json({
            success: false,
            message: '安装包 SHA256 格式无效'
          });
        }
        SettingsDB.set('desktop_installer_sha256', normalizedDigest);
      }
      if (desktop_update.installer_size !== undefined) {
        const rawSize = Number(desktop_update.installer_size);
        if (!Number.isFinite(rawSize) || rawSize < 0) {
          return res.status(400).json({
            success: false,
            message: '安装包大小格式无效'
          });
        }
        SettingsDB.set('desktop_installer_size', String(normalizeNonNegativeInteger(rawSize, 0)));
      }
      if (desktop_update.release_notes !== undefined) {
        SettingsDB.set('desktop_release_notes', String(desktop_update.release_notes || '').trim());
      }
      if (desktop_update.force_update !== undefined) {
        SettingsDB.set('desktop_force_update', desktop_update.force_update ? 'true' : 'false');
      }
    }

    const payload = {
      success: true,
      message: '系统设置已更新'
    };
    if (desktopInstallerCleanup) {
      payload.desktop_installer_cleanup = desktopInstallerCleanup;
    }
    res.json(payload);
  } catch (error) {
    console.error('更新系统设置失败:', error);
    res.status(500).json({
      success: false,
      message: '更新系统设置失败: ' + error.message
    });
  }
});

// 测试SMTP
app.post('/api/admin/settings/test-smtp', authMiddleware, adminMiddleware, async (req, res) => {
  const { to } = req.body;
  try {
    const smtpConfig = getSmtpConfig();
    if (!smtpConfig) {
      return res.status(400).json({ success: false, message: 'SMTP未配置' });
    }
    const target = to || req.user.email || smtpConfig.user;
    await sendMail(
      target,
      'SMTP测试 - 玩玩云',
      `<p>您好，这是一封测试邮件，说明SMTP配置可用。</p><p>时间：${new Date().toISOString()}</p>`
    );
    res.json({ success: true, message: `测试邮件已发送至 ${target}` });
  } catch (error) {
    console.error('测试SMTP失败:', error);
    res.status(500).json({ success: false, message: '测试邮件发送失败: ' + (error.response?.message || error.message) });
  }
});

// ===== 统一 OSS 配置管理（管理员专用） =====

// 获取统一 OSS 配置
app.get('/api/admin/unified-oss-config', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const config = SettingsDB.getUnifiedOssConfig();

    if (!config) {
      return res.json({
        success: true,
        configured: false,
        config: null
      });
    }

    // 返回配置（隐藏 Secret Key）
    res.json({
      success: true,
      configured: true,
      config: {
        provider: config.provider,
        region: config.region,
        access_key_id: config.access_key_id,
        bucket: config.bucket,
        endpoint: config.endpoint,
        has_secret: !!config.access_key_secret
      }
    });
  } catch (error) {
    console.error('获取统一 OSS 配置失败:', error);
    res.status(500).json({
      success: false,
      message: '获取配置失败: ' + error.message
    });
  }
});

// 设置/更新统一 OSS 配置
app.post('/api/admin/unified-oss-config',
  authMiddleware,
  adminMiddleware,
  [
    body('provider').isIn(['aliyun', 'tencent', 'aws']).withMessage('无效的OSS服务商'),
    body('region').notEmpty().withMessage('地域不能为空'),
    body('access_key_id').notEmpty().withMessage('Access Key ID不能为空'),
    body('access_key_secret').notEmpty().withMessage('Access Key Secret不能为空'),
    body('bucket').notEmpty().withMessage('存储桶名称不能为空'),
    body('endpoint').optional({ checkFalsy: true }).isURL().withMessage('Endpoint必须是有效的URL')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { provider, region, access_key_id, access_key_secret, bucket, endpoint } = req.body;

      // 验证 OSS 连接
      try {
        const testUser = {
          id: 0, // 系统测试用户
          oss_provider: provider,
          oss_region: region,
          oss_access_key_id: access_key_id,
          oss_access_key_secret: access_key_secret,
          oss_bucket: bucket,
          oss_endpoint: endpoint
        };
        const ossClient = new OssStorageClient(testUser);
        await ossClient.connect();

        // 尝试列出 bucket 内容（验证配置是否正确）
        await ossClient.list('/');
        await ossClient.end();
      } catch (error) {
        return res.status(400).json({
          success: false,
          message: 'OSS连接失败，请检查配置: ' + error.message
        });
      }

      // 保存统一 OSS 配置
      SettingsDB.setUnifiedOssConfig({
        provider,
        region,
        access_key_id,
        access_key_secret,
        bucket,
        endpoint: endpoint || ''
      });

      // 记录系统日志
      SystemLogDB.log({
        level: SystemLogDB.LEVELS.INFO,
        category: SystemLogDB.CATEGORIES.SYSTEM,
        action: 'update_unified_oss_config',
        message: '管理员更新了统一 OSS 配置',
        userId: req.user.id,
        username: req.user.username,
        details: {
          provider,
          region,
          bucket,
          endpoint: endpoint || ''
        }
      });

      res.json({
        success: true,
        message: '统一 OSS 配置已更新，所有用户将使用此配置'
      });
    } catch (error) {
      console.error('更新统一 OSS 配置失败:', error);
      res.status(500).json({
        success: false,
        message: '更新配置失败: ' + error.message
      });
    }
  }
);

// 测试统一 OSS 配置（不保存）
app.post('/api/admin/unified-oss-config/test',
  authMiddleware,
  adminMiddleware,
  [
    body('provider').isIn(['aliyun', 'tencent', 'aws']).withMessage('无效的OSS服务商'),
    body('region').notEmpty().withMessage('地域不能为空'),
    body('access_key_id').notEmpty().withMessage('Access Key ID不能为空'),
    body('bucket').notEmpty().withMessage('存储桶名称不能为空')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { provider, region, access_key_id, access_key_secret, bucket, endpoint } = req.body;

      // 验证 OSS 连接
      const testUser = {
        id: 0,
        oss_provider: provider,
        oss_region: region,
        oss_access_key_id: access_key_id,
        oss_access_key_secret: access_key_secret,
        oss_bucket: bucket,
        oss_endpoint: endpoint
      };
      const ossClient = new OssStorageClient(testUser);
      await ossClient.connect();

      // 尝试列出 bucket 内容
      await ossClient.list('/');
      await ossClient.end();

      res.json({
        success: true,
        message: 'OSS 连接测试成功'
      });
    } catch (error) {
      console.error('[OSS测试] 连接失败:', error);
      res.status(400).json({
        success: false,
        message: 'OSS 连接失败: ' + error.message
      });
    }
  }
);

// 删除统一 OSS 配置
app.delete('/api/admin/unified-oss-config',
  authMiddleware,
  adminMiddleware,
  (req, res) => {
  try {
    SettingsDB.clearUnifiedOssConfig();

    // 记录系统日志
    SystemLogDB.log({
      level: SystemLogDB.LEVELS.INFO,
      category: SystemLogDB.CATEGORIES.SYSTEM,
      action: 'delete_unified_oss_config',
      message: '管理员删除了统一 OSS 配置',
      userId: req.user.id,
      username: req.user.username
    });

    res.json({
      success: true,
      message: '统一 OSS 配置已删除，用户将使用个人配置'
    });
  } catch (error) {
    console.error('删除统一 OSS 配置失败:', error);
    res.status(500).json({
      success: false,
      message: '删除配置失败: ' + error.message
    });
  }
});

// 系统健康检测API
app.get('/api/admin/health-check', authMiddleware, adminMiddleware, async (req, res) => {
  try {
    const checks = [];
    let overallStatus = 'healthy'; // healthy, warning, critical

    // 1. JWT密钥安全检查
    const jwtSecure = isJwtSecretSecure();
    checks.push({
      name: 'JWT密钥',
      category: 'security',
      status: jwtSecure ? 'pass' : 'fail',
      message: jwtSecure ? 'JWT密钥已正确配置（随机生成）' : 'JWT密钥使用默认值或长度不足，存在安全风险！',
      suggestion: jwtSecure ? null : '请在.env中设置随机生成的JWT_SECRET，至少32字符'
    });
    if (!jwtSecure) overallStatus = 'critical';

    // 2. CORS配置检查
    const allowedOrigins = process.env.ALLOWED_ORIGINS;
    const corsConfigured = allowedOrigins && allowedOrigins.trim().length > 0;
    checks.push({
      name: 'CORS跨域配置',
      category: 'security',
      status: corsConfigured ? 'pass' : 'warning',
      message: corsConfigured
        ? `已配置允许的域名: ${allowedOrigins}`
        : 'CORS未配置，允许所有来源（仅适合开发环境）',
      suggestion: corsConfigured ? null : '生产环境建议配置ALLOWED_ORIGINS环境变量'
    });
    if (!corsConfigured && overallStatus === 'healthy') overallStatus = 'warning';

    // 3. HTTPS/Cookie安全配置
    const enforceHttps = process.env.ENFORCE_HTTPS === 'true';
    const cookieSecure = SHOULD_USE_SECURE_COOKIES;
    const httpsConfigured = enforceHttps && cookieSecure;
    checks.push({
      name: 'HTTPS安全配置',
      category: 'security',
      status: httpsConfigured ? 'pass' : 'warning',
      message: httpsConfigured
        ? 'HTTPS强制开启，Cookie安全标志已设置'
        : `ENFORCE_HTTPS=${enforceHttps}, COOKIE_SECURE=${cookieSecure}`,
      suggestion: httpsConfigured ? null : '生产环境建议开启ENFORCE_HTTPS和COOKIE_SECURE'
    });

    // 4. 管理员密码强度检查（检查是否为默认值）
    const adminUsername = process.env.ADMIN_USERNAME;
    const adminConfigured = adminUsername && adminUsername !== 'admin';
    checks.push({
      name: '管理员账号配置',
      category: 'security',
      status: adminConfigured ? 'pass' : 'warning',
      message: adminConfigured
        ? '管理员用户名已自定义'
        : '管理员使用默认用户名"admin"',
      suggestion: adminConfigured ? null : '建议使用自定义管理员用户名'
    });

    // 5. SMTP邮件配置检查
    const smtpHost = SettingsDB.get('smtp_host') || process.env.SMTP_HOST;
    const smtpUser = SettingsDB.get('smtp_user') || process.env.SMTP_USER;
    const smtpPassword = SettingsDB.get('smtp_password') || process.env.SMTP_PASSWORD;
    const smtpConfigured = smtpHost && smtpUser && smtpPassword;
    checks.push({
      name: 'SMTP邮件服务',
      category: 'service',
      status: smtpConfigured ? 'pass' : 'warning',
      message: smtpConfigured
        ? `已配置: ${smtpHost}`
        : '未配置SMTP，邮箱验证和密码重置功能不可用',
      suggestion: smtpConfigured ? null : '配置SMTP以启用邮箱验证功能'
    });

    // 6. 数据库连接检查
    let dbStatus = 'pass';
    let dbMessage = '数据库连接正常';
    try {
      const testQuery = db.prepare('SELECT 1').get();
      if (!testQuery) throw new Error('查询返回空');
    } catch (dbError) {
      dbStatus = 'fail';
      dbMessage = '数据库连接异常: ' + dbError.message;
      overallStatus = 'critical';
    }
    checks.push({
      name: '数据库连接',
      category: 'service',
      status: dbStatus,
      message: dbMessage,
      suggestion: dbStatus === 'fail' ? '检查数据库文件权限和路径配置' : null
    });

    // 7. 存储目录检查
    const storageRoot = getResolvedStorageRoot();
    let storageStatus = 'pass';
    let storageMessage = `存储目录正常: ${storageRoot}`;
    try {
      if (!fs.existsSync(storageRoot)) {
        fs.mkdirSync(storageRoot, { recursive: true });
        storageMessage = `存储目录已创建: ${storageRoot}`;
      }
      // 检查写入权限
      const testFile = path.join(storageRoot, '.health-check-test');
      fs.writeFileSync(testFile, 'test');
      fs.unlinkSync(testFile);
    } catch (storageError) {
      storageStatus = 'fail';
      storageMessage = '存储目录不可写: ' + storageError.message;
      overallStatus = 'critical';
    }
    checks.push({
      name: '存储目录',
      category: 'service',
      status: storageStatus,
      message: storageMessage,
      suggestion: storageStatus === 'fail' ? '检查存储目录权限，确保Node进程有写入权限' : null
    });

    // 8. 限流器状态
    const rateLimiterActive = typeof loginLimiter !== 'undefined' && loginLimiter !== null;
    checks.push({
      name: '登录防爆破',
      category: 'security',
      status: rateLimiterActive ? 'pass' : 'warning',
      message: rateLimiterActive
        ? '限流器已启用（5次/15分钟，封锁30分钟）'
        : '限流器未正常初始化',
      suggestion: null
    });

    // 9. 信任代理配置（安全检查）
    const trustProxy = app.get('trust proxy');
    let trustProxyStatus = 'pass';
    let trustProxyMessage = '';
    let trustProxySuggestion = null;

    if (trustProxy === true) {
      // trust proxy = true 是不安全的配置
      trustProxyStatus = 'fail';
      trustProxyMessage = 'trust proxy = true（信任所有代理），客户端可伪造 IP/协议！';
      trustProxySuggestion = '建议设置 TRUST_PROXY=1（单层代理）或具体的代理 IP 段';
      if (overallStatus !== 'critical') overallStatus = 'critical';
    } else if (trustProxy === false || !trustProxy) {
      trustProxyStatus = 'info';
      trustProxyMessage = '未启用 trust proxy（直接暴露模式）';
      trustProxySuggestion = '如在 Nginx/CDN 后部署，需配置 TRUST_PROXY=1';
    } else if (typeof trustProxy === 'number') {
      trustProxyStatus = 'pass';
      trustProxyMessage = `trust proxy = ${trustProxy}（信任前 ${trustProxy} 跳代理）`;
    } else {
      trustProxyStatus = 'pass';
      trustProxyMessage = `trust proxy = "${trustProxy}"（信任指定代理）`;
    }

    checks.push({
      name: '信任代理配置',
      category: 'security',
      status: trustProxyStatus,
      message: trustProxyMessage,
      suggestion: trustProxySuggestion
    });

    // 10. Node环境
    const nodeEnv = process.env.NODE_ENV || 'development';
    checks.push({
      name: '运行环境',
      category: 'config',
      status: nodeEnv === 'production' ? 'pass' : 'info',
      message: `当前环境: ${nodeEnv}`,
      suggestion: nodeEnv !== 'production' ? '生产部署建议设置NODE_ENV=production' : null
    });

    // 11. CSRF 保护检查
    const csrfEnabled = ENABLE_CSRF;
    checks.push({
      name: 'CSRF保护',
      category: 'security',
      status: csrfEnabled ? 'pass' : 'warning',
      message: csrfEnabled
        ? 'CSRF保护已启用（Double Submit Cookie模式）'
        : 'CSRF保护未启用（通过ENABLE_CSRF=true开启）',
      suggestion: csrfEnabled ? null : '生产环境建议启用CSRF保护以防止跨站请求伪造攻击'
    });

    // 12. Session密钥检查
    const sessionSecure = !DEFAULT_SESSION_SECRETS.includes(SESSION_SECRET) && SESSION_SECRET.length >= 32;
    checks.push({
      name: 'Session密钥',
      category: 'security',
      status: sessionSecure ? 'pass' : 'fail',
      message: sessionSecure ? 'Session密钥已正确配置' : 'Session密钥使用默认值或长度不足，存在安全风险！',
      suggestion: sessionSecure ? null : '请在.env中设置随机生成的SESSION_SECRET，至少32字符'
    });
    if (!sessionSecure && overallStatus !== 'critical') overallStatus = 'critical';

    // 统计
    const summary = {
      total: checks.length,
      pass: checks.filter(c => c.status === 'pass').length,
      warning: checks.filter(c => c.status === 'warning').length,
      fail: checks.filter(c => c.status === 'fail').length,
      info: checks.filter(c => c.status === 'info').length
    };

    res.json({
      success: true,
      overallStatus,
      summary,
      checks,
      timestamp: new Date().toISOString(),
      version: process.env.npm_package_version || '1.1.0'
    });
  } catch (error) {
    console.error('健康检测失败:', error);
    res.status(500).json({
      success: false,
      message: '健康检测失败: ' + error.message
    });
  }
});

// ===== 第二轮修复：WAL 文件管理接口 =====

/**
 * 获取 WAL 文件信息
 * GET /api/admin/wal-info
 */
app.get('/api/admin/wal-info', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const walSize = WalManager.getWalFileSize();
    const walSizeMB = (walSize / 1024 / 1024).toFixed(2);

    res.json({
      success: true,
      data: {
        walSize,
        walSizeMB: parseFloat(walSizeMB),
        status: walSize > 100 * 1024 * 1024 ? 'warning' : 'normal'
      }
    });
  } catch (error) {
    console.error('获取 WAL 信息失败:', error);
    res.status(500).json({
      success: false,
      message: '获取 WAL 信息失败: ' + error.message
    });
  }
});

/**
 * 手动执行 WAL 检查点（清理 WAL 文件）
 * POST /api/admin/wal-checkpoint
 */
app.post('/api/admin/wal-checkpoint',
  authMiddleware,
  adminMiddleware,
  (req, res) => {
    try {
      const beforeSize = WalManager.getWalFileSize();
      const success = WalManager.performCheckpoint();
      const afterSize = WalManager.getWalFileSize();

      if (!success) {
        return res.status(500).json({
          success: false,
          message: 'WAL 检查点执行失败'
        });
      }

      // 记录 WAL 清理操作
      logSystem(req, 'wal_checkpoint', `管理员手动执行 WAL 检查点`, {
        beforeSize,
        afterSize,
        freed: beforeSize - afterSize
      });

      res.json({
        success: true,
        message: `WAL 检查点完成: ${(beforeSize / 1024 / 1024).toFixed(2)}MB → ${(afterSize / 1024 / 1024).toFixed(2)}MB`,
        data: {
          beforeSize,
          afterSize,
          freed: beforeSize - afterSize
        }
      });
    } catch (error) {
      console.error('执行 WAL 检查点失败:', error);
      res.status(500).json({
        success: false,
        message: '执行 WAL 检查点失败: ' + error.message
      });
    }
  }
);

// 获取服务器存储统计信息
app.get('/api/admin/storage-stats', authMiddleware, adminMiddleware, async (req, res) => {
  try {
    // 获取本地存储目录（与 storage.js 保持一致）
    const localStorageDir = getResolvedStorageRoot();

    // 确保存储目录存在
    if (!fs.existsSync(localStorageDir)) {
      fs.mkdirSync(localStorageDir, { recursive: true });
    }

    // 获取磁盘信息（使用df命令）
    let totalDisk = 0;
    let usedDisk = 0;
    let availableDisk = 0;

    try {
      // 获取本地存储目录所在分区的磁盘信息（避免使用shell）
      const { stdout: dfOutput } = await execFileAsync('df', ['-B', '1', localStorageDir], { encoding: 'utf8' });
      // 取最后一行数据
      const lines = dfOutput.trim().split('\n');
      const parts = lines[lines.length - 1].trim().split(/\s+/);

      if (parts.length >= 4) {
        totalDisk = parseInt(parts[1], 10) || 0;  // 总大小
        usedDisk = parseInt(parts[2], 10) || 0;   // 已使用
        availableDisk = parseInt(parts[3], 10) || 0;  // 可用
      }
    } catch (dfError) {
      console.error('获取磁盘信息失败:', dfError.message);
      // 如果df命令失败，尝试使用Windows的wmic命令
      try {
        // 获取本地存储目录所在的驱动器号
        const driveLetter = localStorageDir.charAt(0);
        const normalizedDrive = driveLetter.toUpperCase();
        if (!/^[A-Z]$/.test(normalizedDrive)) {
          throw new Error('Invalid drive letter');
        }
        const { stdout: wmicOutput } = await execFileAsync(
          'wmic',
          ['logicaldisk', 'where', `DeviceID='${normalizedDrive}:'`, 'get', 'Size,FreeSpace', '/value'],
          { encoding: 'utf8' }
        );

        const freeMatch = wmicOutput.match(/FreeSpace=(\d+)/);
        const sizeMatch = wmicOutput.match(/Size=(\d+)/);

        if (sizeMatch && freeMatch) {
          totalDisk = parseInt(sizeMatch[1]) || 0;
          availableDisk = parseInt(freeMatch[1]) || 0;
          usedDisk = totalDisk - availableDisk;
        }
      } catch (wmicError) {
        console.error('获取Windows磁盘信息失败:', wmicError.message);
      }
    }
    
    // 从数据库获取所有用户的本地存储配额和使用情况
    const users = UserDB.getAll();
    let totalUserQuotas = 0;
    let totalUserUsed = 0;
    
    users.forEach(user => {
      // 只统计使用本地存储的用户（local_only 或 user_choice）
      const storagePermission = user.storage_permission || 'oss_only';
      if (storagePermission === 'local_only' || storagePermission === 'user_choice') {
        totalUserQuotas += user.local_storage_quota || 0;
        totalUserUsed += user.local_storage_used || 0;
      }
    });
    
    res.json({
      success: true,
      stats: {
        totalDisk,            // 磁盘总容量
        usedDisk,             // 磁盘已使用
        availableDisk,        // 磁盘可用空间
        totalUserQuotas,      // 用户配额总和
        totalUserUsed,        // 用户实际使用总和
        totalUsers: users.length  // 用户总数
      }
    });

  } catch (error) {
    console.error('获取存储统计失败:', error);
    res.status(500).json({
      success: false,
      message: '获取存储统计失败: ' + error.message
    });
  }
});

// 获取所有用户
app.get('/api/admin/users', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const toAdminUserPayload = (u) => ({
      id: u.id,
      username: u.username,
      email: u.email,
      is_admin: u.is_admin,
      is_active: u.is_active,
      is_verified: u.is_verified,
      is_banned: u.is_banned,
      has_oss_config: u.has_oss_config,
      created_at: u.created_at,
      storage_permission: u.storage_permission || 'oss_only',
      current_storage_type: u.current_storage_type || 'oss',
      local_storage_quota: u.local_storage_quota || DEFAULT_LOCAL_STORAGE_QUOTA_BYTES,
      local_storage_used: u.local_storage_used || 0,
      oss_storage_quota: normalizeOssQuota(u.oss_storage_quota),
      storage_used: u.storage_used || 0,
      download_traffic_quota: normalizeDownloadTrafficQuota(u.download_traffic_quota),
      download_traffic_used: normalizeDownloadTrafficUsed(
        u.download_traffic_used,
        normalizeDownloadTrafficQuota(u.download_traffic_quota)
      ),
      download_traffic_quota_expires_at: u.download_traffic_quota_expires_at || null,
      download_traffic_reset_cycle: u.download_traffic_reset_cycle || 'none',
      download_traffic_last_reset_at: u.download_traffic_last_reset_at || null
    });

    const hasPagedQuery = req.query?.paged === '1'
      || ['page', 'pageSize', 'keyword', 'role', 'status', 'storage', 'sort']
        .some((key) => req.query && req.query[key] !== undefined);

    if (hasPagedQuery) {
      const queryResult = UserDB.queryAdminUsers(req.query || {});
      const users = queryResult.rows.map((user) => {
        const policyState = enforceDownloadTrafficPolicy(user.id, 'admin_list_page');
        return policyState?.user || user;
      });

      return res.json({
        success: true,
        users: users.map(toAdminUserPayload),
        pagination: queryResult.pagination,
        summary: queryResult.summary
      });
    }

    const users = UserDB.getAll().map((user) => {
      const policyState = enforceDownloadTrafficPolicy(user.id, 'admin_list');
      return policyState?.user || user;
    });

    res.json({
      success: true,
      users: users.map(toAdminUserPayload)
    });
  } catch (error) {
    console.error('获取用户列表失败:', error);
    res.status(500).json({
      success: false,
      message: '获取用户列表失败: ' + error.message
    });
  }
});

// 获取系统日志
app.get('/api/admin/logs', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const {
      page = 1,
      pageSize = 50,
      level,
      category,
      userId,
      startDate,
      endDate,
      keyword
    } = req.query;

    const result = SystemLogDB.query({
      page: parseInt(page),
      pageSize: Math.min(parseInt(pageSize) || 50, 200), // 限制最大每页200条
      level: level || null,
      category: category || null,
      userId: userId ? parseInt(userId) : null,
      startDate: startDate || null,
      endDate: endDate || null,
      keyword: keyword || null
    });

    res.json({
      success: true,
      ...result
    });
  } catch (error) {
    console.error('获取系统日志失败:', error);
    res.status(500).json({
      success: false,
      message: '获取系统日志失败: ' + error.message
    });
  }
});

// 获取日志统计
app.get('/api/admin/logs/stats', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const categoryStats = SystemLogDB.getStatsByCategory();
    const dateStats = SystemLogDB.getStatsByDate(7);

    res.json({
      success: true,
      stats: {
        byCategory: categoryStats,
        byDate: dateStats
      }
    });
  } catch (error) {
    console.error('获取日志统计失败:', error);
    res.status(500).json({
      success: false,
      message: '获取日志统计失败: ' + error.message
    });
  }
});

// 清理旧日志
app.post('/api/admin/logs/cleanup',
  authMiddleware,
  adminMiddleware,
  (req, res) => {
  try {
    const { keepDays = 90 } = req.body;
    const days = Math.max(7, Math.min(parseInt(keepDays) || 90, 365)); // 最少保留7天，最多365天

    const deletedCount = SystemLogDB.cleanup(days);

    // 记录清理操作
    logSystem(req, 'logs_cleanup', `管理员清理了 ${deletedCount} 条日志（保留 ${days} 天）`, { deletedCount, keepDays: days });

    res.json({
      success: true,
      message: `已清理 ${deletedCount} 条日志`,
      deletedCount
    });
  } catch (error) {
    console.error('清理日志失败:', error);
    res.status(500).json({
      success: false,
      message: '清理日志失败: ' + error.message
    });
  }
});

// 下载流量预扣运维面板：查询
app.get('/api/admin/download-reservations', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const queryResult = DownloadTrafficReservationDB.getAdminList({
      status: req.query?.status,
      userId: req.query?.user_id,
      keyword: req.query?.keyword,
      page: req.query?.page,
      pageSize: req.query?.pageSize
    });

    res.json({
      success: true,
      reservations: queryResult.rows || [],
      pagination: queryResult.pagination,
      summary: DownloadTrafficReservationDB.getAdminSummary()
    });
  } catch (error) {
    console.error('获取下载预扣列表失败:', error);
    res.status(500).json({
      success: false,
      message: '获取下载预扣列表失败'
    });
  }
});

// 下载流量预扣运维面板：手动释放单条 pending 预扣
app.post('/api/admin/download-reservations/:id/cancel', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const reservationId = parseInt(req.params.id, 10);
    if (!Number.isFinite(reservationId) || reservationId <= 0) {
      return res.status(400).json({
        success: false,
        message: '无效的预扣ID'
      });
    }

    const cancelResult = DownloadTrafficReservationDB.cancelPendingById(reservationId);
    if (!cancelResult?.changes) {
      return res.status(404).json({
        success: false,
        message: '预扣记录不存在或已完成'
      });
    }

    logSystem(
      req,
      'download_reservation_cancel',
      `管理员手动释放预扣 #${reservationId}`,
      {
        reservationId,
        row: cancelResult.row || null
      }
    );

    res.json({
      success: true,
      message: '预扣额度已释放',
      reservation: cancelResult.row || null
    });
  } catch (error) {
    console.error('释放下载预扣失败:', error);
    res.status(500).json({
      success: false,
      message: '释放下载预扣失败'
    });
  }
});

// 下载流量预扣运维面板：批量清理（过期 pending + 历史 finalized）
app.post('/api/admin/download-reservations/cleanup', authMiddleware, adminMiddleware, (req, res) => {
  try {
    // 默认 keep_days=0：立即清理全部已完成历史记录，避免“清理无效果”的感知
    const rawKeepDays = req.body?.keep_days;
    const parsedKeepDays = Number.isFinite(Number(rawKeepDays)) ? parseInt(rawKeepDays, 10) : 0;
    const keepDays = Math.min(365, Math.max(0, Number.isFinite(parsedKeepDays) ? parsedKeepDays : 0));
    const expireResult = DownloadTrafficReservationDB.expirePendingReservations();
    const cleanupResult = DownloadTrafficReservationDB.cleanupFinalizedHistory(keepDays);

    logSystem(
      req,
      'download_reservation_cleanup',
      `管理员执行预扣清理（保留${keepDays}天）`,
      {
        keep_days: keepDays,
        expired_pending: Number(expireResult?.changes || 0),
        deleted_finalized: Number(cleanupResult?.changes || 0)
      }
    );

    res.json({
      success: true,
      message: `预扣清理完成（过期待确认 ${Number(expireResult?.changes || 0)} 条，删除历史 ${Number(cleanupResult?.changes || 0)} 条）`,
      result: {
        keep_days: keepDays,
        expired_pending: Number(expireResult?.changes || 0),
        deleted_finalized: Number(cleanupResult?.changes || 0),
        summary: DownloadTrafficReservationDB.getAdminSummary()
      }
    });
  } catch (error) {
    console.error('清理下载预扣失败:', error);
    res.status(500).json({
      success: false,
      message: '清理下载预扣失败'
    });
  }
});

// ===== 第二轮修复：存储缓存一致性检查和修复接口 =====

/**
 * 检查单个用户的存储缓存完整性
 * GET /api/admin/storage-cache/check/:userId
 */
app.get('/api/admin/storage-cache/check/:userId',
  authMiddleware,
  adminMiddleware,
  async (req, res) => {
    try {
      const { userId } = req.params;
      const user = UserDB.findById(userId);

      if (!user) {
        return res.status(404).json({
          success: false,
          message: '用户不存在'
        });
      }

      // 检查是否有可用的OSS配置（个人配置或系统级统一配置）
      const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
      if (!user.has_oss_config && !hasUnifiedConfig) {
        return res.json({
          success: true,
          message: '用户未配置 OSS，无需检查',
          data: {
            userId: user.id,
            username: user.username,
            hasOssConfig: false,
            consistent: true
          }
        });
      }

      // 创建 OSS 客户端
      const ossClient = createOssClientForUser(user);
      const result = await StorageUsageCache.checkIntegrity(userId, ossClient);

      // 记录检查操作
      logSystem(req, 'storage_cache_check', `管理员检查用户 ${user.username} 的存储缓存`, {
        userId,
        username: user.username,
        ...result
      });

      res.json({
        success: true,
        message: result.consistent ? '缓存一致' : '缓存不一致',
        data: {
          userId: user.id,
          username: user.username,
          ...result
        }
      });
    } catch (error) {
      console.error('检查存储缓存失败:', error);
      res.status(500).json({
        success: false,
        message: '检查存储缓存失败: ' + error.message
      });
    }
  }
);

/**
 * 重建单个用户的存储缓存
 * POST /api/admin/storage-cache/rebuild/:userId
 */
app.post('/api/admin/storage-cache/rebuild/:userId',
  authMiddleware,
  adminMiddleware,
  async (req, res) => {
    try {
      const { userId } = req.params;
      const user = UserDB.findById(userId);

      if (!user) {
        return res.status(404).json({
          success: false,
          message: '用户不存在'
        });
      }

      // 检查是否有可用的OSS配置（个人配置或系统级统一配置）
      const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
      if (!user.has_oss_config && !hasUnifiedConfig) {
        return res.status(400).json({
          success: false,
          message: '用户未配置 OSS'
        });
      }

      // 创建 OSS 客户端
      const ossClient = createOssClientForUser(user);
      const result = await StorageUsageCache.rebuildCache(userId, ossClient);

      // 记录修复操作
      logSystem(req, 'storage_cache_rebuild', `管理员重建用户 ${user.username} 的存储缓存`, {
        userId,
        username: user.username,
        ...result
      });

      res.json({
        success: true,
        message: `缓存已重建: ${formatFileSize(result.previous)} → ${formatFileSize(result.current)} (${result.fileCount} 个文件)`,
        data: {
          userId: user.id,
          username: user.username,
          ...result
        }
      });
    } catch (error) {
      console.error('重建存储缓存失败:', error);
      res.status(500).json({
        success: false,
        message: '重建存储缓存失败: ' + error.message
      });
    }
  }
);

/**
 * 批量检查所有用户的存储缓存一致性
 * GET /api/admin/storage-cache/check-all
 */
app.get('/api/admin/storage-cache/check-all',
  authMiddleware,
  adminMiddleware,
  async (req, res) => {
    try {
      const users = UserDB.getAll();

      // 创建获取 OSS 客户端的函数
      const getOssClient = (user) => createOssClientForUser(user);

      const results = await StorageUsageCache.checkAllUsersIntegrity(users, getOssClient);

      // 统计
      const total = results.length;
      const inconsistent = results.filter(r => !r.consistent && !r.error).length;
      const errors = results.filter(r => r.error).length;

      // 记录批量检查操作
      logSystem(req, 'storage_cache_check_all', `管理员批量检查存储缓存`, {
        total,
        inconsistent,
        errors
      });

      res.json({
        success: true,
        message: `检查完成: ${total} 个用户，${inconsistent} 个不一致，${errors} 个错误`,
        data: {
          summary: {
            total,
            consistent: total - inconsistent - errors,
            inconsistent,
            errors
          },
          results
        }
      });
    } catch (error) {
      console.error('批量检查存储缓存失败:', error);
      res.status(500).json({
        success: false,
        message: '批量检查存储缓存失败: ' + error.message
      });
    }
  }
);

/**
 * 自动检测并修复所有用户的缓存不一致
 * POST /api/admin/storage-cache/auto-fix
 */
app.post('/api/admin/storage-cache/auto-fix',
  authMiddleware,
  adminMiddleware,
  async (req, res) => {
    try {
      const { threshold = 0 } = req.body; // 差异阈值（字节）

      const users = UserDB.getAll();
      const fixResults = [];

      // 检查是否有系统级统一OSS配置
      const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();

      for (const user of users) {
        // 跳过没有配置 OSS 的用户（个人配置或系统级配置都没有）
        if (!user.has_oss_config && !hasUnifiedConfig) {
          continue;
        }

        try {
          const ossClient = createOssClientForUser(user);
          const result = await StorageUsageCache.autoDetectAndFix(user.id, ossClient, threshold);

          fixResults.push({
            userId: user.id,
            username: user.username,
            ...result
          });
        } catch (error) {
          console.error(`自动修复用户 ${user.id} 失败:`, error.message);
          fixResults.push({
            userId: user.id,
            username: user.username,
            error: error.message
          });
        }
      }

      // 统计
      const total = fixResults.length;
      const fixed = fixResults.filter(r => r.autoFixed).length;
      const errors = fixResults.filter(r => r.error).length;

      // 记录批量修复操作
      logSystem(req, 'storage_cache_auto_fix', `管理员自动修复存储缓存`, {
        total,
        fixed,
        errors,
        threshold
      });

      res.json({
        success: true,
        message: `自动修复完成: ${total} 个用户，${fixed} 个已修复，${errors} 个错误`,
        data: {
          summary: {
            total,
            fixed,
            skipped: total - fixed - errors,
            errors
          },
          results: fixResults
        }
      });
    } catch (error) {
      console.error('自动修复存储缓存失败:', error);
      res.status(500).json({
        success: false,
        message: '自动修复存储缓存失败: ' + error.message
      });
    }
  }
);

// 封禁/解封用户
app.post('/api/admin/users/:id/ban',
  authMiddleware,
  adminMiddleware,
  (req, res) => {
  try {
    const { id } = req.params;
    const { banned } = req.body;

    // 参数验证：验证 ID 格式
    const userId = parseInt(id, 10);
    if (isNaN(userId) || userId <= 0) {
      return res.status(400).json({
        success: false,
        message: '无效的用户ID'
      });
    }

    // 参数验证：验证 banned 是否为布尔值
    if (typeof banned !== 'boolean') {
      return res.status(400).json({
        success: false,
        message: 'banned 参数必须为布尔值'
      });
    }

    // 安全检查：不能封禁自己
    if (userId === req.user.id) {
      return res.status(400).json({
        success: false,
        message: '不能封禁自己的账号'
      });
    }

    // 检查目标用户是否存在
    const targetUser = UserDB.findById(userId);
    if (!targetUser) {
      return res.status(404).json({
        success: false,
        message: '用户不存在'
      });
    }

    // 安全检查：不能封禁其他管理员（除非是超级管理员）
    if (targetUser.is_admin && !req.user.is_super_admin) {
      return res.status(403).json({
        success: false,
        message: '不能封禁管理员账号'
      });
    }

    UserDB.setBanStatus(userId, banned);

    // 记录管理员操作日志
    logUser(req, banned ? 'ban_user' : 'unban_user',
      `管理员${banned ? '封禁' : '解封'}用户: ${targetUser.username}`,
      { targetUserId: userId, targetUsername: targetUser.username }
    );

    res.json({
      success: true,
      message: banned ? '用户已封禁' : '用户已解封'
    });
  } catch (error) {
    console.error('操作失败:', error);
    res.status(500).json({
      success: false,
      message: '操作失败: ' + error.message
    });
  }
});

// 删除用户（级联删除文件和分享）
app.delete('/api/admin/users/:id',
  authMiddleware,
  adminMiddleware,
  async (req, res) => {
  try {
    const { id } = req.params;

    // 参数验证：验证 ID 格式
    const userId = parseInt(id, 10);
    if (isNaN(userId) || userId <= 0) {
      return res.status(400).json({
        success: false,
        message: '无效的用户ID'
      });
    }

    if (userId === req.user.id) {
      return res.status(400).json({
        success: false,
        message: '不能删除自己的账号'
      });
    }

    // 获取用户信息
    const user = UserDB.findById(userId);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: '用户不存在'
      });
    }

    const deletionLog = {
      userId: userId,
      username: user.username,
      deletedFiles: [],
      deletedShares: 0,
      warnings: []
    };

    // 1. 删除本地存储文件（如果用户使用了本地存储）
    const storagePermission = user.storage_permission || 'oss_only';
    if (storagePermission === 'local_only' || storagePermission === 'user_choice') {
      const storageRoot = getResolvedStorageRoot();
      const userStorageDir = path.join(storageRoot, `user_${userId}`);

      if (fs.existsSync(userStorageDir)) {
        try {
          // 递归删除用户目录
          const deletedSize = getUserDirectorySize(userStorageDir);
          fs.rmSync(userStorageDir, { recursive: true, force: true });
          deletionLog.deletedFiles.push({
            type: 'local',
            path: userStorageDir,
            size: deletedSize
          });
          console.log(`[删除用户] 已删除本地存储目录: ${userStorageDir}`);
        } catch (error) {
          console.error(`[删除用户] 删除本地存储失败:`, error);
          deletionLog.warnings.push(`删除本地存储失败: ${error.message}`);
        }
      }
    }

    // 2. OSS存储文件 - 只记录警告，不实际删除（安全考虑）
    if (user.has_oss_config && (storagePermission === 'oss_only' || storagePermission === 'user_choice')) {
      deletionLog.warnings.push(
        `用户配置了OSS存储 (${user.oss_provider}:${user.oss_bucket})，OSS文件未自动删除，请手动处理`
      );
    }

    // 3. 删除用户的所有分享记录
    try {
      const userShares = ShareDB.getUserShares(userId);
      deletionLog.deletedShares = userShares.length;

      userShares.forEach(share => {
        ShareDB.delete(share.id);
        // 清除分享缓存
        if (shareFileCache.has(share.share_code)) {
          shareFileCache.delete(share.share_code);
        }
      });

      console.log(`[删除用户] 已删除 ${deletionLog.deletedShares} 条分享记录`);
    } catch (error) {
      console.error(`[删除用户] 删除分享记录失败:`, error);
      deletionLog.warnings.push(`删除分享记录失败: ${error.message}`);
    }

    // 4. 删除用户记录
    UserDB.delete(userId);

    // 构建响应消息
    let message = `用户 ${user.username} 已删除`;
    if (deletionLog.deletedFiles.length > 0) {
      const totalSize = deletionLog.deletedFiles.reduce((sum, f) => sum + f.size, 0);
      message += `，已清理本地文件 ${formatFileSize(totalSize)}`;
    }
    if (deletionLog.deletedShares > 0) {
      message += `，已删除 ${deletionLog.deletedShares} 条分享`;
    }

    // 记录管理员删除用户操作日志
    logUser(req, 'delete_user', `管理员删除用户: ${user.username}`, {
      targetUserId: userId,
      targetUsername: user.username,
      targetEmail: user.email,
      deletedShares: deletionLog.deletedShares,
      deletedFiles: deletionLog.deletedFiles.length,
      warnings: deletionLog.warnings
    });

    res.json({
      success: true,
      message,
      details: deletionLog
    });
  } catch (error) {
    console.error('删除用户失败:', error);
    res.status(500).json({
      success: false,
      message: '删除用户失败: ' + error.message
    });
  }
});

// 辅助函数：计算目录大小
function getUserDirectorySize(dirPath) {
  if (!dirPath || !fs.existsSync(dirPath)) {
    return 0;
  }

  let totalSize = 0;

  function calculateSize(currentPath) {
    try {
      const stats = fs.statSync(currentPath);

      if (stats.isDirectory()) {
        const files = fs.readdirSync(currentPath);
        files.forEach(file => {
          calculateSize(path.join(currentPath, file));
        });
      } else {
        totalSize += stats.size;
      }
    } catch (error) {
      console.error(`计算大小失败: ${currentPath}`, error);
    }
  }

  calculateSize(dirPath);
  return totalSize;
}

// 设置用户存储权限（管理员）
app.post('/api/admin/users/:id/storage-permission',
  authMiddleware,
  adminMiddleware,
  [
    body('storage_permission').isIn(['local_only', 'oss_only', 'user_choice']).withMessage('无效的存储权限'),
    body('local_storage_quota').optional({ nullable: true }).isInt({ min: 1048576, max: 10995116277760 }).withMessage('本地配额必须在 1MB 到 10TB 之间'),
    body('oss_storage_quota').optional({ nullable: true }).isInt({ min: 1048576, max: 10995116277760 }).withMessage('OSS配额必须在 1MB 到 10TB 之间'),
    body('download_traffic_quota').optional({ nullable: true }).isInt({ min: -1, max: MAX_DOWNLOAD_TRAFFIC_BYTES }).withMessage('下载流量配额必须在 -1 到 10TB 之间（-1表示不限，0表示禁止下载）'),
    body('download_traffic_delta').optional({ nullable: true }).isInt({ min: -MAX_DOWNLOAD_TRAFFIC_BYTES, max: MAX_DOWNLOAD_TRAFFIC_BYTES }).withMessage('下载流量增减值必须在 -10TB 到 10TB 之间'),
    body('download_traffic_reset_cycle').optional({ nullable: true }).isIn(['none', 'daily', 'weekly', 'monthly']).withMessage('下载流量重置周期无效'),
    body('download_traffic_quota_expires_at').optional({ nullable: true }).custom((value) => {
      if (value === null || value === undefined || value === '') {
        return true;
      }
      const parsedDate = parseDateTimeValue(String(value));
      if (!parsedDate) {
        throw new Error('下载流量到期时间格式无效');
      }
      return true;
    }),
    body('reset_download_traffic_used').optional({ nullable: true }).isBoolean().withMessage('reset_download_traffic_used 必须是布尔值')
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        success: false,
        errors: errors.array()
      });
    }

    try {
      const { id } = req.params;
      const {
        storage_permission,
        local_storage_quota,
        oss_storage_quota,
        download_traffic_quota,
        download_traffic_delta,
        download_traffic_quota_expires_at,
        download_traffic_reset_cycle,
        reset_download_traffic_used
      } = req.body;

      // 参数验证：验证 ID 格式
      const userId = parseInt(id, 10);
      if (isNaN(userId) || userId <= 0) {
        return res.status(400).json({
          success: false,
          message: '无效的用户ID'
        });
      }

      const now = new Date();
      const nowSql = formatDateTimeForSqlite(now);
      const updates = { storage_permission };
      const hasSetTrafficQuota = download_traffic_quota !== undefined && download_traffic_quota !== null;
      const hasTrafficDelta = download_traffic_delta !== undefined && download_traffic_delta !== null && Number(download_traffic_delta) !== 0;
      const shouldResetTrafficUsedNow = reset_download_traffic_used === true || reset_download_traffic_used === 'true';

      if (hasSetTrafficQuota && hasTrafficDelta) {
        return res.status(400).json({
          success: false,
          message: '下载流量配额不能同时使用“直接设置”和“增减调整”'
        });
      }

      // 如果提供了本地配额，更新配额（单位：字节）
      if (local_storage_quota !== undefined && local_storage_quota !== null) {
        updates.local_storage_quota = parseInt(local_storage_quota, 10);
      }

      // 如果提供了 OSS 配额，更新配额（单位：字节）
      if (oss_storage_quota !== undefined && oss_storage_quota !== null) {
        updates.oss_storage_quota = parseInt(oss_storage_quota, 10);
      }

      // 根据权限设置自动调整存储类型
      const beforePolicyState = enforceDownloadTrafficPolicy(userId, 'admin_update_before');
      const user = beforePolicyState?.user || UserDB.findById(userId);
      if (!user) {
        return res.status(404).json({
          success: false,
          message: '用户不存在'
        });
      }

      const beforeTrafficState = getDownloadTrafficState(user);
      let targetTrafficQuota = beforeTrafficState.quota;

      if (hasSetTrafficQuota) {
        targetTrafficQuota = normalizeDownloadTrafficQuota(parseInt(download_traffic_quota, 10));
      } else if (hasTrafficDelta) {
        const delta = parseInt(download_traffic_delta, 10);
        targetTrafficQuota = Math.max(
          0,
          Math.min(MAX_DOWNLOAD_TRAFFIC_BYTES, beforeTrafficState.quota + delta)
        );
      }

      if (hasSetTrafficQuota || hasTrafficDelta) {
        updates.download_traffic_quota = targetTrafficQuota;
        updates.download_traffic_used = normalizeDownloadTrafficUsed(
          user.download_traffic_used,
          targetTrafficQuota
        );
      }

      if (targetTrafficQuota <= 0) {
        updates.download_traffic_quota_expires_at = null;
        updates.download_traffic_reset_cycle = 'none';
        updates.download_traffic_last_reset_at = null;
      } else {
        if (download_traffic_quota_expires_at !== undefined) {
          if (download_traffic_quota_expires_at === null || download_traffic_quota_expires_at === '') {
            updates.download_traffic_quota_expires_at = null;
          } else {
            const parsedExpireAt = parseDateTimeValue(String(download_traffic_quota_expires_at));
            updates.download_traffic_quota_expires_at = parsedExpireAt
              ? formatDateTimeForSqlite(parsedExpireAt)
              : null;
          }
        }

        if (download_traffic_reset_cycle !== undefined && download_traffic_reset_cycle !== null) {
          updates.download_traffic_reset_cycle = download_traffic_reset_cycle;
          if (download_traffic_reset_cycle === 'none') {
            updates.download_traffic_last_reset_at = null;
          } else if (!user.download_traffic_last_reset_at) {
            updates.download_traffic_last_reset_at = nowSql;
          }
        }
      }

      if (shouldResetTrafficUsedNow) {
        updates.download_traffic_used = 0;
        const effectiveCycle = updates.download_traffic_reset_cycle || user.download_traffic_reset_cycle || 'none';
        updates.download_traffic_last_reset_at = effectiveCycle === 'none' ? null : nowSql;
      }

      if (storage_permission === 'local_only') {
        updates.current_storage_type = 'local';
      } else if (storage_permission === 'oss_only') {
        // 只有配置了OSS才切换到OSS（个人配置或系统级统一配置）
        const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
        if (user.has_oss_config || hasUnifiedConfig) {
          updates.current_storage_type = 'oss';
        }
      }
      // user_choice 不自动切换，保持用户当前选择

      UserDB.update(userId, updates);

      const afterPolicyState = enforceDownloadTrafficPolicy(userId, 'admin_update_after');
      const updatedUser = afterPolicyState?.user || UserDB.findById(userId);

      logUser(
        req,
        'update_user_storage_and_traffic',
        `管理员更新用户额度策略: ${user.username}`,
        {
          targetUserId: userId,
          targetUsername: user.username,
          storage_permission,
          downloadTrafficOperation: hasSetTrafficQuota
            ? 'set'
            : (hasTrafficDelta ? (parseInt(download_traffic_delta, 10) > 0 ? 'increase' : 'decrease') : 'none'),
          before: {
            quota: beforeTrafficState.quota,
            used: beforeTrafficState.used,
            expires_at: user.download_traffic_quota_expires_at || null,
            reset_cycle: user.download_traffic_reset_cycle || 'none',
            last_reset_at: user.download_traffic_last_reset_at || null
          },
          request: {
            set_quota: hasSetTrafficQuota ? parseInt(download_traffic_quota, 10) : null,
            delta: hasTrafficDelta ? parseInt(download_traffic_delta, 10) : 0,
            expires_at: download_traffic_quota_expires_at ?? undefined,
            reset_cycle: download_traffic_reset_cycle ?? undefined,
            reset_used_now: shouldResetTrafficUsedNow
          },
          after: updatedUser ? {
            quota: normalizeDownloadTrafficQuota(updatedUser.download_traffic_quota),
            used: normalizeDownloadTrafficUsed(
              updatedUser.download_traffic_used,
              normalizeDownloadTrafficQuota(updatedUser.download_traffic_quota)
            ),
            expires_at: updatedUser.download_traffic_quota_expires_at || null,
            reset_cycle: updatedUser.download_traffic_reset_cycle || 'none',
            last_reset_at: updatedUser.download_traffic_last_reset_at || null
          } : null
        }
      );

      res.json({
        success: true,
        message: '存储权限已更新',
        user: updatedUser ? {
          id: updatedUser.id,
          storage_permission: updatedUser.storage_permission || 'oss_only',
          current_storage_type: updatedUser.current_storage_type || 'oss',
          local_storage_quota: updatedUser.local_storage_quota || DEFAULT_LOCAL_STORAGE_QUOTA_BYTES,
          local_storage_used: updatedUser.local_storage_used || 0,
          oss_storage_quota: normalizeOssQuota(updatedUser.oss_storage_quota),
          download_traffic_quota: normalizeDownloadTrafficQuota(updatedUser.download_traffic_quota),
          download_traffic_used: normalizeDownloadTrafficUsed(
            updatedUser.download_traffic_used,
            normalizeDownloadTrafficQuota(updatedUser.download_traffic_quota)
          ),
          download_traffic_quota_expires_at: updatedUser.download_traffic_quota_expires_at || null,
          download_traffic_reset_cycle: updatedUser.download_traffic_reset_cycle || 'none',
          download_traffic_last_reset_at: updatedUser.download_traffic_last_reset_at || null
        } : null
      });
    } catch (error) {
      console.error('设置存储权限失败:', error);
      res.status(500).json({
        success: false,
        message: '设置存储权限失败: ' + error.message
      });
    }
  }
);

// ===== 管理员文件审查功能 =====

// 查看用户文件列表（管理员，只读）
app.get('/api/admin/users/:id/files', authMiddleware, adminMiddleware, async (req, res) => {
  const { id } = req.params;
  const dirPath = req.query.path || '/';
  let ossClient;

  // 参数验证：验证 ID 格式
  const userId = parseInt(id, 10);
  if (isNaN(userId) || userId <= 0) {
    return res.status(400).json({
      success: false,
      message: '无效的用户ID'
    });
  }

  try {
    const user = UserDB.findById(userId);

    if (!user) {
      return res.status(404).json({
        success: false,
        message: '用户不存在'
      });
    }

    // 检查是否有可用的OSS配置（个人配置或系统级统一配置）
    const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
    if (!user.has_oss_config && !hasUnifiedConfig) {
      return res.status(400).json({
        success: false,
        message: '该用户未配置OSS服务'
      });
    }

    // OssStorageClient 已在文件顶部导入
    ossClient = createOssClientForUser(user);
    await ossClient.connect();
    const list = await ossClient.list(dirPath);

    const formattedList = list.map(item => ({
      name: item.name,
      type: item.type === 'd' ? 'directory' : 'file',
      size: item.size,
      sizeFormatted: formatFileSize(item.size),
      modifiedAt: new Date(item.modifyTime),
      isDirectory: item.type === 'd'
    }));

    formattedList.sort((a, b) => {
      if (a.isDirectory && !b.isDirectory) return -1;
      if (!a.isDirectory && b.isDirectory) return 1;
      return a.name.localeCompare(b.name);
    });

    res.json({
      success: true,
      username: user.username,
      path: dirPath,
      items: formattedList
    });
  } catch (error) {
    console.error('管理员查看用户文件失败:', error);
    res.status(500).json({
      success: false,
      message: '获取文件列表失败: ' + error.message
    });
  } finally {
    if (ossClient) await ossClient.end();
  }
});

// 获取所有分享（管理员）
app.get('/api/admin/shares', authMiddleware, adminMiddleware, (req, res) => {
  try {
    const shares = ShareDB.getAll();

    res.json({
      success: true,
      shares
    });
  } catch (error) {
    console.error('获取分享列表失败:', error);
    res.status(500).json({
      success: false,
      message: '获取分享列表失败: ' + error.message
    });
  }
});

// 删除分享（管理员）
app.delete('/api/admin/shares/:id',
  authMiddleware,
  adminMiddleware,
  (req, res) => {
  try {
    // 参数验证：验证 ID 格式
    const shareId = parseInt(req.params.id, 10);
    if (isNaN(shareId) || shareId <= 0) {
      return res.status(400).json({
        success: false,
        message: '无效的分享ID'
      });
    }

    // 先获取分享信息以获得share_code
    const share = ShareDB.findById(shareId);

    if (share) {
      // 删除缓存
      if (shareFileCache.has(share.share_code)) {
        shareFileCache.delete(share.share_code);
        console.log(`[缓存清除] 分享码: ${share.share_code} (管理员操作)`);
      }

      // 删除数据库记录
      ShareDB.delete(shareId);

      // 记录管理员操作日志
      logShare(req, 'admin_delete_share',
        `管理员删除分享: ${share.share_code}`,
        { shareId, shareCode: share.share_code, sharePath: share.share_path, ownerId: share.user_id }
      );

      res.json({
        success: true,
        message: '分享已删除'
      });
    } else {
      res.status(404).json({
        success: false,
        message: '分享不存在'
      });
    }
  } catch (error) {
    console.error('删除分享失败:', error);
    res.status(500).json({
      success: false,
      message: '删除分享失败: ' + error.message
    });
  }
});

// 直链访问路由（公开，直接下载）
app.get('/d/:code', async (req, res) => {
  const { code } = req.params;
  let storage;
  let storageEnded = false;
  let transferFinalized = false;
  let downloadedBytes = 0;
  let responseBodyStartSocketBytes = 0;
  let linkOwnerId = null;

  // 显式拒绝 HEAD，避免误触发计量
  if (req.method === 'HEAD') {
    res.setHeader('Allow', 'GET');
    return res.status(405).end();
  }

  const safeEndStorage = async () => {
    if (storage && !storageEnded) {
      storageEnded = true;
      try {
        await storage.end();
      } catch (err) {
        console.error('关闭存储连接失败:', err);
      }
    }
  };

  const finalizeTransfer = async (reason = '') => {
    if (transferFinalized) {
      return;
    }
    transferFinalized = true;

    try {
      if (linkOwnerId && downloadedBytes > 0) {
        const socketBytesWritten = Number(res.socket?.bytesWritten);
        const socketBodyBytes = Number.isFinite(socketBytesWritten) && socketBytesWritten > responseBodyStartSocketBytes
          ? Math.floor(socketBytesWritten - responseBodyStartSocketBytes)
          : 0;
        const billableBytes = socketBodyBytes > 0
          ? Math.min(downloadedBytes, socketBodyBytes)
          : downloadedBytes;

        const usageResult = applyDownloadTrafficUsage(linkOwnerId, billableBytes);
        if (usageResult) {
          const quotaText = usageResult.quota > 0 ? formatFileSize(usageResult.quota) : '不限';
          console.log(
            `[直链下载流量] 用户 ${linkOwnerId} 新增 ${formatFileSize(usageResult.added)}，` +
            `累计 ${formatFileSize(usageResult.usedAfter)} / ${quotaText} ` +
            `(reason=${reason || 'unknown'}, streamed=${downloadedBytes}, socket=${socketBodyBytes})`
          );
        }
      }
    } catch (error) {
      console.error(`[直链下载流量] 结算失败: user=${linkOwnerId}, bytes=${downloadedBytes}`, error);
    }

    await safeEndStorage();
  };

  if (!isValidShareCode(code)) {
    return sendPlainTextError(res, 404, '直链不存在');
  }

  try {
    const directLink = DirectLinkDB.findByCode(code);
    if (!directLink) {
      return sendPlainTextError(res, 404, '直链不存在或已过期');
    }

    const normalizedPath = normalizeVirtualPath(directLink.file_path || '');
    if (!normalizedPath) {
      return sendPlainTextError(res, 404, '直链不存在或已失效');
    }

    const ownerPolicyState = enforceDownloadTrafficPolicy(directLink.user_id, 'direct_link_download');
    const linkOwner = ownerPolicyState?.user || UserDB.findById(directLink.user_id);
    if (!linkOwner || linkOwner.is_banned) {
      return sendPlainTextError(res, 404, '直链不存在或已失效');
    }
    linkOwnerId = linkOwner.id;

    const securityResult = evaluateDownloadSecurityPolicy(req, {
      ownerUserId: linkOwner.id,
      filePath: normalizedPath,
      source: 'direct_link_download'
    });
    if (!securityResult.allowed) {
      return handleDownloadSecurityBlock(req, res, securityResult, {
        statusCode: 503,
        plainText: true,
        ownerUserId: linkOwner.id,
        filePath: normalizedPath,
        source: 'direct_link_download'
      });
    }

    const ownerTrafficState = getDownloadTrafficState(linkOwner);
    const storageType = directLink.storage_type || 'oss';
    const directFileName = (directLink.file_name && String(directLink.file_name).trim())
      ? String(directLink.file_name).trim()
      : (normalizedPath.split('/').pop() || 'download.bin');

    // OSS 直链下载：重定向到签名 URL
    if (storageType === 'oss') {
      const hasUnifiedConfig = SettingsDB.hasUnifiedOssConfig();
      if (!linkOwner.has_oss_config && !hasUnifiedConfig) {
        return sendPlainTextError(res, 404, '文件不存在或暂不可用');
      }

      const { GetObjectCommand, HeadObjectCommand } = require('@aws-sdk/client-s3');
      const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

      const { client, bucket, ossClient } = createS3ClientContextForUser(linkOwner);
      const objectKey = ossClient.getObjectKey(normalizedPath);
      let fileSize = 0;

      try {
        const headResponse = await client.send(new HeadObjectCommand({
          Bucket: bucket,
          Key: objectKey
        }));
        const contentLength = Number(headResponse?.ContentLength || 0);
        fileSize = Number.isFinite(contentLength) && contentLength > 0
          ? Math.floor(contentLength)
          : 0;
      } catch (headError) {
        const statusCode = headError?.$metadata?.httpStatusCode;
        if (headError?.name === 'NotFound' || headError?.name === 'NoSuchKey' || statusCode === 404) {
          return sendPlainTextError(res, 404, '文件不存在');
        }
        throw headError;
      }

      if (!ownerTrafficState.isUnlimited) {
        if (fileSize <= 0 || fileSize > ownerTrafficState.remaining) {
          return sendPlainTextError(res, 503, getBusyDownloadMessage());
        }

        const reserveResult = reserveDirectDownloadTraffic(linkOwner.id, fileSize, {
          source: 'direct_link',
          objectKey,
          ttlMs: DOWNLOAD_RESERVATION_TTL_MS
        });
        if (!reserveResult?.ok) {
          return sendPlainTextError(res, 503, getBusyDownloadMessage());
        }
      }

      const command = new GetObjectCommand({
        Bucket: bucket,
        Key: objectKey,
        ResponseContentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(directFileName)}`
      });

      const signedUrl = await getSignedUrl(client, command, {
        expiresIn: DOWNLOAD_SIGNED_URL_EXPIRES_SECONDS
      });
      DirectLinkDB.incrementDownloadCount(code);

      logShare(
        req,
        'direct_link_download',
        `访问直链下载: ${normalizedPath}`,
        {
          linkCode: code,
          ownerId: linkOwner.id,
          storageType: 'oss'
        }
      );

      return res.redirect(signedUrl);
    }

    // 本地存储：通过后端流式下载
    const { StorageInterface } = require('./storage');
    const userForStorage = buildStorageUserContext(linkOwner, {
      current_storage_type: storageType
    });
    const storageInterface = new StorageInterface(userForStorage);
    storage = await storageInterface.connect();

    const fileStats = await storage.stat(normalizedPath);
    const fileSize = Number(fileStats?.size || 0);

    if (fileStats?.isDirectory) {
      await safeEndStorage();
      return sendPlainTextError(res, 400, '直链仅支持文件下载');
    }

    if (!Number.isFinite(fileSize) || fileSize <= 0) {
      await safeEndStorage();
      return sendPlainTextError(res, 404, '文件不存在');
    }

    if (!ownerTrafficState.isUnlimited && fileSize > ownerTrafficState.remaining) {
      await safeEndStorage();
      return sendPlainTextError(res, 503, getBusyDownloadMessage());
    }

    DirectLinkDB.incrementDownloadCount(code);

    res.setHeader('Content-Type', 'application/octet-stream');
    res.setHeader('Content-Length', fileSize);
    res.setHeader('X-Accel-Buffering', 'no');
    res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(directFileName)}"; filename*=UTF-8''${encodeURIComponent(directFileName)}`);
    if (typeof res.flushHeaders === 'function') {
      res.flushHeaders();
    }
    responseBodyStartSocketBytes = Number(res.socket?.bytesWritten) || 0;

    const stream = await storage.createReadStream(normalizedPath);
    stream.on('data', (chunk) => {
      if (!chunk) return;
      downloadedBytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
    });

    res.on('finish', () => {
      finalizeTransfer('finish').catch(err => {
        console.error('直链下载完成后资源释放失败:', err);
      });
    });

    res.on('close', () => {
      finalizeTransfer('close').catch(err => {
        console.error('直链下载连接关闭后资源释放失败:', err);
      });
    });

    stream.on('error', (error) => {
      console.error('直链文件流错误:', error);
      if (!res.headersSent) {
        sendPlainTextError(res, 500, '下载失败，请稍后重试');
      }
      finalizeTransfer('stream_error').catch(err => {
        console.error('直链下载流错误后资源释放失败:', err);
      });
    });

    logShare(
      req,
      'direct_link_download',
      `访问直链下载: ${normalizedPath}`,
      {
        linkCode: code,
        ownerId: linkOwner.id,
        storageType: storageType || 'local'
      }
    );

    stream.pipe(res);
  } catch (error) {
    console.error('直链下载失败:', error);
    if (!res.headersSent) {
      sendPlainTextError(res, 500, '下载失败，请稍后重试');
    }
    await finalizeTransfer('catch_error');
  }
});

// 分享页面访问路由
app.get("/s/:code", (req, res) => {
  const shareCode = req.params.code;
  if (!isValidShareCode(shareCode)) {
    return res.status(404).send('分享链接不存在');
  }
  // 使用相对路径重定向，浏览器会自动使用当前的协议和host
  const frontendUrl = `/share.html?code=${shareCode}`;
  console.log(`[分享] 重定向到: ${frontendUrl}`);
  res.redirect(frontendUrl);
});

// 启动时清理旧临时文件
cleanupOldTempFiles();
const desktopCleanupOnStartup = cleanupDesktopInstallerPackages(getDesktopUpdateConfig().installerUrl);
if (desktopCleanupOnStartup.executed && desktopCleanupOnStartup.removed > 0) {
  console.log(`[桌面端更新] 启动时已清理 ${desktopCleanupOnStartup.removed} 个旧安装包`);
}

// 启动服务器
app.listen(PORT, '0.0.0.0', () => {
  console.log(`\n========================================`);
  console.log(`玩玩云已启动`);
  console.log(`服务器地址: http://localhost:${PORT}`);
  console.log(`外网访问地址: http://0.0.0.0:${PORT}`);
  console.log(`========================================\n`);
});
