|
|
@@ -1,9 +1,169 @@
|
|
|
import { GenericCrudService } from '@/server/utils/generic-crud.service';
|
|
|
-import { DataSource } from 'typeorm';
|
|
|
+import { DataSource, Repository } from 'typeorm';
|
|
|
import { StockData } from './stock-data.entity';
|
|
|
+import debug from 'debug';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+
|
|
|
+const log = {
|
|
|
+ api: debug('backend:api:stock'),
|
|
|
+ db: debug('backend:db:stock'),
|
|
|
+};
|
|
|
|
|
|
export class StockDataService extends GenericCrudService<StockData> {
|
|
|
constructor(dataSource: DataSource) {
|
|
|
super(dataSource, StockData);
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取股票历史数据
|
|
|
+ * 优先从数据库获取,如果没有则调用外部API
|
|
|
+ * @param code 股票代码
|
|
|
+ * @returns 股票历史数据
|
|
|
+ */
|
|
|
+ async getStockHistory(code: string = '001339'): Promise<any> {
|
|
|
+ try {
|
|
|
+ // 查询数据库中是否存在今天的数据
|
|
|
+ const today = dayjs().format('YYYY-MM-DD');
|
|
|
+ const existingData = await this.repository
|
|
|
+ .createQueryBuilder('stock')
|
|
|
+ .where('stock.code = :code', { code })
|
|
|
+ .andWhere('stock.updatedAt >= :today', { today: `${today} 00:00:00` })
|
|
|
+ .getOne();
|
|
|
+
|
|
|
+ if (existingData) {
|
|
|
+ log.db(`Found existing data for ${code} on ${today}`);
|
|
|
+ return existingData.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有今天的数据,调用外部API
|
|
|
+ log.api(`Fetching fresh data for ${code} from external API`);
|
|
|
+ const dh = 'dn'; // 固定值
|
|
|
+
|
|
|
+ const license = process.env.STOCK_API_LICENSE;
|
|
|
+ if (!license) {
|
|
|
+ throw new Error('STOCK_API_LICENSE environment variable not set');
|
|
|
+ }
|
|
|
+
|
|
|
+ const apiUrl = `http://api.mairui.club/hszbl/fsjy/${code}/${dh}/${license}`;
|
|
|
+ const response = await fetch(apiUrl, {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Accept': 'application/json'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`API request failed with status ${response.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const newData = await response.json();
|
|
|
+
|
|
|
+ // 更新或插入数据库
|
|
|
+ const stockData = new StockData();
|
|
|
+ stockData.code = code;
|
|
|
+ stockData.data = newData;
|
|
|
+
|
|
|
+ await this.repository
|
|
|
+ .createQueryBuilder()
|
|
|
+ .insert()
|
|
|
+ .values(stockData)
|
|
|
+ .orUpdate(
|
|
|
+ ['data', 'updatedAt'],
|
|
|
+ ['code']
|
|
|
+ )
|
|
|
+ .execute();
|
|
|
+
|
|
|
+ log.api(`Successfully saved fresh data for ${code}`);
|
|
|
+ return newData;
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ log.api('Error getting stock history:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取股票备忘录数据
|
|
|
+ * @param code 股票代码
|
|
|
+ * @returns 股票备忘录列表
|
|
|
+ */
|
|
|
+ async getStockMemos(code?: string): Promise<Array<{
|
|
|
+ date: string;
|
|
|
+ memo: string;
|
|
|
+ [key: string]: string;
|
|
|
+ }>> {
|
|
|
+ try {
|
|
|
+ if (!code) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const notes = await this.repository.manager.query(`
|
|
|
+ SELECT
|
|
|
+ note_date as date,
|
|
|
+ note as memo
|
|
|
+ FROM date_notes
|
|
|
+ WHERE code = ?
|
|
|
+ ORDER BY note_date ASC
|
|
|
+ `, [code]);
|
|
|
+
|
|
|
+ if (!notes || notes.length === 0) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 转换数据格式
|
|
|
+ const formattedNotes = notes.map((note: any) => ({
|
|
|
+ date: dayjs(note.date).format('YYYY-MM-DD'),
|
|
|
+ memo: note.memo,
|
|
|
+ 日期: dayjs(note.date).format('YYYY-MM-DD'),
|
|
|
+ 提示: note.memo
|
|
|
+ }));
|
|
|
+
|
|
|
+ log.db(`Retrieved ${formattedNotes.length} memos for ${code}`);
|
|
|
+ return formattedNotes;
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ log.db('Error fetching memo data:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取股票最新数据
|
|
|
+ * @param code 股票代码
|
|
|
+ * @returns 股票最新数据
|
|
|
+ */
|
|
|
+ async getLatestStockData(code: string): Promise<StockData | null> {
|
|
|
+ try {
|
|
|
+ const stockData = await this.repository
|
|
|
+ .createQueryBuilder('stock')
|
|
|
+ .where('stock.code = :code', { code })
|
|
|
+ .orderBy('stock.updatedAt', 'DESC')
|
|
|
+ .getOne();
|
|
|
+
|
|
|
+ return stockData;
|
|
|
+ } catch (error) {
|
|
|
+ log.db('Error fetching latest stock data:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取多个股票的历史数据
|
|
|
+ * @param codes 股票代码数组
|
|
|
+ * @returns 股票历史数据映射
|
|
|
+ */
|
|
|
+ async getMultipleStockHistories(codes: string[]): Promise<Record<string, any>> {
|
|
|
+ const results: Record<string, any> = {};
|
|
|
+
|
|
|
+ for (const code of codes) {
|
|
|
+ try {
|
|
|
+ results[code] = await this.getStockHistory(code);
|
|
|
+ } catch (error) {
|
|
|
+ log.api(`Error fetching data for ${code}:`, error);
|
|
|
+ results[code] = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return results;
|
|
|
+ }
|
|
|
}
|