Refactoring the backend

This commit is contained in:
jiangrui
2025-03-10 18:33:47 +08:00
parent 755a424530
commit a78ea7e5bd
36 changed files with 974 additions and 474 deletions

View File

@@ -1,7 +1,11 @@
import { AxiosHeaders, AxiosInstance } from "axios"; // 导入 AxiosHeaders
import { createAxiosInstance } from "../utils/axiosInstance";
import { Logger } from "../utils/logger";
import { ShareInfoResponse } from "../types/cloud115";
import { injectable } from "inversify";
import { Request } from "express";
import UserSetting from "../models/UserSetting";
import { ICloudService } from "../types/services";
import { logger } from "@/utils/logger";
interface Cloud115ListItem {
cid: string;
@@ -20,11 +24,12 @@ interface Cloud115PathItem {
name: string;
}
export class Cloud115Service {
@injectable()
export class Cloud115Service implements ICloudService {
private api: AxiosInstance;
private cookie: string = "";
constructor(cookie?: string) {
constructor() {
this.api = createAxiosInstance(
"https://webapi.115.com",
AxiosHeaders.from({
@@ -44,19 +49,23 @@ export class Cloud115Service {
"Accept-Language": "zh-CN,zh;q=0.9",
})
);
if (cookie) {
this.setCookie(cookie);
} else {
console.log("请注意:115网盘需要提供cookie进行身份验证");
}
this.api.interceptors.request.use((config) => {
config.headers.cookie = cookie || this.cookie;
config.headers.cookie = this.cookie;
return config;
});
}
public setCookie(cookie: string): void {
this.cookie = cookie;
async setCookie(req: Request): Promise<void> {
const userId = req.user?.userId;
const userSetting = await UserSetting.findOne({
where: { userId },
});
if (userSetting && userSetting.dataValues.cloud115Cookie) {
this.cookie = userSetting.dataValues.cloud115Cookie;
} else {
throw new Error("请先设置115网盘cookie");
}
}
async getShareInfo(shareCode: string, receiveCode = ""): Promise<ShareInfoResponse> {
@@ -114,7 +123,7 @@ export class Cloud115Service {
})),
};
} else {
Logger.error("获取目录列表失败:", response.data.error);
logger.error("获取目录列表失败:", response.data.error);
throw new Error("获取115pan目录列表失败:" + response.data.error);
}
}
@@ -132,14 +141,14 @@ export class Cloud115Service {
file_id: params.fileId,
});
const response = await this.api.post("/share/receive", param.toString());
Logger.info("保存文件:", response.data);
logger.info("保存文件:", response.data);
if (response.data.state) {
return {
message: response.data.error,
data: response.data.data,
};
} else {
Logger.error("保存文件失败:", response.data.error);
logger.error("保存文件失败:", response.data.error);
throw new Error("保存115pan文件失败:" + response.data.error);
}
}

View File

@@ -0,0 +1,38 @@
import { Sequelize, QueryTypes } from "sequelize";
export class DatabaseService {
private sequelize: Sequelize;
constructor() {
this.sequelize = new Sequelize({
dialect: "sqlite",
storage: "./data/database.sqlite",
});
}
async initialize(): Promise<void> {
try {
await this.sequelize.query("PRAGMA foreign_keys = OFF");
await this.cleanupBackupTables();
await this.sequelize.sync({ alter: true });
await this.sequelize.query("PRAGMA foreign_keys = ON");
} catch (error) {
throw new Error(`数据库初始化失败: ${(error as Error).message}`);
}
}
private async cleanupBackupTables(): Promise<void> {
const backupTables = await this.sequelize.query<{ name: string }>(
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%\\_backup%' ESCAPE '\\'",
{ type: QueryTypes.SELECT }
);
for (const table of backupTables) {
if (table?.name) {
await this.sequelize.query(`DROP TABLE IF EXISTS ${table.name}`);
}
}
}
// ... 其他数据库相关方法
}

View File

@@ -10,7 +10,7 @@ interface DoubanSubject {
is_new: boolean;
}
class DoubanService {
export class DoubanService {
private baseUrl: string;
private api: AxiosInstance;
@@ -62,5 +62,3 @@ class DoubanService {
}
}
}
export default DoubanService;

