From 9257c78e724e56da025d6935edd9cb613d576f5f Mon Sep 17 00:00:00 2001 From: dmy Date: Fri, 16 Jan 2026 00:00:00 +0800 Subject: [PATCH] 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. --- src/bids/services/bid.service.ts | 37 +++++++++++++-------- src/common/utils/timezone.util.ts | 20 +++++++++++ src/crawler/services/bid-crawler.service.ts | 21 +++++++----- src/database/database.module.ts | 2 +- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/src/bids/services/bid.service.ts b/src/bids/services/bid.service.ts index 0887e9a..37fc847 100644 --- a/src/bids/services/bid.service.ts +++ b/src/bids/services/bid.service.ts @@ -7,6 +7,7 @@ import { getDaysAgo, setStartOfDay, setEndOfDay, + utcToBeijing, } from '../../common/utils/timezone.util'; interface FindAllQuery { @@ -23,7 +24,7 @@ interface SourceResult { export interface CrawlInfoAddStats { source: string; count: number; - latestUpdate: Date | string; + latestUpdate: Date | string | null; latestPublishDate: Date | string | null; error: string | null; } @@ -164,7 +165,7 @@ export class BidsService { count, latestPublishDate, error, - strftime('%Y-%m-%d %H:%M:%S', createdAt, '+8 hours') as latestUpdate + createdAt as latestUpdate FROM crawl_info_add WHERE (source, createdAt) IN ( SELECT source, MAX(createdAt) @@ -177,16 +178,26 @@ export class BidsService { const results = await this.crawlInfoRepository.query(query); - return results.map((item) => ({ - source: String(item.source), - count: Number(item.count), - latestUpdate: item.latestUpdate, - latestPublishDate: item.latestPublishDate, - // 确保 error 字段正确处理:null 或空字符串都转换为 null,非空字符串保留 - error: - item.error && String(item.error).trim() !== '' - ? String(item.error) - : null, - })); + 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), + count: Number(item.count), + latestUpdate: latestUpdateBeijing, + latestPublishDate: latestPublishDateBeijing, + // 确保 error 字段正确处理:null 或空字符串都转换为 null,非空字符串保留 + error: + item.error && String(item.error).trim() !== '' + ? String(item.error) + : null, + }; + }); } } diff --git a/src/common/utils/timezone.util.ts b/src/common/utils/timezone.util.ts index 408945b..3a7ab47 100644 --- a/src/common/utils/timezone.util.ts +++ b/src/common/utils/timezone.util.ts @@ -5,6 +5,26 @@ 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对象 * @returns Date 当前时间的东八区表示 diff --git a/src/crawler/services/bid-crawler.service.ts b/src/crawler/services/bid-crawler.service.ts index 927ede5..7e6526e 100644 --- a/src/crawler/services/bid-crawler.service.ts +++ b/src/crawler/services/bid-crawler.service.ts @@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import * as puppeteer from 'puppeteer'; import { BidsService } from '../../bids/services/bid.service'; +import { beijingToUtc } from '../../common/utils/timezone.util'; import { CrawlInfoAdd } from '../entities/crawl-info-add.entity'; import { ChdtpCrawler } from './chdtp_target'; import { ChngCrawler } from './chng_target'; @@ -146,19 +147,21 @@ export class BidCrawlerService { : null; for (const item of results) { + // 将北京时间转换为UTC存储 + const publishDateUtc = beijingToUtc(new Date(item.publishDate)); await this.bidsService.createOrUpdate({ title: item.title, url: item.url, - publishDate: item.publishDate, + publishDate: publishDateUtc, source: crawler.name, }); } - // 保存爬虫统计信息到数据库 + // 保存爬虫统计信息到数据库(将北京时间转为UTC) await this.saveCrawlInfo( crawler.name, results.length, - latestPublishDate, + latestPublishDate ? beijingToUtc(latestPublishDate) : null, ); } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); @@ -219,11 +222,11 @@ export class BidCrawlerService { }); } - // 更新爬虫统计信息到数据库 + // 更新爬虫统计信息到数据库(将北京时间转为UTC) await this.saveCrawlInfo( crawler.name, results.length, - latestPublishDate, + latestPublishDate ? beijingToUtc(latestPublishDate) : null, ); } catch (err) { const errorMessage = @@ -361,19 +364,21 @@ export class BidCrawlerService { : null; for (const item of results) { + // 将北京时间转换为UTC存储 + const publishDateUtc = beijingToUtc(new Date(item.publishDate)); await this.bidsService.createOrUpdate({ title: item.title, url: item.url, - publishDate: item.publishDate, + publishDate: publishDateUtc, source: targetCrawler.name, }); } - // 保存爬虫统计信息到数据库 + // 保存爬虫统计信息到数据库(将北京时间转为UTC) await this.saveCrawlInfo( targetCrawler.name, results.length, - latestPublishDate, + latestPublishDate ? beijingToUtc(latestPublishDate) : null, ); return { diff --git a/src/database/database.module.ts b/src/database/database.module.ts index 5b8ba65..c672314 100644 --- a/src/database/database.module.ts +++ b/src/database/database.module.ts @@ -20,7 +20,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; database: configService.get('DATABASE_NAME', 'bidding'), entities: [__dirname + '/../**/*.entity{.ts,.js}'], synchronize: false, - timezone: '+08:00', + timezone: 'Z', }), }), ],