feat: refactor config

This commit is contained in:
shinya
2025-08-13 13:20:58 +08:00
parent b7d8b51da4
commit 974870ee83
2 changed files with 127 additions and 377 deletions
+5
View File
@@ -1,4 +1,9 @@
export interface AdminConfig { export interface AdminConfig {
ConfigSubscribtion: {
URL: string;
AutoUpdate: boolean;
LastCheck: string;
};
ConfigFile: string; ConfigFile: string;
SiteConfig: { SiteConfig: {
SiteName: string; SiteName: string;
+96 -351
View File
@@ -44,23 +44,25 @@ export const API_CONFIG = {
}; };
// 在模块加载时根据环境决定配置来源 // 在模块加载时根据环境决定配置来源
let fileConfig: ConfigFileStruct;
let cachedConfig: AdminConfig; let cachedConfig: AdminConfig;
// 从配置文件补充管理员配置
export function refineConfig(adminConfig: AdminConfig): AdminConfig { export function refineConfig(adminConfig: AdminConfig): AdminConfig {
let fileConfig: ConfigFileStruct;
try { try {
fileConfig = JSON.parse(adminConfig.ConfigFile) as ConfigFileStruct; fileConfig = JSON.parse(adminConfig.ConfigFile) as ConfigFileStruct;
} catch (e) { } catch (e) {
fileConfig = {} as ConfigFileStruct; fileConfig = {} as ConfigFileStruct;
} }
// 合并文件中的源信息 // 合并文件中的源信息
const apiSiteEntries = Object.entries(fileConfig.api_site || []); const apiSitesFromFile = Object.entries(fileConfig.api_site || []);
const sourceConfigMap = new Map( const currentApiSites = new Map(
(adminConfig.SourceConfig || []).map((s) => [s.key, s]) (adminConfig.SourceConfig || []).map((s) => [s.key, s])
); );
apiSiteEntries.forEach(([key, site]) => { apiSitesFromFile.forEach(([key, site]) => {
const existingSource = sourceConfigMap.get(key); const existingSource = currentApiSites.get(key);
if (existingSource) { if (existingSource) {
// 如果已存在,只覆盖 name、api、detail 和 from // 如果已存在,只覆盖 name、api、detail 和 from
existingSource.name = site.name; existingSource.name = site.name;
@@ -69,7 +71,7 @@ export function refineConfig(adminConfig: AdminConfig): AdminConfig {
existingSource.from = 'config'; existingSource.from = 'config';
} else { } else {
// 如果不存在,创建新条目 // 如果不存在,创建新条目
sourceConfigMap.set(key, { currentApiSites.set(key, {
key, key,
name: site.name, name: site.name,
api: site.api, api: site.api,
@@ -81,32 +83,32 @@ export function refineConfig(adminConfig: AdminConfig): AdminConfig {
}); });
// 检查现有源是否在 fileConfig.api_site 中,如果不在则标记为 custom // 检查现有源是否在 fileConfig.api_site 中,如果不在则标记为 custom
const apiSiteKeys = new Set(apiSiteEntries.map(([key]) => key)); const apiSitesFromFileKey = new Set(apiSitesFromFile.map(([key]) => key));
sourceConfigMap.forEach((source) => { currentApiSites.forEach((source) => {
if (!apiSiteKeys.has(source.key)) { if (!apiSitesFromFileKey.has(source.key)) {
source.from = 'custom'; source.from = 'custom';
} }
}); });
// 将 Map 转换回数组 // 将 Map 转换回数组
adminConfig.SourceConfig = Array.from(sourceConfigMap.values()); adminConfig.SourceConfig = Array.from(currentApiSites.values());
// 覆盖 CustomCategories // 覆盖 CustomCategories
const customCategories = fileConfig.custom_category || []; const customCategoriesFromFile = fileConfig.custom_category || [];
const customCategoriesMap = new Map( const currentCustomCategories = new Map(
(adminConfig.CustomCategories || []).map((c) => [c.query + c.type, c]) (adminConfig.CustomCategories || []).map((c) => [c.query + c.type, c])
); );
customCategories.forEach((category) => { customCategoriesFromFile.forEach((category) => {
const key = category.query + category.type; const key = category.query + category.type;
const existedCategory = customCategoriesMap.get(key); const existedCategory = currentCustomCategories.get(key);
if (existedCategory) { if (existedCategory) {
existedCategory.name = category.name; existedCategory.name = category.name;
existedCategory.query = category.query; existedCategory.query = category.query;
existedCategory.type = category.type; existedCategory.type = category.type;
existedCategory.from = 'config'; existedCategory.from = 'config';
} else { } else {
customCategoriesMap.set(key, { currentCustomCategories.set(key, {
name: category.name, name: category.name,
type: category.type, type: category.type,
query: category.query, query: category.query,
@@ -117,157 +119,39 @@ export function refineConfig(adminConfig: AdminConfig): AdminConfig {
}); });
// 检查现有 CustomCategories 是否在 fileConfig.custom_category 中,如果不在则标记为 custom // 检查现有 CustomCategories 是否在 fileConfig.custom_category 中,如果不在则标记为 custom
const customCategoriesKeys = new Set( const customCategoriesFromFileKeys = new Set(
customCategories.map((c) => c.query + c.type) customCategoriesFromFile.map((c) => c.query + c.type)
); );
customCategoriesMap.forEach((category) => { currentCustomCategories.forEach((category) => {
if (!customCategoriesKeys.has(category.query + category.type)) { if (!customCategoriesFromFileKeys.has(category.query + category.type)) {
category.from = 'custom'; category.from = 'custom';
} }
}); });
// 将 Map 转换回数组 // 将 Map 转换回数组
adminConfig.CustomCategories = Array.from(customCategoriesMap.values()); adminConfig.CustomCategories = Array.from(currentCustomCategories.values());
return adminConfig; return adminConfig;
} }
async function initConfig() { async function getInitConfig(configFile: string, subConfig: {
if (cachedConfig) { URL: string;
// 自检补全配置 AutoUpdate: boolean;
cachedConfig = refineConfig(cachedConfig); LastCheck: string;
return; } = {
} URL: "",
AutoUpdate: false,
// 数据库存储,读取并补全管理员配置 LastCheck: "",
const storage = getStorage(); }): Promise<AdminConfig> {
let cfgFile: ConfigFileStruct;
try { try {
// 尝试从数据库获取管理员配置 cfgFile = JSON.parse(configFile) as ConfigFileStruct;
let adminConfig: AdminConfig | null = null;
if (storage && typeof (storage as any).getAdminConfig === 'function') {
adminConfig = await (storage as any).getAdminConfig();
}
// 获取所有用户名,用于补全 Users
let userNames: string[] = [];
if (storage && typeof (storage as any).getAllUsers === 'function') {
try {
userNames = await (storage as any).getAllUsers();
} catch (e) { } catch (e) {
console.error('获取用户列表失败:', e); cfgFile = {} as ConfigFileStruct;
} }
} let adminConfig: AdminConfig = {
ConfigFile: configFile,
if (adminConfig) { ConfigSubscribtion: subConfig,
try {
fileConfig = JSON.parse(adminConfig.ConfigFile) as ConfigFileStruct;
} catch (e) {
console.error('解析配置文件失败:', e);
fileConfig = {} as ConfigFileStruct;
}
const apiSiteEntries = Object.entries(fileConfig.api_site || []);
const customCategories = fileConfig.custom_category || [];
// 补全 SourceConfig
const sourceConfigMap = new Map(
(adminConfig.SourceConfig || []).map((s) => [s.key, s])
);
apiSiteEntries.forEach(([key, site]) => {
sourceConfigMap.set(key, {
key,
name: site.name,
api: site.api,
detail: site.detail,
from: 'config',
disabled: false,
});
});
// 将 Map 转换回数组
adminConfig.SourceConfig = Array.from(sourceConfigMap.values());
// 检查现有源是否在 fileConfig.api_site 中,如果不在则标记为 custom
const apiSiteKeys = new Set(apiSiteEntries.map(([key]) => key));
adminConfig.SourceConfig.forEach((source) => {
if (!apiSiteKeys.has(source.key)) {
source.from = 'custom';
}
});
// 确保 CustomCategories 被初始化
if (!adminConfig.CustomCategories) {
adminConfig.CustomCategories = [];
}
// 补全 CustomCategories
const customCategoriesMap = new Map(
adminConfig.CustomCategories.map((c) => [c.query + c.type, c])
);
customCategories.forEach((category) => {
customCategoriesMap.set(category.query + category.type, {
name: category.name,
type: category.type,
query: category.query,
from: 'config',
disabled: false,
});
});
// 检查现有 CustomCategories 是否在 fileConfig.custom_category 中,如果不在则标记为 custom
const customCategoriesKeys = new Set(
customCategories.map((c) => c.query + c.type)
);
customCategoriesMap.forEach((category) => {
if (!customCategoriesKeys.has(category.query + category.type)) {
category.from = 'custom';
}
});
// 将 Map 转换回数组
adminConfig.CustomCategories = Array.from(customCategoriesMap.values());
const existedUsers = new Set(
(adminConfig.UserConfig.Users || []).map((u) => u.username)
);
userNames.forEach((uname) => {
if (!existedUsers.has(uname)) {
adminConfig!.UserConfig.Users.push({
username: uname,
role: 'user',
});
}
});
// 站长
const ownerUser = process.env.USERNAME;
if (ownerUser) {
adminConfig!.UserConfig.Users = adminConfig!.UserConfig.Users.filter(
(u) => u.username !== ownerUser
);
adminConfig!.UserConfig.Users.unshift({
username: ownerUser,
role: 'owner',
});
}
} else {
fileConfig = {} as ConfigFileStruct;
// 数据库中没有配置,创建新的管理员配置
let allUsers = userNames.map((uname) => ({
username: uname,
role: 'user',
}));
const ownerUser = process.env.USERNAME;
if (ownerUser) {
allUsers = allUsers.filter((u) => u.username !== ownerUser);
allUsers.unshift({
username: ownerUser,
role: 'owner',
});
}
adminConfig = {
ConfigFile: '',
SiteConfig: { SiteConfig: {
SiteName: process.env.NEXT_PUBLIC_SITE_NAME || 'MoonTV', SiteName: process.env.NEXT_PUBLIC_SITE_NAME || 'MoonTV',
Announcement: Announcement:
@@ -275,7 +159,7 @@ async function initConfig() {
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。', '本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
SearchDownstreamMaxPage: SearchDownstreamMaxPage:
Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5, Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5,
SiteInterfaceCacheTime: fileConfig.cache_time || 7200, SiteInterfaceCacheTime: cfgFile.cache_time || 7200,
DoubanProxyType: DoubanProxyType:
process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'direct', process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'direct',
DoubanProxy: process.env.NEXT_PUBLIC_DOUBAN_PROXY || '', DoubanProxy: process.env.NEXT_PUBLIC_DOUBAN_PROXY || '',
@@ -287,159 +171,14 @@ async function initConfig() {
}, },
UserConfig: { UserConfig: {
AllowRegister: process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true', AllowRegister: process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true',
Users: allUsers as any, Users: [],
}, },
SourceConfig: [], SourceConfig: [],
CustomCategories: [], CustomCategories: [],
}; };
}
// 写回数据库(更新/创建) // 补充用户信息
if (storage && typeof (storage as any).setAdminConfig === 'function') {
await (storage as any).setAdminConfig(adminConfig);
}
// 更新缓存
cachedConfig = adminConfig;
} catch (err) {
console.error('加载管理员配置失败:', err);
}
}
export async function getConfig(): Promise<AdminConfig> {
if (process.env.DOCKER_ENV === 'true') {
await initConfig();
return cachedConfig;
}
// 非 docker 环境且 DB 存储,直接读 db 配置
const storage = getStorage(); const storage = getStorage();
let adminConfig: AdminConfig | null = null;
if (storage && typeof (storage as any).getAdminConfig === 'function') {
adminConfig = await (storage as any).getAdminConfig();
}
if (adminConfig) {
// 确保 CustomCategories 被初始化
if (!adminConfig.CustomCategories) {
adminConfig.CustomCategories = [];
}
// 合并一些环境变量配置
adminConfig.SiteConfig.SiteName =
process.env.NEXT_PUBLIC_SITE_NAME || 'MoonTV';
adminConfig.SiteConfig.Announcement =
process.env.ANNOUNCEMENT ||
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。';
adminConfig.UserConfig.AllowRegister =
process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true';
adminConfig.SiteConfig.DoubanProxyType =
process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'direct';
adminConfig.SiteConfig.DoubanProxy =
process.env.NEXT_PUBLIC_DOUBAN_PROXY || '';
adminConfig.SiteConfig.DoubanImageProxyType =
process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE || 'direct';
adminConfig.SiteConfig.DoubanImageProxy =
process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY || '';
adminConfig.SiteConfig.DisableYellowFilter =
process.env.NEXT_PUBLIC_DISABLE_YELLOW_FILTER === 'true';
try {
fileConfig = JSON.parse(adminConfig.ConfigFile) as ConfigFileStruct;
} catch (e) {
console.error('解析配置文件失败:', e);
fileConfig = {} as ConfigFileStruct;
}
// 合并文件中的源信息
const apiSiteEntries = Object.entries(fileConfig.api_site || []);
const sourceConfigMap = new Map(
(adminConfig.SourceConfig || []).map((s) => [s.key, s])
);
apiSiteEntries.forEach(([key, site]) => {
const existingSource = sourceConfigMap.get(key);
if (existingSource) {
// 如果已存在,只覆盖 name、api、detail 和 from
existingSource.name = site.name;
existingSource.api = site.api;
existingSource.detail = site.detail;
existingSource.from = 'config';
} else {
// 如果不存在,创建新条目
sourceConfigMap.set(key, {
key,
name: site.name,
api: site.api,
detail: site.detail,
from: 'config',
disabled: false,
});
}
});
// 检查现有源是否在 fileConfig.api_site 中,如果不在则标记为 custom
const apiSiteKeys = new Set(apiSiteEntries.map(([key]) => key));
sourceConfigMap.forEach((source) => {
if (!apiSiteKeys.has(source.key)) {
source.from = 'custom';
}
});
// 将 Map 转换回数组
adminConfig.SourceConfig = Array.from(sourceConfigMap.values());
// 覆盖 CustomCategories
const customCategories = fileConfig.custom_category || [];
adminConfig.CustomCategories = customCategories.map((category) => ({
name: category.name,
type: category.type,
query: category.query,
from: 'config',
disabled: false,
}));
const ownerUser = process.env.USERNAME || '';
// 检查配置中的站长用户是否和 USERNAME 匹配,如果不匹配则降级为普通用户
let containOwner = false;
adminConfig.UserConfig.Users.forEach((user) => {
if (user.username !== ownerUser && user.role === 'owner') {
user.role = 'user';
}
if (user.username === ownerUser) {
containOwner = true;
user.role = 'owner';
}
});
// 如果不在则添加
if (!containOwner) {
adminConfig.UserConfig.Users.unshift({
username: ownerUser,
role: 'owner',
});
}
cachedConfig = adminConfig;
} else {
// DB 无配置,执行一次初始化
await initConfig();
}
return cachedConfig;
}
export async function resetConfig() {
let originConfig: AdminConfig | null = null;
const storage = getStorage();
if (storage && typeof (storage as any).getAdminConfig === 'function') {
originConfig = await (storage as any).getAdminConfig();
}
if (originConfig) {
fileConfig = JSON.parse(originConfig.ConfigFile) as ConfigFileStruct;
} else {
fileConfig = {} as ConfigFileStruct;
}
const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE;
// 获取所有用户名,用于补全 Users
let userNames: string[] = []; let userNames: string[] = [];
if (storage && typeof (storage as any).getAllUsers === 'function') { if (storage && typeof (storage as any).getAllUsers === 'function') {
try { try {
@@ -448,75 +187,81 @@ export async function resetConfig() {
console.error('获取用户列表失败:', e); console.error('获取用户列表失败:', e);
} }
} }
const allUsers = userNames.filter((u) => u !== process.env.USERNAME).map((u) => ({
const apiSiteEntries = Object.entries(fileConfig.api_site || []); username: u,
const customCategories = fileConfig.custom_category || [];
let allUsers = userNames.map((uname) => ({
username: uname,
role: 'user', role: 'user',
banned: false,
})); }));
const ownerUser = process.env.USERNAME;
if (ownerUser) {
allUsers = allUsers.filter((u) => u.username !== ownerUser);
allUsers.unshift({ allUsers.unshift({
username: ownerUser, username: process.env.USERNAME!,
role: 'owner', role: 'owner',
banned: false,
}); });
} adminConfig.UserConfig.Users = allUsers as any;
const adminConfig = {
ConfigFile: originConfig?.ConfigFile || '', // 从配置文件中补充源信息
SiteConfig: { Object.entries(cfgFile.api_site || []).forEach(([key, site]) => {
SiteName: process.env.NEXT_PUBLIC_SITE_NAME || 'MoonTV', adminConfig.SourceConfig.push({
Announcement: key: key,
process.env.ANNOUNCEMENT ||
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。',
SearchDownstreamMaxPage:
Number(process.env.NEXT_PUBLIC_SEARCH_MAX_PAGE) || 5,
SiteInterfaceCacheTime: fileConfig.cache_time || 7200,
DoubanProxyType: process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'direct',
DoubanProxy: process.env.NEXT_PUBLIC_DOUBAN_PROXY || '',
DoubanImageProxyType:
process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE || 'direct',
DoubanImageProxy: process.env.NEXT_PUBLIC_DOUBAN_IMAGE_PROXY || '',
DisableYellowFilter:
process.env.NEXT_PUBLIC_DISABLE_YELLOW_FILTER === 'true',
},
UserConfig: {
AllowRegister: process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true',
Users: allUsers as any,
},
SourceConfig: apiSiteEntries.map(([key, site]) => ({
key,
name: site.name, name: site.name,
api: site.api, api: site.api,
detail: site.detail, detail: site.detail,
from: 'config', from: 'config',
disabled: false, disabled: false,
})), });
CustomCategories: });
storageType === 'redis'
? customCategories?.map((category) => ({ // 从配置文件中补充自定义分类信息
name: category.name, cfgFile.custom_category?.forEach((category) => {
adminConfig.CustomCategories.push({
name: category.name || category.query,
type: category.type, type: category.type,
query: category.query, query: category.query,
from: 'config', from: 'config',
disabled: false, disabled: false,
})) || [] });
: [], });
} as AdminConfig;
return adminConfig;
}
export async function getConfig(): Promise<AdminConfig> {
// 直接使用内存缓存
if (cachedConfig) {
return cachedConfig;
}
// 读 db
const storage = getStorage();
let adminConfig: AdminConfig | null = null;
if (storage && typeof (storage as any).getAdminConfig === 'function') {
adminConfig = await (storage as any).getAdminConfig();
}
// db 中无配置,执行一次初始化
if (!adminConfig) {
adminConfig = await getInitConfig("");
}
cachedConfig = adminConfig;
return cachedConfig;
}
export async function resetConfig() {
let originConfig: AdminConfig;
const storage = getStorage();
if (storage && typeof (storage as any).getAdminConfig === 'function') {
originConfig = await (storage as any).getAdminConfig();
} else {
originConfig = {} as AdminConfig;
const adminConfig = await getInitConfig(originConfig.ConfigFile, originConfig.ConfigSubscribtion);
cachedConfig = adminConfig;
if (storage && typeof (storage as any).setAdminConfig === 'function') { if (storage && typeof (storage as any).setAdminConfig === 'function') {
await (storage as any).setAdminConfig(adminConfig); await (storage as any).setAdminConfig(adminConfig);
} }
if (cachedConfig == null) {
// serverless 环境,直接使用 adminConfig return;
cachedConfig = adminConfig;
} }
cachedConfig.ConfigFile = adminConfig.ConfigFile;
cachedConfig.SiteConfig = adminConfig.SiteConfig;
cachedConfig.UserConfig = adminConfig.UserConfig;
cachedConfig.SourceConfig = adminConfig.SourceConfig;
cachedConfig.CustomCategories = adminConfig.CustomCategories || [];
} }
export async function getCacheTime(): Promise<number> { export async function getCacheTime(): Promise<number> {