Experience
개발연구원
·(주) 웰비아닷컴·2021.12 – 2023.02게임의 안티치트 모듈 및 프로그램인 Xigncode3를 개발하는 회사입니다. · C++로 개발된 보안 모듈을 Unity, 안드로이드 플랫폼에 적용 · JNI 프레임워크를 활용한 C++, Java 간 크로스플랫폼 연동 구현 · 언리얼 엔진(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를 설계 및 구현하여 사용자가 이용하기에 어려움이 없는 서비스를 만들고자 하였습니다. 브루트포스와 인터벌 병합 알고리즘의 두 알고리즘을 상황에 맞게 사용하여, 최대한 빠르게 빈 일정을 찾도록 구현하였습니다.
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.