View File

@@ -0,0 +1,42 @@
import { injectable } from "inversify";
import axios, { AxiosInstance } from "axios";
import tunnel from "tunnel";
import GlobalSetting from "../models/GlobalSetting";
import { GlobalSettingAttributes } from "../models/GlobalSetting";
@injectable()
export class ImageService {
private axiosInstance: AxiosInstance | null = null;
constructor() {
this.initializeAxiosInstance();
}
private async initializeAxiosInstance(): Promise<void> {
const settings = await GlobalSetting.findOne();
const globalSetting = settings?.dataValues || ({} as GlobalSettingAttributes);
this.axiosInstance = axios.create({
timeout: 3000,
httpsAgent: globalSetting.isProxyEnabled
? tunnel.httpsOverHttp({
proxy: {
host: globalSetting.httpProxyHost,
port: globalSetting.httpProxyPort,
headers: {
"Proxy-Authorization": "",
},
},
})
: undefined,
withCredentials: true,
});
}
async getImages(url: string): Promise<any> {
if (!this.axiosInstance) {
throw new Error("Axios instance not initialized");
}
return await this.axiosInstance.get(url, { responseType: "stream" });
}
}

View File

@@ -1,6 +1,9 @@
import { AxiosInstance, AxiosHeaders } from "axios";
import { Logger } from "../utils/logger";
import { logger } from "../utils/logger";
import { createAxiosInstance } from "../utils/axiosInstance";
import { injectable } from "inversify";
import { Request } from "express";
import UserSetting from "../models/UserSetting";
interface QuarkShareInfo {
stoken?: string;
@@ -20,11 +23,12 @@ interface QuarkFolderItem {
file_type: number;
}
@injectable()
export class QuarkService {
private api: AxiosInstance;
private cookie: string = "";
constructor(cookie?: string) {
constructor() {
this.api = createAxiosInstance(
"https://drive-h.quark.cn",
AxiosHeaders.from({
@@ -41,19 +45,23 @@ export class QuarkService {
"sec-fetch-site": "same-site",
})
);
if (cookie) {
this.setCookie(cookie);
} else {
console.log("请注意:夸克网盘需要提供cookie进行身份验证");
}
this.api.interceptors.request.use((config) => {
config.headers.cookie = cookie || this.cookie;
config.headers.cookie = this.cookie;
return config;
});
}
public setCookie(cookie: string): void {
this.cookie = cookie;
async setCookie(req: Request): Promise<void> {
const userId = req.user?.userId;
const userSetting = await UserSetting.findOne({
where: { userId },
});
if (userSetting && userSetting.dataValues.quarkCookie) {
this.cookie = userSetting.dataValues.quarkCookie;
} else {
throw new Error("请先设置夸克网盘cookie");
}
}
async getShareInfo(pwdId: string, passcode = ""): Promise<{ data: QuarkShareInfo }> {
@@ -148,7 +156,7 @@ export class QuarkService {
};
} else {
const message = "获取夸克目录列表失败:" + response.data.error;
Logger.error(message);
logger.error(message);
throw new Error(message);
}
}

View File

@@ -4,7 +4,8 @@ import GlobalSetting from "../models/GlobalSetting";
import { GlobalSettingAttributes } from "../models/GlobalSetting";
import * as cheerio from "cheerio";
import { config } from "../config";
import { Logger } from "../utils/logger";
import { logger } from "../utils/logger";
import { injectable } from "inversify";
interface sourceItem {
messageId?: string;
@@ -20,20 +21,20 @@ interface sourceItem {
cloudType?: string;
}
@injectable()
export class Searcher {
private axiosInstance: AxiosInstance | null = null;
private static instance: Searcher;
private api: AxiosInstance;
constructor() {
this.initializeAxiosInstance();
this.initAxiosInstance();
Searcher.instance = this;
}
private async initializeAxiosInstance(isUpdate = false): Promise<void> {
let settings = null;
if (isUpdate) {
settings = await GlobalSetting.findOne();
}
private async initAxiosInstance() {
const settings = await GlobalSetting.findOne();
const globalSetting = settings?.dataValues || ({} as GlobalSettingAttributes);
this.axiosInstance = createAxiosInstance(
this.api = createAxiosInstance(
config.telegram.baseUrl,
AxiosHeaders.from({
accept:
@@ -56,8 +57,11 @@ export class Searcher {
: undefined
);
}
public async updateAxiosInstance() {
await this.initializeAxiosInstance(true);
public static async updateAxiosInstance(): Promise<void> {
if (Searcher.instance) {
await Searcher.instance.initAxiosInstance();
}
}
private extractCloudLinks(text: string): { links: string[]; cloudType: string } {
@@ -111,7 +115,7 @@ export class Searcher {
}
});
} catch (error) {
Logger.error(`搜索频道 ${channel.name} 失败:`, error);
logger.error(`搜索频道 ${channel.name} 失败:`, error);
}
});
@@ -125,10 +129,10 @@ export class Searcher {
async searchInWeb(url: string) {
try {
if (!this.axiosInstance) {
if (!this.api) {
throw new Error("Axios instance is not initialized");
}
const response = await this.axiosInstance.get(url);
const response = await this.api.get(url);
const html = response.data;
const $ = cheerio.load(html);
const items: sourceItem[] = [];
@@ -205,7 +209,7 @@ export class Searcher {
});
return { items: items, channelLogo };
} catch (error) {
Logger.error(`搜索错误: ${url}`, error);
logger.error(`搜索错误: ${url}`, error);
return {
items: [],
channelLogo: "",

View File

@@ -0,0 +1,46 @@
import { injectable } from "inversify";
import UserSetting from "../models/UserSetting";
import GlobalSetting from "../models/GlobalSetting";
import { Searcher } from "./Searcher";
@injectable()
export class SettingService {
async getSettings(userId: number | undefined, role: number | undefined) {
if (!userId) {
throw new Error("用户ID无效");
}
let userSettings = await UserSetting.findOne({ where: { userId: userId.toString() } });
if (!userSettings) {
userSettings = await UserSetting.create({
userId: userId.toString(),
cloud115Cookie: "",
quarkCookie: "",
});
}
const globalSetting = await GlobalSetting.findOne();
return {
data: {
userSettings,
globalSetting: role === 1 ? globalSetting : null,
},
};
}
async saveSettings(userId: number | undefined, role: number | undefined, settings: any) {
if (!userId) {
throw new Error("用户ID无效");
}
const { userSettings, globalSetting } = settings;
await UserSetting.update(userSettings, { where: { userId: userId.toString() } });
if (role === 1 && globalSetting) {
await GlobalSetting.update(globalSetting, { where: {} });
}
await Searcher.updateAxiosInstance();
return { message: "保存成功" };
}
}

View File

@@ -0,0 +1,64 @@
import { injectable } from "inversify";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { config } from "../config";
import User from "../models/User";
import GlobalSetting from "../models/GlobalSetting";
@injectable()
export class UserService {
private isValidInput(input: string): boolean {
// 检查是否包含空格或汉字
const regex = /^[^\s\u4e00-\u9fa5]+$/;
return regex.test(input);
}
async register(username: string, password: string, registerCode: string) {
const globalSetting = await GlobalSetting.findOne();
const registerCodeList = [
globalSetting?.dataValues.CommonUserCode,
globalSetting?.dataValues.AdminUserCode,
];
if (!registerCode || !registerCodeList.includes(Number(registerCode))) {
throw new Error("注册码错误");
}
// 验证输入
if (!this.isValidInput(username) || !this.isValidInput(password)) {
throw new Error("用户名、密码或注册码不能包含空格或汉字");
}
// 检查用户名是否已存在
const existingUser = await User.findOne({ where: { username } });
if (existingUser) {
throw new Error("用户名已存在");
}
const hashedPassword = await bcrypt.hash(password, 10);
const role = registerCodeList.findIndex((x) => x === Number(registerCode));
const user = await User.create({ username, password: hashedPassword, role });
return {
data: user,
message: "用户注册成功",
};
}
async login(username: string, password: string) {
const user = await User.findOne({ where: { username } });
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new Error("用户名或密码错误");
}
const token = jwt.sign({ userId: user.userId, role: user.role }, config.jwtSecret, {
expiresIn: "6h",
});
return {
data: {
token,
},
};
}
}