添加命令
得益于NestJS框架的灵感,我们在实现Web Worker
服务框架@istock/iswork
时采用了类似架构。意味着如果你熟悉NestJS框架,那么上手命令开发将会非常自然且高效。
下面将引导您通过对接AKShare
的个股信息查询
接口,为A股市场添加一个个股信息查询的命令。
步骤 1: 准备工作
首先,确保您已经熟悉了AKShare的接口文档。我们将使用的是个股信息查询
接口,您可以访问它查看接口的详细文档。
步骤 2: 创建命令文件夹
在src/worker/domains/ag
目录下,根据命令的功能创建一个新的文件夹ggxxcx
(个股信息查询的拼音首字母缩写)。
注意
文件夹命名通常采用命令名称的拼音首字母
。
步骤 3: 定义数据模型
在ggxxcx
文件夹中,创建一个名为ggxxcx.model.ts
的文件,并根据AKShare文档中提供的输出参数定义数据模型,需要引入模型定义装饰器Model
。示例代码如下:
import { BaseModel, Model } from '@istock/iswork';
@Model('ggxxcx')
export class GgxxcxModel extends BaseModel {
item!: string;
value!: number | string;
}
注意
模型文件名称格式规范为[文件夹名].model.ts
;命令模型和命令名保持一致。比如上面模型名是ggxxcx
,模型类名是[模型名]Model
,后面命令名也应该定义为ggxxcx
。
步骤 4: 注册数据源
为了使我们的模型能够从AKShare获取数据,我们需要将其注册为Fetch
数据源。在src/worker/datasource-register.ts
文件中添加如下代码:
// ...
import { GgxxcxModel } from '@domains/ag/ggxxcx/ggxxcx.model.ts';
// ...
export const akShareFetchDataSourceModels = [
// ...
GgxxcxModel,
// ...
];
// ...
const akShareFetchDataSource = new DataSource<'fetch'>({
name: 'fetch',
type: 'fetch',
entities: akShareFetchDataSourceModels,
prefixUrl: import.meta.env.VITE_AKSHARE_API ?? '/api/akshare',
});
await akShareFetchDataSource.initialize();
// ...
步骤 5: 开发服务层
在ggxxcx
文件夹下创建ggxxcx.service.ts
文件,用于处理与AKShare接口的通信。 根据AKShare数据接口文档,个股信息查询
接口地址为stock_individual_info_em
,输入参数需要symbol
和timeout
,我需要引入GgxxcxModel
模型及服务注入装饰器Injectable
,示例代码如下:
import { Injectable, type TModelData } from '@istock/iswork';
import { GgxxcxModel } from './ggxxcx.model';
@Injectable()
export class GgxxcxService {
async getStockIndividualInfoEm(symbol: string) {
return await GgxxcxModel.run<Array<TModelData<GgxxcxModel>>>('/stock_individual_info_em', {
method: 'get',
query: {
symbol,
},
});
}
}
注意
服务文件名通常为[文件夹名].service.ts
。
步骤 6: 定义命令描述
接下来,我们需要为命令提供一个清晰的描述。在ggxxcx
文件夹中创建ggxxcx.cmd.ts
命令描述文件,并定义命令。示例代码如下:
import { getUnitOption, getStockCode, getStockName } from '@/worker/common';
export default {
个股信息查询: {
name: '个股信息查询',
cmd: 'ggxxcx', // 命令
usage: 'ggxxcx [-gpdm <股票代码>] [-gpmc <股票名称>]', // 命令用法
options: {
单位: getUnitOption({
default: '*:总市值·亿,*:流通市值·亿,*:总股本·亿,*:流通股·亿',
}), // 单位格式化
股票代码: getStockCode(), // 支持传入股票代码
股票名称: getStockName(), // 支持传入股票名称
},
source: {
title: '个股信息查询', // akshare标题
url: 'http://quote.eastmoney.com/concept/sh603777.html?from=classic', // akshare目标地址
},
description: '东方财富-个股-股票信息', // akshare描述
remarks: '限量: 单次返回指定 symbol 的个股信息', // akshare备注信息
example: 'ggxxcx -gpmc 贵州茅台 -dw', // 示例命令
},
};
命令描述对象是TControllerMethodCmdRoute
类型,类型详细如下:
export type TControllerMethodCmdRouteOptions = {
name: string; // 参数名称
parameter: string[]; // 参数键
parameterType: string[]; // 对应参数类型 string | number | boolean | array
description?: string; // 参数描述
default?: any; // 参数默认数据
optional?: boolean; // 参数是否可选
choices?: Array<string | number | boolean | null>; // 参数可选值
};
export type TControllerMethodCmdRoute = {
name: string; // 命令名称
cmd: string; // 命令
usage?: string; // 命令用法
shortDescription?: string; // 命令短介绍
description?: string; // 命令介绍
options?: Record<string, TControllerMethodCmdRouteOptions>; // 命令参数选项
subcommand?: TControllerMethodCmdRoute; // 子命令
arguments?: TControllerMethodCmdRouteOptions[]; // 命令参数
source?: { title?: string; url?: string };
remarks?: string;
example?: string;
};
注意
命令描述文件名通常为[文件夹名].cmd.ts或[文件夹名].cmd.json。 部分选项参数可以通过通用函数方法创建,函数方法放在@/worker/common
中,例如示例中的getStockCode
、getUnitOption
。
步骤 7: 创建控制器
现在,将命令模型、命令服务和命令描述绑定到命令控制器。创建ggxxcx.controller.ts
,创建GgxxcxController
控制器类,代码示例:
import { Controller } from '@istock/iswork';
import { GgxxcxModel } from './ggxxcx.model';
import { GgxxcxService } from './ggxxcx.service';
import cmdJson from './ggxxcx.cmd';
@Controller({
alias: 'ggxxcx', // 控制器别名
component: { name: 'ShTable' }, // 控制器所有方法返回的数据用表格组件展示
})
export class GgxxcxController {
// 依赖注入GgxxcxService服务
constructor(private readonly ggxxcxService: GgxxcxService) {}
}
接下来创建命令方法,需要使用CmdRoute
装饰器定义命名路由、Method
定义方法别名及相关信息、AKshareReturn
装饰器对AKshare接口返回的数据进行标准化输出。代码示例:
import { CmdRoute, CmdRouteOptions, Controller, Method } from '@istock/iswork';
import { AKshareReturn } from '@/worker/common';
import { GgxxcxModel } from './ggxxcx.model';
import { GgxxcxService } from './ggxxcx.service';
import cmdJson from './ggxxcx.cmd';
@Controller({
alias: 'ggxxcx', // 控制器别名
component: { name: 'ShTable' }, // 控制器所有方法返回的数据用表格组件展示
})
export class GgxxcxController {
// 依赖注入GgxxcxController服务
constructor(private readonly ggxxcxService: GgxxcxService) {}
@CmdRoute(cmdJson.个股信息查询) // 定义命令路由
@Method({
alias: cmdJson.个股信息查询.cmd, // 定义控制器方法别名
})
@AKshareReturn({
Model: GgxxcxModel, // 对应数据模型,方便把接口数据解析成二维数组
caption: cmdJson.个股信息查询.source.title, // 表格显示标题
})
async getStockIndividualInfoEm(@CmdRouteOptions(cmdJson.个股信息查询.options.股票代码) symbol: string) {
return await this.ggxxcxService.getStockIndividualInfoEm(symbol);
}
}
注意
命令控制器文件名通常为[文件夹名].controller.ts
。
步骤 8: 导入命令到应用域
到目前位置,src/worker/domains/ag/ggxxcx
文件夹下应该有ggxxcx.model.ts
、ggxxcx.service.ts
、ggxxcx.cmd.ts
、ggxxcx.controller.ts
这四个文件。 最后需要确保ggxxcx
命令被导入到ag
命令应用域下。在src/worker/domains/ag/ag.domain.ts
中添加对应的引用。代码示例:
import { Domain } from '@istock/iswork';
import { GgxxcxController } from './ggxxcx/ggxxcx.controller';
import { GgxxcxService } from './ggxxcx/ggxxcx.service';
@Domain({
name: 'ag',
viewName: 'A股',
providers: [
// ...
GgxxcxService,
// ...
],
controllers: [
// ...
GgxxcxController,
// ...
],
})
export class AgDomain {}
最后将ag
命令应用域导入到src/worker/domains/root.domain.ts
根命令应用域。
// ...
import { AgDomain } from './ag/ag.domain';
// ...
@Global()
@Domain({
name: 'root',
viewName: '根',
imports: [
// ...
AgDomain,
// ...
],
providers: [],
})
export class RootDomain {}
至此,我们已经走完整个命令开发流程,接下来我们需要测试该命令。
命令测试
进入A股
输入命令yyjr ag
进入A股应用。
查看命令文档
输入命令mlcz ggxxcx
查询ggxxcx
命令的文档。
测试命令
输入命令ggxxcx -gpmc 贵州茅台
查询贵州茅台
的个股信息。
输入命令ggxxcx -gpdm 600519
查询600519(贵州茅台)
的个股信息。
如果一切顺利,您将看到请求的数据以表格形式展示,至此,您已成功添加了一个新命令。