Experience
開発研究員
·(株)ウェルビアドットコム·2021.12 – 2023.02ゲームのアンチチートモジュールおよびプログラムであるXigncode3を開発する会社です。 · C++で開発されたセキュリティモジュールをUnity、Androidプラットフォームに適用 · JNIフレームワークを活用したC++、Java間クロスプラットフォーム連動実装 · Unreal Engine(UE4)ソースコード分析によるグローバルオフセット脆弱性発見および対策提示 · ACTk Obscured Variable分析およびメモリ改ざん防止メカニズム研究 · Windows Powershellを利用した開発環境自動化スクリプト構築
副会長兼リバースエンジニアリングメンター
·国立金烏工科大学校 情報セキュリティサークル BOSS·2023.03 – 2024.08会社勤務中に習得したゲームセキュリティ知識に基づいて後輩にメンタリングを提供しました。 · リバーシング教育カリキュラム設計および実習資料作成 · デバッグツール(IDA)を活用した静的分析メンタリング · メモリ改ざんツール(Cheat Engineなど)を利用した動的分析メンタリング · 非専攻者でも理解できるセキュリティ基礎概念教育進行 · 内部CTF開催および動的分析関連問題作成
学部研究生
·信号処理および知能型ネットワーク研究室·2023.07 – 2025.02学部研究生として研究室で進めた様々なプロジェクトに参加しました。 · プロジェクト内フロントエンド開発 · プロジェクト内UI/UXデザイン · マークダウンエディターおよび数式エディター結合研究
Projects





