chore: 更新.gitignore并添加新文件
在.gitignore中添加对*.png、*.log、*-lock.json、*.woff2文件的忽略规则,并新增OFL.txt文件。同时,添加vue.svg图标文件以支持前端展示。更新多个TypeScript文件以优化代码格式和增强可读性。
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
import { Controller, Get, Query, Patch, Param, Body } from '@nestjs/common';
|
||||
import { BidsService } from '../services/bid.service';
|
||||
|
||||
interface FindAllQuery {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
source?: string;
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
@Controller('api/bids')
|
||||
export class BidsController {
|
||||
constructor(private readonly bidsService: BidsService) {}
|
||||
|
||||
@Get()
|
||||
findAll(@Query() query: any) {
|
||||
findAll(@Query() query: FindAllQuery) {
|
||||
return this.bidsService.findAll(query);
|
||||
}
|
||||
|
||||
@@ -26,9 +33,17 @@ export class BidsController {
|
||||
}
|
||||
|
||||
@Get('by-date-range')
|
||||
getByDateRange(@Query('startDate') startDate: string, @Query('endDate') endDate?: string, @Query('keywords') keywords?: string) {
|
||||
getByDateRange(
|
||||
@Query('startDate') startDate: string,
|
||||
@Query('endDate') endDate?: string,
|
||||
@Query('keywords') keywords?: string,
|
||||
) {
|
||||
const keywordsArray = keywords ? keywords.split(',') : undefined;
|
||||
return this.bidsService.getBidsByDateRange(startDate, endDate, keywordsArray);
|
||||
return this.bidsService.getBidsByDateRange(
|
||||
startDate,
|
||||
endDate,
|
||||
keywordsArray,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('crawl-info-stats')
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity('bid_items')
|
||||
export class BidItem {
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, LessThan, MoreThanOrEqual } from 'typeorm';
|
||||
import { Repository, LessThan } from 'typeorm';
|
||||
import { BidItem } from '../entities/bid-item.entity';
|
||||
import { CrawlInfoAdd } from '../../crawler/entities/crawl-info-add.entity';
|
||||
|
||||
interface FindAllQuery {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
source?: string;
|
||||
keyword?: string;
|
||||
}
|
||||
|
||||
interface SourceResult {
|
||||
source: string;
|
||||
}
|
||||
|
||||
interface CrawlInfoAddStats {
|
||||
source: string;
|
||||
count: number;
|
||||
latestUpdate: Date | string;
|
||||
latestPublishDate: Date | string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface CrawlInfoAddRawResult {
|
||||
source: string;
|
||||
count: number;
|
||||
latestPublishDate: Date | string | null;
|
||||
error: string | null;
|
||||
latestUpdate: Date | string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class BidsService {
|
||||
constructor(
|
||||
@@ -13,7 +40,7 @@ export class BidsService {
|
||||
private crawlInfoRepository: Repository<CrawlInfoAdd>,
|
||||
) {}
|
||||
|
||||
async findAll(query?: any) {
|
||||
async findAll(query?: FindAllQuery) {
|
||||
const { page = 1, limit = 10, source, keyword } = query || {};
|
||||
const qb = this.bidRepository.createQueryBuilder('bid');
|
||||
|
||||
@@ -26,8 +53,8 @@ export class BidsService {
|
||||
}
|
||||
|
||||
qb.orderBy('bid.publishDate', 'DESC')
|
||||
.skip((page - 1) * limit)
|
||||
.take(limit);
|
||||
.skip((Number(page) - 1) * Number(limit))
|
||||
.take(Number(limit));
|
||||
|
||||
const [items, total] = await qb.getManyAndCount();
|
||||
return { items, total };
|
||||
@@ -35,7 +62,9 @@ export class BidsService {
|
||||
|
||||
async createOrUpdate(data: Partial<BidItem>) {
|
||||
// Use title or a hash of title to check for duplicates
|
||||
let item = await this.bidRepository.findOne({ where: { title: data.title } });
|
||||
const item = await this.bidRepository.findOne({
|
||||
where: { title: data.title },
|
||||
});
|
||||
if (item) {
|
||||
Object.assign(item, data);
|
||||
return this.bidRepository.save(item);
|
||||
@@ -51,21 +80,21 @@ export class BidsService {
|
||||
});
|
||||
}
|
||||
|
||||
async getSources() {
|
||||
async getSources(): Promise<string[]> {
|
||||
const result = await this.bidRepository
|
||||
.createQueryBuilder('bid')
|
||||
.select('DISTINCT bid.source')
|
||||
.select('DISTINCT bid.source', 'source')
|
||||
.where('bid.source IS NOT NULL')
|
||||
.orderBy('bid.source', 'ASC')
|
||||
.getRawMany();
|
||||
return result.map((item: any) => item.source);
|
||||
.getRawMany<SourceResult>();
|
||||
return result.map((item) => item.source);
|
||||
}
|
||||
|
||||
async getRecentBids() {
|
||||
const thirtyDaysAgo = new Date();
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||
thirtyDaysAgo.setHours(0, 0, 0, 0);
|
||||
|
||||
|
||||
return this.bidRepository
|
||||
.createQueryBuilder('bid')
|
||||
.where('bid.publishDate >= :thirtyDaysAgo', { thirtyDaysAgo })
|
||||
@@ -81,7 +110,11 @@ export class BidsService {
|
||||
.getMany();
|
||||
}
|
||||
|
||||
async getBidsByDateRange(startDate?: string, endDate?: string, keywords?: string[]) {
|
||||
async getBidsByDateRange(
|
||||
startDate?: string,
|
||||
endDate?: string,
|
||||
keywords?: string[],
|
||||
) {
|
||||
const qb = this.bidRepository.createQueryBuilder('bid');
|
||||
|
||||
if (startDate) {
|
||||
@@ -97,13 +130,18 @@ export class BidsService {
|
||||
}
|
||||
|
||||
if (keywords && keywords.length > 0) {
|
||||
const keywordConditions = keywords.map((keyword, index) => {
|
||||
return `bid.title LIKE :keyword${index}`;
|
||||
}).join(' OR ');
|
||||
qb.andWhere(`(${keywordConditions})`, keywords.reduce((params, keyword, index) => {
|
||||
params[`keyword${index}`] = `%${keyword}%`;
|
||||
return params;
|
||||
}, {}));
|
||||
const keywordConditions = keywords
|
||||
.map((keyword, index) => {
|
||||
return `bid.title LIKE :keyword${index}`;
|
||||
})
|
||||
.join(' OR ');
|
||||
qb.andWhere(
|
||||
`(${keywordConditions})`,
|
||||
keywords.reduce((params, keyword, index) => {
|
||||
params[`keyword${index}`] = `%${keyword}%`;
|
||||
return params;
|
||||
}, {}),
|
||||
);
|
||||
}
|
||||
|
||||
return qb.orderBy('bid.publishDate', 'DESC').getMany();
|
||||
@@ -118,7 +156,7 @@ export class BidsService {
|
||||
return this.bidRepository.save(item);
|
||||
}
|
||||
|
||||
async getCrawlInfoAddStats() {
|
||||
async getCrawlInfoAddStats(): Promise<CrawlInfoAddStats[]> {
|
||||
// 获取每个来源的最新一次爬虫记录(按 createdAt 降序)
|
||||
const query = `
|
||||
SELECT
|
||||
@@ -136,15 +174,19 @@ export class BidsService {
|
||||
ORDER BY source ASC
|
||||
`;
|
||||
|
||||
const results = await this.crawlInfoRepository.query(query);
|
||||
const results =
|
||||
await this.crawlInfoRepository.query<CrawlInfoAddRawResult[]>(query);
|
||||
|
||||
return results.map((item: any) => ({
|
||||
source: item.source,
|
||||
count: item.count,
|
||||
return results.map((item) => ({
|
||||
source: String(item.source),
|
||||
count: Number(item.count),
|
||||
latestUpdate: item.latestUpdate,
|
||||
latestPublishDate: item.latestPublishDate,
|
||||
// 确保 error 字段正确处理:null 或空字符串都转换为 null,非空字符串保留
|
||||
error: item.error && item.error.trim() !== '' ? item.error : null,
|
||||
error:
|
||||
item.error && String(item.error).trim() !== ''
|
||||
? String(item.error)
|
||||
: null,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user