@@ -78,6 +78,144 @@
+# 必看!必看 !必看!
+如果是使用KV存储想转D1存储。建议重新创建一个图床。使用系统的备份和恢复功能进行数据迁移!!!!
+
+
+ KV转D1存储详细如下
+
+- 首先确认您的 D1 数据库已经创建:数据库名称必须为: `imgbed-database` 将数据库sql语句一段一段的全部执行
+```sql
+-- CloudFlare ImgBed D1 Database Initialization Script
+-- 这个脚本用于初始化D1数据库
+
+-- 删除已存在的表(如果需要重新初始化)
+-- 注意:在生产环境中使用时请谨慎
+-- DROP TABLE IF EXISTS files;
+-- DROP TABLE IF EXISTS settings;
+-- DROP TABLE IF EXISTS index_operations;
+-- DROP TABLE IF EXISTS index_metadata;
+-- DROP TABLE IF EXISTS other_data;
+
+-- 执行主要的数据库架构创建
+-- 这里会包含 schema.sql 的内容
+
+-- 1. 文件表 - 存储文件元数据
+CREATE TABLE IF NOT EXISTS files (
+ id TEXT PRIMARY KEY,
+ value TEXT,
+ metadata TEXT NOT NULL,
+ file_name TEXT,
+ file_type TEXT,
+ file_size TEXT,
+ upload_ip TEXT,
+ upload_address TEXT,
+ list_type TEXT,
+ timestamp INTEGER,
+ label TEXT,
+ directory TEXT,
+ channel TEXT,
+ channel_name TEXT,
+ tg_file_id TEXT,
+ tg_chat_id TEXT,
+ tg_bot_token TEXT,
+ is_chunked BOOLEAN DEFAULT FALSE,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 2. 系统配置表
+CREATE TABLE IF NOT EXISTS settings (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL,
+ category TEXT,
+ description TEXT,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 3. 索引操作表
+CREATE TABLE IF NOT EXISTS index_operations (
+ id TEXT PRIMARY KEY,
+ type TEXT NOT NULL,
+ timestamp INTEGER NOT NULL,
+ data TEXT NOT NULL,
+ processed BOOLEAN DEFAULT FALSE,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 4. 索引元数据表
+CREATE TABLE IF NOT EXISTS index_metadata (
+ key TEXT PRIMARY KEY,
+ last_updated INTEGER,
+ total_count INTEGER DEFAULT 0,
+ last_operation_id TEXT,
+ chunk_count INTEGER DEFAULT 0,
+ chunk_size INTEGER DEFAULT 0,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 5. 其他数据表
+CREATE TABLE IF NOT EXISTS other_data (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL,
+ type TEXT,
+ description TEXT,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 插入初始的索引元数据
+INSERT OR REPLACE INTO index_metadata (key, last_updated, total_count, last_operation_id)
+VALUES ('main_index', 0, 0, NULL);
+
+-- 初始化完成
+-- 数据库已准备就绪,可以开始迁移数据
+
+```
+
+### 在 Cloudflare Dashboard 配置 Pages 绑定
+
+#### 步骤 A: 登录 Cloudflare Dashboard
+1. 访问 https://dash.cloudflare.com
+2. 登录您的账户
+
+#### 步骤 B: 进入 Pages 项目
+1. 在左侧菜单中点击 **"Pages"**
+2. 找到并点击您的图床项目
+
+#### 步骤 C: 配置 Functions 绑定
+1. 在项目页面中,点击 **"Settings"** 标签
+2. 在左侧菜单中点击 **"Functions"**
+3. 向下滚动找到 **"D1 database bindings"** 部分
+
+#### 步骤 D: 添加 D1 绑定
+1. 点击 **"Add binding"** 按钮
+2. 填写以下信息:
+ - **Variable name**: `DB` (必须是大写的 DB)
+ - **D1 database**: 从下拉菜单中选择您创建的 `imgbed-database`
+3. 点击 **"Save"** 按钮
+
+#### 步骤 E: 重新部署 Pages
+
+配置绑定后,需要重新部署:
+
+#### 步骤 F: 验证配置
+
+部署完成后,访问以下URL验证配置:
+
+```
+https://your-domain.com/api/manage/migrate?action=check
+```
+
+查看详细的配置状态
+```
+https://your-domain.com/api/manage/migrate?action=status
+```
+
+
+
# 1. Introduction
diff --git a/README_en.md b/README_en.md
index 08732c0..8859d97 100644
--- a/README_en.md
+++ b/README_en.md
@@ -1,9 +1,8 @@