+1
Discord上でメイプルストーリーに関する情報をユーザーに提供するチャットボットです。 Nexon Open APIを活用してリアルタイムのゲーム情報、キャラクター検索、ギルド情報などを提供しています。 約3年間サービス中で、現在約4,700サーバーで累計18万人以上が利用しています。
1人開発
チャットボットおよびウェブサイト開発 運用および保守全般
// 스레드풀 크기별 채널 분할하여 병렬 처리
int threadPoolSize = MESSAGE_THREAD_POOL_SIZE;
int channelsPerThread = (int) Math.ceil((double) activeChannelInfos.size() / threadPoolSize);
// 각 채널별 완료 추적을 위한 카운터
CountDownLatch messageCompletionLatch = new CountDownLatch(activeChannelInfos.size());
// 채널을 스레드별로 분할하여 병렬 처리
for (int i = 0; i < activeChannelInfos.size(); i += channelsPerThread) {
final List<ChannelInfo> channelShard = activeChannelInfos.subList(startIndex, endIndex);
messageExecutor.submit(() -> {
// 이 샤드의 채널들에 순차 전송
for (ChannelInfo channelInfo : channelShard) {
channel.sendMessageEmbeds(embed).queue(
success -> messageCompletionLatch.countDown(),
failure -> {
String failureReason = getFailureReason(failure);
// 에러 타입별 채널 상태 관리
messageCompletionLatch.countDown();
}
);
}
});
}// GetCharacterBasicAPI.java - API 호출 및 JSON 파싱
public static String[] getCharacterInfo(String name) {
// OCID 획득
String ocid = GetOcidAPI.getOcid(name);
if(ocid.equals("error")) return null;
// API 호출 및 데이터 파싱
String url = NexonOpenAPIConfig.base_url + "character/basic?ocid=" + ocid;
String jsonStr = fetchDataFromUrl(url);
if(jsonStr.equals("error")) {
return new String[] { "API is in Error" };
}
Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(jsonStr, Map.class);
// 데이터 가공 및 반환
String worldImageUrl = worldImageUrlTable(map.get("world_name").toString());
String level = "Lv." + (int)(Double.parseDouble(map.get("character_level").toString()))
+ " (" + map.get("character_exp_rate").toString() + "%)";
String imageUrl = map.get("character_image").toString().replaceAll("https://", "http://");
return new String[] {worldImageUrl, level, job, popular, guild, updated, userName, imageUrl, ocid};
}
private static String fetchDataFromUrl(String url) {
try {
Document doc = Jsoup.connect(url)
.header("x-nxopen-api-key", NexonOpenAPIConfig.API_KEY)
.ignoreContentType(true).get();
return doc.select("body").text();
} catch (Exception e) {
System.out.println("ERROR :: " + url);
e.printStackTrace();
return "error";
}
}// BigDecimal을 사용한 정밀 경험치 계산
private static String upExpCalc(int sLevel, int eLevel, double sPercent, double ePercent) {
BigDecimal exp = new BigDecimal(0);
for(int i = sLevel; i <= eLevel; i++) {
if(i == sLevel) {
BigDecimal tmp = ExpTables.calcExpPercentToDecimal(sLevel,
(Math.round((100.0 - sPercent) * 1000) / 1000.0));
exp = exp.add(tmp);
} else if(i == eLevel) {
BigDecimal tmp = ExpTables.calcExpPercentToDecimal(eLevel,
(Math.round(ePercent * 1000) / 1000.0));
exp = exp.add(tmp);
} else {
exp = exp.add(ExpTables.expTable()[i - 200]);
}
}
return exp.setScale(0, RoundingMode.HALF_UP).toString();
}// 슬래시 커맨드 처리 - 스타포스 시뮬레이터
if(e.getName().equals("스타포스")) {
e.deferReply().queue();
// 파라미터 검증
if(option1 == null || option2 == null || /* ... 기타 옵션들 */) {
e.getHook().sendMessageEmbeds(mapleUserTypingFail()).queue();
return;
}
// 즉시 대기 메시지 전송 후 백그라운드 처리
e.getHook().sendMessageEmbeds(mapleStarForceSimulatorWaiting()).queue(hook -> {
mapleStarForceSimulatorThreading(level, start, end, starCatch, retry,
preventDestroy, mvpGrade, isPCRoom, eventType, e, hook);
System.out.println("Waiting Message Send Success :: " + Main.getNowTimeOnly());
});
}
// 비동기 시뮬레이션 처리
void mapleStarForceSimulatorThreading(@params) {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
try {
e.getHook().editMessageEmbedsById(hook.getId(),
mapleNewStarForceSimulator(level, start, end, starCatch, retry,
preventDestroy, eventType, mvpGrade, isPCRoom)).queue();
} catch (Exception ex) {
e.getHook().editMessageEmbedsById(hook.getId(),
mapleStarForceSimulatorFailed()).queue();
ex.printStackTrace();
}
}
}, 0);
}// 스타포스 시뮬레이션 결과 임베드 생성
MessageEmbed mapleNewStarForceSimulator(@params) {
MapleNewStarForceSimulator mscs = new MapleNewStarForceSimulator(level, start, end,
starCatch, retry, preventDestroy, eventType, mvpGrade, isPCRoom);
maple.MapleNewStarForceSimulator.ReturnNode n = mscs.calculate();
if (n.getCount() == -1) {
return mapleStarForceSimulatorErrorMessage();
}
EmbedBuilder eb = new EmbedBuilder();
eb.setAuthor("스타포스 시뮬레이터");
eb.setColor(new Color(255, 192, 203));
// 이벤트별 상세 정보 처리
String eventTypeText = getEventTypeText(eventType);
String mvpGradeText = getMvpGradeText(mvpGrade);
if ((n.getDestroyed() == 1) && !retry) {
eb.setDescription("아쉽지만 아이템이 파괴되었어.");
eb.addField("파괴 전 마지막 스타포스", n.getEnd() + " 성", false);
} else {
eb.setDescription("강화에 성공했어!");
eb.addField("결과", n.getEnd() + " 성", false);
}
// 상세 통계 정보 추가
eb.addField("소모 메소", mesoFormatWithUnit(n.getMeso()), false);
eb.addField("강화 횟수", mesoFormat(n.getCount()) + " 회", true);
eb.addField("성공 횟수", mesoFormat(n.getSuccess()) + " 회", true);
eb.addField("실패 횟수", mesoFormat(n.getFail()) + " 회", true);
eb.addField("파괴 횟수", mesoFormat(n.getDestroyed()) + " 회", true);
return eb.build();
}



