Skip to content

Commit

Permalink
Merge branch 'dev' into be/feature/get_my_rank
Browse files Browse the repository at this point in the history
  • Loading branch information
edder773 committed Nov 21, 2024
2 parents 4952676 + a51702c commit c69bd8b
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 80 deletions.
3 changes: 3 additions & 0 deletions apps/backend/src/global/successhandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const successMessage = {
GET_MAIL_SUCCESS: { code: 200, message: '메일 조회를 완료했습니다.' },
DELETE_MAIL_SUCCESS: { code: 200, message: '메일 삭제를 완료했습니다.' },
GET_MAIL_ALARM_SUCCESS: { code: 200, message: 'Catch alarm!.' },
GET_TRANSACTION_SUCCESS: { code: 200, message: '거래 내역 조회를 완료했습니다.' },
CREATE_ORDER_SUCCESS: { code: 201, message: '주문이 성공적으로 생성되었습니다.' },
DELETE_ORDER_SUCCESS: { code: 201, message: '주문이 성공적으로 삭제되었습니다.' },
BUY_LOTTO_SUCCESS: { code: 200, message: '로또 구매를 완료했습니다.' },
GET_TOP5_RANK_SUCCESS: { code: 200, message: '상위 5명을 조회했습니다.' },
GET_RANK_SUCCESS: { code: 200, message: '현재 랭킹을 조회했습니다.' }
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/market/dto/cropPrice.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface CropPrice {
crop: string;
crop: number;
price: number;
}
6 changes: 3 additions & 3 deletions apps/backend/src/market/market.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { MarketService } from './market.service';
export class MarketController {
constructor(private readonly marketService: MarketService) {}

@Get(':crop/price')
getPrice(@Param('crop') crop: string) {
@Get('price/:crop')
async getPrice(@Param('crop') crop: number) {
return this.marketService.getCropPrice(crop);
}

@Get('crop/prices')
getAllPrices() {
async getAllPrices() {
return this.marketService.getAllCropPrices();
}
}
3 changes: 2 additions & 1 deletion apps/backend/src/market/market.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MarketService } from './market.service';

@Module({
controllers: [MarketController],
providers: [MarketService]
providers: [MarketService],
exports: [MarketService]
})
export class MarketModule {}
19 changes: 15 additions & 4 deletions apps/backend/src/market/market.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,31 @@ export class MarketService {
constructor(@Inject('REDIS_CLIENT') private readonly redisClient: RedisClientType) {}

async setCropPrice(data: CropPrice): Promise<void> {
await this.redisClient.hSet(this.redisKey, data.crop, data.price.toString());
await this.redisClient.hSet(this.redisKey, data.crop.toString(), data.price.toString());
}

async getCropPrice(crop: string): Promise<CropPrice | null> {
const price = await this.redisClient.hGet(this.redisKey, crop);
async getCropPrice(crop: number): Promise<CropPrice | null> {
const price = await this.redisClient.hGet(this.redisKey, crop.toString());

if (!price) {
return null;
}
return { crop, price: parseFloat(price) };
}

async getCropIdFromName(crop: string): Promise<number> {
const cropId = await this.redisClient.hGet(this.redisKey, crop);
if (!cropId) {
return -1;
}
return parseInt(cropId);
}

async getAllCropPrices(): Promise<CropPrice[]> {
const prices = await this.redisClient.hGetAll(this.redisKey);
return Object.entries(prices).map(([crop, price]) => ({ crop, price: parseFloat(price) }));
return Object.entries(prices).map(([crop, price]) => ({
crop: parseInt(crop),
price: parseFloat(price)
}));
}
}
14 changes: 13 additions & 1 deletion apps/backend/src/order/dto/orderBook.dto.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { ApiProperty } from '@nestjs/swagger';
import { OrderType } from '../enums/orderType';
import { OrderType, TradingType } from '../enums/orderType';

export class OrderBookDto {
@ApiProperty({ description: '주문 ID', example: 1 })
orderId: number;

@ApiProperty({ description: '회원 ID', example: 15 })
memberId: number;

@ApiProperty({ description: '상품 ID', example: 1 })
cropId: number;

@ApiProperty({ description: '주문 유형', example: 'buy' })
orderType: OrderType;

@ApiProperty({ description: '오더 유형', example: 'limit' })
tradingType: TradingType;

@ApiProperty({ description: '가격', example: 50 })
price: number;

@ApiProperty({ description: '주문 수량', example: 150 })
quantity: number;

@ApiProperty({ description: '체결된 수량', example: 50 })
filledQuantity: number;

@ApiProperty({ description: '미체결 수량', example: 100 })
unfilledQuantity: number;

Expand Down
65 changes: 61 additions & 4 deletions apps/backend/src/order/matching.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Injectable } from '@nestjs/common';
import { OrderBookService } from './orderBook.service';
import { OrderType } from './enums/orderType';
import { OrderStatus, OrderType } from './enums/orderType';
import { OrderService } from './order.service';
import { MarketService } from '../market/market.service';

@Injectable()
export class MatchingService {
constructor(private readonly orderBookService: OrderBookService) {}
constructor(
private readonly orderBookService: OrderBookService,
private readonly orderService: OrderService,
private readonly marketService: MarketService
) {}

async matchOrders(cropId: number): Promise<void> {
const buyOrders = await this.orderBookService.getBuyOrders(cropId);
const sellOrders = await this.orderBookService.getSellOrders(cropId);
const buyOrders = await this.orderBookService.getBuyOrdersFromRedis(cropId);
const sellOrders = await this.orderBookService.getSellOrdersFromRedis(cropId);

let sellIndex = 0;
let buyIndex = 0;
Expand All @@ -24,7 +30,51 @@ export class MatchingService {
const matchedQuantity = Math.min(sellOrder.unfilledQuantity, buyOrder.unfilledQuantity);

//TODO DB 트랜잭션 업데이트,체결 이벤트 발생
// 1. 주문 DB 업데이트 v
// 2. 체결 트랜잭션 DB 생성 및 저장 v
// 3. 레디스 오더북 수정 v
// 4. 현재 가격 업데이트 (레디스) v
// 5. 체결 이벤트 발생
// 이후 트랜잭션 적용 및 분리 예정

// 1. 주문 DB 업데이트
if (sellOrder.unfilledQuantity <= matchedQuantity) {
await this.orderService.updateOrder(
sellOrder.orderId,
OrderStatus.COMPLETED,
matchedQuantity,
sellOrder.unfilledQuantity - matchedQuantity
);
} else if (sellOrder.unfilledQuantity > matchedQuantity) {
await this.orderService.updateOrder(
sellOrder.orderId,
OrderStatus.PARTIALLY_FILLED,
matchedQuantity,
sellOrder.unfilledQuantity - matchedQuantity
);
}

if (buyOrder.unfilledQuantity <= matchedQuantity) {
await this.orderService.updateOrder(
buyOrder.orderId,
OrderStatus.COMPLETED,
matchedQuantity,
buyOrder.unfilledQuantity - matchedQuantity
);
} else if (buyOrder.unfilledQuantity > matchedQuantity) {
await this.orderService.updateOrder(
buyOrder.orderId,
OrderStatus.PARTIALLY_FILLED,
matchedQuantity,
buyOrder.unfilledQuantity - matchedQuantity
);
}

// 2. 체결 트랜잭션 DB 생성 및 저장
await this.orderService.saveTransaction(sellOrder, buyOrder.price, matchedQuantity);
await this.orderService.saveTransaction(buyOrder, buyOrder.price, matchedQuantity);

// 3. 레디스 오더북 수정
await this.orderBookService.updateOrder(
cropId,
OrderType.BUY,
Expand All @@ -38,6 +88,13 @@ export class MatchingService {
matchedQuantity
);

// 4. 현재 가격 업데이트 (레디스)
const cropPrice = {
crop: cropId,
price: buyOrder.price
};
await this.marketService.setCropPrice(cropPrice);

if (sellOrder.unfilledQuantity <= matchedQuantity) {
sellIndex++;
}
Expand Down
52 changes: 24 additions & 28 deletions apps/backend/src/order/order.controller.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,52 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { OrderService } from './order.service';
import { OrderBookService } from './orderBook.service';
import { OrderBookDto } from './dto/orderBook.dto';
import DtoTransformer from './utils/dtoTransformer';
import { LimitOrderDto } from './dto/limitOrder.dto';
import { ApiOperation, ApiResponse } from '@nestjs/swagger';
import { ApiOperation } from '@nestjs/swagger';
import { MatchingService } from './matching.service';
import { successhandler, successMessage } from '../global/successhandler';

@Controller('api/order')
export class OrderController {
constructor(
private readonly orderService: OrderService,
private readonly orderBookService: OrderBookService
private readonly orderBookService: OrderBookService,
private readonly machineService: MatchingService
) {}

@Post('buy')
@Post('buy/limit')
@ApiOperation({ summary: '구매 주문 생성' })
@ApiResponse({ status: 200, description: '구매 주문이 성공적으로 생성되었습니다.' })
async createBuyOrder(@Body() limitOrderDto: LimitOrderDto): Promise<string> {
async createBuyOrder(@Body() limitOrderDto: LimitOrderDto) {
const orderDto = DtoTransformer.toOrderDto(limitOrderDto);
const orderId = await this.orderService.saveOrder(orderDto);

await this.orderService.saveOrderToOrderBook(orderDto, orderId);
return '구매 주문이 성공적으로 생성되었습니다.';
await this.orderService.saveOrder(orderDto);
await this.machineService.matchOrders(limitOrderDto.cropId);
return successhandler(successMessage.CREATE_ORDER_SUCCESS);
}

@Post('sell')
@Post('sell/limit')
@ApiOperation({ summary: '판매 주문 생성' })
@ApiResponse({ status: 200, description: '판매 주문이 성공적으로 생성되었습니다.' })
async createSellOrder(@Body() limitOrderDto: LimitOrderDto): Promise<string> {
async createSellOrder(@Body() limitOrderDto: LimitOrderDto) {
const orderDto = DtoTransformer.toOrderDto(limitOrderDto);
const orderId = await this.orderService.saveOrder(orderDto);

await this.orderService.saveOrderToOrderBook(orderDto, orderId);
return '판매 주문이 성공적으로 생성되었습니다.';
}

@Get('buy/:cropId')
async getBuyOrders(@Param('cropId') cropId: number): Promise<OrderBookDto[]> {
return await this.orderBookService.getBuyOrders(cropId);
await this.orderService.saveOrder(orderDto);
await this.machineService.matchOrders(limitOrderDto.cropId);
return successhandler(successMessage.CREATE_ORDER_SUCCESS);
}

@Get('sell/:cropId')
async getSellOrders(@Param('cropId') cropId: number): Promise<OrderBookDto[]> {
return await this.orderBookService.getSellOrders(cropId);
@Get('')
@ApiOperation({ summary: '각 회원 체결 내역 조회' })
async getTransactionsByMemberId(@Query('memberId') memberId: number) {
const transactions = await this.orderBookService.getTransactionsByMemberId(memberId);
return successhandler(successMessage.GET_TRANSACTION_SUCCESS, transactions);
}

@Post('cancel')
@ApiOperation({ summary: '주문 취소' })
async cancelOrder(
@Body()
{ cropId, orderId, orderType }: { cropId: number; orderId: number; orderType: 'buy' | 'sell' }
): Promise<string> {
) {
await this.orderBookService.removeOrder(cropId, orderId, orderType);
return '주문이 성공적으로 취소되었습니다.';
return successhandler(successMessage.DELETE_ORDER_SUCCESS);
}
}
6 changes: 4 additions & 2 deletions apps/backend/src/order/order.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { OrderController } from './order.controller';
import { OrderBookService } from './orderBook.service';
import { OrderRepository } from './order.repository';
import { DatabaseModule } from '../database/database.module';
import { MatchingService } from './matching.service';
import { MarketModule } from '../market/market.module';

@Module({
providers: [OrderService, OrderBookService, OrderRepository],
providers: [OrderService, OrderBookService, OrderRepository, MatchingService],
controllers: [OrderController],
imports: [DatabaseModule]
imports: [DatabaseModule, MarketModule]
})
export class OrderModule {}
68 changes: 65 additions & 3 deletions apps/backend/src/order/order.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../database/database.service';
import { OrderDto } from './dto/order.dto';
import { OrderStatus } from './enums/orderType';
import { OrderBookDto } from './dto/orderBook.dto';

@Injectable()
export class OrderRepository {
constructor(private readonly databaseService: DatabaseService) {}

async saveOrder(order: OrderDto): Promise<number> {
async saveOrder(order: OrderDto): Promise<number[]> {
const query = `
INSERT INTO orders (crop_id,
member_id,
Expand All @@ -19,7 +20,7 @@ export class OrderRepository {
filled_quantity,
unfilled_quantity,
time)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING order_id
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING order_id, member_id
`;

const values = [
Expand All @@ -36,6 +37,67 @@ export class OrderRepository {
];

const result = await this.databaseService.query(query, values);
return result.rows[0].order_id;
return [result.rows[0].order_id, result.rows[0].member_id];
}

async updateOrder({
orderId,
status,
filledQuantity,
unfilledQuantity
}: {
orderId: number;
status: OrderStatus;
filledQuantity: number;
unfilledQuantity: number;
}): Promise<void> {
const query = `
UPDATE orders
SET status = $1,
filled_quantity = $2,
unfilled_quantity = $3
WHERE order_id = $4
`;

const values = [status, filledQuantity, unfilledQuantity, orderId];

await this.databaseService.query(query, values);
}

async saveTransaction(
order: OrderBookDto,
price: number,
matchedQuantity: number
): Promise<void> {
const query = `
INSERT INTO transactions (order_id, member_id, crop_id, trading_type, price, total_price, amount)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`;

console.log(order);
const values = [
order.orderId,
order.memberId,
order.cropId,
order.tradingType,
price,
price * matchedQuantity,
matchedQuantity
];

await this.databaseService.query(query, values);
}

async getTransactionsByMemberId(memberId: number): Promise<OrderDto[]> {
const query = `
SELECT *
FROM transactions
WHERE member_id = $1
`;

const values = [memberId];

const result = await this.databaseService.query(query, values);
return result.rows;
}
}
Loading

0 comments on commit c69bd8b

Please sign in to comment.