feat: 后端搜索接口开发
This commit is contained in:
@@ -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"]
|
||||
Generated
+3781
-283
File diff suppressed because it is too large
Load Diff
+32
-21
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+3054
-1237
File diff suppressed because it is too large
Load Diff
@@ -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])
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
@@ -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`);
|
||||
});
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user