์ธ๋ชจ๋ ์ํ ๋ฌธ์ ์ฌ์ง์ ์ ๋ก๋ํ๋ฉด OCR/AI ๋ถ์์ ํตํด ๋จ์ยท์ ํ์ ์ถ์ฒํ๊ณ , ์ฌ์ฉ์๋ ์ต์ ์์ ๋ง์ผ๋ก ์ค๋ต์นด๋๋ฅผ ์์ฑ/๊ด๋ฆฌํ ์ ์๋ ์๋น์ค์ ๋๋ค.
- Java 17
- Spring Boot 3.5.x
- Gradle (Wrapper)
- MySQL, Redis
- JPA + QueryDSL
- Spring Security + JWT
- Swagger (springdoc-openapi)
- AWS S3 (Asset + Presigned URL)
- ์ธ๋ถ ์ฐ๋: Mathpix(OCR), Gemini(AI), Kakao/Apple OAuth
- ๋จ์ ํ ์คํธ: JUnit 5, Mockito
- ๋ชฉํ: ์ ์ฒด ์ปค๋ฒ๋ฆฌ์ง 60%+ ์ ์ง, ์ฃผ์ ๋๋ฉ์ธ/์๋น์ค ์ฐ์
- ํ์ฌ: 65%+ ์ ์ง ์ค
- ์ฌ์ง ์
๋ก๋ โ
problem_scan์์ ์นด๋ ์์ฑ โ ์๋ณธ ์ด๋ฏธ์ง๋ฅผAsset(ORIGINAL)๋ก S3 ์ ์ฅ - ๋น๋๊ธฐ OCR ์ํ โ ๊ฒฐ๊ณผ ์ ์ฅ โ ์ํ
OCR_DONE - ๋น๋๊ธฐ AI ๋ถ๋ฅ(๋จ์/์ ํ) ์ํ โ ๊ฒฐ๊ณผ ์ ์ฅ โ ์ํ
AI_DONE - ์ถ์ฒ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์๊ฐ ์ต์ ์์ ํ ์ต์ข ๋ฑ๋ก
- ์ค๋ณต ์ฒ๋ฆฌ ๋ฐฉ์ง(๋ฝ):
locked_at / lock_owner / lock_token - ์ฌ์๋/๋ฐฑ์คํ:
*_attempt_count,next_retry_at - 429(๋ ์ดํธ๋ฆฌ๋ฐ): ์คํจ๊ฐ ์๋๋ผ ๋๊ธฐ ์ฑ๊ฒฉ์ผ๋ก ๋ณ๋ ์ํ/๋๋ ์ด ์ ์ฑ
- ์ํ ์ ์ด ์ ํ:
UPLOADED โ OCR_DONE โ AI_DONE๋๋FAILED - ์๋ฌธ ์ ์ฅ:
ocr_raw_json(OCR),ai_draft_json(AI)
- Asset ํ ์ด๋ธ SSOT: ์๋ณธ/๋ณด์ /ํฌ๋กญ ์์ฐ ์ผ์ํ
storage_key๊ท์นํ ๋ฐ Presigned URL ๊ธฐ๋ฐ ์กฐํ
- Hexagonal Architecture (Ports & Adapters)
- ์ธ๋ถ ์์คํ
(S3/OCR/AI/OAuth)์
port/out+adapter/out๋ก ๋ถ๋ฆฌ - Controller๋ ์์ฒญ ๋ฐ์ธ๋ฉ/๊ฒ์ฆ/์ ์ค์ผ์ด์ค ํธ์ถ ์ค์ฌ์ผ๋ก ์ ์ง
- ๊ฒ์ฆ/๊ฐ๊ณต/์ธ๋ถ ํธ์ถ/ํ์ฒ๋ฆฌ๋ Validator/Mapper/Client/PostProcessor๋ก ๋ถ๋ฆฌ
- ์ธ๋ถ ์์คํ
(S3/OCR/AI/OAuth)์
- ๋ชจ๋ ์์ฒญ์
X-Trace-Id๋ฅผ ๋ถ์ฌํ๊ณ , ์๋ต ํค๋๋ก๋ ๋ด๋ ค์ค๋๋ค. - ์๋ฌ ๋ก๊ทธ๋
traceId,errorCode,exType,exMsg๋ฅผ ํจ๊ป ๋จ๊น๋๋ค.
- ๋น์ฆ๋์ค/๋๋ฉ์ธ ์๋ฌ๋
BusinessException+ErrorCode๋ก ํต์ผํฉ๋๋ค. - 5xx(ErrorCode.status๊ฐ 5xx)์ธ ๊ฒฝ์ฐ, ํด๋ผ์ด์ธํธ์๋ ์์ธ ๋ฉ์์ง/๋ฐ์ดํฐ๋ฅผ ์จ๊ธฐ๊ณ ๊ธฐ๋ณธ ๋ฉ์์ง๋ฅผ ๋ด๋ ค์ค๋๋ค.
- ์ค๋ณต ์ฒ๋ฆฌ ๋ฐฉ์ง: DB ๊ธฐ๋ฐ ๋ฝ(
locked_at / lock_owner / lock_token) - ์ฌ์๋/๋ฐฑ์คํ:
*_attempt_count,next_retry_at - AI๋ 429(๋ ์ดํธ๋ฆฌ๋ฐ) ์ผ์ด์ค๋ฅผ ๋ณ๋ ์นด์ดํธ/์ํ์ผ๋ก ๋ ๊ธธ๊ฒ ํ์ฉํฉ๋๋ค.
- provider 4xx(์๋ชป๋ code/ํ ํฐ ๋ฑ): ์ธ์ฆ ์คํจ๋ก ๊ฐ์ฃผ
- provider 5xx/timeout: ์ธ๋ถ ์์คํ ์ฅ์ ๋ก ๊ฐ์ฃผ(์ด์ ์๋ฆผ/์ฌ์๋ ๋์)
- ํฌ๋งท ์ ์ฉ:
./gradlew spotlessApply - ํฌ๋งท ๊ฒ์ฌ:
./gradlew spotlessCheck


