feat: 后端搜索接口开发

This commit is contained in:
2025-05-11 09:41:48 +08:00
parent 7350f43a72
commit 9b2584d32c
20 changed files with 12571 additions and 6790 deletions
+28
View File
@@ -0,0 +1,28 @@
# Build stage
FROM node:20-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/prisma ./prisma
RUN npm install --production
ENV NODE_ENV=production
ENV PORT=4000
EXPOSE 4000
CMD ["node", "dist/index.js"]
+3781 -283
View File
File diff suppressed because it is too large Load Diff
+32 -21
View File
@@ -1,34 +1,45 @@
{
"name": "search-service-backend",
"name": "bps-backend",
"version": "1.0.0",
"description": "Backend service for search functionality",
"description": "Backend for BPS (Baidu Pan Search)",
"main": "dist/index.js",
"scripts": {
"start": "node dist/index.js",
"dev": "ts-node-dev src/index.ts",
"build": "tsc",
"test": "jest"
"start": "node dist/index.js",
"dev": "ts-node-dev --respawn src/index.ts",
"test": "jest",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev"
},
"dependencies": {
"axios": "^1.9.0",
"dotenv": "^16.5.0",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.9.1",
"redis": "^4.7.0",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/express-rate-limit": "^6.0.2",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.17.32",
"@prisma/client": "^5.10.0",
"@types/axios": "^0.14.4",
"@types/redis": "^4.0.11",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8",
"axios": "^1.9.0",
"cors": "^2.8.5",
"dotenv": "^16.4.0",
"express": "^4.18.0",
"express-rate-limit": "^7.1.0",
"ioredis": "^5.3.0",
"node-cron": "^3.0.0",
"redis": "^5.0.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"winston": "^3.11.0"
},
"devDependencies": {
"@types/cors": "^2.8.0",
"@types/express": "^4.17.0",
"@types/jest": "^29.0.0",
"@types/node": "^20.0.0",
"@types/node-cron": "^3.0.0",
"jest": "^29.0.0",
"prisma": "^5.10.0",
"ts-jest": "^29.0.0",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.8.3"
"typescript": "^5.0.0"
}
}
+3054 -1237
View File
File diff suppressed because it is too large Load Diff
+60
View File
@@ -0,0 +1,60 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Account {
id Int @id @default(autoincrement())
driveType String @unique
accountIdentifier String
credentials String @db.Text
status String @default("active")
lastChecked DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
transcodeRecords TranscodeRecord[]
activityLogs ActivityLog[]
}
model TranscodeRecord {
id Int @id @default(autoincrement())
itemId String @index
sourceLink String @db.Text
driveType String
managedAccountId Int
status String @default("pending")
tempLink String? @db.Text
transferredFileId String?
transferStartTime DateTime @default(now())
transferEndTime DateTime?
errorCode String?
errorMessage String? @db.Text
cleanupStatus String @default("pending") @index
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
account Account @relation(fields: [managedAccountId], references: [id])
}
model ActivityLog {
id BigInt @id @default(autoincrement())
timestamp DateTime @default(now())
operationType String
clientIp String?
keyword String?
itemId String? @index
driveType String?
managedAccountId Int?
generatedLink String? @db.Text
errorCode String?
errorMessage String? @db.Text
durationMs Int?
details String? @db.Text
account Account? @relation(fields: [managedAccountId], references: [id])
}
+56
View File
@@ -0,0 +1,56 @@
import { Account } from '@prisma/client';
import { DriveAdapter, TransferResult } from './DriveAdapter';
import { logger } from '../utils/logger';
export class BaiduDriveAdapter implements DriveAdapter {
async transferAndShare(
sourceLink: string,
sourceLinkType: string,
account: Account
): Promise<TransferResult> {
try {
// TODO: 实现百度网盘的具体转存和分享逻辑
// 1. 解析账号凭证
const credentials = JSON.parse(account.credentials);
// 2. 转存文件
// const transferredFileId = await this.transferFile(sourceLink, credentials);
// 3. 生成分享链接
// const tempLink = await this.generateShareLink(transferredFileId, credentials);
// 临时返回模拟数据
return {
tempLink: 'https://pan.baidu.com/s/example',
transferredFileId: 'example_file_id'
};
} catch (error) {
logger.error('Baidu drive transfer failed', { error, accountId: account.id });
throw new Error('Failed to transfer and share file');
}
}
async delete(fileId: string, account: Account): Promise<void> {
try {
// TODO: 实现百度网盘的文件删除逻辑
const credentials = JSON.parse(account.credentials);
// await this.deleteFile(fileId, credentials);
} catch (error) {
logger.error('Baidu drive delete failed', { error, accountId: account.id });
throw new Error('Failed to delete file');
}
}
async checkAccountStatus(account: Account): Promise<boolean> {
try {
// TODO: 实现百度网盘的账号状态检查逻辑
const credentials = JSON.parse(account.credentials);
// const isValid = await this.checkAccount(credentials);
// return isValid;
return true; // 临时返回
} catch (error) {
logger.error('Baidu drive account check failed', { error, accountId: account.id });
return false;
}
}
}
+35
View File
@@ -0,0 +1,35 @@
import { Account } from '@prisma/client';
export interface TransferResult {
tempLink: string;
transferredFileId: string;
}
export interface DriveAdapter {
/**
* 转存文件并生成临时分享链接
* @param sourceLink 源文件链接
* @param sourceLinkType 源链接类型
* @param account 网盘账号信息
* @returns 包含临时链接和转存文件ID的结果
*/
transferAndShare(
sourceLink: string,
sourceLinkType: string,
account: Account
): Promise<TransferResult>;
/**
* 删除网盘中的文件
* @param fileId 文件ID
* @param account 网盘账号信息
*/
delete(fileId: string, account: Account): Promise<void>;
/**
* 检查账号状态
* @param account 网盘账号信息
* @returns 账号是否有效
*/
checkAccountStatus(account: Account): Promise<boolean>;
}
+7 -3
View File
@@ -4,9 +4,13 @@ dotenv.config();
// Add debug logging
console.log('Environment variables loaded:', {
REDIS_HOST: process.env.REDIS_HOST,
REDIS_PORT: process.env.REDIS_PORT,
REDIS_PASSWORD: process.env.REDIS_PASSWORD
PORT: process.env.PORT,
SEARCH_SERVICE_URL: process.env.SEARCH_SERVICE_URL,
JWT_SECRET: process.env.JWT_SECRET,
MYSQL_HOST: process.env.MYSQL_HOST,
MYSQL_PORT: process.env.MYSQL_PORT,
MYSQL_USER: process.env.MYSQL_USER,
MYSQL_PASSWORD: process.env.MYSQL_PASSWORD,
});
export const config = {
+4 -3
View File
@@ -1,4 +1,5 @@
import swaggerJsdoc from 'swagger-jsdoc';
import { config } from './config';
const options: swaggerJsdoc.Options = {
definition: {
@@ -14,7 +15,7 @@ const options: swaggerJsdoc.Options = {
},
servers: [
{
url: 'http://localhost:3005',
url: `http://localhost:${config.server.port}`,
description: '开发服务器'
}
],
@@ -31,7 +32,7 @@ const options: swaggerJsdoc.Options = {
bearerAuth: []
}]
},
apis: ['./src/routes/*.ts', './src/controllers/*.ts'] // 指定API注释文件的位置
apis: ['./src/routes/*.ts', './src/controllers/*.ts']
};
export const swaggerSpec = swaggerJsdoc(options);
export const swaggerSpec = swaggerJsdoc(options);
+18 -3
View File
@@ -1,12 +1,25 @@
import express from 'express';
import { config } from './config/config';
import searchRoutes from './routes/searchRoutes';
import swaggerUi from 'swagger-ui-express';
import { swaggerSpec } from './config/swagger';
import cors from 'cors';
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
// Swagger UI
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// API Documentation in JSON format
app.get('/api-docs.json', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send(swaggerSpec);
});
// Routes
app.use('/api', searchRoutes);
@@ -20,7 +33,9 @@ app.use((err: Error, req: express.Request, res: express.Response, next: express.
});
});
const PORT = config.server.port;
// Start server
app.listen(config.server.port, () => {
console.log(`Server is running on port ${config.server.port}`);
});
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
console.log(`Swagger documentation available at http://localhost:${PORT}/api-docs`);
});
+26
View File
@@ -0,0 +1,26 @@
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}),
new winston.transports.File({
filename: 'error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'combined.log'
})
]
});
export { logger };