Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] locust를 통해 비교하기 위해 MySQL로 랭킹 계산 로직 구현 #244

Closed
wants to merge 66 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
0fc4cfe
🔧 fix: 이동 평균 관련 오류 수정 #192
dannysir Nov 27, 2024
16e5ce9
💄 design: 메인 페이지 상위 5개 종목 관련 부호 표시
dannysir Nov 27, 2024
8cc0b9e
✨ feat: 주식 즐겨찾기 등록/취소 버튼 레이아웃 구현 #212 #214
dongree Nov 27, 2024
3d18875
✨ feat: bookmark API 연동 #213 #215
dongree Nov 27, 2024
e31c7dd
➕ add: 로그인 아닐 시 bookmark 버튼 누를 경우 예외처리 & toastify 추가
dongree Nov 27, 2024
856de51
✨ feat: 마이페이지 즐겨찾기 레이아웃 구현 #216
dongree Nov 27, 2024
2e3c6ae
✨ feat: 마이페이지 즐겨찾기 주식 API 연동 #217
dongree Nov 27, 2024
93b5a24
⚙️ chore: console.log 제거
dongree Nov 27, 2024
69f5756
➕ add: 검색 안내 문구 추가
dongree Nov 27, 2024
6c1ae58
🔧 fix: 즐겨찾기 로직 수정
dongree Nov 27, 2024
4ca6cbb
Merge pull request #222 from boostcampwm-2024/feature/bookmark
dannysir Nov 28, 2024
3593d63
➕ add: 등락률 +인 경우 '+' 마크 추가
dongree Nov 28, 2024
e062752
🔧 fix: getStockByCode 즐겨찾기 여부 확인을 위해 credential 옵션 설정
dongree Nov 28, 2024
d92bed6
♻️ refactor: X축 라벨의 갯수를 데이터 갯수에 맞게 출력하도록 수정. #192
dannysir Nov 28, 2024
4e640a9
🚑 !HOTFIX: 체결 가능한 주문 조회 시 write lock 추가
sieunie Nov 28, 2024
4ac4ff9
✨ feat: 이동평균선 on/off 기능 추가 #192
dannysir Nov 28, 2024
a7e29bc
♻️ refactor: 로고 이미지 포멧 추가 & favicon 설정.
dannysir Nov 28, 2024
a782b2c
➕ add: 내 정보 수정 레이아웃 구현
dongree Nov 28, 2024
60702e4
✨ feat: 유저 닉네임 수정 API 연동
dongree Nov 28, 2024
902a706
💄 design: 내 정보 반응형 디자인 추가
dongree Nov 28, 2024
934ee66
➕ add: 같은 닉네임으로 수정했을 때 예외처리
dongree Nov 28, 2024
0865ee7
➕ add: 닉네임 변경 취소 버튼 추가
dongree Nov 28, 2024
db5a2d7
♻️ refactor: user 정보 api customhooks로 관리
dongree Nov 28, 2024
369594f
🚑 !HOTFIX: 트랜잭션 격리 수준 변경
sieunie Nov 28, 2024
307df08
✨ 차트 값 표시 화면 추가 #192
dannysir Nov 28, 2024
6f2c7d4
🔧 fix: 불필요한 log 제거
dannysir Nov 28, 2024
e7e2a43
🔧 fix: 다른 종목간 주식 디테일 페이지 이동시 매수, 매도 가격 입력란 변경안되는 문제 해결
dongree Nov 28, 2024
3fe9d95
Merge pull request #227 from boostcampwm-2024/back/main
uuuo3o Nov 28, 2024
4a48853
💄 design: 검색창 기본 크기 수정
dannysir Nov 28, 2024
ab7f9a5
💄 design: 최근 검색 부분을 위한 높이 설정 수정
dannysir Nov 28, 2024
19ca1aa
🔧 fix: 차트 데이터 요청 body 수정. #192
dannysir Nov 28, 2024
99b4903
Merge pull request #225 from boostcampwm-2024/feature/mypage/myInfo
dannysir Nov 28, 2024
576c292
Merge pull request #228 from boostcampwm-2024/feature/layout/detail/c…
dongree Nov 28, 2024
5710b6a
Merge branch 'dev' into front/main
dannysir Nov 28, 2024
74df95d
🚑 !HOTFIX: 트랜잭션 커밋 삭제
sieunie Nov 28, 2024
bed6e57
Merge pull request #229 from boostcampwm-2024/front/main
dannysir Nov 28, 2024
2b7526a
🚑 !HOTFIX: 트랜잭션 격리 수준 제거 및 pessimistic write 추가
sieunie Nov 28, 2024
fd2c632
Merge branch 'dev' of https://github.com/boostcampwm-2024/web16-JuGa …
sieunie Nov 28, 2024
04239ae
✨ feat: locust 를 이용한 부하 테스트 스크립트 구현
jinddings Nov 28, 2024
138f2bc
🚑 !HOTFIX: 데드락 수정
sieunie Nov 28, 2024
a68295f
🔧 fix: 종목 간 디테일 페이지 이동시 헤더 부분 데이터 변경되지 않는 문제 해결
dongree Nov 28, 2024
f3d60c1
Merge branch 'dev' of https://github.com/boostcampwm-2024/web16-JuGa …
jinddings Nov 28, 2024
d64715d
Update README.md
dannysir Nov 28, 2024
c5b90b7
⚙️ chore: 시연에서 랭킹 업데이트를 보여주기 위해 cron 동작 시간 변경
uuuo3o Nov 28, 2024
5ed762d
Update README.md
dongree Nov 28, 2024
37f06c0
Update README.md
dongree Nov 28, 2024
d3bfca1
📝 docs: 소프트웨어 아키텍처 이미지 변경
uuuo3o Nov 28, 2024
332aa16
Update README.md
dannysir Nov 28, 2024
93848c0
🔧 fix: 중복된 링크를 넣지 않게 중복을 확인하는 로직 추가#181
uuuo3o Nov 29, 2024
9b60f8a
Merge pull request #231 from boostcampwm-2024/feature/api/news-#181
uuuo3o Nov 29, 2024
2310e1a
Update README.md
uuuo3o Nov 29, 2024
fc19700
Update README.md
uuuo3o Nov 29, 2024
84439df
Update README.md
uuuo3o Nov 29, 2024
a83a127
🔧 fix: favicon 배경 삭제
dannysir Nov 29, 2024
f2d82cd
✨ feat: 트랜잭션 추가
uuuo3o Dec 1, 2024
7d33746
Merge pull request #233 from boostcampwm-2024/feature/api/news-#181
uuuo3o Dec 1, 2024
b733123
🔧 fix: commitTransaction 추가
uuuo3o Dec 1, 2024
b8e5415
Merge pull request #234 from boostcampwm-2024/back/main
uuuo3o Dec 1, 2024
f81b3c5
🔧 fix: 크론탭 시간 변경
uuuo3o Dec 1, 2024
c0aac78
🔧 fix: dev 서버에서 크론탭이 동작하지 않게 주석처리
uuuo3o Dec 1, 2024
93e3c5d
Update README.md
dongree Dec 2, 2024
8ede780
🔧 fix: stock index 데이터 안 들어왔을 때 예외 처리 추가
sieunie Dec 2, 2024
5d33dea
✅ test: stock index service 테스트 코드 통합 및 수정
sieunie Dec 2, 2024
4d93680
Merge pull request #236 from boostcampwm-2024/test/stockIndex
sieunie Dec 2, 2024
fd3bdde
Revert "[BE] 주가 지수 서비스 테스트 코드 작성"
sieunie Dec 2, 2024
f8b41ef
Merge pull request #239 from boostcampwm-2024/revert-236-test/stockIndex
sieunie Dec 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 39 additions & 16 deletions BE/src/news/news.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { In } from 'typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource, In } from 'typeorm';
import { NaverApiDomianService } from './naver-api-domian.service';
import { NewsApiResponse } from './interface/news-value.interface';
import { NewsDataOutputDto } from './dto/news-data-output.dto';
Expand All @@ -14,6 +15,7 @@ export class NewsService {
constructor(
private readonly naverApiDomainService: NaverApiDomianService,
private readonly newsRepository: NewsRepository,
@InjectDataSource() private dataSource: DataSource,
) {}

async getNews(): Promise<NewsResponseDto> {
Expand Down Expand Up @@ -46,18 +48,41 @@ export class NewsService {
};
}

@Cron('*/1 8-16 * * 1-5')
@Cron('*/1 * * * *')
async cronNewsData() {
await this.newsRepository.delete({ query: In(['증권', '주식']) });
await this.getNewsDataByQuery('주식');
await this.getNewsDataByQuery('증권');

await this.newsRepository.update(
{},
{
updatedAt: new Date(),
},
);
const queryRunner = this.dataSource.createQueryRunner();

try {
if (!queryRunner.isTransactionActive) {
await queryRunner.startTransaction('SERIALIZABLE');
}

await this.newsRepository.delete({ query: In(['증권', '주식']) });
const stockNews = await this.getNewsDataByQuery('주식');
const securityNews = await this.getNewsDataByQuery('증권');

const allNews = [...stockNews, ...securityNews];
const uniqueNews = allNews.filter(
(news, index) =>
allNews.findIndex((i) => i.originallink === news.originallink) ===
index,
);

await this.newsRepository.save(uniqueNews);
await this.newsRepository.update(
{},
{
updatedAt: new Date(),
},
);

await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException(err);
} finally {
await queryRunner.release();
}
}

private async getNewsDataByQuery(value: string) {
Expand All @@ -67,9 +92,7 @@ export class NewsService {

const response =
await this.naverApiDomainService.requestApi<NewsApiResponse>(queryParams);
const formattedData = this.formatNewsData(value, response.items);

return this.newsRepository.save(formattedData);
return this.newsRepository.save(this.formatNewsData(value, response.items));
}

private formatNewsData(query: string, items: NewsDataOutputDto[]) {
Expand Down
2 changes: 1 addition & 1 deletion BE/src/ranking/ranking.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class RankingService {
};
}

@Cron('0 16 * * 1-5')
@Cron('*/1 8-16 * * 1-5')
async updateRanking() {
const [profitRateRanking, assetRanking] = await Promise.all([
this.calculateRanking(SortType.PROFIT_RATE),
Expand Down
34 changes: 27 additions & 7 deletions BE/src/stockSocket/stock-execute-order.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class StockExecuteOrderRepository extends Repository<Order> {
.getRawMany();
}

async checkExecutableOrder(stockCode, value) {
async checkExecutableBuyOrder(stockCode, value) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.startTransaction();

Expand All @@ -39,26 +39,48 @@ export class StockExecuteOrderRepository extends Repository<Order> {
status: StatusType.PENDING,
price: MoreThanOrEqual(value),
},
lock: {
mode: 'pessimistic_write',
},
});

await Promise.all(
buyOrders.map((buyOrder) => this.executeBuy(queryRunner, buyOrder)),
);

await queryRunner.commitTransaction();
return buyOrders.length;
} catch (err) {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException(err);
} finally {
await queryRunner.release();
}
}

async checkExecutableSellOrder(stockCode, value) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.startTransaction();

try {
const sellOrders = await queryRunner.manager.find(Order, {
where: {
stock_code: stockCode,
trade_type: TradeType.SELL,
status: StatusType.PENDING,
price: LessThanOrEqual(value),
},
lock: {
mode: 'pessimistic_write',
},
});

await Promise.all(
buyOrders.map((buyOrder) => this.executeBuy(queryRunner, buyOrder)),
);
await Promise.all(
sellOrders.map((sellOrder) => this.executeSell(queryRunner, sellOrder)),
);

await queryRunner.commitTransaction();
return buyOrders.length + sellOrders.length;
return sellOrders.length;
} catch (err) {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException(err);
Expand Down Expand Up @@ -158,8 +180,6 @@ export class StockExecuteOrderRepository extends Repository<Order> {
.where({ user_id: order.user_id, stock_code: order.stock_code })
.setParameters({ newQuantity: order.amount })
.execute();

await queryRunner.commitTransaction();
}

private calculateFee(totalPrice: number) {
Expand Down
12 changes: 9 additions & 3 deletions BE/src/stockSocket/stock-price-socket.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,20 @@ export class StockPriceSocketService extends BaseStockSocketDomainService {
}

private async checkExecutableOrder(stockCode: string, value) {
const affectedRow =
await this.stockExecuteOrderRepository.checkExecutableOrder(
const affectedBuyRow =
await this.stockExecuteOrderRepository.checkExecutableBuyOrder(
stockCode,
value,
);

const affectedSellRow =
await this.stockExecuteOrderRepository.checkExecutableSellOrder(
stockCode,
value,
);

if (
affectedRow > 0 &&
affectedBuyRow + affectedSellRow > 0 &&
!(await this.stockExecuteOrderRepository.existsBy({
stock_code: stockCode,
status: StatusType.PENDING,
Expand Down
2 changes: 1 addition & 1 deletion FE/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" href="src/assets/favicon.ico" type="image/x-icon" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css"
Expand Down
21 changes: 21 additions & 0 deletions FE/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions FE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-router-dom": "^6.27.0",
"react-toastify": "^10.0.6",
"socket.io-client": "^4.8.1",
"vite-tsconfig-paths": "^5.0.1",
"zustand": "^5.0.1"
Expand Down
3 changes: 3 additions & 0 deletions FE/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import Login from 'components/Login';
import SearchModal from './components/Search';
import MyPage from 'page/MyPage';
import Rank from 'page/Rank.tsx';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

function App() {
return (
Expand Down Expand Up @@ -39,6 +41,7 @@ function Layout() {
</main>
<Login />
<SearchModal />
<ToastContainer />
</>
);
}
Binary file added FE/src/assets/favicon.ico
Binary file not shown.
File renamed without changes
Binary file added FE/src/assets/logo.webp
Binary file not shown.
12 changes: 10 additions & 2 deletions FE/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import useAuthStore from 'store/authStore';
import useLoginModalStore from 'store/useLoginModalStore';
import useSearchModalStore from '../store/useSearchModalStore.ts';
import useSearchInputStore from '../store/useSearchInputStore.ts';
import logo from 'assets/Logo.png';
import logoPng from 'assets/logo.png';
import logoWebp from 'assets/logo.webp';
import { checkAuth, logout } from 'service/auth.ts';
import { useEffect } from 'react';

Expand Down Expand Up @@ -42,7 +43,14 @@ export default function Header() {
<header className='fixed left-0 top-0 h-[60px] w-full bg-white'>
<div className='mx-auto flex h-full max-w-[1280px] items-center justify-between px-8'>
<Link to={'/'} className='flex items-center gap-2'>
<img src={logo} className={'h-[32px]'} />
<picture>
<source
srcSet={logoWebp}
type='image/webp'
className={'h-[32px]'}
/>
<img src={logoPng} className={'h-[32px]'} />
</picture>
<h1 className='text-xl font-bold text-juga-grayscale-black'>JuGa</h1>
</Link>

Expand Down
2 changes: 0 additions & 2 deletions FE/src/components/Mypage/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ export default function Account() {

const { asset, stocks } = data;

console.log(asset, stocks);

return (
<div className='flex min-h-[500px] flex-col gap-3'>
<AccountCondition asset={asset} />
Expand Down
61 changes: 61 additions & 0 deletions FE/src/components/Mypage/BookMark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import { getBookmarkedStocks } from 'service/bookmark';

export default function BookMark() {
const navigation = useNavigate();

const handleClick = (code: string) => {
navigation(`/stocks/${code}`);
};

const { data, isLoading, isError } = useQuery(
['bookmark', 'stock'],
() => getBookmarkedStocks(),
{
staleTime: 1000,
},
);

if (isLoading) return <div>loading</div>;
if (!data) return <div>No data</div>;
if (isError) return <div>error</div>;

return (
<div className='mx-auto flex min-h-[500px] w-full flex-1 flex-col rounded-md bg-white p-4 shadow-md'>
<div className='flex pb-2 text-sm font-bold border-b'>
<p className='w-1/2 text-left truncate'>종목</p>
<p className='w-1/4 text-center'>현재가</p>
<p className='w-1/4 text-right'>등락률</p>
</div>

<ul className='flex flex-col text-sm divide-y'>
{data.map((stock) => {
const { code, name, stck_prpr, prdy_ctrt, prdy_vrss_sign } = stock;

return (
<li
className='flex py-2 transition-colors hover:cursor-pointer hover:bg-gray-50'
key={code}
onClick={() => handleClick(code)}
>
<div className='flex w-1/2 gap-2 text-left truncate'>
<p className='font-semibold'>{name}</p>
<p className='text-gray-500'>{code}</p>
</div>
<p className='w-1/4 text-center truncate'>
{(+stck_prpr).toLocaleString()}원
</p>
<p
className={`w-1/4 truncate text-right ${+prdy_vrss_sign > 3 ? 'text-juga-blue-50' : 'text-juga-red-60'}`}
>
{+prdy_vrss_sign < 3 && '+'}
{prdy_ctrt}%
</p>
</li>
);
})}
</ul>
</div>
);
}
Loading
Loading