韓国労総大邱支部の公式ウェブサイトです。 ウェブサイト内の受講申込機能を主として、全体的なウェブサイト開発外注を進めました。 開発主要機能としては、受講申込システムと管理者システムのフロントエンドとバックエンド全体を開発しました。
1人開発
プロジェクト設計 フロントエンド開発 バックエンド開発
// 숫자만 입력 가능하도록 하는 함수 (생년월일, 전화번호)
const handleNumericInput = (e, fieldName, maxLength, nextFieldRef) => {
const { value } = e.target;
const numericValue = value.replace(/[^0-9]/g, "");
setFormData((prevState) => ({
...prevState,
[fieldName]: numericValue,
}));
// 최대 길이에 도달하면 다음 필드로 포커스 이동
if (numericValue.length === maxLength && nextFieldRef && nextFieldRef.current) {
nextFieldRef.current.focus();
}
};
// 폼 유효성 검사
const validateForm = () => {
const newErrors = {};
// 차량번호 검증 (특별 처리)
if (formData.carNumber && formData.carNumber.trim() === "없음") {
// "없음"은 유효한 값이므로 에러에서 제거
if (newErrors.carNumber) delete newErrors.carNumber;
} else if (formData.carNumber && formData.carNumber.trim() !== "") {
const carNumberRegex = /^[\d]{2,3}[가-힣]{1}[\d]{4}$/;
if (!carNumberRegex.test(formData.carNumber.replace(/\s+/g, ""))) {
newErrors.carNumber = "올바른 차량번호 형식이 아닙니다. (예: 12가1234)";
}
}
return newErrors;
};// 상태 우선순위 매핑으로 정렬 최적화
const STATUS_PRIORITY = {
enrolled: 1, // 승인대기
approved: 2, // 승인완료
canceled: 3, // 승인취소
complete: 4 // 과정수료
};
// 필터링된 데이터 정렬
const filteredEnrollments = enrollments
.filter((item) => {
const nameMatch = enrollmentRequest.applicant_name
.toLowerCase().includes(nameFilter.toLowerCase());
const statusMatch = statusFilter === "all" ||
enrollment.status === getStatusCodeFromFilter(statusFilter);
const targetAudienceMatch = targetAudienceFilter === "all" ||
courseSummary.target_audience === targetAudienceFilter;
return nameMatch && statusMatch && targetAudienceMatch;
})
.sort((a, b) => {
// 상태 우선순위에 따라 정렬
const statusPriorityA = STATUS_PRIORITY[a.enrollment.status] || 999;
const statusPriorityB = STATUS_PRIORITY[b.enrollment.status] || 999;
if (statusPriorityA !== statusPriorityB) {
return statusPriorityA - statusPriorityB;
}
// 같은 상태면 강의 시작일 기준 정렬
return new Date(a.course_summary.start_date) - new Date(b.course_summary.start_date);
});株式Talk
2023.09 - 2023.12



1時間単位でニュース記事と株価変動率を活用して、前日終値に対する当日終値の変動率を予測する人工知能カカオトークチャットボットです。 Python FlaskサーバーをKakao i OpenBuilderに接続してユーザーの発話意図を分析し、より精密な動作を可能にする設計をしました。 AIが生成した結果に基づいて動的にグラフ画像を生成し、独自にデザインしたカカオトークメッセージに添付することで、ユーザーが情報を読みやすいように実装しました。
4人開発
Kakao i OpenBuilder内のシナリオおよびスキル制作 チャットボット通信のためのFlaskベースチャットボットサーバーおよびコンテンツダウンロードサーバーの実装 株式分野と銘柄の固有表現認識および類似度分析機能の実装 動的グラフ生成およびカカオトーク内メッセージデザイン設計 マルチスレッドを活用し、国内株式全銘柄に対する8か月分・約20万件以上の記事データをクロール
@app.route('/api/message', methods=['POST'])
def message():
content = request.get_json()
user_input = content['userRequest']['utterance'].replace("\n", "")
# 의도 파악
intention_predict = intention_understanding_instance.intentionPrediction(user_input)
intention_correct = intention_predict in [3, 4, 6]
if not intention_correct:
return jsonify(makeMessage(data=None, type=4))
# 단어 대체 및 예외 처리
user_input = alterWords(user_input.replace(" ", "").strip())
exception_type, exception_word = findException(user_input.replace(" ", "").upper().strip())
# 종목명 및 분야 체크
if exception_type == -1:
found_word = checker.check_words(user_input.replace(" ","").upper())
if found_word is not None:
try:
# 분야 데이터 처리
messageData, stock_count = findSectorData(found_word)
return jsonify(makeMessage(data=messageData, type=2, count=stock_count))
except:
# 종목 데이터 처리
messageData, news_count = findStockData(found_word)
return jsonify(makeMessage(data=messageData, type=1, count=news_count))