-
🗂️Open-source file hosting solution, supporting Docker and serverless deployment, supporting multiple storage channels such as Telegram Bot, Cloudflare R2, S3, etc.
+
🗂️Open-source file hosting solution, supporting Docker and serverless deployment, supporting multiple storage channels such as Telegram Bot, Cloudflare R2, S3, etc. Modified version that replaces KV with D1 storage
- 简体中文 | English | Official Website
+ 简体中文 | English | KV Version (Original) | D1 Version | Official Website
@@ -68,6 +67,145 @@
+# Important! Important! Important!
+If you are using KV storage and want to migrate to D1 storage, it is recommended to create a new image hosting service. Use the system's backup and restore functions for data migration!!!!
+
+
+ Detailed KV to D1 Storage Migration Guide
+
+- First, confirm that your D1 database has been created: The database name must be: `imgbed-database`. Execute all SQL statements section by section:
+```sql
+-- CloudFlare ImgBed D1 Database Initialization Script
+-- This script is used to initialize the D1 database
+
+-- Drop existing tables (if re-initialization is needed)
+-- Note: Use with caution in production environment
+-- DROP TABLE IF EXISTS files;
+-- DROP TABLE IF EXISTS settings;
+-- DROP TABLE IF EXISTS index_operations;
+-- DROP TABLE IF EXISTS index_metadata;
+-- DROP TABLE IF EXISTS other_data;
+
+-- Execute main database schema creation
+-- This will include the content of schema.sql
+
+-- 1. Files table - stores file metadata
+CREATE TABLE IF NOT EXISTS files (
+ id TEXT PRIMARY KEY,
+ value TEXT,
+ metadata TEXT NOT NULL,
+ file_name TEXT,
+ file_type TEXT,
+ file_size TEXT,
+ upload_ip TEXT,
+ upload_address TEXT,
+ list_type TEXT,
+ timestamp INTEGER,
+ label TEXT,
+ directory TEXT,
+ channel TEXT,
+ channel_name TEXT,
+ tg_file_id TEXT,
+ tg_chat_id TEXT,
+ tg_bot_token TEXT,
+ is_chunked BOOLEAN DEFAULT FALSE,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 2. System configuration table
+CREATE TABLE IF NOT EXISTS settings (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL,
+ category TEXT,
+ description TEXT,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 3. Index operations table
+CREATE TABLE IF NOT EXISTS index_operations (
+ id TEXT PRIMARY KEY,
+ type TEXT NOT NULL,
+ timestamp INTEGER NOT NULL,
+ data TEXT NOT NULL,
+ processed BOOLEAN DEFAULT FALSE,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 4. Index metadata table
+CREATE TABLE IF NOT EXISTS index_metadata (
+ key TEXT PRIMARY KEY,
+ last_updated INTEGER,
+ total_count INTEGER DEFAULT 0,
+ last_operation_id TEXT,
+ chunk_count INTEGER DEFAULT 0,
+ chunk_size INTEGER DEFAULT 0,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 5. Other data table
+CREATE TABLE IF NOT EXISTS other_data (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL,
+ type TEXT,
+ description TEXT,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+);
+
+-- Insert initial index metadata
+INSERT OR REPLACE INTO index_metadata (key, last_updated, total_count, last_operation_id)
+VALUES ('main_index', 0, 0, NULL);
+
+-- Initialization complete
+-- Database is ready, data migration can begin
+
+```
+
+### Configure Pages Bindings in Cloudflare Dashboard
+
+#### Step A: Login to Cloudflare Dashboard
+1. Visit https://dash.cloudflare.com
+2. Login to your account
+
+#### Step B: Enter Pages Project
+1. Click **"Pages"** in the left menu
+2. Find and click your image hosting project
+
+#### Step C: Configure Functions Bindings
+1. Click the **"Settings"** tab on the project page
+2. Click **"Functions"** in the left menu
+3. Scroll down to find the **"D1 database bindings"** section
+
+#### Step D: Add D1 Binding
+1. Click the **"Add binding"** button
+2. Fill in the following information:
+ - **Variable name**: `DB` (must be uppercase DB)
+ - **D1 database**: Select your created `imgbed-database` from the dropdown
+3. Click the **"Save"** button
+
+#### Step E: Redeploy Pages
+
+After configuring bindings, you need to redeploy:
+
+#### Step F: Verify Configuration
+
+After deployment is complete, visit the following URL to verify configuration:
+
+```
+https://your-domain.com/api/manage/migrate?action=check
+```
+
+View detailed configuration status:
+```
+https://your-domain.com/api/manage/migrate?action=status
+```
+
+
+
+
# 1. Introduction
diff --git a/functions/utils/indexManager.js b/functions/utils/indexManager.js
index 63e1993..1f9cdc9 100644
--- a/functions/utils/indexManager.js
+++ b/functions/utils/indexManager.js
@@ -447,12 +447,46 @@ export async function readIndex(context, options = {}) {
// 处理目录满足无头有尾的格式,根目录为空
const dirPrefix = directory === '' || directory.endsWith('/') ? directory : directory + '/';
- // 处理挂起的操作
- await mergeOperationsToIndex(context);
+ // 直接从数据库读取文件,不依赖索引
+ const { env } = context;
+ const db = getDatabase(env);
- // 获取当前索引
- const index = await getIndex(context);
- let filteredFiles = index.files;
+ let allFiles = [];
+ let cursor = null;
+
+ // 分批获取所有文件
+ while (true) {
+ const response = await db.listFiles({
+ limit: 1000,
+ cursor: cursor
+ });
+
+ if (!response || !response.keys || !Array.isArray(response.keys)) {
+ break;
+ }
+
+ for (const item of response.keys) {
+ // 跳过管理相关的键和分块数据
+ if (item.name.startsWith('manage@') || item.name.startsWith('chunk_')) {
+ continue;
+ }
+
+ // 跳过没有元数据的文件
+ if (!item.metadata || !item.metadata.TimeStamp) {
+ continue;
+ }
+
+ allFiles.push({
+ id: item.name,
+ metadata: item.metadata
+ });
+ }
+
+ cursor = response.cursor;
+ if (!cursor) break;
+ }
+
+ let filteredFiles = allFiles;
// 目录过滤
if (directory) {
@@ -531,9 +565,9 @@ export async function readIndex(context, options = {}) {
files: resultFiles,
directories: Array.from(directories),
totalCount: totalCount,
- indexLastUpdated: index.lastUpdated,
+ indexLastUpdated: Date.now(),
returnedCount: resultFiles.length,
- success: index.success ?? true
+ success: true
};
} catch (error) {
@@ -643,23 +677,65 @@ export async function rebuildIndex(context, progressCallback = null) {
*/
export async function getIndexInfo(context) {
try {
- const index = await getIndex(context);
+ // 直接从数据库读取文件信息,不依赖索引
+ const { env } = context;
+ const db = getDatabase(env);
- // 检查索引是否成功获取
- if (index.success === false) {
- return {
- success: false,
- error: 'Failed to retrieve index',
- message: 'Index is not available or corrupted'
+ let allFiles = [];
+ let cursor = null;
+
+ // 分批获取所有文件
+ while (true) {
+ const response = await db.listFiles({
+ limit: 1000,
+ cursor: cursor
+ });
+
+ if (!response || !response.keys || !Array.isArray(response.keys)) {
+ break;
}
+
+ for (const item of response.keys) {
+ // 跳过管理相关的键和分块数据
+ if (item.name.startsWith('manage@') || item.name.startsWith('chunk_')) {
+ continue;
+ }
+
+ // 跳过没有元数据的文件
+ if (!item.metadata || !item.metadata.TimeStamp) {
+ continue;
+ }
+
+ allFiles.push({
+ id: item.name,
+ metadata: item.metadata
+ });
+ }
+
+ cursor = response.cursor;
+ if (!cursor) break;
+ }
+
+ // 如果没有文件,返回空结果
+ if (allFiles.length === 0) {
+ return {
+ success: true,
+ totalFiles: 0,
+ lastUpdated: Date.now(),
+ channelStats: {},
+ directoryStats: {},
+ typeStats: {},
+ oldestFile: null,
+ newestFile: null
+ };
}
// 统计各渠道文件数量
const channelStats = {};
const directoryStats = {};
const typeStats = {};
-
- index.files.forEach(file => {
+
+ allFiles.forEach(file => {
// 渠道统计
let channel = file.metadata.Channel || 'Telegraph';
if (channel === 'TelegramNew') {
@@ -680,15 +756,31 @@ export async function getIndexInfo(context) {
typeStats[listType] = (typeStats[listType] || 0) + 1;
});
+ // 找到最新和最旧的文件
+ let oldestFile = null;
+ let newestFile = null;
+
+ if (allFiles.length > 0) {
+ // 按时间戳排序
+ const sortedFiles = [...allFiles].sort((a, b) => {
+ const timeA = a.metadata.TimeStamp || 0;
+ const timeB = b.metadata.TimeStamp || 0;
+ return timeA - timeB;
+ });
+
+ oldestFile = sortedFiles[0];
+ newestFile = sortedFiles[sortedFiles.length - 1];
+ }
+
return {
success: true,
- totalFiles: index.totalCount,
- lastUpdated: index.lastUpdated,
+ totalFiles: allFiles.length,
+ lastUpdated: Date.now(),
channelStats,
directoryStats,
typeStats,
- oldestFile: index.files[index.files.length - 1],
- newestFile: index.files[0]
+ oldestFile,
+ newestFile
};
} catch (error) {
console.error('Error getting index info:', error);