fix(core,admin_basic): S3 스토리지 드라이버 실동작 결함 수정 및 S3 호환 스토리지 연결 지원
공개 제보 — 파일 스토리지에서 S3 를 선택해 저장해도 실제 파일 저장이 동작하지 않던 결함의 전면 수정. - S3 어댑터(league/flysystem-aws-s3-v3)·predis 를 코어 기본 의존성으로 포함 — 어댑터 부재 즉사, phpredis 확장 없는 서버의 redis 선택 전면 다운 차단 (부트 시 확장 부재 감지 → predis 자동 폴백) - storage_driver=s3 저장 시 코어 첨부 업로드 디스크를 s3 로 전환 (ATTACHMENT_DISK env 명시가 항상 우선, 기존 행은 저장 당시 disk 로 서빙) - 첨부·템플릿 레이아웃 첨부 서빙을 행 disk 를 따르는 스토리지 스트림으로 교체 — 로컬 절대 경로 전제 fileResponse 는 S3 행에서 filemtime stat 500 (streamedFileResponse: 행 메타 기반 ETag/304/Cache-Control) - S3 호환 스토리지(R2/MinIO/NCP) 연결 지원: 엔드포인트 URL·path-style 설정 신설, 리전 목록 선택 → 자유 입력 전환, 연결 테스트를 실제 저장 경로와 동일 설정(endpoint/path-style)으로 정렬 - 사용 불능 드라이버(어댑터·PHP 확장 부재)의 저장/테스트 요청을 사유와 함께 422 로 차단하는 서버 게이트 신설 (DriverRegistryService 능력 판정) - 웹소켓 연결 테스트에 서버(백엔드 발송용) endpoint 검사 추가 — 클라이언트만 검사해 테스트 성공 + 실제 발송 실패가 가능하던 비대칭 해소 - env 빈 값(`KEY=`) 함정 정규화: AWS_URL/AWS_ENDPOINT/ATTACHMENT_DISK 빈 문자열을 미설정으로 취급 (config 정규화 + 예시 파일 주석 처리) - 플러그인 드라이버 폴백의 log 카테고리 죽은 키(logging.default) 정정 및 websocket 유령 설정 키 제거 - 실 AWS S3 종단 검증 완료 (설정 저장 → 업로드 S3 실저장 → 서빙 200/304)
This commit is contained in:
1 parent
c128a6ea3a
commit
ea58c606a2
56 files changed
+2899
-243
No files matched your search
@@ -72,11 +72,13 @@ _단건 응답: `data` 객체의 필드._
|
||||
| notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 탭 설정 그룹. channels 는 알림 채널 목록으로 각 원소가 id(채널 식별자)·is_active(활성 여부)·sort_order(표시 순서)를 가짐 |
|
||||
| identity | object | `{"default_provider":"g7:core.mail","purpose_providers":{"…` | 본인인증(IDV) 탭 설정 그룹 (기본 provider·목적별 provider 매핑(purpose_providers)·챌린지 유효시간(분)·최대 시도 횟수) |
|
||||
| available_drivers | object | `{"storage":[{"id":"local","label":{"ko":"로컬","en":"Local"…` | 드라이버 선택지 카탈로그 (DriverRegistryService 산물). 종류별(storage/cache/session/queue 등) 선택 가능한 드라이버 목록을 id/다국어 label 형태로 제공 |
|
||||
| _meta | object | `{"limits":{"upload_max_file_size_min":1,"upload_max_file_…` | <!-- TODO: 설명 --> |
|
||||
| _meta | object | `{"limits":{"upload_max_file_size_min":1,"upload_max_file_…` | 화면 검증 메타 — `limits` 는 각 설정 항목의 min/max 경계값 맵 (`config/core.php` 의 `settings_limits` 가 SSoT, 화면 입력 힌트와 FormRequest 검증이 같은 값을 공유) |
|
||||
| abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) |
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- @probed -->
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
@@ -120,19 +122,19 @@ HTTP/1.1 200
|
||||
"webp",
|
||||
"... (총 11건 중 5건 표시)"
|
||||
],
|
||||
"image_max_width": "800",
|
||||
"image_max_height": "600",
|
||||
"image_max_width": 2000,
|
||||
"image_max_height": 2000,
|
||||
"image_quality": 85
|
||||
},
|
||||
"seo": {
|
||||
"meta_title_suffix": null,
|
||||
"meta_description": null,
|
||||
"meta_keywords": null,
|
||||
"google_analytics_id": null,
|
||||
"google_site_verification": null,
|
||||
"...": "(24개 키 생략, 총 29개)"
|
||||
"meta_title_suffix": "",
|
||||
"meta_description": "",
|
||||
"meta_keywords": "",
|
||||
"google_analytics_id": "",
|
||||
"google_site_verification": "",
|
||||
"...": "(23개 키 생략, 총 28개)"
|
||||
},
|
||||
"...": "(11개 키 생략, 총 16개)"
|
||||
"...": "(12개 키 생략, 총 17개)"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -243,10 +245,12 @@ HTTP/1.1 200
|
||||
| advanced.geoip_auto_update_enabled | body | boolean | 아니오 | — | GeoIP DB 자동 업데이트 사용 여부 (주 1회 자동 재다운로드) |
|
||||
| drivers.storage_driver | body | string | 아니오 | — | 스토리지 드라이버 (local/s3) |
|
||||
| drivers.s3_bucket | body | string | 아니오 | max 255 | S3 버킷명 |
|
||||
| drivers.s3_region | body | string | 아니오 | — | S3 리전 |
|
||||
| drivers.s3_region | body | string | 아니오 | max 64, 소문자 영숫자·하이픈 (`^[a-z0-9-]+$`) | S3 리전 — AWS 리전 코드 또는 S3 호환 스토리지 값 (Cloudflare R2 는 `auto`, MinIO 관례는 `us-east-1`) |
|
||||
| drivers.s3_access_key | body | string | 아니오 | max 255 | S3 액세스 키 |
|
||||
| drivers.s3_secret_key | body | string | 아니오 | max 255 | S3 시크릿 키 |
|
||||
| drivers.s3_url | body | string | 아니오 | max 500 | S3 엔드포인트 URL |
|
||||
| drivers.s3_url | body | string | 아니오 | url, max 500 | S3 공개 URL(CDN) base — 파일 URL 생성에만 사용 (API 요청 주소 아님) |
|
||||
| drivers.s3_endpoint | body | string | 아니오 | url, max 500 | S3 API 엔드포인트 — S3 호환 스토리지(R2/MinIO/NCP 등)용. AWS S3 는 미입력 (예: `https://<account-id>.r2.cloudflarestorage.com`) |
|
||||
| drivers.s3_use_path_style | body | boolean | 아니오 | — | S3 path-style 주소 사용 여부 — MinIO 등 path-style 전용 스토리지에서 true |
|
||||
| drivers.cache_driver | body | string | 아니오 | — | 캐시 드라이버 (file/redis/memcached) |
|
||||
| drivers.redis_host | body | string | 아니오 | max 255 | Redis 호스트 주소 |
|
||||
| drivers.redis_port | body | integer | 아니오 | min 1, max 65535 | Redis 포트 번호 |
|
||||
@@ -328,6 +332,7 @@ Content-Type: application/json
|
||||
"general.language": "예시값",
|
||||
"general.currency": "예시값",
|
||||
"general.maintenance_mode": true,
|
||||
"general.asset_url_mode": "https://example.com",
|
||||
"general.site_logo": [
|
||||
"예시값"
|
||||
],
|
||||
@@ -370,6 +375,11 @@ Content-Type: application/json
|
||||
"seo.cache_ttl": 1,
|
||||
"seo.sitemap_enabled": true,
|
||||
"seo.sitemap_cache_ttl": 1,
|
||||
"seo.sitemap_urls_per_file": 1,
|
||||
"seo.sitemap_gzip": true,
|
||||
"seo.sitemap_serve_stale_on_miss": true,
|
||||
"seo.sitemap_max_urls_per_contributor": 1,
|
||||
"seo.sitemap_hreflang_enabled": true,
|
||||
"seo.sitemap_schedule": "예시값",
|
||||
"seo.sitemap_schedule_time": "예시값",
|
||||
"seo.generator_enabled": true,
|
||||
@@ -379,6 +389,10 @@ Content-Type: application/json
|
||||
"security.auth_token_lifetime": 1,
|
||||
"security.max_login_attempts": 1,
|
||||
"security.login_lockout_time": 1,
|
||||
"security.password_min_length": 1,
|
||||
"security.require_password_special_char": true,
|
||||
"security.two_factor_auth": true,
|
||||
"security.allow_internal_outbound_urls": true,
|
||||
"advanced.cache_enabled": true,
|
||||
"advanced.layout_cache_enabled": true,
|
||||
"advanced.layout_cache_ttl": 1,
|
||||
@@ -386,6 +400,7 @@ Content-Type: application/json
|
||||
"advanced.stats_cache_ttl": 1,
|
||||
"advanced.seo_cache_enabled": true,
|
||||
"advanced.seo_cache_ttl": 1,
|
||||
"advanced.seo_sitemap_cache_ttl": 1,
|
||||
"advanced.debug_mode": true,
|
||||
"advanced.sql_query_log": true,
|
||||
"advanced.core_update_github_url": "https://example.com",
|
||||
@@ -393,12 +408,16 @@ Content-Type: application/json
|
||||
"advanced.geoip_enabled": true,
|
||||
"advanced.geoip_license_key": "예시값",
|
||||
"advanced.geoip_auto_update_enabled": true,
|
||||
"advanced.pagination_result_cap": 1,
|
||||
"advanced.pagination_max_page": 1,
|
||||
"drivers.storage_driver": "예시값",
|
||||
"drivers.s3_bucket": "예시값",
|
||||
"drivers.s3_region": "예시값",
|
||||
"drivers.s3_access_key": "예시값",
|
||||
"drivers.s3_secret_key": "예시값",
|
||||
"drivers.s3_url": "https://example.com",
|
||||
"drivers.s3_endpoint": "예시값",
|
||||
"drivers.s3_use_path_style": true,
|
||||
"drivers.cache_driver": "예시값",
|
||||
"drivers.redis_host": "예시값",
|
||||
"drivers.redis_port": 1,
|
||||
@@ -529,6 +548,8 @@ _단건 응답: `data` 객체의 필드._
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- @probed -->
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
@@ -795,6 +816,8 @@ _이 엔드포인트는 `data` 를 반환하지 않습니다 (성공 메시지
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- @probed -->
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
@@ -983,7 +1006,7 @@ _단건 응답: `data` 객체의 필드._
|
||||
| php_memory_limit | string | `512M` | PHP `memory_limit` ini 값 |
|
||||
| max_execution_time | string | `36000초` | PHP `max_execution_time` ini 값 (초 단위 접미사 부착) |
|
||||
| upload_max_filesize | string | `2G` | PHP `upload_max_filesize` ini 값 |
|
||||
| opcache | object | `{"loaded":true,"enabled":true}` | <!-- TODO: 설명 --> |
|
||||
| opcache | object | `{"loaded":true,"enabled":true}` | PHP OPcache 상태 — `loaded`(확장 로드 여부) / `enabled`(런타임 활성화 여부, `opcache.enable` 설정 기준) |
|
||||
| install_path | string | `C:\Users\HeuJung\htdocs\g7_2` | 애플리케이션 설치 루트 경로 (`base_path()`) |
|
||||
| config_path | string | `C:\Users\HeuJung\htdocs\g7_2\storage\…` | 설정 파일 저장 경로 (`storage/app/settings`) |
|
||||
| log_path | string | `C:\Users\HeuJung\htdocs\g7_2\storage\…` | 로그 파일 저장 경로 (`storage/logs`) |
|
||||
@@ -995,6 +1018,8 @@ _단건 응답: `data` 객체의 필드._
|
||||
|
||||
**응답 예시**
|
||||
|
||||
<!-- @probed -->
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
```
|
||||
@@ -1008,22 +1033,22 @@ HTTP/1.1 200
|
||||
"web_server": "nginx/1.27.3",
|
||||
"php_version": "8.3.26",
|
||||
"mysql_version": "Mysql 8.4.3",
|
||||
"g7_version": "7.0.6",
|
||||
"g7_version": "7.0.7",
|
||||
"g7_release_year": "2026",
|
||||
"laravel_version": "12.62.0",
|
||||
"environment": "local",
|
||||
"environment": "production",
|
||||
"cpu_info": "Intel(R) Core(TM) Ultra 5 225H",
|
||||
"memory_usage": {
|
||||
"total": "31.49 GB",
|
||||
"used": "24.31 GB",
|
||||
"free": "7.18 GB",
|
||||
"percentage": 77.2
|
||||
"used": "28.24 GB",
|
||||
"free": "3.25 GB",
|
||||
"percentage": 89.69
|
||||
},
|
||||
"disk_usage": {
|
||||
"total": "474.72 GB",
|
||||
"used": "408.15 GB",
|
||||
"free": "66.57 GB",
|
||||
"percentage": 85.98
|
||||
"used": "375.43 GB",
|
||||
"free": "99.28 GB",
|
||||
"percentage": 79.09
|
||||
},
|
||||
"php_memory_limit": "512M",
|
||||
"max_execution_time": "36000초",
|
||||
@@ -1032,10 +1057,10 @@ HTTP/1.1 200
|
||||
"loaded": true,
|
||||
"enabled": true
|
||||
},
|
||||
"install_path": "C:\\Users\\HeuJung\\htdocs\\g7_2",
|
||||
"config_path": "C:\\Users\\HeuJung\\htdocs\\g7_2\\storage\\app/settings",
|
||||
"log_path": "C:\\Users\\HeuJung\\htdocs\\g7_2\\storage\\logs",
|
||||
"upload_path": "C:\\Users\\HeuJung\\htdocs\\g7_2\\storage\\app/public",
|
||||
"install_path": "C:\\Users\\HeuJung\\htdocs\\g7",
|
||||
"config_path": "C:\\Users\\HeuJung\\htdocs\\g7\\storage\\app/settings",
|
||||
"log_path": "C:\\Users\\HeuJung\\htdocs\\g7\\storage\\logs",
|
||||
"upload_path": "C:\\Users\\HeuJung\\htdocs\\g7\\storage\\app/public",
|
||||
"php_extensions": {
|
||||
"required": {
|
||||
"openssl": true,
|
||||
@@ -1066,13 +1091,13 @@ HTTP/1.1 200
|
||||
"write": {
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"database": "g7_2",
|
||||
"username": "g7_2"
|
||||
"database": "g7",
|
||||
"username": "g7"
|
||||
},
|
||||
"read": []
|
||||
},
|
||||
"timezone": "UTC",
|
||||
"server_time": "2026-08-04 12:53:55"
|
||||
"server_time": "2026-08-13 05:00:24"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1107,10 +1132,12 @@ HTTP/1.1 200
|
||||
| queue_driver | body | string | 아니오 | — | 큐 드라이버 (sync/database/redis) |
|
||||
| websocket_enabled | body | boolean | 아니오 | — | WebSocket 사용 여부 |
|
||||
| s3_bucket | body | string | 아니오 | max 255 | S3 버킷명 |
|
||||
| s3_region | body | string | 아니오 | — | S3 리전 |
|
||||
| s3_region | body | string | 아니오 | max 64, 소문자 영숫자·하이픈 (`^[a-z0-9-]+$`) | S3 리전 — AWS 리전 코드 또는 S3 호환 스토리지 값 (Cloudflare R2 는 `auto`) |
|
||||
| s3_access_key | body | string | 아니오 | max 255 | S3 액세스 키 |
|
||||
| s3_secret_key | body | string | 아니오 | max 255 | S3 시크릿 키 |
|
||||
| s3_url | body | string | 아니오 | max 500 | S3 엔드포인트 URL |
|
||||
| s3_url | body | string | 아니오 | url, max 500 | S3 공개 URL(CDN) base — 연결 테스트에는 사용되지 않음 |
|
||||
| s3_endpoint | body | string | 아니오 | url, max 500 | S3 API 엔드포인트 — 테스트 시 실제 아웃바운드 대상에 반영 (S3 호환 스토리지용) |
|
||||
| s3_use_path_style | body | boolean | 아니오 | — | S3 path-style 주소 사용 여부 (MinIO 등) |
|
||||
| redis_host | body | string | 아니오 | max 255 | Redis 호스트 주소 |
|
||||
| redis_port | body | integer | 아니오 | min 1, max 65535 | Redis 포트 번호 |
|
||||
| redis_password | body | string | 아니오 | max 255 | Redis 비밀번호 |
|
||||
@@ -1118,9 +1145,18 @@ HTTP/1.1 200
|
||||
| memcached_host | body | string | 아니오 | max 255 | Memcached 호스트 주소 |
|
||||
| memcached_port | body | integer | 아니오 | min 1, max 65535 | Memcached 포트 번호 |
|
||||
| websocket_app_key | body | string | 아니오 | max 255 | WebSocket 앱 키 |
|
||||
| websocket_host | body | string | 아니오 | max 255 | WebSocket 호스트 주소 |
|
||||
| websocket_port | body | integer | 아니오 | min 1, max 65535 | WebSocket 포트 번호 |
|
||||
| websocket_scheme | body | string | 아니오 | — | WebSocket 스킴 (http/https) |
|
||||
| websocket_host | body | string | 아니오 | max 255 | WebSocket 클라이언트(브라우저 접속) 호스트 주소 |
|
||||
| websocket_port | body | integer | 아니오 | min 1, max 65535 | WebSocket 클라이언트 포트 번호 |
|
||||
| websocket_scheme | body | string | 아니오 | — | WebSocket 클라이언트 스킴 (http/https) |
|
||||
| websocket_server_host | body | string | 아니오 | max 255 | WebSocket 서버(백엔드 발송용) 호스트 주소 — 미입력 시 클라이언트 값으로 폴백 |
|
||||
| websocket_server_port | body | integer | 아니오 | min 1, max 65535 | WebSocket 서버 포트 번호 — 미입력 시 클라이언트 값으로 폴백 |
|
||||
| websocket_server_scheme | body | string | 아니오 | — | WebSocket 서버 스킴 (http/https) — 미입력 시 클라이언트 값으로 폴백 |
|
||||
|
||||
> WebSocket 테스트는 클라이언트/서버 양측 endpoint 를 모두 probe 합니다. 백엔드 broadcast 는 서버 endpoint 를 사용하므로, 서버 endpoint 실패 시 별도 메시지(`settings.websocket_server_test_failed`)로 구분 보고됩니다.
|
||||
|
||||
> S3 테스트는 Flysystem 어댑터(`league/flysystem-aws-s3-v3`) 존재를 선검사하며, `s3_endpoint`/`s3_use_path_style` 이 실제 아웃바운드 대상에 반영됩니다.
|
||||
|
||||
> 사용 불능 드라이버(어댑터 클래스·PHP 확장 부재)는 저장/테스트 모두 422 (`validation.settings.driver_unusable`) 로 거부됩니다.
|
||||
|
||||
> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.settings.test_driver_connection_validation_rules`).
|
||||
|
||||
@@ -1144,6 +1180,8 @@ Content-Type: application/json
|
||||
"s3_access_key": "예시값",
|
||||
"s3_secret_key": "예시값",
|
||||
"s3_url": "https://example.com",
|
||||
"s3_endpoint": "예시값",
|
||||
"s3_use_path_style": true,
|
||||
"redis_host": "예시값",
|
||||
"redis_port": 1,
|
||||
"redis_password": "Password123!",
|
||||
@@ -1153,7 +1191,10 @@ Content-Type: application/json
|
||||
"websocket_app_key": "예시값",
|
||||
"websocket_host": "예시값",
|
||||
"websocket_port": 1,
|
||||
"websocket_scheme": "예시값"
|
||||
"websocket_scheme": "예시값",
|
||||
"websocket_server_host": "예시값",
|
||||
"websocket_server_port": 1,
|
||||
"websocket_server_scheme": "예시값"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in new issue
Block a user