feat: 푸시 알림 파이프라인 — PushAdapter(FCM HTTP v1) + 디바이스토큰 등록 + 디스패치
마지막 외부연동 영역(푸시)을 채널=FCM HTTP v1 로 확정해 서버측 완전 구현.
Toast/KFTC 와 동일 패턴: 실 API·config-gated(app.adapter.push, 기본 mock)·fail-fast.
- ga-common: PushAdapter 인터페이스 + PushRequest/PushResponse
- ga-external-adapter: MockPushAdapter(기본) + FcmPushAdapter(POST /v1/projects/{id}/messages:send,
Bearer, notification+data 매핑, name→messageId) + FcmProperties. 의존성 추가 없음.
- V107 user_device_token(user_id/token/platform/is_active, UNIQUE token) + UserDeviceTokenVO/Mapper(upsert/selectActiveByUser/deactivate)
- ga-api:
- PushDispatchService.sendToUser — 사용자 활성 토큰 전부에 best-effort 푸시(예외 흡수)
- MobileMe: POST /api/mobile/me/device-tokens (등록 upsert) + /unregister (본인검증 deactivate)
- WithdrawBankSettleService.notify 에 푸시 디스패치 연동(출금 SETTLED/FAILED 시 인앱+푸시)
- ExternalCallLoggingAspect 에 PushAdapter 포인트컷 추가(외부호출 감사로그)
- application.yml app.adapter.push + app.adapter.fcm.* 설정
- ga-mobile-pwa: api/push.ts(register/unregister) + push/initPush.ts(config-gated, VITE_FCM_* 미설정 시 no-op) + 로그인 성공 시 initPush() 훅
- AdapterFailFastTest 에 FCM fail-fast 케이스 추가
검증: 전체 ./gradlew build(test) SUCCESSFUL, PWA 빌드 OK, Flyway V107 success,
디바이스토큰 등록/해제/검증 라이브 e2e PASS(등록200·해제200·is_active=N·빈토큰400).
실 푸시 발송은 Firebase 프로젝트(서버 FCM_PROJECT_ID/ACCESS_TOKEN + PWA VITE_FCM_* + firebase SDK) 주입 후 가능.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package com.ga.core.mapper.notice;
|
||||
|
||||
import com.ga.core.vo.notice.UserDeviceTokenVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* user_device_token Mapper (V107).
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserDeviceTokenMapper {
|
||||
|
||||
/** 토큰 등록(UPSERT) — 동일 token 재등록 시 user/플랫폼 갱신 + 활성화. */
|
||||
int upsert(UserDeviceTokenVO vo);
|
||||
|
||||
/** 사용자의 활성 토큰 목록 — 푸시 디스패치용. */
|
||||
List<UserDeviceTokenVO> selectActiveByUser(@Param("userId") Long userId);
|
||||
|
||||
/** 토큰 비활성화(로그아웃/만료). user_id 검증 포함. */
|
||||
int deactivate(@Param("token") String token, @Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ga.core.vo.notice;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* user_device_token — 푸시 알림용 디바이스 토큰 (V107)
|
||||
*/
|
||||
@Data
|
||||
public class UserDeviceTokenVO {
|
||||
private Long tokenId;
|
||||
private Long userId;
|
||||
private String token;
|
||||
/** WEB / ANDROID / IOS */
|
||||
private String platform;
|
||||
/** Y / N */
|
||||
private String isActive;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ga.core.mapper.notice.UserDeviceTokenMapper">
|
||||
|
||||
<resultMap id="VOMap" type="com.ga.core.vo.notice.UserDeviceTokenVO"/>
|
||||
|
||||
<!-- 동일 토큰 재등록 시 소유자/플랫폼 갱신 + 재활성화 -->
|
||||
<insert id="upsert" useGeneratedKeys="true" keyProperty="tokenId">
|
||||
INSERT INTO user_device_token (user_id, token, platform, is_active)
|
||||
VALUES (#{userId}, #{token}, #{platform}, 'Y')
|
||||
ON CONFLICT (token) DO UPDATE
|
||||
SET user_id = EXCLUDED.user_id,
|
||||
platform = EXCLUDED.platform,
|
||||
is_active = 'Y',
|
||||
updated_at = NOW()
|
||||
</insert>
|
||||
|
||||
<select id="selectActiveByUser" resultMap="VOMap">
|
||||
SELECT token_id, user_id, token, platform, is_active, created_at, updated_at
|
||||
FROM user_device_token
|
||||
WHERE user_id = #{userId}
|
||||
AND is_active = 'Y'
|
||||
ORDER BY updated_at DESC NULLS LAST, created_at DESC
|
||||
</select>
|
||||
|
||||
<update id="deactivate">
|
||||
UPDATE user_device_token
|
||||
SET is_active = 'N', updated_at = NOW()
|
||||
WHERE token = #{token} AND user_id = #{userId}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user