+1
グループおよび個人のスケジュール管理を支援し、グループ内メンバーの空き時間を見つけるコミュニティ形式の共有協働カレンダーです。 直感的なUIを設計・実装し、ユーザーが利用しやすいサービスを作ることを目指しました。 ブルートフォースとインターバルマージアルゴリズムの2つのアルゴリズムを状況に応じて使用し、可能な限り速く空き時間を見つけるように実装しました。
4人開発
フロントエンドUI実装 フロントエンドAxios通信、データレンダリング、セッション管理機能の実装 ブルートフォースと区間マージアルゴリズムを活用した空き時間検索アルゴリズムの実装 バックエンドCORS管理およびコントローラ一部実装
private List<CommonSchedule> interval(LocalDateTime startTime, LocalDateTime endTime,
int duration, List<String> members) {
List<Interval> combined = new ArrayList<>();
final int timeSlot = 10;
// 모든 멤버의 일정을 수집
for (String memberId : members) {
List<PersonalSchedule> personalSchedules =
personalScheduleRepository.findPersonalScheduleByDateRange(memberId, startTime, endTime);
if (personalSchedules != null && personalSchedules.size() > 0) {
for (PersonalSchedule schedule : personalSchedules) {
combined.add(new Interval(schedule.getStartTime(), schedule.getEndTime()));
}
}
}
// 인터벌 병합으로 중복 제거 및 최적화
List<Interval> mergeSchedule = IntervalMerge.intervalMerge(combined);
Collections.sort(mergeSchedule, Comparator.comparing(Interval::getStart));
// 빈 시간 계산
List<CommonSchedule> resultSchedule = new ArrayList<>();
LocalDateTime current = startTime;
for (Interval interval : mergeSchedule) {
while (current.plusMinutes(duration).isBefore(interval.getStart()) ||
current.plusMinutes(duration).isEqual(interval.getStart())) {
resultSchedule.add(new CommonSchedule(current, current.plusMinutes(duration)));
current = current.plusMinutes(timeSlot);
}
if (current.isBefore(interval.getEnd())) {
current = interval.getEnd();
}
}
return resultSchedule;
}// JWT 토큰 자동 갱신 처리
const getGroupList = async (id, navigate) => {
try {
const config = {
headers: {
Authorization: `Bearer ${accessToken}`,
},
};
const res = await axios.get(
process.env.REACT_APP_SERVER_URL + `/api/calendar/member/${id}`,
config
);
if (res.data.code === 200) {
return res.data.data;
} else if (res.data.code === 401 || res.data.data === null) {
// 토큰 만료 시 자동 갱신 후 재시도
await refreshAccessToken(navigate);
return getGroupList(id, navigate);
} else {
throw new Error("unknown Error");
}
} catch (error) {
console.error(error);
Swal.fire({
position: "center",
icon: "error",
title: "에러!",
text: "서버와의 통신에 문제가 생겼어요!",
showConfirmButton: false,
timer: 1500,
});
return [];
}
};// 페이지 이동 시 상태 유지
useEffect(() => {
// 로컬스토리지에서 이전 상태 복원
const savedState = localStorage.getItem('calendarState');
if (savedState) {
const parsedState = JSON.parse(savedState);
setSelectedDate(parsedState.selectedDate);
setViewMode(parsedState.viewMode);
}
}, []);
// 상태 변경 시 자동 저장
useEffect(() => {
const stateToSave = {
selectedDate,
viewMode,
currentGroup
};
localStorage.setItem('calendarState', JSON.stringify(stateToSave));
}, [selectedDate, viewMode, currentGroup]);


