feat(timezone): implement UTC and Beijing time conversion utilities and update bid handling

- Add functions to convert between UTC and Beijing time in timezone utility.
- Update BidsService to return latest update and publish dates in Beijing time.
- Modify BidCrawlerService to store publish dates in UTC format.
- Change database timezone configuration to UTC for consistency.
This commit is contained in:
dmy
2026-01-16 00:00:00 +08:00
parent e8beeec2b9
commit 9257c78e72
4 changed files with 58 additions and 22 deletions

View File

@@ -7,6 +7,7 @@ import {
getDaysAgo, getDaysAgo,
setStartOfDay, setStartOfDay,
setEndOfDay, setEndOfDay,
utcToBeijing,
} from '../../common/utils/timezone.util'; } from '../../common/utils/timezone.util';
interface FindAllQuery { interface FindAllQuery {
@@ -23,7 +24,7 @@ interface SourceResult {
export interface CrawlInfoAddStats { export interface CrawlInfoAddStats {
source: string; source: string;
count: number; count: number;
latestUpdate: Date | string; latestUpdate: Date | string | null;
latestPublishDate: Date | string | null; latestPublishDate: Date | string | null;
error: string | null; error: string | null;
} }
@@ -164,7 +165,7 @@ export class BidsService {
count, count,
latestPublishDate, latestPublishDate,
error, error,
strftime('%Y-%m-%d %H:%M:%S', createdAt, '+8 hours') as latestUpdate createdAt as latestUpdate
FROM crawl_info_add FROM crawl_info_add
WHERE (source, createdAt) IN ( WHERE (source, createdAt) IN (
SELECT source, MAX(createdAt) SELECT source, MAX(createdAt)
@@ -177,16 +178,26 @@ export class BidsService {
const results = const results =
await this.crawlInfoRepository.query<CrawlInfoAddRawResult[]>(query); await this.crawlInfoRepository.query<CrawlInfoAddRawResult[]>(query);
return results.map((item) => ({ return results.map((item) => {
// 将UTC时间转换为北京时间显示
const latestUpdateBeijing = item.latestUpdate
? utcToBeijing(new Date(item.latestUpdate))
: null;
const latestPublishDateBeijing = item.latestPublishDate
? utcToBeijing(new Date(item.latestPublishDate))
: null;
return {
source: String(item.source), source: String(item.source),
count: Number(item.count), count: Number(item.count),
latestUpdate: item.latestUpdate, latestUpdate: latestUpdateBeijing,
latestPublishDate: item.latestPublishDate, latestPublishDate: latestPublishDateBeijing,
// 确保 error 字段正确处理null 或空字符串都转换为 null非空字符串保留 // 确保 error 字段正确处理null 或空字符串都转换为 null非空字符串保留
error: error:
item.error && String(item.error).trim() !== '' item.error && String(item.error).trim() !== ''
? String(item.error) ? String(item.error)
: null, : null,
})); };
});
} }
} }

View File

@@ -5,6 +5,26 @@
const TIMEZONE_OFFSET = 8 * 60 * 60 * 1000; const TIMEZONE_OFFSET = 8 * 60 * 60 * 1000;
/**
* 将北京时间(+8)转换为UTC
* 用于将爬取的北京时间字符串解析后的Date对象转为UTC存储
* @param date 北京时间的Date对象
* @returns UTC时间的Date对象
*/
export function beijingToUtc(date: Date): Date {
return new Date(date.getTime() - TIMEZONE_OFFSET);
}
/**
* 将UTC时间转换为北京时间(+8)
* 用于将数据库中的UTC时间转为北京时间显示
* @param date UTC时间的Date对象
* @returns 北京时间的Date对象
*/
export function utcToBeijing(date: Date): Date {
return new Date(date.getTime() + TIMEZONE_OFFSET);
}
/** /**
* 获取当前时间的东八区Date对象 * 获取当前时间的东八区Date对象
* @returns Date 当前时间的东八区表示 * @returns Date 当前时间的东八区表示

View File

@@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import * as puppeteer from 'puppeteer'; import * as puppeteer from 'puppeteer';
import { BidsService } from '../../bids/services/bid.service'; import { BidsService } from '../../bids/services/bid.service';
import { beijingToUtc } from '../../common/utils/timezone.util';
import { CrawlInfoAdd } from '../entities/crawl-info-add.entity'; import { CrawlInfoAdd } from '../entities/crawl-info-add.entity';
import { ChdtpCrawler } from './chdtp_target'; import { ChdtpCrawler } from './chdtp_target';
import { ChngCrawler } from './chng_target'; import { ChngCrawler } from './chng_target';
@@ -146,19 +147,21 @@ export class BidCrawlerService {
: null; : null;
for (const item of results) { for (const item of results) {
// 将北京时间转换为UTC存储
const publishDateUtc = beijingToUtc(new Date(item.publishDate));
await this.bidsService.createOrUpdate({ await this.bidsService.createOrUpdate({
title: item.title, title: item.title,
url: item.url, url: item.url,
publishDate: item.publishDate, publishDate: publishDateUtc,
source: crawler.name, source: crawler.name,
}); });
} }
// 保存爬虫统计信息到数据库 // 保存爬虫统计信息到数据库将北京时间转为UTC
await this.saveCrawlInfo( await this.saveCrawlInfo(
crawler.name, crawler.name,
results.length, results.length,
latestPublishDate, latestPublishDate ? beijingToUtc(latestPublishDate) : null,
); );
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err); const errorMessage = err instanceof Error ? err.message : String(err);
@@ -219,11 +222,11 @@ export class BidCrawlerService {
}); });
} }
// 更新爬虫统计信息到数据库 // 更新爬虫统计信息到数据库将北京时间转为UTC
await this.saveCrawlInfo( await this.saveCrawlInfo(
crawler.name, crawler.name,
results.length, results.length,
latestPublishDate, latestPublishDate ? beijingToUtc(latestPublishDate) : null,
); );
} catch (err) { } catch (err) {
const errorMessage = const errorMessage =
@@ -361,19 +364,21 @@ export class BidCrawlerService {
: null; : null;
for (const item of results) { for (const item of results) {
// 将北京时间转换为UTC存储
const publishDateUtc = beijingToUtc(new Date(item.publishDate));
await this.bidsService.createOrUpdate({ await this.bidsService.createOrUpdate({
title: item.title, title: item.title,
url: item.url, url: item.url,
publishDate: item.publishDate, publishDate: publishDateUtc,
source: targetCrawler.name, source: targetCrawler.name,
}); });
} }
// 保存爬虫统计信息到数据库 // 保存爬虫统计信息到数据库将北京时间转为UTC
await this.saveCrawlInfo( await this.saveCrawlInfo(
targetCrawler.name, targetCrawler.name,
results.length, results.length,
latestPublishDate, latestPublishDate ? beijingToUtc(latestPublishDate) : null,
); );
return { return {

View File

@@ -20,7 +20,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
database: configService.get<string>('DATABASE_NAME', 'bidding'), database: configService.get<string>('DATABASE_NAME', 'bidding'),
entities: [__dirname + '/../**/*.entity{.ts,.js}'], entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: false, synchronize: false,
timezone: '+08:00', timezone: 'Z',
}), }),
}), }),
], ],