번들 확장 README 제목이 manifest 확장명 그대로(「게시판」)라 그 문서만 연
사람이 그누보드7의 확장인지, 모듈인지 템플릿인지 알 수 없었다.
README · AGENTS.md · docs/README.md 진입 문서 60개의 제목을
「그누보드7 {확장명} {유형}」 으로 바꾸고, 조립 규칙을 ExtensionInventory::docTitle
한 곳에 두어 골격 생성기와 계약 테스트가 같은 헬퍼를 쓰게 했다.
확장명이 이미 유형으로 끝나면 겹쳐 붙이지 않는다.
공개 규정에는 번들 확장 전용 관례임을 주어로 명시하고 제3자 확장에는
요구하지 않는다는 문장을 두었다. 언어팩은 README 가 없어 대상 밖이다.
17 KiB
그누보드7 비즈뿌리오 메시지 발송 플러그인 — 에이전트 가이드
이 문서는 이 플러그인을 수정하는 에이전트·확장개발자를 위한 것입니다. 도입 검토·운영 관점은 README.md 를 보세요.
TL;DR (5초 요약)
1. 유형: 플러그인 (sirsoft-message_bizppurio) — 코어 알림에 문자(SMS/LMS)·카카오 알림톡 채널을 추가한다. 알림 자체는 정의하지 않고 발송 수단만 담당
2. 확장 방식: 발행 훅 1개 — 잔액 부족·한도 초과 시 `balance.low`(쿨다운 내 1회). 그 외 배선은 구독 6종(코어 알림 로그·설정, 이커머스 비회원 연락처)
3. 건드리면 안 되는 것: webhook 라우트의 IP 화이트리스트 제거, 승인 템플릿 직접 수정(승인 취소 선행), 잔액부족 통지를 같은 채널로만 보내기, 크리덴셜 프론트 노출
4. 작업 위치: `plugins/_bundled/sirsoft-message_bizppurio` — 활성 디렉토리 직접 수정 금지
5. 반영: `php artisan plugin:update sirsoft-message_bizppurio --force`
1. 이 확장은 무엇인가
코어 알림 시스템에 문자(SMS/LMS)와 카카오 알림톡 채널을 추가하는 플러그인입니다. 알림을 새로 만들지 않고, 코어와 모듈이 이미 발화하는 알림을 비즈뿌리오 API 로 내보냅니다.
설계 원칙 넷:
- 채널만 추가하고 알림은 만들지 않는다. 어떤 사건에 알림을 보낼지는 코어와 각 모듈이
정합니다. 이 플러그인은
RegisterNotificationChannelsListener로 채널을 등록하고SeedChannelTemplatesListener로 그 채널의 템플릿 자리를 시드할 뿐입니다. 그래서 삭제하면 채널만 사라지고 알림 자체는 남습니다. - 발송 결과는 나중에 돌아온다. 비즈뿌리오는 발송 API 응답이 아니라 webhook 통보로 최종 결과를 줍니다. 그래서 발송과 결과 기록이 분리되어 있고, webhook 이 등록되지 않은 사이트에서는 발송은 되지만 이력에 결과가 남지 않습니다 — 오류가 아니라 결과 미상입니다.
- 승인된 내용을 박제한다. 알림톡 템플릿은 카카오 승인 시점의 내용을 로컬에 저장해 두고 발송하며, 발송할 때마다 카카오를 조회하지 않습니다. 그래서 승인 후 내용을 고치려면 승인을 먼저 취소해야 하고, 취소하는 순간 알림톡 발송이 멈춥니다.
- 실패를 종류별로 다르게 다룬다. 결과 코드를 성공 / 재시도(일시 오류) / 잔액 부족 / 영구 실패 넷으로 분류합니다. 잔액 부족은 재시도해도 소용없으므로 즉시 실패 처리하고 관리자에게 알립니다 — 다만 그 알림이 같은 채널로 나가면 함께 실패하므로 쿨다운을 두고 다른 채널 병행을 권합니다.
의도적으로 하지 않는 것: 알림 정의·수신자 해석·발송 대상 판정. 그 셋은 코어
GenericNotification 과 각 도메인의 일이며, 이 플러그인은 채널 구현체입니다.
2. 디렉토리 지도
| 경로 | 역할 | 수정 시 필요한 절차 |
|---|---|---|
plugin.json |
manifest (버전 SSoT) | version 변경 시 package.json·package-lock.json·composer.json 동기화 |
plugin.php |
진입 클래스 (선언형 표면 SSoT) | 표면 변경 시 ext:docgen 재실행 + 코어 최소 버전 검토 |
src/Controllers/ |
컨트롤러 | API 표면 변경 시 api:docgen 재실행 |
src/Http/Requests/ |
FormRequest (검증 SSoT) | 검증 규칙은 Service 가 아니라 여기에 둔다 |
src/Services/ |
비즈니스 로직 | Repository 인터페이스 주입 (구체 클래스 금지) |
src/Repositories/ |
데이터 접근 | 목록 쿼리는 컬럼 프루닝·정렬 화이트리스트 확인 |
src/Models/ |
Eloquent 모델 | 스키마 변경 시 마이그레이션 + 업그레이드 스텝 동반 |
src/Listeners/ |
훅 리스너 | Repository 경유 (Model·DB 파사드 직접 접근 금지) |
src/Enums/ |
상태·타입·분류 | 문자열 리터럴 대신 Enum 을 SSoT 로 둔다 |
src/routes/ |
라우트 | 모든 라우트에 name() 필수 |
database/migrations/ |
마이그레이션 | 한국어 comment + down() 필수, 기설치본은 업그레이드 스텝으로 백필 |
resources/layouts/ |
레이아웃 JSON | php artisan plugin:update sirsoft-message_bizppurio --force (빌드 불필요) |
resources/js/ |
프론트 엔트리·핸들러 | php artisan plugin:build → php artisan plugin:update sirsoft-message_bizppurio --force |
resources/extensions/ |
다른 확장 레이아웃에 주입하는 조각 | php artisan plugin:update sirsoft-message_bizppurio --force |
dist/ |
커밋되는 빌드 산출물 | --production 으로 재빌드 (sourceMappingURL 잔존 금지) |
config/ |
확장 config | 설정 기본값은 settings 스키마와 어긋나지 않게 |
tests/ |
테스트 | 변경 범위만 필터 실행 |
CHANGELOG.md |
변경 이력 | 버전 상향 시 항목 추가 (미기재 시 버전 상향 불가) |
components.json |
편집기 컴포넌트 선언 (레이아웃 저작자가 읽는 props 계약) | php artisan plugin:update sirsoft-message_bizppurio --force |
docs/ |
개발자 문서 | 표면 변경 시 php artisan ext:docgen 재실행 |
lang/ |
다국어 | 키 추가 시 ko·en 동시 반영 + 번들 ja 팩 동기화 |
3. 핵심 흐름
발송: 코어·모듈이 알림 발화 → 코어 알림 시스템이 켜져 있는 채널별로 발송 작업을 큐잉 →
이 플러그인의 채널 구현이 그 작업을 받음 → 알림톡이면 승인된 템플릿 내용으로, 문자면 본문
길이에 따라 SMS/LMS 를 골라 비즈뿌리오 발송 API 호출 → 발송 기록(bizppurio_dispatches)
적재. 알림톡 발송이 불가하거나 실패하면 설정에 따라 대체 SMS 로 내려갑니다.
결과 수신: 비즈뿌리오가 POST /webhook 으로 결과 통보 →
BizppurioWebhookIpWhitelist 미들웨어가 발신 IP 를 검사 → WebhookReportService 가 결과
코드를 넷으로 분류해 발송 기록을 갱신. 잔액 부족(9070/9071/7436)이면
sirsoft-message_bizppurio.balance.low 액션을 발행하고 관리자 알림을 보냅니다 — 대량 실패
시 반복을 막기 위해 채널별 쿨다운(기본 3600초) 안에서는 최초 1회만 실행됩니다.
템플릿 수명주기: 관리자 화면에서 작성(POST templates) → 검수 신청
(POST templates/{id}/request) → 카카오 검수 → 승인/반려. 결과는
bizppurio:sync-template-status 스케줄이 30분마다 확인하고, 화면의 [새로고침]
(POST templates/{id}/sync)으로 즉시 확인할 수도 있습니다. 승인된 템플릿을 고치려면
POST templates/{id}/cancel-approval 로 승인을 먼저 취소합니다.
알림 이력 연결: 코어가 알림 발송 성공·실패를 기록하면
(core.notification_log.after_log_sent / after_log_failed) LinkNotificationLogListener
가 그 로그와 이 플러그인의 발송 기록을 연결합니다. 관리자 "알림 발송 이력" 화면에서 문자·
알림톡 결과가 함께 보이는 것이 이 연결 덕분입니다.
4. 확장점
| 확장점 | 수 | 상세 |
|---|---|---|
| 발행 훅 | 1개 | 발행 훅 |
| 구독 훅 | 6개 | 구독 훅 |
| 훅 리스너 | 7개 | 훅 리스너 |
| 레이아웃 확장 | 7개 | 레이아웃 확장 |
| 미들웨어 | 1개 | 미들웨어 |
| 브로드캐스트 채널 | 0개 | 브로드캐스트 채널 |
| 스케줄 | 1개 | 스케줄 |
| 알림 정의 | 1개 | 알림 정의 |
발행 훅은 하나뿐입니다.
sirsoft-message_bizppurio.balance.low — 잔액 부족·후불 한도 초과로 발송이 실패했을 때
(쿨다운 내 최초 1회) 발화합니다. 인수는 string $resultCode, string $channel 이며,
$resultCode 는 9070(문자 잔액 부족) · 7436(알림톡 지갑 잔액 부족) · 9071(후불 한도
초과) 중 하나입니다.
use App\Extension\HookManager;
HookManager::addAction(
'sirsoft-message_bizppurio.balance.low',
function (string $resultCode, string $channel) {
// 예: 잔액 부족 시 Slack 으로도 별도 알림
SlackNotifier::send("비즈뿌리오 잔액 부족: 채널={$channel}, 코드={$resultCode}");
},
priority: 10
);
이 훅이 관리자 자체 알림(잔액부족·후불한도초과 안내)을 발화하는 지점과 동일합니다. 잔액 부족 알림을 문자·알림톡이 아닌 다른 경로로 받고 싶을 때 여기에 붙입니다 — 같은 채널로 보내면 잔액이 없으므로 그 알림도 함께 실패합니다.
구독 6종이 이 플러그인의 배선 전부입니다. 코어 알림 로그 2종(발송 기록 연결) · 코어 설정
2종(저장 시 토큰 무효화 · 운영 모드 전환 시 필수값 검증) · 이커머스 1종(비회원 연락처 주입) ·
자기 훅 1종(잔액부족 알림 데이터). sirsoft-ecommerce.notification.extract_data 는 manifest
의존이 아니라 훅 구독이므로, 이커머스가 없으면 비회원 문자 발송만 비고 나머지는 정상
동작합니다.
레이아웃 조각 7개가 UI 대부분입니다 — 알림 설정 화면의 비즈뿌리오 탭(코어·게시판·이커머스 각각), 알림 목록 행 하단 요약, 알림 템플릿 편집 창의 알림톡·문자 섹션과 하단 버튼, 발송 이력 화면의 결과 열. 대상 화면이 그 자리를 없애면 조각은 오류 없이 사라집니다.
5. 수정 시 동반 의무
_bundled에서만 수정하고php artisan plugin:update sirsoft-message_bizppurio --force로 반영- manifest version 상향 시
package.json·package-lock.json·composer.json동기화 + CHANGELOG 기재 - 스키마 변경 시 마이그레이션(한국어 comment +
down()) + 기설치본 백필용 업그레이드 스텝 - 발행 훅 추가·이름 변경 시
php artisan ext:docgen재실행 (구독하는 확장의 계약이 바뀝니다) - API 표면 변경 시
php artisan api:docgen --scope=plugin:sirsoft-message_bizppurio재실행 +docs/api/**갱신 - 레이아웃 JSON 변경 시 빌드 없이 update 만 — 신규 Tailwind 클래스는 빌드된 CSS 에 존재하는지 확인
- TSX/TS 변경 시
--production재빌드 후dist/커밋 (sourceMappingURL 잔존 금지) - 다국어 키 추가 시 ko·en 동시 반영 + 번들 ja 언어팩 증분 동기화
- webhook 라우트를 추가·변경했다면
getMiddleware()의targets에 그 라우트 이름을 함께 추가 (누락 시 인증 없는 공개 경로가 된다) - 결과 코드 분류(성공/재시도/잔액부족/영구실패)를 바꿨다면
lang/{ko,en}/result_codes.php의 사유 문구도 함께 갱신 - 코어 알림 시스템(채널 등록·발송 로그 훅)이 바뀌면 이 플러그인의 구독 4종이 조용히 끊기므로 함께 확인
- 레이아웃 조각 7개는 대상 화면(알림 설정·알림 템플릿 편집·발송 이력)의 자리가 사라지면 오류 없이 빠진다 — 코어·게시판·이커머스 업그레이드 후 노출 확인
- 크리덴셜 설정을 추가한다면
frontend_schema에expose: false+sensitive: true를 함께 선언 dist/는 커밋되는 배포 산출물 — TS 를 고쳤으면--production재빌드 후 커밋 (sourceMappingURL잔존 금지)- 레이아웃·컴포넌트·
data_source를 건드렸다면docs/editor-spec.md를 확인 — 이 확장은 편집기 스펙이 없어bizppurioCategories·bizppurioProfiles·bizppurio_templates_list·report_url·templates_readiness가 편집기 캔버스에서 빈 화면으로 보인다.data_source를 더 늘리면 그 자리도 같은 상태가 된다
6. 금지 패턴
| 금지 | 올바른 사용 | 이유 |
|---|---|---|
| 이 플러그인에서 알림 자체(수신자·발화 조건)를 정의 | 채널 등록까지만 — 알림은 코어·모듈이 정의 | 채널 구현이 알림을 소유하면 플러그인을 바꿀 때 알림 이력까지 사라진다 |
| 잔액 부족 알림을 문자·알림톡 채널로만 보내도록 두기 | 사이트 내 알림·메일 등 다른 채널 병행 | 잔액이 없어서 실패한 상황인데 통지도 같은 수단이면 함께 실패한다 |
| 잔액 부족 알림에 쿨다운 없이 매 실패마다 발송 | 채널별 쿨다운(기본 3600초) 안에서 최초 1회 | 대량 발송이 한꺼번에 실패하면 통지가 수백 건 쏟아진다 |
| 발송할 때마다 카카오에 템플릿을 조회 | 승인 시점 내용을 로컬에 박제해 발송 | 외부 조회가 발송 경로에 들어가면 그 서비스 지연이 곧 발송 지연이 된다 |
| 승인된 템플릿을 그대로 수정 | 승인 취소 → 수정 → 재신청 | 카카오 승인 대상은 특정 내용이다. 승인 후 내용이 바뀌면 승인과 발송물이 어긋난다 |
| webhook 라우트를 IP 화이트리스트 없이 공개 | BizppurioWebhookIpWhitelist 부착 유지 |
인증 없는 공개 엔드포인트다 — 위조 통보로 발송 결과를 조작할 수 있다 |
webhook 라우트를 추가·변경하면서 미들웨어 targets 선언을 그대로 두기 |
라우트 이름을 getMiddleware() 선언에도 추가 |
이름이 어긋나면 미들웨어가 붙지 않는데, 정상 응답이 나가므로 오류도 로그도 남지 않는다 |
| 크리덴셜(비밀번호·API 키·발신프로필 키)을 프론트에 노출 | expose: false + sensitive: true 유지 |
발송 권한이 곧 비용이다 |
| 일시 오류와 영구 실패를 같은 방식으로 재시도 | 결과 코드 4분류를 따른다 | 영구 실패를 재시도하면 비용만 늘고, 일시 오류를 포기하면 발송이 누락된다 |
| 문자 본문이 없는 언어에 기본 언어 본문도 없이 발송 시도 | 두 단계 폴백 후 그 알림의 문자 발송을 건너뛴다 | 빈 본문 발송은 비용이 나가면서 수신자에게 아무 정보도 주지 않는다 |
7. 테스트 실행
| 종류 | 개수 | 위치 |
|---|---|---|
| PHPUnit | 37개 | plugins/_bundled/sirsoft-message_bizppurio/tests |
| Vitest | 7개 | vitest.config.ts |
| Playwright | 2개 | tests/Playwright |
| 시나리오 매니페스트 | 6개 | tests/scenarios |
기저 TestCase: tests/PluginTestCase.php — 확장 테스트는 이 클래스를 상속합니다 (Tests\TestCase 직접 상속 금지).
# PHPUnit (변경 범위만) (Bash)
php vendor/bin/phpunit plugins/_bundled/sirsoft-message_bizppurio/tests --filter='<대상클래스>'
# Vitest (확장 디렉토리에서) (PowerShell)
cd plugins/_bundled/sirsoft-message_bizppurio && powershell -Command "npm run test:run -- <대상>"
# Playwright E2E (확장 디렉토리에서) (Bash)
cd plugins/_bundled/sirsoft-message_bizppurio && npm run test:e2e -- specs/<대상>.spec.ts
무필터 전체 실행은 금지되어 있습니다 — 변경 범위에 걸리는 대상만 지정해 실행합니다.
8. 문서 목차
| 문서 | 내용 | 상태 |
|---|---|---|
| docs/README.md | 문서 통합 목차와 실측 집계 | ✅ |
| docs/architecture.md | 설계 의도·계층 지도·디렉토리 맵 | ✅ |
| docs/extension-points.md | 발행/구독 훅·미들웨어·채널·스케줄 | ✅ |
| docs/data-model.md | 모델·소유 테이블·마이그레이션·Enum | ✅ |
| docs/settings.md | 설정 스키마·권한·메뉴·라우트·의존 관계 | ✅ |
| docs/frontend.md | 레이아웃·액션 핸들러·전역 진입점·에셋 | ✅ |
| docs/editor-spec.md | 레이아웃 편집기에 선언한 팔레트·컨트롤·샘플 데이터 | ✅ |
| docs/api/ | API 레퍼런스 (엔드포인트별 파라미터·응답 필드) | ✅ |
| CHANGELOG.md | 변경 이력 | ✅ |