style(FEAT-DS-001): 统一超短行情代码格式
This commit is contained in:
@@ -27,7 +27,9 @@ describe('AKTools HTTP 客户端', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('将 HTTP 错误转换为脱敏的客户端错误', async () => {
|
it('将 HTTP 错误转换为脱敏的客户端错误', async () => {
|
||||||
const fetchImpl = vi.fn().mockResolvedValue(new Response('secret upstream detail', { status: 500 }));
|
const fetchImpl = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(new Response('secret upstream detail', { status: 500 }));
|
||||||
const client = createAktoolsClient({
|
const client = createAktoolsClient({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
baseUrl: 'http://127.0.0.1:8080',
|
baseUrl: 'http://127.0.0.1:8080',
|
||||||
@@ -44,7 +46,9 @@ describe('AKTools HTTP 客户端', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('将请求超时转换为稳定错误', async () => {
|
it('将请求超时转换为稳定错误', async () => {
|
||||||
const fetchImpl = vi.fn().mockRejectedValue(Object.assign(new Error('aborted'), { name: 'AbortError' }));
|
const fetchImpl = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValue(Object.assign(new Error('aborted'), { name: 'AbortError' }));
|
||||||
const client = createAktoolsClient({
|
const client = createAktoolsClient({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
baseUrl: 'http://127.0.0.1:8080',
|
baseUrl: 'http://127.0.0.1:8080',
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import type { AktoolsRuntimeConfig } from '../../../../../libs/config/src/runtim
|
|||||||
import type { AktoolsDataClient, AktoolsRawRow } from './aktools.types';
|
import type { AktoolsDataClient, AktoolsRawRow } from './aktools.types';
|
||||||
|
|
||||||
export type AktoolsClientErrorKind =
|
export type AktoolsClientErrorKind =
|
||||||
| 'disabled'
|
'disabled' | 'timeout' | 'network' | 'protocol' | 'unavailable';
|
||||||
| 'timeout'
|
|
||||||
| 'network'
|
|
||||||
| 'protocol'
|
|
||||||
| 'unavailable';
|
|
||||||
|
|
||||||
export class AktoolsClientError extends Error {
|
export class AktoolsClientError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -58,14 +54,11 @@ export function createAktoolsClient({
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
try {
|
try {
|
||||||
const response = await fetchImpl(
|
const response = await fetchImpl(`${endpoint}${path}?date=${encodeURIComponent(tradeDate)}`, {
|
||||||
`${endpoint}${path}?date=${encodeURIComponent(tradeDate)}`,
|
method: 'GET',
|
||||||
{
|
headers: { accept: 'application/json' },
|
||||||
method: 'GET',
|
signal: controller.signal,
|
||||||
headers: { accept: 'application/json' },
|
});
|
||||||
signal: controller.signal,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const contentLength = response.headers.get('content-length');
|
const contentLength = response.headers.get('content-length');
|
||||||
if (contentLength && Number(contentLength) > maxResponseBytes) {
|
if (contentLength && Number(contentLength) > maxResponseBytes) {
|
||||||
throw new AktoolsClientError('protocol', 'AKTools 响应体超过安全上限');
|
throw new AktoolsClientError('protocol', 'AKTools 响应体超过安全上限');
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import { mapBrokenBoardPool, mapLimitUpPool, mapPreviousLimitUpPool } from './aktools.mapper';
|
||||||
mapBrokenBoardPool,
|
|
||||||
mapLimitUpPool,
|
|
||||||
mapPreviousLimitUpPool,
|
|
||||||
} from './aktools.mapper';
|
|
||||||
|
|
||||||
describe('AKTools 超短字段映射', () => {
|
describe('AKTools 超短字段映射', () => {
|
||||||
it('过滤缺失连板数的记录并生成连板梯队', () => {
|
it('过滤缺失连板数的记录并生成连板梯队', () => {
|
||||||
@@ -21,9 +17,7 @@ describe('AKTools 超短字段映射', () => {
|
|||||||
{ board: 2, count: 1 },
|
{ board: 2, count: 1 },
|
||||||
{ board: 3, count: 1 },
|
{ board: 3, count: 1 },
|
||||||
],
|
],
|
||||||
industries: [
|
industries: [{ name: '软件服务', count: 2 }],
|
||||||
{ name: '软件服务', count: 2 },
|
|
||||||
],
|
|
||||||
sealFundsAmount: 1.5,
|
sealFundsAmount: 1.5,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,9 +74,7 @@ export function mapBrokenBoardPool(rows: readonly unknown[]): BrokenBoardSummary
|
|||||||
return { count: rows.filter((row) => hasCode(row)).length };
|
return { count: rows.filter((row) => hasCode(row)).length };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapPreviousLimitUpPool(
|
export function mapPreviousLimitUpPool(rows: readonly AktoolsRawRow[]): PreviousLimitUpSummary {
|
||||||
rows: readonly AktoolsRawRow[],
|
|
||||||
): PreviousLimitUpSummary {
|
|
||||||
const changes = rows.flatMap((row) => {
|
const changes = rows.flatMap((row) => {
|
||||||
if (!hasCode(row)) return [];
|
if (!hasCode(row)) return [];
|
||||||
const change = toNumber(row['涨跌幅']);
|
const change = toNumber(row['涨跌幅']);
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
import { Controller, Get, Inject } from '@nestjs/common';
|
import { Controller, Get, Inject } from '@nestjs/common';
|
||||||
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import {
|
import { ShortTermConclusionResponseDto, ShortTermEmotionResponseDto } from './short-term.dto';
|
||||||
ShortTermConclusionResponseDto,
|
|
||||||
ShortTermEmotionResponseDto,
|
|
||||||
} from './short-term.dto';
|
|
||||||
import { ShortTermMarketService } from './short-term.service';
|
import { ShortTermMarketService } from './short-term.service';
|
||||||
|
|
||||||
@ApiTags('market-overview-short-term')
|
@ApiTags('market-overview-short-term')
|
||||||
@Controller('market-overview/short-term')
|
@Controller('market-overview/short-term')
|
||||||
export class ShortTermMarketController {
|
export class ShortTermMarketController {
|
||||||
constructor(
|
constructor(@Inject(ShortTermMarketService) private readonly service: ShortTermMarketService) {}
|
||||||
@Inject(ShortTermMarketService) private readonly service: ShortTermMarketService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Get('emotion')
|
@Get('emotion')
|
||||||
@ApiOkResponse({ type: ShortTermEmotionResponseDto })
|
@ApiOkResponse({ type: ShortTermEmotionResponseDto })
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ describe('短线行情服务', () => {
|
|||||||
|
|
||||||
it('组合涨停、炸板和昨日涨停数据并返回新鲜情绪快照', async () => {
|
it('组合涨停、炸板和昨日涨停数据并返回新鲜情绪快照', async () => {
|
||||||
const client = {
|
const client = {
|
||||||
getLimitUpPool: vi.fn().mockResolvedValue([
|
getLimitUpPool: vi
|
||||||
{ 代码: '000001', 连板数: 4, 所属行业: '软件服务', 封板资金: 100_000_000 },
|
.fn()
|
||||||
]),
|
.mockResolvedValue([
|
||||||
|
{ 代码: '000001', 连板数: 4, 所属行业: '软件服务', 封板资金: 100_000_000 },
|
||||||
|
]),
|
||||||
getBrokenBoardPool: vi.fn().mockResolvedValue([{ 代码: '000002' }]),
|
getBrokenBoardPool: vi.fn().mockResolvedValue([{ 代码: '000002' }]),
|
||||||
getPreviousLimitUpPool: vi.fn().mockResolvedValue([{ 代码: '000003', 涨跌幅: 2 }]),
|
getPreviousLimitUpPool: vi.fn().mockResolvedValue([{ 代码: '000003', 涨跌幅: 2 }]),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,9 +98,7 @@ function createConclusion(data: ShortTermEmotionDataDto): ShortTermConclusionDat
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ShortTermMarketService {
|
export class ShortTermMarketService {
|
||||||
constructor(
|
constructor(@Inject(AKTOOLS_CLIENT_PORT) private readonly aktoolsClient: AktoolsDataClient) {}
|
||||||
@Inject(AKTOOLS_CLIENT_PORT) private readonly aktoolsClient: AktoolsDataClient,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async getEmotion(): Promise<ShortTermEmotionResponseDto> {
|
async getEmotion(): Promise<ShortTermEmotionResponseDto> {
|
||||||
const tradeDate = getShanghaiDate(new Date());
|
const tradeDate = getShanghaiDate(new Date());
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import request from 'supertest';
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import { ShortTermMarketController } from '../src/market-overview/short-term.controller';
|
import { ShortTermMarketController } from '../src/market-overview/short-term.controller';
|
||||||
import { AKTOOLS_CLIENT_PORT, ShortTermMarketService } from '../src/market-overview/short-term.service';
|
import {
|
||||||
|
AKTOOLS_CLIENT_PORT,
|
||||||
|
ShortTermMarketService,
|
||||||
|
} from '../src/market-overview/short-term.service';
|
||||||
|
|
||||||
describe('短线行情 HTTP API', () => {
|
describe('短线行情 HTTP API', () => {
|
||||||
let app: INestApplication | undefined;
|
let app: INestApplication | undefined;
|
||||||
@@ -31,10 +34,7 @@ describe('短线行情 HTTP API', () => {
|
|||||||
};
|
};
|
||||||
const moduleRef = await Test.createTestingModule({
|
const moduleRef = await Test.createTestingModule({
|
||||||
controllers: [ShortTermMarketController],
|
controllers: [ShortTermMarketController],
|
||||||
providers: [
|
providers: [ShortTermMarketService, { provide: AKTOOLS_CLIENT_PORT, useValue: client }],
|
||||||
ShortTermMarketService,
|
|
||||||
{ provide: AKTOOLS_CLIENT_PORT, useValue: client },
|
|
||||||
],
|
|
||||||
}).compile();
|
}).compile();
|
||||||
app = moduleRef.createNestApplication();
|
app = moduleRef.createNestApplication();
|
||||||
app.setGlobalPrefix('api/v1');
|
app.setGlobalPrefix('api/v1');
|
||||||
@@ -61,10 +61,7 @@ describe('短线行情 HTTP API', () => {
|
|||||||
};
|
};
|
||||||
const moduleRef = await Test.createTestingModule({
|
const moduleRef = await Test.createTestingModule({
|
||||||
controllers: [ShortTermMarketController],
|
controllers: [ShortTermMarketController],
|
||||||
providers: [
|
providers: [ShortTermMarketService, { provide: AKTOOLS_CLIENT_PORT, useValue: client }],
|
||||||
ShortTermMarketService,
|
|
||||||
{ provide: AKTOOLS_CLIENT_PORT, useValue: client },
|
|
||||||
],
|
|
||||||
}).compile();
|
}).compile();
|
||||||
app = moduleRef.createNestApplication();
|
app = moduleRef.createNestApplication();
|
||||||
app.setGlobalPrefix('api/v1');
|
app.setGlobalPrefix('api/v1');
|
||||||
|
|||||||
@@ -60,7 +60,11 @@ describe('NestJS 三进程入口', () => {
|
|||||||
logLevel: 'debug',
|
logLevel: 'debug',
|
||||||
api: Object.freeze({ host: '127.0.0.1', port: 3000 }),
|
api: Object.freeze({ host: '127.0.0.1', port: 3000 }),
|
||||||
tdx: Object.freeze({ baseUrl: 'http://127.0.0.1:17710', timeoutMs: 5000 }),
|
tdx: Object.freeze({ baseUrl: 'http://127.0.0.1:17710', timeoutMs: 5000 }),
|
||||||
aktools: Object.freeze({ baseUrl: 'http://127.0.0.1:8080', enabled: false, timeoutMs: 10000 }),
|
aktools: Object.freeze({
|
||||||
|
baseUrl: 'http://127.0.0.1:8080',
|
||||||
|
enabled: false,
|
||||||
|
timeoutMs: 10000,
|
||||||
|
}),
|
||||||
redis: redisConfig,
|
redis: redisConfig,
|
||||||
mysql: mysqlConfig,
|
mysql: mysqlConfig,
|
||||||
ai: aiConfig,
|
ai: aiConfig,
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ export function useMarketOverview(
|
|||||||
funds.value,
|
funds.value,
|
||||||
shortTermEmotion.value,
|
shortTermEmotion.value,
|
||||||
shortTermConclusion.value,
|
shortTermConclusion.value,
|
||||||
].some(
|
].some((module) => module.status === 'loading'),
|
||||||
(module) => module.status === 'loading',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function setLoading() {
|
function setLoading() {
|
||||||
|
|||||||
Reference in New Issue
Block a user