feat: 开发broker相关代码,开发全局代码
This commit is contained in:
222
apps/api/src/modules/broker/__tests__/broker.controller.spec.ts
Normal file
222
apps/api/src/modules/broker/__tests__/broker.controller.spec.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { BrokerController } from '../broker.controller';
|
||||
import { BrokerService } from '../broker.service';
|
||||
import { Broker } from '../broker.entity';
|
||||
import { CreateBrokerDto } from '../dto/create-broker.dto';
|
||||
import { BatchCreateBrokerDto } from '../dto/batch-create-broker.dto';
|
||||
|
||||
describe('BrokerController', () => {
|
||||
let controller: BrokerController;
|
||||
|
||||
const mockBrokerService = {
|
||||
create: jest.fn(),
|
||||
batchCreate: jest.fn(),
|
||||
findAll: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
update: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [BrokerController],
|
||||
providers: [
|
||||
{
|
||||
provide: BrokerService,
|
||||
useValue: mockBrokerService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<BrokerController>(BrokerController);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
const createBrokerDto: CreateBrokerDto = {
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 1,
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
const mockBroker: Broker = {
|
||||
brokerId: 1,
|
||||
...createBrokerDto,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
it('应该成功创建券商并返回 201 状态码', async () => {
|
||||
mockBrokerService.create.mockResolvedValue(mockBroker);
|
||||
|
||||
const result = await controller.create(createBrokerDto);
|
||||
|
||||
expect(result).toEqual(mockBroker);
|
||||
expect(mockBrokerService.create).toHaveBeenCalledWith(
|
||||
createBrokerDto,
|
||||
);
|
||||
expect(mockBrokerService.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该正确传递 DTO 到服务层', async () => {
|
||||
const dto: CreateBrokerDto = {
|
||||
brokerCode: 'ZSZQ',
|
||||
brokerName: '招商证券',
|
||||
region: 'CN',
|
||||
};
|
||||
|
||||
const mockResult: Broker = {
|
||||
brokerId: 2,
|
||||
...dto,
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mockBrokerService.create.mockResolvedValue(mockResult);
|
||||
|
||||
await controller.create(dto);
|
||||
|
||||
expect(mockBrokerService.create).toHaveBeenCalledWith(dto);
|
||||
expect(mockBrokerService.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
brokerCode: 'ZSZQ',
|
||||
brokerName: '招商证券',
|
||||
region: 'CN',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchCreate', () => {
|
||||
const batchCreateDto: BatchCreateBrokerDto = {
|
||||
brokers: [
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 1,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ZSZQ',
|
||||
brokerName: '招商证券',
|
||||
region: 'CN',
|
||||
sortOrder: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockBrokers: Broker[] = [
|
||||
{
|
||||
brokerId: 1,
|
||||
...batchCreateDto.brokers[0],
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
brokerId: 2,
|
||||
...batchCreateDto.brokers[1],
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
it('应该成功批量创建券商并返回 201 状态码', async () => {
|
||||
mockBrokerService.batchCreate.mockResolvedValue(mockBrokers);
|
||||
|
||||
const result = await controller.batchCreate(batchCreateDto);
|
||||
|
||||
expect(result).toEqual(mockBrokers);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(mockBrokerService.batchCreate).toHaveBeenCalledWith(
|
||||
batchCreateDto,
|
||||
);
|
||||
expect(mockBrokerService.batchCreate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该正确传递批量 DTO 到服务层', async () => {
|
||||
const dto: BatchCreateBrokerDto = {
|
||||
brokers: [
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockResult: Broker[] = [
|
||||
{
|
||||
brokerId: 1,
|
||||
...dto.brokers[0],
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
mockBrokerService.batchCreate.mockResolvedValue(mockResult);
|
||||
|
||||
await controller.batchCreate(dto);
|
||||
|
||||
expect(mockBrokerService.batchCreate).toHaveBeenCalledWith(dto);
|
||||
// 验证调用了 batchCreate,并且参数包含正确的数据
|
||||
expect(mockBrokerService.batchCreate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('应该处理空数组的情况', async () => {
|
||||
const emptyDto: BatchCreateBrokerDto = {
|
||||
brokers: [],
|
||||
};
|
||||
|
||||
mockBrokerService.batchCreate.mockResolvedValue([]);
|
||||
|
||||
const result = await controller.batchCreate(emptyDto);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(result).toHaveLength(0);
|
||||
expect(mockBrokerService.batchCreate).toHaveBeenCalledWith(
|
||||
emptyDto,
|
||||
);
|
||||
});
|
||||
|
||||
it('应该处理大量券商批量创建', async () => {
|
||||
const largeBatchDto: BatchCreateBrokerDto = {
|
||||
brokers: Array.from({ length: 10 }, (_, i) => ({
|
||||
brokerCode: `CODE${i}`,
|
||||
brokerName: `券商${i}`,
|
||||
region: 'CN',
|
||||
})),
|
||||
};
|
||||
|
||||
const mockLargeResult: Broker[] = largeBatchDto.brokers.map(
|
||||
(broker, i) => ({
|
||||
brokerId: i + 1,
|
||||
...broker,
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
);
|
||||
|
||||
mockBrokerService.batchCreate.mockResolvedValue(mockLargeResult);
|
||||
|
||||
const result = await controller.batchCreate(largeBatchDto);
|
||||
|
||||
expect(result).toHaveLength(10);
|
||||
expect(mockBrokerService.batchCreate).toHaveBeenCalledWith(
|
||||
largeBatchDto,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
349
apps/api/src/modules/broker/__tests__/broker.service.spec.ts
Normal file
349
apps/api/src/modules/broker/__tests__/broker.service.spec.ts
Normal file
@@ -0,0 +1,349 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ConflictException } from '@nestjs/common';
|
||||
import { BrokerService } from '../broker.service';
|
||||
import { Broker } from '../broker.entity';
|
||||
import { CreateBrokerDto } from '../dto/create-broker.dto';
|
||||
import { BatchCreateBrokerDto } from '../dto/batch-create-broker.dto';
|
||||
|
||||
describe('BrokerService', () => {
|
||||
let service: BrokerService;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
let repository: Repository<Broker>;
|
||||
|
||||
const mockRepository = {
|
||||
create: jest.fn(),
|
||||
save: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
BrokerService,
|
||||
{
|
||||
provide: getRepositoryToken(Broker),
|
||||
useValue: mockRepository,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<BrokerService>(BrokerService);
|
||||
repository = module.get<Repository<Broker>>(getRepositoryToken(Broker));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
const createBrokerDto: CreateBrokerDto = {
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 1,
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
const mockBroker: Broker = {
|
||||
brokerId: 1,
|
||||
...createBrokerDto,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
it('应该成功创建一个券商', async () => {
|
||||
// 模拟数据库中不存在相同 code 和 region 的券商
|
||||
mockRepository.findOne.mockResolvedValue(null);
|
||||
mockRepository.create.mockReturnValue(mockBroker);
|
||||
mockRepository.save.mockResolvedValue(mockBroker);
|
||||
|
||||
const result = await service.create(createBrokerDto);
|
||||
|
||||
expect(result).toEqual(mockBroker);
|
||||
expect(mockRepository.findOne).toHaveBeenCalledTimes(2); // 检查 code 和 name
|
||||
expect(mockRepository.findOne).toHaveBeenCalledWith({
|
||||
where: {
|
||||
brokerCode: createBrokerDto.brokerCode,
|
||||
region: createBrokerDto.region,
|
||||
},
|
||||
});
|
||||
expect(mockRepository.create).toHaveBeenCalledWith({
|
||||
...createBrokerDto,
|
||||
sortOrder: 1,
|
||||
isActive: true,
|
||||
});
|
||||
expect(mockRepository.save).toHaveBeenCalledWith(mockBroker);
|
||||
});
|
||||
|
||||
it('应该使用默认值当 sortOrder 和 isActive 未提供时', async () => {
|
||||
const dtoWithoutDefaults: CreateBrokerDto = {
|
||||
brokerCode: 'ZSZQ',
|
||||
brokerName: '招商证券',
|
||||
region: 'CN',
|
||||
};
|
||||
|
||||
const mockBrokerWithDefaults: Broker = {
|
||||
brokerId: 2,
|
||||
...dtoWithoutDefaults,
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mockRepository.findOne.mockResolvedValue(null);
|
||||
mockRepository.create.mockReturnValue(mockBrokerWithDefaults);
|
||||
mockRepository.save.mockResolvedValue(mockBrokerWithDefaults);
|
||||
|
||||
const result = await service.create(dtoWithoutDefaults);
|
||||
|
||||
expect(result.sortOrder).toBe(0);
|
||||
expect(result.isActive).toBe(true);
|
||||
expect(mockRepository.create).toHaveBeenCalledWith({
|
||||
...dtoWithoutDefaults,
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('应该抛出 ConflictException 当 broker_code 已存在时', async () => {
|
||||
const existingBroker: Broker = {
|
||||
brokerId: 1,
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mockRepository.findOne.mockResolvedValueOnce(existingBroker);
|
||||
|
||||
await expect(service.create(createBrokerDto)).rejects.toThrow(
|
||||
ConflictException,
|
||||
);
|
||||
|
||||
expect(mockRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('应该抛出 ConflictException 当 broker_name 已存在时', async () => {
|
||||
mockRepository.findOne
|
||||
.mockResolvedValueOnce(null) // 第一次检查 code,不存在
|
||||
.mockResolvedValueOnce({
|
||||
// 第二次检查 name,已存在
|
||||
brokerId: 1,
|
||||
brokerCode: 'OTHER',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
await expect(service.create(createBrokerDto)).rejects.toThrow(
|
||||
ConflictException,
|
||||
);
|
||||
|
||||
expect(mockRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchCreate', () => {
|
||||
const batchCreateDto: BatchCreateBrokerDto = {
|
||||
brokers: [
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 1,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ZSZQ',
|
||||
brokerName: '招商证券',
|
||||
region: 'CN',
|
||||
sortOrder: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockBrokers: Broker[] = [
|
||||
{
|
||||
brokerId: 1,
|
||||
...batchCreateDto.brokers[0],
|
||||
brokerImage: 'https://example.com/broker1.jpg',
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
brokerId: 2,
|
||||
...batchCreateDto.brokers[1],
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
it('应该成功批量创建券商', async () => {
|
||||
// 模拟数据库中不存在这些券商
|
||||
mockRepository.find.mockResolvedValue([]);
|
||||
mockRepository.create
|
||||
.mockReturnValueOnce(mockBrokers[0])
|
||||
.mockReturnValueOnce(mockBrokers[1]);
|
||||
mockRepository.save.mockResolvedValue(mockBrokers);
|
||||
|
||||
const result = await service.batchCreate(batchCreateDto);
|
||||
|
||||
expect(result).toEqual(mockBrokers);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(mockRepository.create).toHaveBeenCalledTimes(2);
|
||||
expect(mockRepository.save).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
brokerCode: 'HTZQ',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
brokerCode: 'ZSZQ',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('应该为每个券商设置默认值', async () => {
|
||||
const dtoWithoutDefaults: BatchCreateBrokerDto = {
|
||||
brokers: [
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockBroker: Broker = {
|
||||
brokerId: 1,
|
||||
...dtoWithoutDefaults.brokers[0],
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mockRepository.find.mockResolvedValue([]);
|
||||
mockRepository.create.mockReturnValue(mockBroker);
|
||||
mockRepository.save.mockResolvedValue([mockBroker]);
|
||||
|
||||
const result = await service.batchCreate(dtoWithoutDefaults);
|
||||
|
||||
expect(result[0].sortOrder).toBe(0);
|
||||
expect(result[0].isActive).toBe(true);
|
||||
});
|
||||
|
||||
it('应该抛出 ConflictException 当批量数据中有已存在的券商时', async () => {
|
||||
const existingBroker: Broker = {
|
||||
brokerId: 1,
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
mockRepository.find.mockResolvedValue([existingBroker]);
|
||||
|
||||
await expect(service.batchCreate(batchCreateDto)).rejects.toThrow(
|
||||
ConflictException,
|
||||
);
|
||||
await expect(service.batchCreate(batchCreateDto)).rejects.toThrow(
|
||||
expect.stringContaining('already exist'),
|
||||
);
|
||||
|
||||
expect(mockRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('应该抛出 ConflictException 当批量数据内部有重复的 code+region 组合时', async () => {
|
||||
const duplicateDto: BatchCreateBrokerDto = {
|
||||
brokers: [
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
},
|
||||
{
|
||||
brokerCode: 'HTZQ', // 重复的 code
|
||||
brokerName: '华泰证券2',
|
||||
region: 'CN', // 相同的 region
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mockRepository.find.mockResolvedValue([]);
|
||||
|
||||
await expect(service.batchCreate(duplicateDto)).rejects.toThrow(
|
||||
ConflictException,
|
||||
);
|
||||
await expect(service.batchCreate(duplicateDto)).rejects.toThrow(
|
||||
'Duplicate broker_code and region combinations in batch data',
|
||||
);
|
||||
|
||||
expect(mockRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('应该成功创建不同地区的相同 code', async () => {
|
||||
const differentRegionDto: BatchCreateBrokerDto = {
|
||||
brokers: [
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
},
|
||||
{
|
||||
brokerCode: 'HTZQ', // 相同的 code
|
||||
brokerName: 'Huatai Securities',
|
||||
region: 'US', // 不同的 region
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockBrokersDifferentRegion: Broker[] = [
|
||||
{
|
||||
brokerId: 1,
|
||||
...differentRegionDto.brokers[0],
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{
|
||||
brokerId: 2,
|
||||
...differentRegionDto.brokers[1],
|
||||
sortOrder: 0,
|
||||
isActive: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
mockRepository.find.mockResolvedValue([]);
|
||||
mockRepository.create
|
||||
.mockReturnValueOnce(mockBrokersDifferentRegion[0])
|
||||
.mockReturnValueOnce(mockBrokersDifferentRegion[1]);
|
||||
mockRepository.save.mockResolvedValue(mockBrokersDifferentRegion);
|
||||
|
||||
const result = await service.batchCreate(differentRegionDto);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].region).toBe('CN');
|
||||
expect(result[1].region).toBe('US');
|
||||
expect(mockRepository.save).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
141
apps/api/src/modules/broker/broker.controller.ts
Normal file
141
apps/api/src/modules/broker/broker.controller.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
Query,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
|
||||
import { BrokerService } from './broker.service';
|
||||
import { CreateBrokerDto } from './dto/create-broker.dto';
|
||||
import { UpdateBrokerDto } from './dto/update-broker.dto';
|
||||
import { QueryBrokerDto } from './dto/query-broker.dto';
|
||||
import { BatchCreateBrokerDto } from './dto/batch-create-broker.dto';
|
||||
import { Broker } from './broker.entity';
|
||||
|
||||
@ApiTags('broker')
|
||||
@Controller('broker')
|
||||
export class BrokerController {
|
||||
constructor(private readonly brokerService: BrokerService) {}
|
||||
|
||||
/**
|
||||
* 单独创建 broker
|
||||
*/
|
||||
@Post()
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: '创建券商', description: '创建单个券商信息' })
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: '创建成功',
|
||||
type: Broker,
|
||||
})
|
||||
@ApiResponse({ status: 400, description: '请求参数错误' })
|
||||
@ApiResponse({ status: 409, description: '券商代码或名称已存在' })
|
||||
create(@Body() createBrokerDto: CreateBrokerDto): Promise<Broker> {
|
||||
return this.brokerService.create(createBrokerDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建 broker
|
||||
*/
|
||||
@Post('batch')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({
|
||||
summary: '批量创建券商',
|
||||
description: '一次性创建多个券商信息',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: '批量创建成功',
|
||||
type: [Broker],
|
||||
})
|
||||
@ApiResponse({ status: 400, description: '请求参数错误' })
|
||||
@ApiResponse({ status: 409, description: '存在重复的券商代码或名称' })
|
||||
batchCreate(
|
||||
@Body() batchCreateBrokerDto: BatchCreateBrokerDto,
|
||||
): Promise<Broker[]> {
|
||||
return this.brokerService.batchCreate(batchCreateBrokerDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 broker(支持多种查询条件)
|
||||
* 支持按 broker_id、broker_code、broker_name、region 查询
|
||||
* 返回一个或多个 broker
|
||||
*/
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: '查询券商列表',
|
||||
description: '支持按多个条件查询券商,支持分页和排序',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '查询成功',
|
||||
type: [Broker],
|
||||
})
|
||||
findAll(@Query() queryDto: QueryBrokerDto): Promise<Broker[]> {
|
||||
return this.brokerService.findAll(queryDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 查询单个 broker
|
||||
*/
|
||||
@Get(':id')
|
||||
@ApiOperation({
|
||||
summary: '根据ID查询券商',
|
||||
description: '根据券商ID获取详细信息',
|
||||
})
|
||||
@ApiParam({ name: 'id', description: '券商ID', type: Number })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '查询成功',
|
||||
type: Broker,
|
||||
})
|
||||
@ApiResponse({ status: 404, description: '券商不存在' })
|
||||
findOne(@Param('id') id: string): Promise<Broker> {
|
||||
return this.brokerService.findOne(+id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 broker
|
||||
*/
|
||||
@Patch(':id')
|
||||
@ApiOperation({
|
||||
summary: '更新券商',
|
||||
description: '更新券商的部分或全部信息',
|
||||
})
|
||||
@ApiParam({ name: 'id', description: '券商ID', type: Number })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '更新成功',
|
||||
type: Broker,
|
||||
})
|
||||
@ApiResponse({ status: 404, description: '券商不存在' })
|
||||
@ApiResponse({ status: 409, description: '更新后的券商代码或名称已存在' })
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() updateBrokerDto: UpdateBrokerDto,
|
||||
): Promise<Broker> {
|
||||
return this.brokerService.update(+id, updateBrokerDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 broker
|
||||
*/
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@ApiOperation({
|
||||
summary: '删除券商',
|
||||
description: '根据券商ID删除券商信息',
|
||||
})
|
||||
@ApiParam({ name: 'id', description: '券商ID', type: Number })
|
||||
@ApiResponse({ status: 204, description: '删除成功' })
|
||||
@ApiResponse({ status: 404, description: '券商不存在' })
|
||||
remove(@Param('id') id: string): Promise<void> {
|
||||
return this.brokerService.remove(+id);
|
||||
}
|
||||
}
|
||||
96
apps/api/src/modules/broker/broker.entity.ts
Normal file
96
apps/api/src/modules/broker/broker.entity.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
} from 'typeorm';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
@Entity('broker')
|
||||
export class Broker {
|
||||
@ApiProperty({ description: '券商ID', example: 1 })
|
||||
@PrimaryGeneratedColumn({ name: 'broker_id' })
|
||||
brokerId: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: '券商代码',
|
||||
example: 'HTZQ',
|
||||
maxLength: 50,
|
||||
})
|
||||
@Column({ name: 'broker_code', type: 'varchar', length: 50 })
|
||||
@Index()
|
||||
brokerCode: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '券商名称',
|
||||
example: '华泰证券',
|
||||
maxLength: 100,
|
||||
})
|
||||
@Column({ name: 'broker_name', type: 'varchar', length: 100 })
|
||||
@Index()
|
||||
brokerName: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '券商图片地址',
|
||||
example: 'https://example.com/broker-image.jpg',
|
||||
maxLength: 200,
|
||||
})
|
||||
@Column({
|
||||
name: 'broker_image',
|
||||
type: 'varchar',
|
||||
length: 200,
|
||||
nullable: true,
|
||||
})
|
||||
brokerImage?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '地区/国家代码',
|
||||
example: 'CN',
|
||||
enum: ['CN', 'US', 'HK', 'SG', 'JP', 'UK', 'AU', 'CA', 'OTHER'],
|
||||
default: 'CN',
|
||||
})
|
||||
@Column({ name: 'region', type: 'varchar', length: 50, default: 'CN' })
|
||||
region: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '排序顺序',
|
||||
example: 1,
|
||||
default: 0,
|
||||
})
|
||||
@Column({
|
||||
name: 'sort_order',
|
||||
type: 'integer',
|
||||
default: 0,
|
||||
nullable: false,
|
||||
})
|
||||
sortOrder?: number; // 可选,数据库有默认值
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '是否启用',
|
||||
example: true,
|
||||
default: true,
|
||||
})
|
||||
@Column({
|
||||
name: 'is_active',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
nullable: false,
|
||||
})
|
||||
isActive?: boolean; // 可选,数据库有默认值
|
||||
|
||||
@ApiProperty({
|
||||
description: '创建时间',
|
||||
example: '2024-01-01T00:00:00.000Z',
|
||||
})
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty({
|
||||
description: '更新时间',
|
||||
example: '2024-01-01T00:00:00.000Z',
|
||||
})
|
||||
@UpdateDateColumn({ name: 'updated_at' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
13
apps/api/src/modules/broker/broker.module.ts
Normal file
13
apps/api/src/modules/broker/broker.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { BrokerService } from './broker.service';
|
||||
import { BrokerController } from './broker.controller';
|
||||
import { Broker } from './broker.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Broker])],
|
||||
controllers: [BrokerController],
|
||||
providers: [BrokerService],
|
||||
exports: [BrokerService],
|
||||
})
|
||||
export class BrokerModule {}
|
||||
256
apps/api/src/modules/broker/broker.service.ts
Normal file
256
apps/api/src/modules/broker/broker.service.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, FindOptionsWhere } from 'typeorm';
|
||||
import { Broker } from './broker.entity';
|
||||
import { CreateBrokerDto } from './dto/create-broker.dto';
|
||||
import { UpdateBrokerDto } from './dto/update-broker.dto';
|
||||
import { QueryBrokerDto } from './dto/query-broker.dto';
|
||||
import { BatchCreateBrokerDto } from './dto/batch-create-broker.dto';
|
||||
|
||||
@Injectable()
|
||||
export class BrokerService {
|
||||
constructor(
|
||||
@InjectRepository(Broker)
|
||||
private readonly brokerRepository: Repository<Broker>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 单独创建 broker
|
||||
*/
|
||||
async create(createBrokerDto: CreateBrokerDto): Promise<Broker> {
|
||||
// 检查同一地区的 broker_code 是否已存在
|
||||
const existingByCode = await this.brokerRepository.findOne({
|
||||
where: {
|
||||
brokerCode: createBrokerDto.brokerCode,
|
||||
region: createBrokerDto.region,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingByCode) {
|
||||
throw new ConflictException(
|
||||
`Broker with code "${createBrokerDto.brokerCode}" already exists in region "${createBrokerDto.region}"`,
|
||||
);
|
||||
}
|
||||
|
||||
// 检查同一地区的 broker_name 是否已存在
|
||||
const existingByName = await this.brokerRepository.findOne({
|
||||
where: {
|
||||
brokerName: createBrokerDto.brokerName,
|
||||
region: createBrokerDto.region,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingByName) {
|
||||
throw new ConflictException(
|
||||
`Broker with name "${createBrokerDto.brokerName}" already exists in region "${createBrokerDto.region}"`,
|
||||
);
|
||||
}
|
||||
|
||||
const broker = this.brokerRepository.create({
|
||||
...createBrokerDto,
|
||||
sortOrder: createBrokerDto.sortOrder ?? 0,
|
||||
isActive: createBrokerDto.isActive ?? true,
|
||||
});
|
||||
|
||||
return this.brokerRepository.save(broker);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建 broker
|
||||
*/
|
||||
async batchCreate(
|
||||
batchCreateBrokerDto: BatchCreateBrokerDto,
|
||||
): Promise<Broker[]> {
|
||||
const brokers = batchCreateBrokerDto.brokers.map((dto) =>
|
||||
this.brokerRepository.create({
|
||||
...dto,
|
||||
sortOrder: dto.sortOrder ?? 0,
|
||||
isActive: dto.isActive ?? true,
|
||||
}),
|
||||
);
|
||||
|
||||
// 检查是否有重复的 broker_code + region 组合
|
||||
const codeRegionPairs = brokers.map((b) => ({
|
||||
brokerCode: b.brokerCode,
|
||||
region: b.region,
|
||||
}));
|
||||
|
||||
const existingBrokers = await this.brokerRepository.find({
|
||||
where: codeRegionPairs.map((pair) => ({
|
||||
brokerCode: pair.brokerCode,
|
||||
region: pair.region,
|
||||
})),
|
||||
});
|
||||
|
||||
if (existingBrokers.length > 0) {
|
||||
const conflicts = existingBrokers.map(
|
||||
(b) => `${b.brokerCode} in ${b.region}`,
|
||||
);
|
||||
throw new ConflictException(
|
||||
`The following brokers already exist: ${conflicts.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 检查批量数据内部是否有重复
|
||||
const uniquePairs = new Set(
|
||||
codeRegionPairs.map((p) => `${p.brokerCode}-${p.region}`),
|
||||
);
|
||||
if (uniquePairs.size !== codeRegionPairs.length) {
|
||||
throw new ConflictException(
|
||||
'Duplicate broker_code and region combinations in batch data',
|
||||
);
|
||||
}
|
||||
|
||||
return this.brokerRepository.save(brokers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 broker(支持多种查询条件)
|
||||
*/
|
||||
async findAll(queryDto: QueryBrokerDto): Promise<Broker[]> {
|
||||
const where: FindOptionsWhere<Broker> = {};
|
||||
|
||||
if (queryDto.brokerId) {
|
||||
where.brokerId = queryDto.brokerId;
|
||||
}
|
||||
|
||||
if (queryDto.brokerCode) {
|
||||
where.brokerCode = queryDto.brokerCode;
|
||||
}
|
||||
|
||||
if (queryDto.brokerName) {
|
||||
where.brokerName = queryDto.brokerName;
|
||||
}
|
||||
|
||||
if (queryDto.region) {
|
||||
where.region = queryDto.region;
|
||||
}
|
||||
|
||||
if (queryDto.isActive !== undefined) {
|
||||
where.isActive = queryDto.isActive;
|
||||
}
|
||||
|
||||
return this.brokerRepository.find({
|
||||
where,
|
||||
order: {
|
||||
sortOrder: 'ASC',
|
||||
brokerId: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 查询单个 broker
|
||||
*/
|
||||
async findOne(id: number): Promise<Broker> {
|
||||
const broker = await this.brokerRepository.findOne({
|
||||
where: { brokerId: id },
|
||||
});
|
||||
|
||||
if (!broker) {
|
||||
throw new NotFoundException(`Broker with ID ${id} not found`);
|
||||
}
|
||||
|
||||
return broker;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单个 broker(返回第一个匹配的)
|
||||
*/
|
||||
async findOneByCondition(queryDto: QueryBrokerDto): Promise<Broker> {
|
||||
const where: FindOptionsWhere<Broker> = {};
|
||||
|
||||
if (queryDto.brokerId) {
|
||||
where.brokerId = queryDto.brokerId;
|
||||
}
|
||||
|
||||
if (queryDto.brokerCode) {
|
||||
where.brokerCode = queryDto.brokerCode;
|
||||
}
|
||||
|
||||
if (queryDto.brokerName) {
|
||||
where.brokerName = queryDto.brokerName;
|
||||
}
|
||||
|
||||
if (queryDto.region) {
|
||||
where.region = queryDto.region;
|
||||
}
|
||||
|
||||
if (queryDto.isActive !== undefined) {
|
||||
where.isActive = queryDto.isActive;
|
||||
}
|
||||
|
||||
const broker = await this.brokerRepository.findOne({ where });
|
||||
|
||||
if (!broker) {
|
||||
throw new NotFoundException(
|
||||
'Broker not found with the given conditions',
|
||||
);
|
||||
}
|
||||
|
||||
return broker;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 broker
|
||||
*/
|
||||
async update(
|
||||
id: number,
|
||||
updateBrokerDto: UpdateBrokerDto,
|
||||
): Promise<Broker> {
|
||||
const broker = await this.findOne(id);
|
||||
|
||||
// 如果更新 broker_code 或 region,检查是否冲突
|
||||
if ('brokerCode' in updateBrokerDto || 'region' in updateBrokerDto) {
|
||||
const newCode = updateBrokerDto.brokerCode ?? broker.brokerCode;
|
||||
const newRegion = updateBrokerDto.region ?? broker.region;
|
||||
|
||||
const existing = await this.brokerRepository.findOne({
|
||||
where: {
|
||||
brokerCode: newCode,
|
||||
region: newRegion,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing && existing.brokerId !== id) {
|
||||
throw new ConflictException(
|
||||
`Broker with code "${newCode}" already exists in region "${newRegion}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果更新 broker_name 或 region,检查是否冲突
|
||||
if ('brokerName' in updateBrokerDto || 'region' in updateBrokerDto) {
|
||||
const newName = updateBrokerDto.brokerName ?? broker.brokerName;
|
||||
const newRegion = updateBrokerDto.region ?? broker.region;
|
||||
|
||||
const existing = await this.brokerRepository.findOne({
|
||||
where: {
|
||||
brokerName: newName,
|
||||
region: newRegion,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing && existing.brokerId !== id) {
|
||||
throw new ConflictException(
|
||||
`Broker with name "${newName}" already exists in region "${newRegion}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(broker, updateBrokerDto);
|
||||
return this.brokerRepository.save(broker);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 broker
|
||||
*/
|
||||
async remove(id: number): Promise<void> {
|
||||
const broker = await this.findOne(id);
|
||||
await this.brokerRepository.remove(broker);
|
||||
}
|
||||
}
|
||||
165
apps/api/src/modules/broker/config/brokers.config.ts
Normal file
165
apps/api/src/modules/broker/config/brokers.config.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { CreateBrokerDto } from '../dto/create-broker.dto';
|
||||
|
||||
/**
|
||||
* 主要券商配置数据
|
||||
* 包含A股、港股和美股的主要券商信息
|
||||
*/
|
||||
export const brokersConfig: CreateBrokerDto[] = [
|
||||
// A股券商
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 1,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ZSZQ',
|
||||
brokerName: '招商证券',
|
||||
region: 'CN',
|
||||
sortOrder: 2,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'GTJA',
|
||||
brokerName: '国泰君安',
|
||||
region: 'CN',
|
||||
sortOrder: 3,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ZXZQ',
|
||||
brokerName: '中信证券',
|
||||
region: 'CN',
|
||||
sortOrder: 4,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'HXZQ',
|
||||
brokerName: '海通证券',
|
||||
region: 'CN',
|
||||
sortOrder: 5,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'GFZQ',
|
||||
brokerName: '广发证券',
|
||||
region: 'CN',
|
||||
sortOrder: 6,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ZJZQ',
|
||||
brokerName: '中金公司',
|
||||
region: 'CN',
|
||||
sortOrder: 7,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'DFZQ',
|
||||
brokerName: '东方证券',
|
||||
region: 'CN',
|
||||
sortOrder: 8,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'XZQ',
|
||||
brokerName: '兴业证券',
|
||||
region: 'CN',
|
||||
sortOrder: 9,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'SWZQ',
|
||||
brokerName: '申万宏源',
|
||||
region: 'CN',
|
||||
sortOrder: 10,
|
||||
isActive: true,
|
||||
},
|
||||
// 港股券商 从21开始
|
||||
{
|
||||
brokerCode: 'FUTU',
|
||||
brokerName: '富途证券',
|
||||
region: 'HK',
|
||||
sortOrder: 21,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'TIGER',
|
||||
brokerName: '老虎证券',
|
||||
region: 'HK',
|
||||
sortOrder: 22,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'HSBC',
|
||||
brokerName: '汇丰银行',
|
||||
region: 'HK',
|
||||
sortOrder: 23,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'CITIC',
|
||||
brokerName: '中信里昂',
|
||||
region: 'HK',
|
||||
sortOrder: 24,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'UBS',
|
||||
brokerName: '瑞银证券',
|
||||
region: 'HK',
|
||||
sortOrder: 25,
|
||||
isActive: true,
|
||||
},
|
||||
// 美股券商 从31开始
|
||||
{
|
||||
brokerCode: 'IBKR',
|
||||
brokerName: 'Interactive Brokers',
|
||||
region: 'US',
|
||||
sortOrder: 31,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'SCHWAB',
|
||||
brokerName: 'Charles Schwab',
|
||||
region: 'US',
|
||||
sortOrder: 32,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'FIDELITY',
|
||||
brokerName: 'Fidelity',
|
||||
region: 'US',
|
||||
sortOrder: 33,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'TD',
|
||||
brokerName: 'TD Ameritrade',
|
||||
region: 'US',
|
||||
sortOrder: 34,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ETRADE',
|
||||
brokerName: 'E*TRADE',
|
||||
region: 'US',
|
||||
sortOrder: 35,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ROBINHOOD',
|
||||
brokerName: 'Robinhood',
|
||||
region: 'US',
|
||||
sortOrder: 36,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
brokerCode: 'WEBULL',
|
||||
brokerName: 'Webull',
|
||||
region: 'US',
|
||||
sortOrder: 37,
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
29
apps/api/src/modules/broker/dto/batch-create-broker.dto.ts
Normal file
29
apps/api/src/modules/broker/dto/batch-create-broker.dto.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { IsArray, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { CreateBrokerDto } from './create-broker.dto';
|
||||
|
||||
export class BatchCreateBrokerDto {
|
||||
@ApiProperty({
|
||||
description: '券商列表',
|
||||
type: [CreateBrokerDto],
|
||||
example: [
|
||||
{
|
||||
brokerCode: 'HTZQ',
|
||||
brokerName: '华泰证券',
|
||||
region: 'CN',
|
||||
sortOrder: 1,
|
||||
},
|
||||
{
|
||||
brokerCode: 'ZSZQ',
|
||||
brokerName: '招商证券',
|
||||
region: 'CN',
|
||||
sortOrder: 2,
|
||||
},
|
||||
],
|
||||
})
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CreateBrokerDto)
|
||||
brokers: CreateBrokerDto[];
|
||||
}
|
||||
76
apps/api/src/modules/broker/dto/create-broker.dto.ts
Normal file
76
apps/api/src/modules/broker/dto/create-broker.dto.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsNumber,
|
||||
IsBoolean,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
IsIn,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class CreateBrokerDto {
|
||||
@ApiProperty({
|
||||
description: '券商代码',
|
||||
example: 'HTZQ',
|
||||
maxLength: 50,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(1)
|
||||
@MaxLength(50)
|
||||
brokerCode: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '券商名称',
|
||||
example: '华泰证券',
|
||||
maxLength: 100,
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
brokerName: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: '地区/国家代码',
|
||||
example: 'CN',
|
||||
enum: ['CN', 'US', 'HK', 'SG', 'JP', 'UK', 'AU', 'CA', 'OTHER'],
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsIn(['CN', 'US', 'HK', 'SG', 'JP', 'UK', 'AU', 'CA', 'OTHER'])
|
||||
region: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '排序顺序',
|
||||
example: 1,
|
||||
minimum: 0,
|
||||
default: 0,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
sortOrder?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '是否启用',
|
||||
example: true,
|
||||
default: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '券商图片地址',
|
||||
example: 'https://example.com/broker-image.jpg',
|
||||
maxLength: 200,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
brokerImage?: string;
|
||||
}
|
||||
99
apps/api/src/modules/broker/dto/query-broker.dto.ts
Normal file
99
apps/api/src/modules/broker/dto/query-broker.dto.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsNumber,
|
||||
IsBoolean,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class QueryBrokerDto {
|
||||
@ApiPropertyOptional({
|
||||
description: '券商ID',
|
||||
example: 1,
|
||||
minimum: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
brokerId?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '券商代码',
|
||||
example: 'HTZQ',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
brokerCode?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '券商名称',
|
||||
example: '华泰证券',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
brokerName?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '地区/国家代码',
|
||||
example: 'CN',
|
||||
enum: ['CN', 'US', 'HK', 'SG', 'JP', 'UK', 'AU', 'CA', 'OTHER'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
region?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '是否启用',
|
||||
example: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Boolean)
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '页码',
|
||||
example: 1,
|
||||
minimum: 1,
|
||||
default: 1,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '每页数量',
|
||||
example: 10,
|
||||
minimum: 1,
|
||||
default: 10,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
limit?: number = 10;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '排序字段',
|
||||
example: 'createdAt',
|
||||
default: 'createdAt',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sortBy?: string = 'createdAt';
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: '排序方向',
|
||||
example: 'DESC',
|
||||
enum: ['ASC', 'DESC'],
|
||||
default: 'DESC',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sortOrder?: 'ASC' | 'DESC' = 'DESC';
|
||||
}
|
||||
4
apps/api/src/modules/broker/dto/update-broker.dto.ts
Normal file
4
apps/api/src/modules/broker/dto/update-broker.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateBrokerDto } from './create-broker.dto';
|
||||
|
||||
export class UpdateBrokerDto extends PartialType(CreateBrokerDto) {}
|
||||
4
apps/api/src/modules/user/user.module.ts
Normal file
4
apps/api/src/modules/user/user.module.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({})
|
||||
export class UserModule {}
|
||||
7
apps/api/src/modules/user/user.spec.ts
Normal file
7
apps/api/src/modules/user/user.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { User } from './user';
|
||||
|
||||
describe('User', () => {
|
||||
it('should be defined', () => {
|
||||
expect(new User()).toBeDefined();
|
||||
});
|
||||
});
|
||||
1
apps/api/src/modules/user/user.ts
Normal file
1
apps/api/src/modules/user/user.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class User {}
|
||||
Reference in New Issue
Block a user