035175e895
00_PROMPTS.md — Claude Code 실행 프롬프트 1~6 01_DB_SCHEMA.md — V1~V17 Flyway 스키마 + 프로젝트 뼈대 02_COMMON_SPEC.md — ga-common 공통 프레임워크 (모델/예외/MyBatis/AOP/엑셀/공통컴포넌트) 03_CORE_SPEC.md — ga-core VO 설계 원칙 + Mapper 패턴 04_BATCH_SPEC.md — ga-batch 정산 8 Step + DataReceiver/MappingEngine 05_API_SPEC.md — ga-api Controller/Service 표준 패턴 + 엔드포인트 목록 06_FRONTEND_SPEC.md — ga-frontend 13개 화면 + 공통 컴포넌트 사용법 07_INFRA_SPEC.md — Docker/Nginx + README + DEVELOPER_GUIDE 08_DBA_SPEC.md — V13~V15 인덱스/파티셔닝/뷰 실제 구현된 ga-commission-system 기준으로 작성. 코드는 변경 없음. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
294 lines
11 KiB
Markdown
294 lines
11 KiB
Markdown
# 05_API_SPEC — ga-api (REST API)
|
||
|
||
> 모든 Controller / Service 가 동일한 패턴으로 작성된다.
|
||
> 신입이 복붙 후 테이블명/필드명만 바꾸면 새 화면이 완성되도록.
|
||
|
||
## 패키지 구조
|
||
|
||
```
|
||
ga-api/src/main/java/com/ga/api/
|
||
├── GaApiApplication.java # @SpringBootApplication(scanBasePackages = "com.ga")
|
||
├── auth/
|
||
│ ├── AuthController.java # /api/auth/*
|
||
│ └── AuthService.java
|
||
├── controller/
|
||
│ ├── batch/ BatchTriggerController.java
|
||
│ ├── ledger/ LedgerController.java # 모집/유지/예외 통합
|
||
│ ├── org/ OrganizationController.java, AgentController.java
|
||
│ ├── product/ CompanyController.java, ProductController.java, ContractController.java
|
||
│ ├── receive/ ReceiveController.java
|
||
│ ├── rule/ RuleController.java
|
||
│ ├── settle/ SettleController.java, PaymentController.java
|
||
│ └── system/ UserController.java, RoleController.java
|
||
└── service/
|
||
└── (controller 미러 구조 — XxxService.java)
|
||
```
|
||
|
||
build.gradle:
|
||
```groovy
|
||
apply plugin: 'org.springframework.boot'
|
||
bootJar { archiveFileName = 'ga-api.jar'; mainClass = 'com.ga.api.GaApiApplication' }
|
||
jar.enabled = false
|
||
dependencies {
|
||
implementation project(':ga-core')
|
||
}
|
||
```
|
||
|
||
`application.yml` 은 `01_DB_SCHEMA.md` 의 yml + `server.port: 8080`.
|
||
|
||
---
|
||
|
||
## 1. 표준 Controller 패턴
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/api/agents")
|
||
@Tag(name = "설계사 관리")
|
||
@RequiredArgsConstructor
|
||
public class AgentController {
|
||
|
||
private final AgentService agentService;
|
||
|
||
@GetMapping
|
||
@RequirePermission(menu = "ORG_AGENT", perm = "READ")
|
||
public ApiResponse<PageResponse<AgentResp>> list(AgentSearchParam param) {
|
||
return ApiResponse.ok(agentService.list(param));
|
||
}
|
||
|
||
@GetMapping("/{id}")
|
||
@RequirePermission(menu = "ORG_AGENT", perm = "READ")
|
||
public ApiResponse<AgentResp> detail(@PathVariable Long id) {
|
||
return ApiResponse.ok(agentService.detail(id));
|
||
}
|
||
|
||
@PostMapping
|
||
@RequirePermission(menu = "ORG_AGENT", perm = "CREATE")
|
||
@DataChangeLog(menu = "ORG_AGENT", table = "agent")
|
||
public ApiResponse<Long> create(@Valid @RequestBody AgentSaveReq req) {
|
||
return ApiResponse.ok(agentService.create(req));
|
||
}
|
||
|
||
@PutMapping("/{id}")
|
||
@RequirePermission(menu = "ORG_AGENT", perm = "UPDATE")
|
||
@DataChangeLog(menu = "ORG_AGENT", table = "agent")
|
||
public ApiResponse<Void> update(@PathVariable Long id, @Valid @RequestBody AgentSaveReq req) {
|
||
agentService.update(id, req); return ApiResponse.ok();
|
||
}
|
||
|
||
@PatchMapping("/{id}/status")
|
||
@RequirePermission(menu = "ORG_AGENT", perm = "UPDATE")
|
||
@DataChangeLog(menu = "ORG_AGENT", table = "agent")
|
||
public ApiResponse<Void> updateStatus(@PathVariable Long id, @RequestParam String status) {
|
||
agentService.updateStatus(id, status); return ApiResponse.ok();
|
||
}
|
||
|
||
@GetMapping("/export")
|
||
@RequirePermission(menu = "ORG_AGENT", perm = "EXPORT")
|
||
public void export(AgentSearchParam p, HttpServletResponse res) {
|
||
agentService.exportExcel(p, res);
|
||
}
|
||
}
|
||
```
|
||
|
||
규칙:
|
||
1. **클래스 어노테이션**: `@RestController @RequestMapping @Tag @RequiredArgsConstructor`
|
||
2. **메서드 어노테이션**: `@RequirePermission` + (CUD 일 때) `@DataChangeLog`
|
||
3. **응답**: `ApiResponse<T>` / `ApiResponse<PageResponse<T>>` / `ApiResponse<Void>`
|
||
4. **요청 바디**: `@Valid @RequestBody XxxSaveReq`
|
||
5. **검색**: `XxxSearchParam` (쿼리 파라미터로 자동 바인딩)
|
||
6. **PathVariable**: id 는 `Long`
|
||
7. **엑셀 export 는 `void` + `HttpServletResponse` 직접 사용**
|
||
|
||
---
|
||
|
||
## 2. 표준 Service 패턴
|
||
|
||
```java
|
||
@Service @RequiredArgsConstructor
|
||
public class AgentService {
|
||
private final AgentMapper mapper;
|
||
private final CommonCodeService codeService;
|
||
private final AgentMapstruct mapstruct;
|
||
private final ExcelService excelService;
|
||
|
||
public PageResponse<AgentResp> list(AgentSearchParam param) {
|
||
param.startPage();
|
||
return PageResponse.of(mapper.selectList(param));
|
||
}
|
||
|
||
public AgentResp detail(Long id) {
|
||
AgentResp r = mapper.selectDetailById(id);
|
||
if (r == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||
return r;
|
||
}
|
||
|
||
@Transactional
|
||
public Long create(AgentSaveReq req) {
|
||
if (req.getLicenseNo() != null && mapper.existsByLicenseNo(req.getLicenseNo()) > 0)
|
||
throw new BizException(ErrorCode.DUPLICATE_DATA);
|
||
AgentVO vo = mapstruct.toVO(req);
|
||
vo.setStatus("ACTIVE");
|
||
mapper.insert(vo);
|
||
return vo.getAgentId();
|
||
}
|
||
|
||
@Transactional
|
||
public void update(Long id, AgentSaveReq req) {
|
||
AgentVO vo = mapper.selectById(id);
|
||
if (vo == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||
mapstruct.update(vo, req);
|
||
mapper.update(vo);
|
||
}
|
||
|
||
@Transactional
|
||
public void updateStatus(Long id, String status) {
|
||
codeService.validateCode("AGENT_STATUS", status);
|
||
mapper.updateStatus(id, status);
|
||
}
|
||
|
||
public void exportExcel(AgentSearchParam p, HttpServletResponse res) {
|
||
excelService.exportLargeExcel("설계사목록", AgentExcelVO.class,
|
||
() -> mapper.selectCursorForExcel(p), res);
|
||
}
|
||
}
|
||
```
|
||
|
||
규칙:
|
||
1. **Service 는 Mapper + 다른 Service 만 의존** (Controller 직접 호출 X)
|
||
2. **CUD 메서드는 `@Transactional`**
|
||
3. **공통코드 검증은 `codeService.validateCode(group, code)`**
|
||
4. **중복 검증 → `DUPLICATE_DATA`, 미존재 → `NOT_FOUND`**
|
||
5. **엑셀은 `excelService.exportLargeExcel(fileName, ExcelVO.class, cursorSupplier, response)`**
|
||
|
||
---
|
||
|
||
## 3. API 엔드포인트 목록 (현재 구현된 것)
|
||
|
||
### 인증 (`AuthController` — `/api/auth`)
|
||
- `POST /login` — `{loginId, password}` → `{accessToken, refreshToken, user}` + `login_log` 적재
|
||
- `POST /refresh` — refresh 토큰으로 access 갱신
|
||
- `POST /logout` — 클라이언트 토큰 폐기 (서버는 로그만)
|
||
- `GET /me` — 현재 사용자 + 역할 + 메뉴 트리
|
||
|
||
### 조직 / 설계사
|
||
- `OrganizationController /api/orgs`
|
||
- `GET /tree` — 트리 (CTE 재귀)
|
||
- `GET /{id}` / `POST` / `PUT /{id}` / `DELETE /{id}`
|
||
- `AgentController /api/agents`
|
||
- `GET` (검색+페이징) / `GET /{id}` / `POST` / `PUT /{id}`
|
||
- `PATCH /{id}/status` / `PATCH /{id}/transfer` (조직/직급 변경)
|
||
- `GET /{id}/history` (조직/직급 이력)
|
||
- `GET /export` (엑셀 다운로드)
|
||
|
||
### 보험상품 / 계약
|
||
- `CompanyController /api/companies` — 보험사 5 건 CRUD
|
||
- `ProductController /api/products` — 보험사 × 상품
|
||
- `ContractController /api/contracts` — 검색/CRUD/엑셀, `policy_no` 중복검증
|
||
|
||
### 수수료 규정
|
||
- `RuleController /api/rules`
|
||
- `GET /commission-rates`, `POST`, `PUT /{id}`, `DELETE /{id}`
|
||
- `GET /payout-rules`, ... 동일 패턴
|
||
- `GET /override-rules`, `GET /chargeback-rules`, `GET /exception-codes`
|
||
|
||
### 데이터 수신 / 매핑
|
||
- `ReceiveController /api/receive`
|
||
- `POST /upload?companyCode=&dataType=` — multipart 업로드 → `raw_commission_data` 적재
|
||
- `POST /process?companyCode=&settleMonth=` — `MappingEngine` 으로 변환 호출
|
||
- `GET /status?companyCode=&settleMonth=` — 진행 현황
|
||
- `GET /errors?companyCode=&resolveStatus=` — 에러 목록
|
||
- `PATCH /errors/{errorId}/resolve` — 에러 해결 처리
|
||
- 매핑 관리: `GET /mapping/{companyCode}/fields`, `POST/PUT/DELETE`
|
||
|
||
### 원장 (`LedgerController /api/ledger` — 통합)
|
||
- `GET /recruit` (검색+페이징) / `GET /recruit/export` (엑셀)
|
||
- `GET /maintain`, `GET /exception` — 동일 패턴
|
||
- `POST /exception` — 예외금액 등록 (수동)
|
||
- `PATCH /exception/{id}/approve` — 승인 (`@RequirePermission perm=APPROVE`)
|
||
- `PATCH /exception/{id}/reject` — 반려
|
||
|
||
### 정산
|
||
- `SettleController /api/settle`
|
||
- `GET` (월별 검색) / `GET /{id}` / `GET /summary?settleMonth=`
|
||
- `PATCH /{id}/confirm` (`@RequirePermission APPROVE`) — `@Transactional`,
|
||
`version` 체크 → `RULE_VERSION_CONFLICT` 가능
|
||
- `PATCH /{id}/hold` `{reason}`
|
||
- `PaymentController /api/payments`
|
||
- `GET` 검색 / `POST` 이체 생성 (`settle_master.status='CONFIRMED'` 인 것만)
|
||
- `POST /{id}/transfer-file` 이체파일 생성
|
||
- `PATCH /{id}/success`, `PATCH /{id}/fail`
|
||
|
||
### 배치 트리거 (`BatchTriggerController /api/batch`)
|
||
- `POST /settlement/run?settleMonth=&companyCode=` — `batch_job_log` 적재 + ga-batch 트리거
|
||
- `GET /settlement/status?jobLogId=` — 진행 상태
|
||
- `POST /settlement/restart?jobLogId=&fromStep=` — 재실행 (선택 구현)
|
||
|
||
### 시스템관리
|
||
- `UserController /api/system/users`
|
||
- `GET` / `POST` / `PUT /{id}` / `DELETE /{id}`
|
||
- `PATCH /{id}/reset-password` — 임시 비밀번호 발급 (이메일/알림)
|
||
- `PATCH /{id}/unlock` — 잠금 해제
|
||
- `PUT /{id}/roles` — 역할 매핑
|
||
- `RoleController /api/system/roles`
|
||
- 역할 CRUD + `GET /{id}/permissions` (메뉴 × 권한 매트릭스)
|
||
- `PUT /{id}/permissions` (전체 매트릭스 갱신)
|
||
|
||
### 공통 (ga-common 에 위치하지만 ga-api 가 노출)
|
||
- `MenuController /api/menus` — `GET /my`, `GET /tree`, CRUD
|
||
- `CommonCodeController /api/common/codes` — 그룹/상세 CRUD
|
||
- `FileController /api/files` — 업로드/다운로드/목록
|
||
- `NotificationController /api/notifications` — 목록/읽음
|
||
- `SystemConfigController /api/system/config` — 시스템 설정
|
||
- `SystemLogController /api/system/logs` — 로그인/API/변경 이력
|
||
|
||
---
|
||
|
||
## 4. 인증 / 권한 흐름
|
||
|
||
1. **로그인**: `POST /api/auth/login` → JWT 발급
|
||
2. **헤더**: 모든 요청에 `Authorization: Bearer <token>`
|
||
3. **`JwtAuthFilter`**: SecurityContext 에 `LoginUser` 주입
|
||
4. **`@RequirePermission`**: `PermissionAspect` 가 `role_menu_permission` 조회 → 없으면 `FORBIDDEN`
|
||
5. **`@DataChangeLog`**: 변경 전/후 데이터 비교 후 `data_change_log` JSONB 비동기 적재
|
||
6. **`ApiLogAspect`**: 모든 요청 `api_access_log` 비동기 적재 (민감정보 마스킹)
|
||
|
||
---
|
||
|
||
## 5. 응답 표준
|
||
|
||
### 성공
|
||
```json
|
||
{
|
||
"success": true, "code": "0000", "message": "OK",
|
||
"data": { ... }, "timestamp": "2026-05-10T12:34:56"
|
||
}
|
||
```
|
||
|
||
### 페이징
|
||
```json
|
||
{
|
||
"success": true, "code": "0000",
|
||
"data": {
|
||
"list": [...], "pageNum": 1, "pageSize": 20, "total": 123, "pages": 7
|
||
}
|
||
}
|
||
```
|
||
|
||
### 실패
|
||
```json
|
||
{
|
||
"success": false, "code": "B009", "message": "중복된 데이터입니다",
|
||
"data": null, "timestamp": "..."
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 테스트 (선택)
|
||
|
||
- MockMvc 기반 Controller 테스트
|
||
- `@WebMvcTest(AgentController.class)` + `@MockBean AgentService`
|
||
- 인증/권한 검증 (403 응답)
|
||
- 요청 바디 검증 (`@Valid` 실패 시 400)
|
||
- WireMock 으로 ga-batch 호출 stub
|