バーチャルグループRE:REVOLUTIONのファンゲームです。 グループメンバーの誕生日のためのファンゲーム外注を受けて開発しました。 Unityを使用して開発し、WebGLでビルドしてウェブ上でゲームをプレイできるように制作しました。 PCだけでなくモバイル環境のためのボタンUIを追加し、様々な環境でも問題なくプレイできるように制作しました。
1人開発
ゲーム開発全般
// 테트로미노 이동 및 충돌 검사
bool MoveTetromino(Vector3 moveDir, bool isRotate)
{
Vector3 oldPos = tetrominoNode.transform.position;
Quaternion oldRot = tetrominoNode.transform.rotation;
tetrominoNode.transform.position += moveDir;
if (isRotate) {
tetrominoNode.transform.rotation *= Quaternion.Euler(0, 0, 90);
}
if (!CanMoveTo(tetrominoNode)) {
// 이동 불가능하면 원래 위치로 복구
tetrominoNode.transform.position = oldPos;
tetrominoNode.transform.rotation = oldRot;
// 아래로 떨어지다가 막힌 경우 보드에 추가
if ((int)moveDir.y == -1 && (int)moveDir.x == 0 && isRotate == false) {
AddToBoard(tetrominoNode);
CheckBoardColumn();
CreateTetromino();
// 게임 오버 체크
if (!CanMoveTo(tetrominoNode)) {
gameOverPanel.SetActive(true);
}
}
return false;
}
return true;
}
// 동적 난이도 조절 시스템
float FallCycleTable()
{
switch (levelInt) {
case 1: return isEasyMode ? 2.0f : 1.0f;
case 2: return isEasyMode ? 1.8f : 0.9f;
// ... 레벨별 속도 조절
default: return 0.001f;
}
}オニュラン
2023.01 - 2023.08
バーチャルグループRE:REVOLUTIONのファンゲームです。 グループメンバーの誕生日のためのファンゲーム外注を受けて開発しました。 Unityを使用して開発したクッキーラン形式の2D横スクロールランニングゲームです。
1人開発
ゲーム開発全般 レベルデザイン ゲームバランシング
// GameManager.cs - 게임 상태 관리
public class GameManager : MonoBehaviour
{
private static GameManager _instance;
private bool gameOver = false;
private int stage = 0;
private bool isStageChanged = false;
public static GameManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType(typeof(GameManager)) as GameManager;
}
return _instance;
}
}
// 게임 상태 프로퍼티들
public bool GameOver { get; set; }
public int Stage { get; set; }
public bool IsStageChanged { get; set; }
}// PatternManager.cs - 패턴 기반 장애물 생성
void Update()
{
switch (GameManager.Instance.Stage)
{
case 0: min = 0; max = 4; break;
case 1: min = 5; max = 9; break;
case 2: min = 10; max = 13; break;
}
if (GameManager.Instance.IsLandDestroyed)
{
if (GameManager.Instance.IsStageChanged)
{
// 스테이지별 시작 패턴 생성
switch (GameManager.Instance.Stage)
{
case 0:
GameObject newPattern1 = Instantiate(startPattern1,
new Vector3(18f, 0.1f, 0), Quaternion.identity);
break;
// ... 다른 스테이지들
}
}
else
{
// 랜덤 패턴 생성
GameObject newPattern = Instantiate(
patternList[Random.Range(min, max)],
new Vector3(18f, 0.1f, 0), Quaternion.identity);
}
GameManager.Instance.IsLandDestroyed = false;
}
}// ScrollController.cs - 백그라운드 무한 스크롤
void Update()
{
if (!GameManager.Instance.GameOver)
{
transform.Translate(-mapSpeed * Time.deltaTime, 0, 0);
if (transform.position.x < ifposX)
{
transform.Translate(posX, 0, 0); // 위치 리셋으로 무한 스크롤
}
}
}Skills & Technologies
Frameworks & Libraries
Languages
Tools & Platforms
Achievements & Licenses
受賞
KIT Engineering Fair 銅賞
·国立金烏工科大学校 LINC3.0 事業団活動
CO-UP CAMPUS CHATBOT HACKATHON 参加
·教育部ほか6機関資格
情報処理技士
·韓国産業人力公団JLPT N2
·国際交流基金Education
SSAFY
14期 · モバイルトラック
国立金烏工科大学校
工学士 · コンピュータソフトウェア工学科
大邱シジ高等学校
高等学校卒業 · 理系
一緒に働きませんか
プロジェクトのご相談・協業のご提案・採用のお話、どうぞお気軽にご連絡ください。
© 2026 イ・サンホン. All rights reserved.
Built with Next.js, FastAPI & Claude Code.