From ac6488b3432c4763d2192751cbe4a81364ef804e Mon Sep 17 00:00:00 2001 From: Hyojin Ahn Date: Thu, 21 May 2026 11:55:34 -0400 Subject: [PATCH] cleanup --- http/change-password.http | 45 ++ http/http-test.http | 17 + http/jpa-auditing.http | 44 ++ jwt-security.drawio | 517 ++++++++++++++++++ logs/integration-service.log | 25 + logs/integration-service.log.2026-03-19.0.gz | Bin 0 -> 1695 bytes logs/integration-service.log.2026-03-30.0.gz | Bin 0 -> 1696 bytes .../erp/dto/CustomerDailyOrderRequestDto.java | 1 + .../goi/erp/dto/CustomerRegionRequestDto.java | 16 + .../erp/dto/CustomerRegionResponseDto.java | 17 + .../com/goi/erp/dto/CustomerRequestDto.java | 3 +- .../com/goi/erp/dto/CustomerResponseDto.java | 1 + .../java/com/goi/erp/entity/Customer.java | 3 +- .../goi/erp/entity/CustomerDailyOrder.java | 2 +- .../{Employee.java => CustomerRegion.java} | 26 +- .../com/goi/erp/entity/EntityChangeLog.java | 2 +- .../service/CustomerDailyOrderService.java | 70 ++- .../com/goi/erp/service/CustomerService.java | 2 + .../goi/erp/service/HcmEmployeeClient.java | 37 +- 19 files changed, 774 insertions(+), 54 deletions(-) create mode 100644 http/change-password.http create mode 100644 http/http-test.http create mode 100644 http/jpa-auditing.http create mode 100644 jwt-security.drawio create mode 100644 logs/integration-service.log create mode 100644 logs/integration-service.log.2026-03-19.0.gz create mode 100644 logs/integration-service.log.2026-03-30.0.gz create mode 100644 src/main/java/com/goi/erp/dto/CustomerRegionRequestDto.java create mode 100644 src/main/java/com/goi/erp/dto/CustomerRegionResponseDto.java rename src/main/java/com/goi/erp/entity/{Employee.java => CustomerRegion.java} (52%) diff --git a/http/change-password.http b/http/change-password.http new file mode 100644 index 0000000..66f47d2 --- /dev/null +++ b/http/change-password.http @@ -0,0 +1,45 @@ +### Register User +POST http://localhost:8080/api/v1/auth/register +Content-Type: application/json + +{ + "firstname": "Ali", + "lastname": "Bouali", + "email": "alibou@mail.com", + "password": "password", + "role": "ADMIN" +} + +> {% client.global.set("auth-token", response.body.access_token); %} + +### Query the Demo endpoint +GET http://localhost:8080/api/v1/demo-controller +Authorization: Bearer {{auth-token}} + + +### Change the password +PATCH http://localhost:8080/api/v1/users +Content-Type: application/json +Authorization: Bearer {{auth-token}} + +{ + "currentPassword": "password", + "newPassword": "newPassword", + "confirmationPassword": "newPassword" +} + +### Login again and update the token +POST http://localhost:8080/api/v1/auth/authenticate +Content-Type: application/json + +{ + "email": "alibou@mail.com", + "password": "newPassword" +} + +> {% client.global.set("new-auth-token", response.body.access_token); %} + + +### Query the Demo endpoint after password change +GET http://localhost:8080/api/v1/demo-controller +Authorization: Bearer {{new-auth-token}} diff --git a/http/http-test.http b/http/http-test.http new file mode 100644 index 0000000..918cb2e --- /dev/null +++ b/http/http-test.http @@ -0,0 +1,17 @@ +### Register User +POST http://localhost:8080/api/v1/auth/register +Content-Type: application/json + +{ + "firstname": "Ali", + "lastname": "Bouali", + "email": "alibou21@mail.com", + "password": "password", + "role": "ADMIN" +} + +> {% client.global.set("auth-token", response.body.access_token); %} + +### Query the Demo endpoint +GET http://localhost:8080/api/v1/demo-controller +Authorization: Bearer {{auth-token}} diff --git a/http/jpa-auditing.http b/http/jpa-auditing.http new file mode 100644 index 0000000..a8245bb --- /dev/null +++ b/http/jpa-auditing.http @@ -0,0 +1,44 @@ +### Register User +POST http://localhost:8080/api/v1/auth/register +Content-Type: application/json + +{ + "firstname": "Ali", + "lastname": "Bouali", + "email": "alibou@mail.com", + "password": "password", + "role": "ADMIN" +} + +> {% client.global.set("auth-token", response.body.access_token); %} + + +###Create a new book +POST http://localhost:8080/api/v1/books +Authorization: Bearer {{auth-token}} +Content-Type: application/json + +{ + "author": "Alibou", + "isbn": "12345" +} + +### Query Books +GET http://localhost:8080/api/v1/books +Authorization: Bearer {{auth-token}} + +### Update one book +POST http://localhost:8080/api/v1/books +Authorization: Bearer {{auth-token}} +Content-Type: application/json + +{ + "id": 1, + "author": "Alibou 2", + "isbn": "12345" +} + + +### Query the Books one more time +GET http://localhost:8080/api/v1/books +Authorization: Bearer {{auth-token}} diff --git a/jwt-security.drawio b/jwt-security.drawio new file mode 100644 index 0000000..75c044a --- /dev/null +++ b/jwt-security.drawio @@ -0,0 +1,517 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/logs/integration-service.log b/logs/integration-service.log new file mode 100644 index 0000000..02a4341 --- /dev/null +++ b/logs/integration-service.log @@ -0,0 +1,25 @@ +2026-05-20T08:25:45.241-04:00 INFO 5596 --- [main] com.goi.erp.SecurityApplication : Starting SecurityApplication using Java 17.0.16 with PID 5596 (D:\workspace-eclipse\crm-rest-api\target\classes started by Hyojin Ahn in D:\workspace-eclipse\crm-rest-api) +2026-05-20T08:25:45.246-04:00 INFO 5596 --- [main] com.goi.erp.SecurityApplication : The following 1 profile is active: "default" +2026-05-20T08:25:45.694-04:00 INFO 5596 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. +2026-05-20T08:25:45.786-04:00 INFO 5596 --- [main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 87 ms. Found 3 JPA repository interfaces. +2026-05-20T08:25:46.383-04:00 INFO 5596 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8082 (http) +2026-05-20T08:25:46.389-04:00 INFO 5596 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat] +2026-05-20T08:25:46.389-04:00 INFO 5596 --- [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.13] +2026-05-20T08:25:46.457-04:00 INFO 5596 --- [main] o.a.c.c.C.[.[localhost].[/crm-rest-api] : Initializing Spring embedded WebApplicationContext +2026-05-20T08:25:46.457-04:00 INFO 5596 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1173 ms +2026-05-20T08:25:46.602-04:00 INFO 5596 --- [main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] +2026-05-20T08:25:46.632-04:00 INFO 5596 --- [main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.2.9.Final +2026-05-20T08:25:46.635-04:00 INFO 5596 --- [main] org.hibernate.cfg.Environment : HHH000406: Using bytecode reflection optimizer +2026-05-20T08:25:46.716-04:00 INFO 5596 --- [main] o.h.b.i.BytecodeProviderInitiator : HHH000021: Bytecode provider name : bytebuddy +2026-05-20T08:25:46.783-04:00 INFO 5596 --- [main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer +2026-05-20T08:25:46.793-04:00 INFO 5596 --- [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... +2026-05-20T08:25:46.891-04:00 INFO 5596 --- [main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@674e4c82 +2026-05-20T08:25:46.892-04:00 INFO 5596 --- [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. +2026-05-20T08:25:47.044-04:00 INFO 5596 --- [main] o.h.b.i.BytecodeProviderInitiator : HHH000021: Bytecode provider name : bytebuddy +2026-05-20T08:25:47.499-04:00 INFO 5596 --- [main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform] +2026-05-20T08:25:47.549-04:00 INFO 5596 --- [main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' +2026-05-20T08:25:47.931-04:00 WARN 5596 --- [main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning +2026-05-20T08:25:48.177-04:00 INFO 5596 --- [main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator' +2026-05-20T08:25:48.210-04:00 INFO 5596 --- [main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@466fd19b, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@348d1a5e, org.springframework.security.web.context.SecurityContextHolderFilter@3fc92211, org.springframework.security.web.header.HeaderWriterFilter@227ae4e4, org.springframework.security.web.authentication.logout.LogoutFilter@3ae5cbb9, org.springframework.web.filter.CorsFilter@7f746160, com.goi.erp.config.JwtAuthenticationFilter@16ea0f22, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@117bfeb8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@31aec04a, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@17524c36, org.springframework.security.web.session.SessionManagementFilter@440309c5, org.springframework.security.web.access.ExceptionTranslationFilter@1b2213b2, org.springframework.security.web.access.intercept.AuthorizationFilter@24e0bdb] +2026-05-20T08:25:48.496-04:00 INFO 5596 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8082 (http) with context path '/crm-rest-api' +2026-05-20T08:25:48.505-04:00 INFO 5596 --- [main] com.goi.erp.SecurityApplication : Started SecurityApplication in 3.548 seconds (process running for 3.992) diff --git a/logs/integration-service.log.2026-03-19.0.gz b/logs/integration-service.log.2026-03-19.0.gz new file mode 100644 index 0000000000000000000000000000000000000000..7e30c440ae0d37b88d186c8a963d02504494d477 GIT binary patch literal 1695 zcmV;Q24MLgiwFP!00000|GijEZ`(K!y)V%JKokqK1!`7bwk)6O<^xHPwn36^4rvdR zG_sk>q)Jk8++V*#N>1!nu9DsE<|0R;W*%qWycsI+g3-VW2YzztB@^GDggy;?ycJD6 zkC5XJhaX8025~?Jg8{i-FfP83OfKk5atf-V7m(G8o7J?cN}e&pr6Biz6LMjgGF;3^ z*ScEUCnxNd5kICL^+#mM&77Pa@3&50?oU21rTVHXmcanBl2;l&XKFD}pv{0){4+Y3 zf%%-3Ol#0YTZfR7bVZI<@`ejCoeP4OUQ4f@{SkCUBk+G)G%n{r3R#wNX*ceZO38wk zK)5C>GyE1NC2IXX$Ee!)9UM+SonDfK%z<`;i-*x~19!*;*KW!T+9pWJX(^6$6-N9jr`^HRT>kg+#LTF;HCI{AiZqMi&(%8x>di;zrFPDGl72e~4*` zbjfJD?|XLqY#uS4V+nTqo;+})N%k2i&9woM!6!s{EDA}k1zTWxTlw0N z&*8Wanwo8Z`^);-l+aBvZJ^;Q2Cq^y|H z198ih6blfh6OuQYkWVg9>B>Ncf{A)ml#sbZ%F6HsDue3AGm3lFXpU;c>ATi8=H-^> zpc;OoG?hX{D?<-TB9=VI{Bp0$) zP9xhmxd&%bmII#*?zM~39UWsFg-`Wep>g`)hwSn_b#jzRA=Y$3`e83KGZeFLCB4bh zjGoQ*?iaj`{n0SZVYCZs51syHm>zU#N34>hUyc4ZK8G|4hy8GX8pmi(PYgRNnXyFt z5zl?OC*Hc;H&zpF!(t`E$v5`BZ5?UaA$2roE|K)cFj}>*KWOp?xnZ?aR&i6~QM+W(>( zogJ5ad8~|E8 z)Xv=~z}OaElN@QCq8IY51~=L+*ovfJcjMG1Cwc9p$k|gMkXqIiIk>YenwxTEtJ9%@ zoj8mnIoB2lF>?g9WXcY!U4eT}StbhBqeNkl1i zbGU$Z9gdy2K>-ZMP`C5S{(A8SQ(0R&ccu$bB|WD=i%L60`%RqJho3`UB6s)ZxT-~U z#mf@4KC>ZXVufMF=xfuA#;!1$Ko$yX*q+j;Zf!aH+KwGqAp1>74}>lJPpWJjy$#1f z66Qhpn)F)An&FDJ04UQdkx@H<*&i**k8#bcp<3&~2MFUVW>4_24n{l99mx`jv<3`C z?2V%I5$5K=@I89uE?0Q_-bgeWWnq%TBaE=x%u$Hi0h5+;CTnBoJiN5lhF%yKQQ~)7 zvqlQHknTyP*9ek0%vn~v-Z}y|#X(P&X1e9JeW@5GISZplpw>7cCPlXh#g{*aF5QbSA1&{`hLfvrx zqPaE{wQXe`fVjwqi1INIGdsW2gS!kW3+B@Ho^lgcG|q-2g!Xu2u1#5c)NT}qxbIv0 zWNDGGc+}apA{vf+KCL|c;JyC*gI%KQ$lWMTTcc|s+&n4n5W_He#1!?EPh$4NM#nN# pOzh@yXvI>BT$7hobLt|hZa#O|&KuGs7`=M-$KNmraS6Q>008VUISBv& literal 0 HcmV?d00001 diff --git a/logs/integration-service.log.2026-03-30.0.gz b/logs/integration-service.log.2026-03-30.0.gz new file mode 100644 index 0000000000000000000000000000000000000000..0364b6bf7852d10965b29cb576de4db90df1561c GIT binary patch literal 1696 zcmV;R24DFfiwFP!00000|GijEZ`(K!y%*?zAc_Ur0`*jqZTaI=Hy=oXZZ}BM%^~f9 zlEyYunN&$Cj{EC(NJ)v^%65wFZZ2{pa^`8~&6}a3Ac{Ldx6=)-g5fv}#=Q{)!$BwL zje~%((~rl0u|c;Vu}-JMZWdg~FDz9Hm?;6M3%JCz)WWVNMUjh?TcIR-_#3lJ%e579 z#@g1E#yvUXcbtU-2q28vQrI~=KRv8`zB(L#UMl_76gbz(58XQnCBhnVAxTO=^= zm9oZaUKBn%g?5)iZaF(UpD^9*7Zg($y`$sFrw>at}_Qkq_|mkroMSo!Ta)`cs`OdA@Qe**LOKuhW_E0-AufRI!L*W*Y2RQf^sQjk4@N@Nea(TM2YjxP>(<5Msf32I3-2Db$V zUmKF=P+F102Q@pvyr`&ra&i&`K}6QrxmHv?UZ~Ge8(}QU6h9HjQ#n=aM)C#awwA3e z$&7n@lBs8F*#35I)|GQzLn~yxFruH$4))i@pUm-*-PK!Th+qUHBRt=RXiuy*5T(-@ z9LYPOm0X~-t%!nn%s%-rB`b?5=_KjUG{@9uQ59A!ND_2AoI!UV+vlW00^ZlADJgd% zLtRmulrDQ;_v+a9yR6eE7t-}{Kd78OI2zw zjdhg#gL9?wPRKeBngy_>O^l-b6VayYf^5U18@A2$#LG~sq->}{)T91oXCz|Za=6Ws z6wYS{4-4MKgC6$MVYChD?&ST;AU*0)YotP&9rrcV|BuQc^!vMq5{CxCdNK<*v-~{g z)^YGhI1hC`3Yt1!Tg`+Eiqi%!+SKbd1*B?$;2F++7U9-%DC+HxQv6Y^S#4F*5aM8W z8QhYVQ$KVKh5ko+D}tyS>;+{!pu;N4%{k{-X8z1HB+T^qa9ruNF4iVl8oA| zIU`fQ5Dt_!^`60W(2K)3c(ZZZtx5yVmUgn?w%!!R$b)GVJps0%$slVobx~ap+*x5l z#e|K$aF9$f89v2cHG|UupglX@Z0)BBOUN+|di>XeJdsMS7OFJuQ5f{2UfPYHgt{XC zWp%AM>Uv7U{$9`xMrr>k5K}j^!_j?;g#&Zt8c)7~D externalId (문자열 email or id) if (dto.getCdoExternalDriverId() != null) { - return hcmEmployeeClient.getEmpIdFromExternalId( dto.getCdoExternalDriverId() ); + Map body = hcmEmployeeClient.getEmpFromExternalId( "MIS_DRIVER", dto.getCdoExternalCreatedBy() ); + if (body != null && body.get("eexEmpId") != null) { + + Object raw = body.get("eexEmpId"); + + if (raw instanceof Number) { + return ((Number) raw).longValue(); + } + + // 예상 밖 타입일 경우 + } } // 3. ERP 내부 요청 (driverUuid 방식) if (dto.getCdoDriverUuid() != null) { - return hcmEmployeeClient.getEmpIdFromUuid(dto.getCdoDriverUuid()); + + Map body = hcmEmployeeClient.getEmpFromUuid(dto.getCdoDriverUuid()); + if (body != null && body.get("empId") != null) { + + Object raw = body.get("empId"); + + if (raw instanceof Number) { + return ((Number) raw).longValue(); // 🔥 모든 숫자를 Long 변환 + } + + // 예상 밖 타입일 경우 + } + } + + return null; + } + + private String resolveCreatedBy(CustomerDailyOrderRequestDto dto) { + + // 1. driver id + if (dto.getCdoLoginUser() != null) { + return dto.getCdoLoginUser(); + } + // 2. MIS -> externalId (id to empNo) + if (dto.getCdoExternalCreatedBy() != null) { + Map body = hcmEmployeeClient.getEmpFromExternalId( "MIS_MEMBER", dto.getCdoExternalCreatedBy() ); + if (body != null && body.get("eexEmpNo") != null) { + + Object raw = body.get("eexEmpNo"); + + if (raw instanceof String) { + return ((String) raw); + } + + // 예상 밖 타입일 경우 + } + } + + // 3. ERP 내부 요청 (driverUuid 방식) + if (dto.getCdoDriverUuid() != null) { + Map body = hcmEmployeeClient.getEmpFromUuid(dto.getCdoDriverUuid()); + if (body != null && body.get("empNo") != null) { + + Object raw = body.get("empNo"); + + if (raw instanceof String) { + return ((String) raw); + } + + // 예상 밖 타입일 경우 + } } return null; diff --git a/src/main/java/com/goi/erp/service/CustomerService.java b/src/main/java/com/goi/erp/service/CustomerService.java index 06a7739..18b66f3 100644 --- a/src/main/java/com/goi/erp/service/CustomerService.java +++ b/src/main/java/com/goi/erp/service/CustomerService.java @@ -36,6 +36,7 @@ public class CustomerService { .cusNo(dto.getCusNo()) .cusName(dto.getCusName()) .cusStatus(dto.getCusStatus()) + .cusRegionId(dto.getCusRegionId()) .cusAreaId(dto.getCusAreaId()) .cusAddress1(dto.getCusAddress1()) .cusAddress2(dto.getCusAddress2()) @@ -102,6 +103,7 @@ public class CustomerService { if (dto.getCusName() != null) customer.setCusName(dto.getCusName()); if (dto.getCusStatus() != null) customer.setCusStatus(dto.getCusStatus()); if (dto.getCusNo() != null) customer.setCusNo(dto.getCusNo()); + if (dto.getCusRegionId() != null) customer.setCusRegionId(dto.getCusRegionId()); if (dto.getCusAreaId() != null) customer.setCusAreaId(dto.getCusAreaId()); if (dto.getCusAddress1() != null) customer.setCusAddress1(dto.getCusAddress1()); if (dto.getCusAddress2() != null) customer.setCusAddress2(dto.getCusAddress2()); diff --git a/src/main/java/com/goi/erp/service/HcmEmployeeClient.java b/src/main/java/com/goi/erp/service/HcmEmployeeClient.java index d07adb7..5c36f63 100644 --- a/src/main/java/com/goi/erp/service/HcmEmployeeClient.java +++ b/src/main/java/com/goi/erp/service/HcmEmployeeClient.java @@ -25,9 +25,9 @@ public class HcmEmployeeClient { @Value("${hcm.api.base-url}") private String hcmBaseUrl; - public Long getEmpIdFromExternalId(String externalId) { + public Map getEmpFromExternalId(String solutionType, String externalId) { - String url = hcmBaseUrl + "/employee/external" + "?solutionType=MIS&externalId=" + externalId; + String url = hcmBaseUrl + "/employee/external" + "?solutionType=" + solutionType + "&externalId=" + externalId; try { // set token in header @@ -45,21 +45,7 @@ public class HcmEmployeeClient { new ParameterizedTypeReference>() {} ); - Map body = response.getBody(); - //System.out.println("RESPONSE ➜ " + body); - - if (body != null && body.get("eexEmpId") != null) { - - Object raw = body.get("eexEmpId"); - - if (raw instanceof Number) { - return ((Number) raw).longValue(); // 🔥 모든 숫자를 Long 변환 - } - - // 예상 밖 타입일 경우 - } - - return null; + return response.getBody(); } catch (Exception e) { // 필요하면 logging @@ -68,7 +54,7 @@ public class HcmEmployeeClient { } } - public Long getEmpIdFromUuid(UUID uuid) { + public Map getEmpFromUuid(UUID uuid) { String url = hcmBaseUrl + "/employee/" + uuid; @@ -88,21 +74,8 @@ public class HcmEmployeeClient { new ParameterizedTypeReference>() {} ); - Map body = response.getBody(); + return response.getBody(); //System.out.println("RESPONSE(UUID) ➜ " + body); - - if (body != null && body.get("empId") != null) { - - Object raw = body.get("empId"); - - if (raw instanceof Number) { - return ((Number) raw).longValue(); // 🔥 모든 숫자를 Long 변환 - } - - // 예상 밖 타입일 경우 - } - - return null; } catch (Exception e) { // 필요하면 로깅