feat: Add Docker Compose configurations for Kvrocks and Redis deployments
- Implemented `docker-compose.kvrocks.auth.yml` for Kvrocks with password authentication. - Created `docker-compose.redis.yml` for Redis deployment. - Added Kvrocks configuration file `kvrocks.auth.conf` with necessary settings. - Updated documentation with deployment guidelines for Kvrocks. - Introduced ESLint configuration for code quality. - Developed deployment configuration check script `check-deployment-configs.js`. - Added D1 database initialization script `d1-init.sql` for KatelyaTV. - Created test script `test-kvrocks-deployment.js` to validate Kvrocks deployment. - Implemented fix verification script `verify-kvrocks-fix.js` for password handling. - Updated `wrangler.toml` for Cloudflare deployment configuration.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
es6: true,
|
||||
},
|
||||
extends: ['eslint:recommended'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'no-console': 'off', // 允许在脚本中使用 console
|
||||
'no-unused-vars': 'off', // 暂时忽略未使用变量
|
||||
'@typescript-eslint/no-var-requires': 'off', // 允许 require
|
||||
'import/no-import-module-exports': 'off',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* KatelyaTV 全方案部署状态检查脚本
|
||||
* 检查所有部署方案的配置文件和环境是否完整
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('🔍 KatelyaTV 部署配置检查开始...\n');
|
||||
|
||||
// 检查结果统计
|
||||
let checkResults = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
warnings: 0,
|
||||
errors: []
|
||||
};
|
||||
|
||||
// 辅助函数
|
||||
function logCheck(name, status, message = '') {
|
||||
checkResults.total++;
|
||||
if (status === 'PASS') {
|
||||
checkResults.passed++;
|
||||
console.log(`✅ ${name}: PASS ${message}`);
|
||||
} else if (status === 'WARN') {
|
||||
checkResults.warnings++;
|
||||
console.log(`⚠️ ${name}: WARN ${message}`);
|
||||
} else {
|
||||
checkResults.failed++;
|
||||
console.log(`❌ ${name}: FAIL ${message}`);
|
||||
checkResults.errors.push(`${name}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function fileExists(filePath) {
|
||||
try {
|
||||
return fs.existsSync(filePath);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function readJsonFile(filePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查1:Docker 部署配置
|
||||
function checkDockerConfigs() {
|
||||
console.log('🐳 检查 Docker 部署配置...');
|
||||
|
||||
const dockerConfigs = [
|
||||
{
|
||||
name: 'Docker + Redis 配置',
|
||||
files: ['docker-compose.redis.yml', '.env.redis.example']
|
||||
},
|
||||
{
|
||||
name: 'Docker + Kvrocks 配置(无密码)',
|
||||
files: ['docker-compose.kvrocks.yml', '.env.kvrocks.example']
|
||||
},
|
||||
{
|
||||
name: 'Docker + Kvrocks 配置(密码认证)',
|
||||
files: ['docker-compose.kvrocks.auth.yml']
|
||||
},
|
||||
{
|
||||
name: 'Docker + Kvrocks 本地构建配置',
|
||||
files: ['docker-compose.kvrocks.local.yml']
|
||||
}
|
||||
];
|
||||
|
||||
for (const config of dockerConfigs) {
|
||||
let allFilesExist = true;
|
||||
let missingFiles = [];
|
||||
|
||||
for (const file of config.files) {
|
||||
if (!fileExists(file)) {
|
||||
allFilesExist = false;
|
||||
missingFiles.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (allFilesExist) {
|
||||
logCheck(config.name, 'PASS', '所有配置文件存在');
|
||||
} else {
|
||||
logCheck(config.name, 'FAIL', `缺失文件: ${missingFiles.join(', ')}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查2:Cloudflare 部署配置
|
||||
function checkCloudflareConfigs() {
|
||||
console.log('\n☁️ 检查 Cloudflare 部署配置...');
|
||||
|
||||
const cloudflareFiles = [
|
||||
'wrangler.toml',
|
||||
'.env.cloudflare.example',
|
||||
'scripts/d1-init.sql'
|
||||
];
|
||||
|
||||
for (const file of cloudflareFiles) {
|
||||
if (fileExists(file)) {
|
||||
logCheck(`Cloudflare 配置文件 ${file}`, 'PASS', '文件存在');
|
||||
} else {
|
||||
logCheck(`Cloudflare 配置文件 ${file}`, 'FAIL', '文件不存在');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 wrangler.toml 内容
|
||||
if (fileExists('wrangler.toml')) {
|
||||
const content = fs.readFileSync('wrangler.toml', 'utf8');
|
||||
if (content.includes('d1_databases') && content.includes('pages:build')) {
|
||||
logCheck('wrangler.toml 内容', 'PASS', '包含必要配置');
|
||||
} else {
|
||||
logCheck('wrangler.toml 内容', 'WARN', '可能缺少部分配置');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查3:Vercel 部署配置
|
||||
function checkVercelConfigs() {
|
||||
console.log('\n▲ 检查 Vercel 部署配置...');
|
||||
|
||||
const vercelFile = 'vercel.json';
|
||||
if (fileExists(vercelFile)) {
|
||||
logCheck('Vercel 配置文件', 'PASS', 'vercel.json 存在');
|
||||
|
||||
const vercelConfig = readJsonFile(vercelFile);
|
||||
if (vercelConfig) {
|
||||
if (vercelConfig.build && vercelConfig.build.env) {
|
||||
logCheck('Vercel 构建配置', 'PASS', '包含环境变量配置');
|
||||
} else {
|
||||
logCheck('Vercel 构建配置', 'WARN', '可能缺少构建环境配置');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logCheck('Vercel 配置文件', 'FAIL', 'vercel.json 不存在');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查4:环境变量示例文件
|
||||
function checkEnvExamples() {
|
||||
console.log('\n⚙️ 检查环境变量示例文件...');
|
||||
|
||||
const envFiles = [
|
||||
'.env.example',
|
||||
'.env.redis.example',
|
||||
'.env.kvrocks.example',
|
||||
'.env.cloudflare.example'
|
||||
];
|
||||
|
||||
for (const envFile of envFiles) {
|
||||
if (fileExists(envFile)) {
|
||||
const content = fs.readFileSync(envFile, 'utf8');
|
||||
const hasStorageType = content.includes('NEXT_PUBLIC_STORAGE_TYPE');
|
||||
const hasAuthConfig = content.includes('NEXTAUTH_SECRET');
|
||||
|
||||
if (hasStorageType && hasAuthConfig) {
|
||||
logCheck(`环境变量文件 ${envFile}`, 'PASS', '包含必要配置');
|
||||
} else {
|
||||
logCheck(`环境变量文件 ${envFile}`, 'WARN', '可能缺少部分配置');
|
||||
}
|
||||
} else {
|
||||
logCheck(`环境变量文件 ${envFile}`, 'FAIL', '文件不存在');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查5:package.json 脚本
|
||||
function checkPackageScripts() {
|
||||
console.log('\n📦 检查 package.json 构建脚本...');
|
||||
|
||||
const packageJson = readJsonFile('package.json');
|
||||
if (packageJson && packageJson.scripts) {
|
||||
const requiredScripts = [
|
||||
'dev',
|
||||
'build',
|
||||
'start',
|
||||
'pages:build', // Cloudflare Pages
|
||||
'lint'
|
||||
];
|
||||
|
||||
for (const script of requiredScripts) {
|
||||
if (packageJson.scripts[script]) {
|
||||
logCheck(`package.json 脚本 ${script}`, 'PASS', '脚本存在');
|
||||
} else {
|
||||
logCheck(`package.json 脚本 ${script}`, 'WARN', '脚本不存在或未配置');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logCheck('package.json', 'FAIL', '文件不存在或格式错误');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查6:Kvrocks 配置文件
|
||||
function checkKvrocksConfigs() {
|
||||
console.log('\n🏪 检查 Kvrocks 配置文件...');
|
||||
|
||||
const kvrocksConfigs = [
|
||||
'docker/kvrocks/kvrocks.conf',
|
||||
'docker/kvrocks/kvrocks.auth.conf'
|
||||
];
|
||||
|
||||
for (const configFile of kvrocksConfigs) {
|
||||
if (fileExists(configFile)) {
|
||||
const content = fs.readFileSync(configFile, 'utf8');
|
||||
const hasBasicConfig = content.includes('bind') && content.includes('port');
|
||||
|
||||
if (hasBasicConfig) {
|
||||
logCheck(`Kvrocks 配置 ${path.basename(configFile)}`, 'PASS', '包含基本配置');
|
||||
} else {
|
||||
logCheck(`Kvrocks 配置 ${path.basename(configFile)}`, 'WARN', '可能缺少基本配置');
|
||||
}
|
||||
} else {
|
||||
logCheck(`Kvrocks 配置 ${path.basename(configFile)}`, 'FAIL', '文件不存在');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查7:文档文件
|
||||
function checkDocumentation() {
|
||||
console.log('\n📚 检查文档文件...');
|
||||
|
||||
const docFiles = [
|
||||
'README.md',
|
||||
'docs/KVROCKS.md',
|
||||
'docs/KVROCKS_DEPLOYMENT.md',
|
||||
'docs/TVBOX.md',
|
||||
'KVROCKS_FIX_REPORT.md'
|
||||
];
|
||||
|
||||
for (const docFile of docFiles) {
|
||||
if (fileExists(docFile)) {
|
||||
logCheck(`文档文件 ${docFile}`, 'PASS', '文件存在');
|
||||
} else {
|
||||
logCheck(`文档文件 ${docFile}`, 'WARN', '文件不存在');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主检查函数
|
||||
async function runChecks() {
|
||||
try {
|
||||
await checkDockerConfigs();
|
||||
await checkCloudflareConfigs();
|
||||
await checkVercelConfigs();
|
||||
await checkEnvExamples();
|
||||
await checkPackageScripts();
|
||||
await checkKvrocksConfigs();
|
||||
await checkDocumentation();
|
||||
|
||||
} catch (error) {
|
||||
console.error('检查执行出错:', error);
|
||||
checkResults.failed++;
|
||||
checkResults.errors.push(`检查执行出错: ${error.message}`);
|
||||
}
|
||||
|
||||
// 输出检查结果
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 部署配置检查结果汇总:');
|
||||
console.log(` 总计: ${checkResults.total} 项检查`);
|
||||
console.log(` 通过: ${checkResults.passed} 项 ✅`);
|
||||
console.log(` 警告: ${checkResults.warnings} 项 ⚠️`);
|
||||
console.log(` 失败: ${checkResults.failed} 项 ❌`);
|
||||
|
||||
if (checkResults.failed > 0) {
|
||||
console.log('\n🚨 失败的检查项:');
|
||||
checkResults.errors.forEach((error, index) => {
|
||||
console.log(` ${index + 1}. ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (checkResults.warnings > 0) {
|
||||
console.log('\n⚠️ 警告说明:');
|
||||
console.log(' - 警告项目不影响基本功能,但建议完善');
|
||||
console.log(' - 可能影响特定部署方案或高级功能');
|
||||
}
|
||||
|
||||
if (checkResults.failed === 0) {
|
||||
console.log('\n🎉 所有必要配置文件检查通过!');
|
||||
console.log(' 您可以选择以下任意部署方案:');
|
||||
console.log(' 1. 🐳 Docker + Redis (docker-compose.redis.yml)');
|
||||
console.log(' 2. 🏪 Docker + Kvrocks (docker-compose.kvrocks.yml)');
|
||||
console.log(' 3. ☁️ Cloudflare Pages + D1 (wrangler.toml)');
|
||||
console.log(' 4. ▲ Vercel + Upstash (vercel.json)');
|
||||
}
|
||||
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 退出代码
|
||||
process.exit(checkResults.failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
runChecks().catch(console.error);
|
||||
@@ -0,0 +1,109 @@
|
||||
-- D1 数据库初始化脚本
|
||||
-- 用于创建 KatelyaTV 所需的数据表
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 播放记录表
|
||||
CREATE TABLE IF NOT EXISTS play_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
record_key TEXT NOT NULL,
|
||||
video_url TEXT,
|
||||
current_time REAL DEFAULT 0,
|
||||
duration REAL DEFAULT 0,
|
||||
episode_index INTEGER DEFAULT 0,
|
||||
episode_url TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE (user_id, record_key)
|
||||
);
|
||||
|
||||
-- 收藏表
|
||||
CREATE TABLE IF NOT EXISTS favorites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
favorite_key TEXT NOT NULL,
|
||||
title TEXT,
|
||||
cover_url TEXT,
|
||||
rating REAL,
|
||||
year TEXT,
|
||||
area TEXT,
|
||||
category TEXT,
|
||||
actors TEXT,
|
||||
director TEXT,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE (user_id, favorite_key)
|
||||
);
|
||||
|
||||
-- 搜索历史表
|
||||
CREATE TABLE IF NOT EXISTS search_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
keyword TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 跳过配置表
|
||||
CREATE TABLE IF NOT EXISTS skip_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
config_key TEXT NOT NULL,
|
||||
start_time INTEGER DEFAULT 0,
|
||||
end_time INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE (user_id, config_key)
|
||||
);
|
||||
|
||||
-- 管理员配置表
|
||||
CREATE TABLE IF NOT EXISTS admin_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
config_key TEXT UNIQUE NOT NULL,
|
||||
config_value TEXT,
|
||||
description TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 插入默认管理员配置
|
||||
INSERT OR IGNORE INTO admin_configs (config_key, config_value, description) VALUES
|
||||
('site_name', 'KatelyaTV', '站点名称'),
|
||||
('site_description', '高性能影视播放平台', '站点描述'),
|
||||
('enable_register', 'true', '是否允许用户注册'),
|
||||
('max_users', '100', '最大用户数量'),
|
||||
('cache_ttl', '3600', '缓存时间(秒)');
|
||||
|
||||
-- 创建索引以提高查询性能
|
||||
CREATE INDEX IF NOT EXISTS idx_play_records_user_id ON play_records(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_play_records_record_key ON play_records(record_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_favorites_user_id ON favorites(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_search_history_user_id ON search_history(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_skip_configs_user_id ON skip_configs(user_id);
|
||||
|
||||
-- 创建视图以简化查询
|
||||
CREATE VIEW IF NOT EXISTS user_stats AS
|
||||
SELECT
|
||||
u.id,
|
||||
u.username,
|
||||
COUNT(DISTINCT pr.id) as play_count,
|
||||
COUNT(DISTINCT f.id) as favorite_count,
|
||||
COUNT(DISTINCT sh.id) as search_count,
|
||||
u.created_at
|
||||
FROM users u
|
||||
LEFT JOIN play_records pr ON u.id = pr.user_id
|
||||
LEFT JOIN favorites f ON u.id = f.user_id
|
||||
LEFT JOIN search_history sh ON u.id = sh.user_id
|
||||
GROUP BY u.id, u.username, u.created_at;
|
||||
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Kvrocks 部署测试脚本
|
||||
* 用于验证 Docker + Kvrocks 部署是否正常工作
|
||||
*/
|
||||
|
||||
const { createClient } = require('redis');
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
|
||||
// 配置
|
||||
const TEST_CONFIG = {
|
||||
KVROCKS_URL: process.env.KVROCKS_URL || 'redis://localhost:6666',
|
||||
KVROCKS_PASSWORD: process.env.KVROCKS_PASSWORD,
|
||||
KVROCKS_DATABASE: parseInt(process.env.KVROCKS_DATABASE || '0'),
|
||||
TEST_TIMEOUT: 30000, // 30秒超时
|
||||
};
|
||||
|
||||
console.log('🧪 Kvrocks 部署测试开始...\n');
|
||||
|
||||
// 测试结果统计
|
||||
let testResults = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
errors: []
|
||||
};
|
||||
|
||||
// 辅助函数
|
||||
function logTest(name, status, message = '') {
|
||||
testResults.total++;
|
||||
if (status === 'PASS') {
|
||||
testResults.passed++;
|
||||
console.log(`✅ ${name}: PASS ${message}`);
|
||||
} else {
|
||||
testResults.failed++;
|
||||
console.log(`❌ ${name}: FAIL ${message}`);
|
||||
testResults.errors.push(`${name}: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试1:检查 Docker Compose 文件
|
||||
async function testDockerComposeFiles() {
|
||||
console.log('📁 测试 Docker Compose 配置文件...');
|
||||
|
||||
const files = [
|
||||
'docker-compose.kvrocks.yml',
|
||||
'docker-compose.kvrocks.auth.yml'
|
||||
];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
if (fs.existsSync(file)) {
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
if (content.includes('kvrocks:') && content.includes('katelyatv:')) {
|
||||
logTest(`Docker Compose 文件 ${file}`, 'PASS', '配置正确');
|
||||
} else {
|
||||
logTest(`Docker Compose 文件 ${file}`, 'FAIL', '配置缺失');
|
||||
}
|
||||
} else {
|
||||
logTest(`Docker Compose 文件 ${file}`, 'FAIL', '文件不存在');
|
||||
}
|
||||
} catch (error) {
|
||||
logTest(`Docker Compose 文件 ${file}`, 'FAIL', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试2:检查环境变量配置
|
||||
async function testEnvironmentConfig() {
|
||||
console.log('\n🔧 测试环境变量配置...');
|
||||
|
||||
// 检查必需的环境变量
|
||||
const requiredVars = ['NEXT_PUBLIC_STORAGE_TYPE'];
|
||||
const optionalVars = ['KVROCKS_PASSWORD', 'NEXTAUTH_SECRET'];
|
||||
|
||||
for (const varName of requiredVars) {
|
||||
if (process.env[varName]) {
|
||||
logTest(`环境变量 ${varName}`, 'PASS', `值: ${process.env[varName]}`);
|
||||
} else {
|
||||
logTest(`环境变量 ${varName}`, 'FAIL', '未设置');
|
||||
}
|
||||
}
|
||||
|
||||
for (const varName of optionalVars) {
|
||||
if (process.env[varName]) {
|
||||
logTest(`环境变量 ${varName}`, 'PASS', '已设置');
|
||||
} else {
|
||||
logTest(`环境变量 ${varName}`, 'PASS', '未设置(可选)');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查存储类型
|
||||
if (process.env.NEXT_PUBLIC_STORAGE_TYPE === 'kvrocks') {
|
||||
logTest('存储类型配置', 'PASS', 'kvrocks');
|
||||
} else {
|
||||
logTest('存储类型配置', 'FAIL', `期望 kvrocks,实际 ${process.env.NEXT_PUBLIC_STORAGE_TYPE}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 测试3:Kvrocks 连接测试
|
||||
async function testKvrocksConnection() {
|
||||
console.log('\n🔌 测试 Kvrocks 连接...');
|
||||
|
||||
let client;
|
||||
try {
|
||||
// 构建客户端配置
|
||||
const clientConfig = {
|
||||
url: TEST_CONFIG.KVROCKS_URL,
|
||||
database: TEST_CONFIG.KVROCKS_DATABASE,
|
||||
socket: {
|
||||
connectTimeout: 5000,
|
||||
},
|
||||
};
|
||||
|
||||
// 只有当密码存在且不为空时才添加密码配置
|
||||
if (TEST_CONFIG.KVROCKS_PASSWORD && TEST_CONFIG.KVROCKS_PASSWORD.trim() !== '') {
|
||||
clientConfig.password = TEST_CONFIG.KVROCKS_PASSWORD;
|
||||
console.log('🔐 使用密码认证连接');
|
||||
} else {
|
||||
console.log('🔓 无密码认证连接');
|
||||
}
|
||||
|
||||
client = createClient(clientConfig);
|
||||
|
||||
// 连接
|
||||
await client.connect();
|
||||
logTest('Kvrocks 连接', 'PASS', '连接成功');
|
||||
|
||||
// 测试 PING
|
||||
const pong = await client.ping();
|
||||
if (pong === 'PONG') {
|
||||
logTest('Kvrocks PING', 'PASS', 'PONG');
|
||||
} else {
|
||||
logTest('Kvrocks PING', 'FAIL', `响应: ${pong}`);
|
||||
}
|
||||
|
||||
// 测试基本操作
|
||||
const testKey = 'test:' + Date.now();
|
||||
const testValue = 'test-value-' + Math.random();
|
||||
|
||||
await client.set(testKey, testValue);
|
||||
const getValue = await client.get(testKey);
|
||||
|
||||
if (getValue === testValue) {
|
||||
logTest('Kvrocks 读写操作', 'PASS', '数据一致');
|
||||
} else {
|
||||
logTest('Kvrocks 读写操作', 'FAIL', `期望 ${testValue},实际 ${getValue}`);
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
await client.del(testKey);
|
||||
|
||||
// 测试数据库信息
|
||||
const info = await client.info();
|
||||
if (info.includes('kvrocks_version')) {
|
||||
const version = info.match(/kvrocks_version:([^\r\n]+)/)?.[1];
|
||||
logTest('Kvrocks 版本信息', 'PASS', `版本: ${version}`);
|
||||
} else {
|
||||
logTest('Kvrocks 版本信息', 'FAIL', '无法获取版本信息');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logTest('Kvrocks 连接', 'FAIL', error.message);
|
||||
} finally {
|
||||
if (client && client.isOpen) {
|
||||
await client.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试4:Docker 服务状态检查
|
||||
async function testDockerServices() {
|
||||
console.log('\n🐳 测试 Docker 服务状态...');
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const docker = spawn('docker-compose', ['ps'], { stdio: 'pipe' });
|
||||
let output = '';
|
||||
|
||||
docker.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
docker.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
if (output.includes('kvrocks') && output.includes('Up')) {
|
||||
logTest('Docker Kvrocks 服务', 'PASS', '服务运行中');
|
||||
} else {
|
||||
logTest('Docker Kvrocks 服务', 'FAIL', '服务未运行');
|
||||
}
|
||||
|
||||
if (output.includes('katelyatv') && output.includes('Up')) {
|
||||
logTest('Docker KatelyaTV 服务', 'PASS', '服务运行中');
|
||||
} else {
|
||||
logTest('Docker KatelyaTV 服务', 'FAIL', '服务未运行或未启动');
|
||||
}
|
||||
} else {
|
||||
logTest('Docker 服务检查', 'FAIL', 'docker-compose 命令执行失败');
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
docker.on('error', (error) => {
|
||||
logTest('Docker 服务检查', 'FAIL', `Docker 未安装或不可用: ${error.message}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTests() {
|
||||
console.log(`🏗️ 测试配置:`);
|
||||
console.log(` Kvrocks URL: ${TEST_CONFIG.KVROCKS_URL}`);
|
||||
console.log(` 密码认证: ${TEST_CONFIG.KVROCKS_PASSWORD ? '是' : '否'}`);
|
||||
console.log(` 数据库: ${TEST_CONFIG.KVROCKS_DATABASE}`);
|
||||
console.log('');
|
||||
|
||||
try {
|
||||
await testDockerComposeFiles();
|
||||
await testEnvironmentConfig();
|
||||
await testDockerServices();
|
||||
await testKvrocksConnection();
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试执行出错:', error);
|
||||
testResults.failed++;
|
||||
testResults.errors.push(`测试执行出错: ${error.message}`);
|
||||
}
|
||||
|
||||
// 输出测试结果
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('📊 测试结果汇总:');
|
||||
console.log(` 总计: ${testResults.total} 项测试`);
|
||||
console.log(` 通过: ${testResults.passed} 项 ✅`);
|
||||
console.log(` 失败: ${testResults.failed} 项 ❌`);
|
||||
|
||||
if (testResults.failed > 0) {
|
||||
console.log('\n🚨 失败的测试项:');
|
||||
testResults.errors.forEach((error, index) => {
|
||||
console.log(` ${index + 1}. ${error}`);
|
||||
});
|
||||
|
||||
console.log('\n💡 解决建议:');
|
||||
console.log(' 1. 检查 Docker 服务是否正常启动');
|
||||
console.log(' 2. 验证环境变量配置是否正确');
|
||||
console.log(' 3. 确认网络连接是否正常');
|
||||
console.log(' 4. 查看详细部署指南: docs/KVROCKS_DEPLOYMENT.md');
|
||||
} else {
|
||||
console.log('\n🎉 所有测试通过!Kvrocks 部署正常工作。');
|
||||
}
|
||||
|
||||
console.log('='.repeat(50));
|
||||
|
||||
// 退出代码
|
||||
process.exit(testResults.failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTests().catch(console.error);
|
||||
@@ -0,0 +1,122 @@
|
||||
/* eslint-disable no-console, @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
* 验证 Kvrocks 密码处理修复
|
||||
* 模拟用户反馈的错误场景
|
||||
*/
|
||||
|
||||
// 模拟用户的环境变量设置
|
||||
process.env.NEXT_PUBLIC_STORAGE_TYPE = 'kvrocks';
|
||||
process.env.KVROCKS_URL = 'redis://kvrocks:6666';
|
||||
process.env.KVROCKS_PASSWORD = ''; // 用户设置了空密码,这是问题所在
|
||||
process.env.KVROCKS_DATABASE = '0';
|
||||
|
||||
// 模拟 Redis 客户端创建函数
|
||||
function createClient(config) {
|
||||
console.log('🔧 创建 Redis 客户端配置:', JSON.stringify(config, null, 2));
|
||||
|
||||
if (config.password === '') {
|
||||
console.log('❌ 检测到空密码,这会导致认证错误!');
|
||||
return {
|
||||
connect: () => Promise.reject(new Error('ERR Client sent AUTH, but no password is set')),
|
||||
isOpen: false
|
||||
};
|
||||
} else if (config.password === undefined) {
|
||||
console.log('✅ 无密码配置,正常连接');
|
||||
return {
|
||||
connect: () => Promise.resolve(),
|
||||
isOpen: true
|
||||
};
|
||||
} else {
|
||||
console.log('✅ 有效密码配置,正常连接');
|
||||
return {
|
||||
connect: () => Promise.resolve(),
|
||||
isOpen: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 使用修复后的客户端创建逻辑
|
||||
function getKvrocksClient() {
|
||||
const kvrocksUrl = process.env.KVROCKS_URL || 'redis://localhost:6666';
|
||||
const kvrocksPassword = process.env.KVROCKS_PASSWORD;
|
||||
const kvrocksDatabase = parseInt(process.env.KVROCKS_DATABASE || '0');
|
||||
|
||||
console.log('🏪 Initializing Kvrocks client...');
|
||||
console.log('🔗 Kvrocks URL:', kvrocksUrl);
|
||||
console.log('🔑 Password configured:', kvrocksPassword ? 'Yes' : 'No');
|
||||
console.log('🔑 Password value:', JSON.stringify(kvrocksPassword));
|
||||
|
||||
// 构建客户端配置
|
||||
const clientConfig = {
|
||||
url: kvrocksUrl,
|
||||
database: kvrocksDatabase,
|
||||
socket: {
|
||||
connectTimeout: 10000,
|
||||
},
|
||||
};
|
||||
|
||||
// 只有当密码存在且不为空时才添加密码配置
|
||||
if (kvrocksPassword && kvrocksPassword.trim() !== '') {
|
||||
clientConfig.password = kvrocksPassword;
|
||||
console.log('🔐 Using password authentication');
|
||||
} else {
|
||||
console.log('🔓 No password authentication (connecting without password)');
|
||||
}
|
||||
|
||||
return createClient(clientConfig);
|
||||
}
|
||||
|
||||
async function testScenarios() {
|
||||
console.log('🧪 测试不同密码配置场景\n');
|
||||
|
||||
// 场景1:用户的问题场景 - 空字符串密码
|
||||
console.log('📝 场景1:用户问题场景(空字符串密码)');
|
||||
console.log('环境变量: KVROCKS_PASSWORD=""');
|
||||
process.env.KVROCKS_PASSWORD = '';
|
||||
try {
|
||||
const client = getKvrocksClient();
|
||||
await client.connect();
|
||||
console.log('✅ 场景1通过:无认证错误\n');
|
||||
} catch (error) {
|
||||
console.log('❌ 场景1失败:', error.message, '\n');
|
||||
}
|
||||
|
||||
// 场景2:未设置密码
|
||||
console.log('📝 场景2:未设置密码');
|
||||
console.log('环境变量: KVROCKS_PASSWORD=undefined');
|
||||
delete process.env.KVROCKS_PASSWORD;
|
||||
try {
|
||||
const client = getKvrocksClient();
|
||||
await client.connect();
|
||||
console.log('✅ 场景2通过:无认证错误\n');
|
||||
} catch (error) {
|
||||
console.log('❌ 场景2失败:', error.message, '\n');
|
||||
}
|
||||
|
||||
// 场景3:有效密码
|
||||
console.log('📝 场景3:有效密码');
|
||||
console.log('环境变量: KVROCKS_PASSWORD="validpassword"');
|
||||
process.env.KVROCKS_PASSWORD = 'validpassword';
|
||||
try {
|
||||
const client = getKvrocksClient();
|
||||
await client.connect();
|
||||
console.log('✅ 场景3通过:正常密码认证\n');
|
||||
} catch (error) {
|
||||
console.log('❌ 场景3失败:', error.message, '\n');
|
||||
}
|
||||
|
||||
// 场景4:只有空格的密码
|
||||
console.log('📝 场景4:只有空格的密码');
|
||||
console.log('环境变量: KVROCKS_PASSWORD=" "');
|
||||
process.env.KVROCKS_PASSWORD = ' ';
|
||||
try {
|
||||
const client = getKvrocksClient();
|
||||
await client.connect();
|
||||
console.log('✅ 场景4通过:空格密码被正确处理\n');
|
||||
} catch (error) {
|
||||
console.log('❌ 场景4失败:', error.message, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
testScenarios().catch(console.error);
|
||||
Reference in New Issue
Block a user