diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..25614cc --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# hp-soa +A fully functional, easy-to-use, and highly scalable Java Micro Service Framework \ No newline at end of file diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..f89cda1 --- /dev/null +++ b/README.zh.md @@ -0,0 +1,2 @@ +# hp-soa +功能完备,简单易用,高度可扩展的 Java 微服务框架。 \ No newline at end of file diff --git a/hp-demo/.gitignore b/hp-demo/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-bff-basic/.gitignore b/hp-demo/hp-demo-bff-basic/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-bff-basic/pom.xml b/hp-demo/hp-demo-bff-basic/pom.xml new file mode 100644 index 0000000..7afcd5b --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/pom.xml @@ -0,0 +1,78 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-bff-basic + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-basic-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + com.alibaba.nacos + nacos-client + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-bff-basic/src/main/assembly/assembly.xml b/hp-demo/hp-demo-bff-basic/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/config/AppConfig.java b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/config/AppConfig.java new file mode 100644 index 0000000..b8b63ae --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/config/AppConfig.java @@ -0,0 +1,9 @@ +package io.github.hpsocket.demo.bff.basic.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig +{ + +} diff --git a/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/contract/req/DemoReuqest.java b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/contract/req/DemoReuqest.java new file mode 100644 index 0000000..0df6732 --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/contract/req/DemoReuqest.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.demo.bff.basic.contract.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "请求对象示例") +public class DemoReuqest +{ + @NotBlank(message = "name is empty") + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, minLength = 1, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; +} diff --git a/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/contract/resp/DemoResponse.java b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/contract/resp/DemoResponse.java new file mode 100644 index 0000000..5af6fdc --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/contract/resp/DemoResponse.java @@ -0,0 +1,30 @@ +package io.github.hpsocket.demo.bff.basic.contract.resp; + +import com.alibaba.fastjson2.JSON; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "响应对象示例") +public class DemoResponse +{ + @Schema(description = "ID", example = "123", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long id; + + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; + + private String token; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/controller/DemoController.java b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/controller/DemoController.java new file mode 100644 index 0000000..8778958 --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/controller/DemoController.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.demo.bff.basic.controller; + +import io.github.hpsocket.demo.bff.basic.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.basic.contract.resp.DemoResponse; +import io.github.hpsocket.soa.framework.web.model.Response; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@RequestMapping(value = "/demo", method = {RequestMethod.POST}) +@Tag(name = "示例Demo接口") +public interface DemoController +{ + @PostMapping(value = "/queryUser") + @Operation(summary = "查询用户", description = "通过姓名查询用户") + Response queryUser(@RequestBody @Valid DemoReuqest request); + + @PostMapping(value = "/test") + Object test(@RequestBody @Valid DemoReuqest request); +} diff --git a/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/controller/impl/DemoControllerImpl.java b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/controller/impl/DemoControllerImpl.java new file mode 100644 index 0000000..32bc37b --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/controller/impl/DemoControllerImpl.java @@ -0,0 +1,53 @@ +package io.github.hpsocket.demo.bff.basic.controller.impl; + +import io.github.hpsocket.demo.bff.basic.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.basic.contract.resp.DemoResponse; +import io.github.hpsocket.demo.bff.basic.controller.DemoController; +import io.github.hpsocket.demo.infra.basic.service.DemoService; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.model.Response; + +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@AccessVerification(Type.NO_LOGIN) +public class DemoControllerImpl implements DemoController +{ + @DubboReference + DemoService demoService; + + @Override + @AccessVerification(Type.REQUIRE_LOGIN) + public Response queryUser(@RequestBody @Valid DemoReuqest request) + { + /* 通过 RequestContext.getXxx() 获取 Request Context 相关信息 */ + System.out.printf("HAPI-INS - clientId: %s, requestId: %s\n", RequestContext.getClientId(), RequestContext.getRequestId()); + + String name = demoService.sayHello(request.getName()); + + DemoResponse resp = new DemoResponse(); + resp.setId(1001L); + resp.setName(name); + resp.setAge(request.getAge()); + resp.setToken("41784a5039322bbe55a8bf8ce29b9280"); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } + + @Override + public Object test(@Valid DemoReuqest request) + { + return "test"; + } +} diff --git a/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/service/impl/AccessVerificationServiceImpl.java b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/service/impl/AccessVerificationServiceImpl.java new file mode 100644 index 0000000..a0fc4a1 --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/java/io/github/hpsocket/demo/bff/basic/service/impl/AccessVerificationServiceImpl.java @@ -0,0 +1,28 @@ +package io.github.hpsocket.demo.bff.basic.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +@Service +public class AccessVerificationServiceImpl implements AccessVerificationService +{ + @Override + public Pair verifyUserByTokenAndGroupId(String token, Long groupId) + { + return new Pair(123L, "OK"); + } + + @Override + public Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId) + { + return new Pair(Boolean.TRUE, "ok"); + } + + @Override + public boolean verifyAppCode(String appCode) + { + return true; + } +} diff --git a/hp-demo/hp-demo-bff-basic/src/main/resources/application.yml b/hp-demo/hp-demo-bff-basic/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-bff-basic/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-bff-basic/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..29dd080 --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/resources/bootstrap.yml @@ -0,0 +1,117 @@ +# app +hp.soa.web: + app: + id: "0010100001" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + cookie: + max-age: 315360000 + cors: + mapping: "/**" + allowed-origins: "*" + allowed-headers: "*" + allowed-methods: "*" + exposed-headers: + allow-credentials: false + maxAge: 3600 + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +# dubbo +dubbo: + scan.base-packages: ${hp.soa.web.component-scan.base-package} + protocols: + #dubbo: + # name: dubbo + # port: 5001 + tri: + name: tri + port: 6001 + registry: + file: "/data/dubbo/.cache/dubbo-registry_${hp.soa.web.app.name}.properties" + application: + qos-port: 7001 + qos-enable: false + name: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + owner: ${hp.soa.web.app.owner:} + organization: ${hp.soa.web.app.organization:} + +# server +server: + port: 9001 + servlet: + context-path: / + tomcat: + max-connections: 10000 + connection-timeout: 30000 + threads: + max: 200 + min-spare: 5 + undertow: + buffer-size: 1024 + direct-buffers: true + eager-filter-init: true + max-http-post-size: 10MB + threads: + worker: 64 + io: 4 + +# spring +spring: + application.name: ${hp.soa.web.app.name} + mvc: + servlet: + load-on-startup: 1 + path: / + throw-exception-if-no-handler-found: true + web: + resources: + add-mappings: false + security: + user: + name: admin + password: "123456" + roles: ADMIN + servlet: + multipart: + enabled: false + max-file-size: 10MB + max-request-size: 10MB + +# management +management: + endpoint: + health.show-details: when-authorized + shutdown.enabled: false + endpoints: + enabled-by-default: true + jmx.exposure.include: "*" + web.base-path: /__admin + web.exposure.include: "*" + metrics: + export.influx.enabled: false + tags.application: ${hp.soa.web.app.name} + +# api doc +springdoc: + api-docs: + enabled: true + path: /v3/api-docs + swagger-ui: + enabled: true + path: /swagger-ui.html + packagesToScan: io.github.hpsocket.soa,${hp.soa.web.component-scan.base-package} + api-infos: + group-name: ${hp.soa.web.app.name} + title: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + description: "Spring Boot Project >> ${hp.soa.web.app.name} (v${hp.soa.web.app.version})" diff --git a/hp-demo/hp-demo-bff-basic/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-bff-basic/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-basic/src/main/resources/log4j2.xml b/hp-demo/hp-demo-bff-basic/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-bff-basic/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-mysql/.gitignore b/hp-demo/hp-demo-bff-mysql/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-bff-mysql/pom.xml b/hp-demo/hp-demo-bff-mysql/pom.xml new file mode 100644 index 0000000..8d39a96 --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/pom.xml @@ -0,0 +1,78 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-bff-mysql + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-mysql-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-bff-mysql/src/main/assembly/assembly.xml b/hp-demo/hp-demo-bff-mysql/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/config/AppConfig.java b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/config/AppConfig.java new file mode 100644 index 0000000..65d017e --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/config/AppConfig.java @@ -0,0 +1,9 @@ +package io.github.hpsocket.demo.bff.mysql.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig +{ + +} diff --git a/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/contract/req/DemoReuqest.java b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/contract/req/DemoReuqest.java new file mode 100644 index 0000000..32588cb --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/contract/req/DemoReuqest.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.demo.bff.mysql.contract.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "请求对象示例") +public class DemoReuqest { + @Schema(description = "id", example = "123", requiredMode = RequiredMode.NOT_REQUIRED, nullable = true) + private Long id; + @NotBlank(message = "name is empty") + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, minLength = 1, nullable = false) + private String name; + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; +} diff --git a/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/contract/resp/DemoResponse.java b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/contract/resp/DemoResponse.java new file mode 100644 index 0000000..5d08bc6 --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/contract/resp/DemoResponse.java @@ -0,0 +1,30 @@ +package io.github.hpsocket.demo.bff.mysql.contract.resp; + +import com.alibaba.fastjson2.JSON; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "响应对象示例") +public class DemoResponse +{ + @Schema(description = "ID", example = "123", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long id; + + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; + + private String token; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/controller/DemoController.java b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/controller/DemoController.java new file mode 100644 index 0000000..c1795de --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/controller/DemoController.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.demo.bff.mysql.controller; + +import io.github.hpsocket.demo.bff.mysql.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.mysql.contract.resp.DemoResponse; +import io.github.hpsocket.soa.framework.web.model.Response; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@RequestMapping(value = "/demo", method = {RequestMethod.POST}) +@Tag(name = "示例Demo接口") +public interface DemoController +{ + @PostMapping(value = "/queryUser") + @Operation(summary = "查询用户", description = "通过姓名查询用户") + Response queryUser(@RequestBody @Valid DemoReuqest request); + + @PostMapping(value = "/test") + Object test(@RequestBody @Valid DemoReuqest request); +} diff --git a/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/controller/impl/DemoControllerImpl.java b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/controller/impl/DemoControllerImpl.java new file mode 100644 index 0000000..608ca29 --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/controller/impl/DemoControllerImpl.java @@ -0,0 +1,80 @@ +package io.github.hpsocket.demo.bff.mysql.controller.impl; + +import io.github.hpsocket.demo.bff.mysql.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.mysql.contract.resp.DemoResponse; +import io.github.hpsocket.demo.bff.mysql.controller.DemoController; +import io.github.hpsocket.demo.infra.mysql.bo.UserBo; +import io.github.hpsocket.demo.infra.mysql.service.DemoService; +import io.github.hpsocket.demo.infra.mysql.service.UserService; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.model.Response; + +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@AccessVerification(Type.NO_LOGIN) +public class DemoControllerImpl implements DemoController +{ + @DubboReference + DemoService demoService; + + @DubboReference + UserService userService; + + @Override + @AccessVerification(Type.REQUIRE_LOGIN) + public Response queryUser(@RequestBody @Valid DemoReuqest request) + { + /* 通过 RequestContext.getXxx() 获取 Request Context 相关信息 */ + System.out.printf("HAPI-INS - clientId: %s, requestId: %s\n", RequestContext.getClientId(), RequestContext.getRequestId()); + + UserBo userBo = null; + String name = request.getName(); + + if(name.equalsIgnoreCase("master")) + userBo = userService.getMasterUser(); + else if(name.equalsIgnoreCase("slave")) + userBo = userService.getSlaveUser(); + else if(name.equalsIgnoreCase("slave-1")) + userBo = userService.getSlave1User(); + else if(name.equalsIgnoreCase("slave-2")) + userBo = userService.getSlave2User(); + else + userBo = userService.getDefaultUser(); + + Long userId = userBo != null ? userBo.getId() : null; + String userName = userBo != null ? userBo.getName() : "null"; + Integer userAge = userBo != null ? userBo.getAge() : null; + + DemoResponse resp = new DemoResponse(); + resp.setId(userId); + resp.setName(userName); + resp.setAge(userAge); + resp.setToken("41784a5039322bbe55a8bf8ce29b9280"); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } + + @Override + public Object test(@Valid DemoReuqest request) + { + UserBo userBo = new UserBo(); + + userBo.setId(request.getId()); + userBo.setName(request.getName()); + userBo.setAge(request.getAge()); + + return "save user: " + userService.saveUser(userBo); + } +} diff --git a/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/service/impl/AccessVerificationServiceImpl.java b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/service/impl/AccessVerificationServiceImpl.java new file mode 100644 index 0000000..c42cd11 --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/java/io/github/hpsocket/demo/bff/mysql/service/impl/AccessVerificationServiceImpl.java @@ -0,0 +1,28 @@ +package io.github.hpsocket.demo.bff.mysql.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +@Service +public class AccessVerificationServiceImpl implements AccessVerificationService +{ + @Override + public Pair verifyUserByTokenAndGroupId(String token, Long groupId) + { + return new Pair(123L, "OK"); + } + + @Override + public Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId) + { + return new Pair(Boolean.TRUE, "ok"); + } + + @Override + public boolean verifyAppCode(String appCode) + { + return true; + } +} diff --git a/hp-demo/hp-demo-bff-mysql/src/main/resources/application.yml b/hp-demo/hp-demo-bff-mysql/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-bff-mysql/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-bff-mysql/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..857e91f --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/resources/bootstrap.yml @@ -0,0 +1,51 @@ +# app +hp.soa.web: + app: + id: "0010100009" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: redis.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5009 + tri: + name: tri + port: 6009 + +# server +server.port: 9009 diff --git a/hp-demo/hp-demo-bff-mysql/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-bff-mysql/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-mysql/src/main/resources/log4j2.xml b/hp-demo/hp-demo-bff-mysql/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-bff-mysql/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-nacos/.gitignore b/hp-demo/hp-demo-bff-nacos/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-bff-nacos/pom.xml b/hp-demo/hp-demo-bff-nacos/pom.xml new file mode 100644 index 0000000..c00c6ea --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/pom.xml @@ -0,0 +1,78 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-bff-nacos + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-nacos-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-bff-nacos/src/main/assembly/assembly.xml b/hp-demo/hp-demo-bff-nacos/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/config/AppConfig.java b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/config/AppConfig.java new file mode 100644 index 0000000..438e21f --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/config/AppConfig.java @@ -0,0 +1,9 @@ +package io.github.hpsocket.demo.bff.nacos.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig +{ + +} diff --git a/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/contract/req/DemoReuqest.java b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/contract/req/DemoReuqest.java new file mode 100644 index 0000000..5444cb9 --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/contract/req/DemoReuqest.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.demo.bff.nacos.contract.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "请求对象示例") +public class DemoReuqest +{ + @NotBlank(message = "name is empty") + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, minLength = 1, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; +} diff --git a/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/contract/resp/DemoResponse.java b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/contract/resp/DemoResponse.java new file mode 100644 index 0000000..53acc28 --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/contract/resp/DemoResponse.java @@ -0,0 +1,30 @@ +package io.github.hpsocket.demo.bff.nacos.contract.resp; + +import com.alibaba.fastjson2.JSON; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "响应对象示例") +public class DemoResponse +{ + @Schema(description = "ID", example = "123", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long id; + + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; + + private String token; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/controller/DemoController.java b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/controller/DemoController.java new file mode 100644 index 0000000..bffed64 --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/controller/DemoController.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.demo.bff.nacos.controller; + +import io.github.hpsocket.demo.bff.nacos.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.nacos.contract.resp.DemoResponse; +import io.github.hpsocket.soa.framework.web.model.Response; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@RequestMapping(value = "/demo", method = {RequestMethod.POST}) +@Tag(name = "示例Demo接口") +public interface DemoController +{ + @PostMapping(value = "/queryUser") + @Operation(summary = "查询用户", description = "通过姓名查询用户") + Response queryUser(@RequestBody @Valid DemoReuqest request); + + @PostMapping(value = "/test") + Object test(@RequestBody @Valid DemoReuqest request); +} diff --git a/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/controller/impl/DemoControllerImpl.java b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/controller/impl/DemoControllerImpl.java new file mode 100644 index 0000000..0010c98 --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/controller/impl/DemoControllerImpl.java @@ -0,0 +1,53 @@ +package io.github.hpsocket.demo.bff.nacos.controller.impl; + +import io.github.hpsocket.demo.bff.nacos.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.nacos.contract.resp.DemoResponse; +import io.github.hpsocket.demo.bff.nacos.controller.DemoController; +import io.github.hpsocket.demo.infra.nacos.service.DemoService; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.model.Response; + +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@AccessVerification(Type.NO_LOGIN) +public class DemoControllerImpl implements DemoController +{ + @DubboReference + DemoService demoService; + + @Override + @AccessVerification(Type.REQUIRE_LOGIN) + public Response queryUser(@RequestBody @Valid DemoReuqest request) + { + /* 通过 RequestContext.getXxx() 获取 Request Context 相关信息 */ + System.out.printf("HAPI-INS - clientId: %s, requestId: %s\n", RequestContext.getClientId(), RequestContext.getRequestId()); + + String name = demoService.sayHello(request.getName()); + + DemoResponse resp = new DemoResponse(); + resp.setId(1001L); + resp.setName(name); + resp.setAge(request.getAge()); + resp.setToken("41784a5039322bbe55a8bf8ce29b9280"); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } + + @Override + public Object test(@Valid DemoReuqest request) + { + return "test"; + } +} diff --git a/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/service/impl/AccessVerificationServiceImpl.java b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/service/impl/AccessVerificationServiceImpl.java new file mode 100644 index 0000000..21bb7de --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/java/io/github/hpsocket/demo/bff/nacos/service/impl/AccessVerificationServiceImpl.java @@ -0,0 +1,28 @@ +package io.github.hpsocket.demo.bff.nacos.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +@Service +public class AccessVerificationServiceImpl implements AccessVerificationService +{ + @Override + public Pair verifyUserByTokenAndGroupId(String token, Long groupId) + { + return new Pair(123L, "OK"); + } + + @Override + public Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId) + { + return new Pair(Boolean.TRUE, "ok"); + } + + @Override + public boolean verifyAppCode(String appCode) + { + return true; + } +} diff --git a/hp-demo/hp-demo-bff-nacos/src/main/resources/application.yml b/hp-demo/hp-demo-bff-nacos/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-bff-nacos/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-bff-nacos/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..35ec11a --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/resources/bootstrap.yml @@ -0,0 +1,51 @@ +# app +hp.soa.web: + app: + id: "0010100003" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: redis.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5003 + tri: + name: tri + port: 6003 + +# server +server.port: 9003 diff --git a/hp-demo/hp-demo-bff-nacos/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-bff-nacos/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-nacos/src/main/resources/log4j2.xml b/hp-demo/hp-demo-bff-nacos/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-bff-nacos/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-sentinel/.gitignore b/hp-demo/hp-demo-bff-sentinel/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-bff-sentinel/pom.xml b/hp-demo/hp-demo-bff-sentinel/pom.xml new file mode 100644 index 0000000..8c889a5 --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/pom.xml @@ -0,0 +1,82 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-bff-sentinel + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-sentinel-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-sentinel + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/assembly/assembly.xml b/hp-demo/hp-demo-bff-sentinel/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/config/AppConfig.java b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/config/AppConfig.java new file mode 100644 index 0000000..745350c --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/config/AppConfig.java @@ -0,0 +1,9 @@ +package io.github.hpsocket.demo.bff.sentinel.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig +{ + +} diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/contract/req/DemoReuqest.java b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/contract/req/DemoReuqest.java new file mode 100644 index 0000000..0deca5c --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/contract/req/DemoReuqest.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.demo.bff.sentinel.contract.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "请求对象示例") +public class DemoReuqest +{ + @NotBlank(message = "name is empty") + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, minLength = 1, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; +} diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/contract/resp/DemoResponse.java b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/contract/resp/DemoResponse.java new file mode 100644 index 0000000..8c92341 --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/contract/resp/DemoResponse.java @@ -0,0 +1,31 @@ +package io.github.hpsocket.demo.bff.sentinel.contract.resp; + +import com.alibaba.fastjson2.JSON; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "响应对象示例") +public class DemoResponse +{ + + @Schema(description = "ID", example = "123", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long id; + + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; + + private String token; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/controller/DemoController.java b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/controller/DemoController.java new file mode 100644 index 0000000..72069b2 --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/controller/DemoController.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.demo.bff.sentinel.controller; + +import io.github.hpsocket.demo.bff.sentinel.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.sentinel.contract.resp.DemoResponse; +import io.github.hpsocket.soa.framework.web.model.Response; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@RequestMapping(value = "/demo", method = {RequestMethod.POST}) +@Tag(name = "示例Demo接口") +public interface DemoController +{ + @PostMapping(value = "/queryUser") + @Operation(summary = "查询用户", description = "通过姓名查询用户") + Response queryUser(@RequestBody @Valid DemoReuqest request); + + @PostMapping(value = "/test") + Object test(@RequestBody @Valid DemoReuqest request); +} diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/controller/impl/DemoControllerImpl.java b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/controller/impl/DemoControllerImpl.java new file mode 100644 index 0000000..10afb8f --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/controller/impl/DemoControllerImpl.java @@ -0,0 +1,55 @@ +package io.github.hpsocket.demo.bff.sentinel.controller.impl; + +import com.alibaba.csp.sentinel.annotation.SentinelResource; +import io.github.hpsocket.demo.bff.sentinel.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.sentinel.contract.resp.DemoResponse; +import io.github.hpsocket.demo.bff.sentinel.controller.DemoController; +import io.github.hpsocket.demo.infra.sentinel.service.DemoService; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.model.Response; + +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@AccessVerification(Type.NO_LOGIN) +public class DemoControllerImpl implements DemoController +{ + @DubboReference + DemoService demoService; + + @Override + @AccessVerification(Type.REQUIRE_LOGIN) + public Response queryUser(@RequestBody @Valid DemoReuqest request) + { + /* 通过 RequestContext.getXxx() 获取 Request Context 相关信息 */ + System.out.printf("HAPI-INS - clientId: %s, requestId: %s\n", RequestContext.getClientId(), RequestContext.getRequestId()); + + String name = demoService.sayHello(request.getName()); + + DemoResponse resp = new DemoResponse(); + resp.setId(1001L); + resp.setName(name); + resp.setAge(request.getAge()); + resp.setToken("41784a5039322bbe55a8bf8ce29b9280"); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } + + @Override + @SentinelResource(value = "test") + public Object test(@Valid DemoReuqest request) + { + return "test"; + } +} diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/service/impl/AccessVerificationServiceImpl.java b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/service/impl/AccessVerificationServiceImpl.java new file mode 100644 index 0000000..4302aef --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/java/io/github/hpsocket/demo/bff/sentinel/service/impl/AccessVerificationServiceImpl.java @@ -0,0 +1,28 @@ +package io.github.hpsocket.demo.bff.sentinel.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +@Service +public class AccessVerificationServiceImpl implements AccessVerificationService +{ + @Override + public Pair verifyUserByTokenAndGroupId(String token, Long groupId) + { + return new Pair(123L, "OK"); + } + + @Override + public Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId) + { + return new Pair(Boolean.TRUE, "ok"); + } + + @Override + public boolean verifyAppCode(String appCode) + { + return true; + } +} diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/resources/application.yml b/hp-demo/hp-demo-bff-sentinel/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-bff-sentinel/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..160eab3 --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/resources/bootstrap.yml @@ -0,0 +1,51 @@ +# app +hp.soa.web: + app: + id: "0010100005" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: sentinel.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5005 + tri: + name: tri + port: 6005 + +# server +server.port: 9005 diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-bff-sentinel/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-sentinel/src/main/resources/log4j2.xml b/hp-demo/hp-demo-bff-sentinel/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-bff-sentinel/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-skywalking/.gitignore b/hp-demo/hp-demo-bff-skywalking/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-bff-skywalking/pom.xml b/hp-demo/hp-demo-bff-skywalking/pom.xml new file mode 100644 index 0000000..3e182cc --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/pom.xml @@ -0,0 +1,86 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-bff-skywalking + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-skywalking-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-sentinel + + + io.github.hpsocket + hp-soa-starter-skywalking + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/assembly/assembly.xml b/hp-demo/hp-demo-bff-skywalking/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/config/AppConfig.java b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/config/AppConfig.java new file mode 100644 index 0000000..77fa61c --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/config/AppConfig.java @@ -0,0 +1,9 @@ +package io.github.hpsocket.demo.bff.skywalking.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig +{ + +} diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/contract/req/DemoReuqest.java b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/contract/req/DemoReuqest.java new file mode 100644 index 0000000..b1d378c --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/contract/req/DemoReuqest.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.demo.bff.skywalking.contract.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "请求对象示例") +public class DemoReuqest +{ + @NotBlank(message = "name is empty") + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, minLength = 1, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; +} diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/contract/resp/DemoResponse.java b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/contract/resp/DemoResponse.java new file mode 100644 index 0000000..b2c9e72 --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/contract/resp/DemoResponse.java @@ -0,0 +1,30 @@ +package io.github.hpsocket.demo.bff.skywalking.contract.resp; + +import com.alibaba.fastjson2.JSON; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "响应对象示例") +public class DemoResponse +{ + @Schema(description = "ID", example = "123", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long id; + + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; + + private String token; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/controller/DemoController.java b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/controller/DemoController.java new file mode 100644 index 0000000..814777d --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/controller/DemoController.java @@ -0,0 +1,23 @@ +package io.github.hpsocket.demo.bff.skywalking.controller; + +import io.github.hpsocket.demo.bff.skywalking.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.skywalking.contract.resp.DemoResponse; +import io.github.hpsocket.soa.framework.web.model.Response; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@RequestMapping(value = "/demo", method = {RequestMethod.POST}) +@Tag(name = "示例Demo接口") +public interface DemoController +{ + @PostMapping(value = "/queryUser") + @Operation(summary = "查询用户", description = "通过姓名查询用户") + Response queryUser(@RequestBody @Valid DemoReuqest request); +} diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/controller/impl/DemoControllerImpl.java b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/controller/impl/DemoControllerImpl.java new file mode 100644 index 0000000..073a4d3 --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/controller/impl/DemoControllerImpl.java @@ -0,0 +1,50 @@ +package io.github.hpsocket.demo.bff.skywalking.controller.impl; + +import io.github.hpsocket.demo.bff.skywalking.contract.req.DemoReuqest; +import io.github.hpsocket.demo.bff.skywalking.contract.resp.DemoResponse; +import io.github.hpsocket.demo.bff.skywalking.controller.DemoController; +import io.github.hpsocket.demo.infra.skywalking.service.DemoService; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.model.Response; + +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@AccessVerification(Type.NO_LOGIN) +public class DemoControllerImpl implements DemoController +{ + @DubboReference + DemoService demoService; + + @Override + @AccessVerification(Type.REQUIRE_LOGIN) + public Response queryUser(@RequestBody @Valid DemoReuqest request) + { + /* 通过 RequestContext.getXxx() 获取 Request Context 相关信息 */ + System.out.printf("HAPI-INS - clientId: %s, requestId: %s\n", RequestContext.getClientId(), RequestContext.getRequestId()); + + String name = demoService.sayHello(request.getName()); + + DemoResponse resp = new DemoResponse(); + resp.setId(1001L); + resp.setName(name); + resp.setAge(request.getAge()); + resp.setToken("41784a5039322bbe55a8bf8ce29b9280"); + + log.info("Trace: {}, {}, {}", TraceContext.traceId(), TraceContext.segmentId(), TraceContext.spanId()); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } +} diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/service/impl/AccessVerificationServiceImpl.java b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/service/impl/AccessVerificationServiceImpl.java new file mode 100644 index 0000000..5a23ceb --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/java/io/github/hpsocket/demo/bff/skywalking/service/impl/AccessVerificationServiceImpl.java @@ -0,0 +1,28 @@ +package io.github.hpsocket.demo.bff.skywalking.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +@Service +public class AccessVerificationServiceImpl implements AccessVerificationService +{ + @Override + public Pair verifyUserByTokenAndGroupId(String token, Long groupId) + { + return new Pair(123L, "OK"); + } + + @Override + public Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId) + { + return new Pair(Boolean.TRUE, "ok"); + } + + @Override + public boolean verifyAppCode(String appCode) + { + return true; + } +} diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/resources/application.yml b/hp-demo/hp-demo-bff-skywalking/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-bff-skywalking/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..71cb6c8 --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/resources/bootstrap.yml @@ -0,0 +1,51 @@ +# app +hp.soa.web: + app: + id: "0010100007" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: sentinel.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5007 + tri: + name: tri + port: 6007 + +# server +server.port: 9007 diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-bff-skywalking/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-bff-skywalking/src/main/resources/log4j2.xml b/hp-demo/hp-demo-bff-skywalking/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-bff-skywalking/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-basic/.gitignore b/hp-demo/hp-demo-infra-basic/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/.gitignore b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/pom.xml b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/pom.xml new file mode 100644 index 0000000..3a22307 --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-basic + ${revision} + + hp-demo-infra-basic-api + ${project.artifactId} + + + + + + + jakarta.validation + jakarta.validation-api + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/src/main/java/io/github/hpsocket/demo/infra/basic/service/DemoService.java b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/src/main/java/io/github/hpsocket/demo/infra/basic/service/DemoService.java new file mode 100644 index 0000000..d95ccaa --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-api/src/main/java/io/github/hpsocket/demo/infra/basic/service/DemoService.java @@ -0,0 +1,8 @@ +package io.github.hpsocket.demo.infra.basic.service; + +import jakarta.validation.constraints.NotBlank; + +public interface DemoService +{ + String sayHello(@NotBlank(message="姓名不能为空") String name); +} diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/.gitignore b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/pom.xml b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/pom.xml new file mode 100644 index 0000000..edbc2e7 --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-basic + ${revision} + + hp-demo-infra-basic-service + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-basic-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + com.alibaba.nacos + nacos-client + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + + diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/assembly/assembly.xml b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/java/io/github/hpsocket/demo/infra/basic/service/impl/DemoServiceImpl.java b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/java/io/github/hpsocket/demo/infra/basic/service/impl/DemoServiceImpl.java new file mode 100644 index 0000000..b92102c --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/java/io/github/hpsocket/demo/infra/basic/service/impl/DemoServiceImpl.java @@ -0,0 +1,39 @@ +package io.github.hpsocket.demo.infra.basic.service.impl; + +import org.apache.dubbo.config.annotation.DubboService; + +import io.github.hpsocket.demo.infra.basic.service.DemoService; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@DubboService +public class DemoServiceImpl implements DemoService +{ + @Override + public String sayHello(String name) + { + MdcAttr mdc = MdcAttr.fromMdc(); + log.info("MDC: {}", mdc.getCtxMap()); + + if(name.length() < 4) + throw new DemoException("Just test exception: name length < 4"); + + return "Hello Mr. " + name; + } + + @SuppressWarnings("serial") + public static class DemoException extends RuntimeException + { + public DemoException() + { + super(); + } + + public DemoException(String msg) + { + super(msg); + } + } +} diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/application.yml b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..9839b70 --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/bootstrap.yml @@ -0,0 +1,117 @@ +# app +hp.soa.web: + app: + id: "0010100002" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + cookie: + max-age: 315360000 + cors: + mapping: "/**" + allowed-origins: "*" + allowed-headers: "*" + allowed-methods: "*" + exposed-headers: + allow-credentials: false + maxAge: 3600 + access-verification: + enabled: false + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +# dubbo +dubbo: + scan.base-packages: ${hp.soa.web.component-scan.base-package} + protocols: + #dubbo: + # name: dubbo + # port: 5002 + tri: + name: tri + port: 6002 + registry: + file: "/data/dubbo/.cache/dubbo-registry_${hp.soa.web.app.name}.properties" + application: + qos-port: 7002 + qos-enable: false + name: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + owner: ${hp.soa.web.app.owner:} + organization: ${hp.soa.web.app.organization:} + +# server +server: + port: 9002 + servlet: + context-path: / + tomcat: + max-connections: 10000 + connection-timeout: 30000 + threads: + max: 200 + min-spare: 5 + undertow: + buffer-size: 1024 + direct-buffers: true + eager-filter-init: true + max-http-post-size: 10MB + threads: + worker: 64 + io: 4 + +# spring +spring: + application.name: ${hp.soa.web.app.name} + mvc: + servlet: + load-on-startup: 1 + path: / + throw-exception-if-no-hand1ler-found: true + web: + resources: + add-mappings: false + security: + user: + name: admin + password: "123456" + roles: ADMIN + servlet: + multipart: + enabled: false + max-file-size: 10MB + max-request-size: 10MB + +# management +management: + endpoint: + health.show-details: when-authorized + shutdown.enabled: false + endpoints: + enabled-by-default: true + jmx.exposure.include: "*" + web.base-path: /__admin + web.exposure.include: "*" + metrics: + export.influx.enabled: false + tags.application: ${hp.soa.web.app.name} + +# api doc +springdoc: + api-docs: + enabled: true + path: /v3/api-docs + swagger-ui: + enabled: true + path: /swagger-ui.html + packagesToScan: io.github.hpsocket.soa,${hp.soa.web.component-scan.base-package} + api-infos: + group-name: ${hp.soa.web.app.name} + title: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + description: "Spring Boot Project >> ${hp.soa.web.app.name} (v${hp.soa.web.app.version})" diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/log4j2.xml b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/hp-demo-infra-basic-service/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-basic/pom.xml b/hp-demo/hp-demo-infra-basic/pom.xml new file mode 100644 index 0000000..ee5eae4 --- /dev/null +++ b/hp-demo/hp-demo-infra-basic/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-infra-basic + ${project.artifactId} + pom + + + + + + hp-demo-infra-basic-api + hp-demo-infra-basic-service + + + + + + diff --git a/hp-demo/hp-demo-infra-mysql/.gitignore b/hp-demo/hp-demo-infra-mysql/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/.gitignore b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/pom.xml b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/pom.xml new file mode 100644 index 0000000..e00f9af --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-mysql + ${revision} + + hp-demo-infra-mysql-api + ${project.artifactId} + + + + + + + jakarta.validation + jakarta.validation-api + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/bo/UserBo.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/bo/UserBo.java new file mode 100644 index 0000000..5a0b445 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/bo/UserBo.java @@ -0,0 +1,16 @@ +package io.github.hpsocket.demo.infra.mysql.bo; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressWarnings("serial") +public class UserBo implements Serializable +{ + private Long id; + private String name; + private Integer age; +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/service/DemoService.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/service/DemoService.java new file mode 100644 index 0000000..953210d --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/service/DemoService.java @@ -0,0 +1,8 @@ +package io.github.hpsocket.demo.infra.mysql.service; + +import jakarta.validation.constraints.NotBlank; + +public interface DemoService +{ + String sayHello(@NotBlank(message="姓名不能为空") String name); +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/service/UserService.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/service/UserService.java new file mode 100644 index 0000000..f7bee8e --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-api/src/main/java/io/github/hpsocket/demo/infra/mysql/service/UserService.java @@ -0,0 +1,14 @@ +package io.github.hpsocket.demo.infra.mysql.service; + +import io.github.hpsocket.demo.infra.mysql.bo.UserBo; + +public interface UserService +{ + UserBo getDefaultUser(); + UserBo getMasterUser(); + UserBo getSlaveUser(); + UserBo getSlave1User(); + UserBo getSlave2User(); + + boolean saveUser(UserBo userBo); +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/.gitignore b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/pom.xml b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/pom.xml new file mode 100644 index 0000000..4456004 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-mysql + ${revision} + + hp-demo-infra-mysql-service + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-mysql-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-data-mysql + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + + diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/assembly/assembly.xml b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/config/AppConfig.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/config/AppConfig.java new file mode 100644 index 0000000..def9772 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/config/AppConfig.java @@ -0,0 +1,11 @@ +package io.github.hpsocket.demo.infra.mysql.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; + +@AutoConfiguration +/* default mybatis mapper scan package -> ${hp.soa.web.mapper-scan.base-package} */ +//@MapperScan("io.github.hpsocket.demo.infra.mysql.mapper") +public class AppConfig +{ + +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/converter/UserConverter.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/converter/UserConverter.java new file mode 100644 index 0000000..a3c47f5 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/converter/UserConverter.java @@ -0,0 +1,17 @@ +package io.github.hpsocket.demo.infra.mysql.converter; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.factory.Mappers; + +import io.github.hpsocket.demo.infra.mysql.bo.UserBo; +import io.github.hpsocket.demo.infra.mysql.entity.User; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public interface UserConverter +{ + UserConverter INSTANCE = Mappers.getMapper(UserConverter.class); + + UserBo toBo(User user); + User fromBo(UserBo userBo); +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/entity/User.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/entity/User.java new file mode 100644 index 0000000..a19e6f2 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/entity/User.java @@ -0,0 +1,22 @@ +package io.github.hpsocket.demo.infra.mysql.entity; + +import com.alibaba.fastjson2.JSONObject; +import io.github.hpsocket.soa.starter.data.mysql.entity.BaseLogicDeleteVersioningEntity; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressWarnings("serial") +public class User extends BaseLogicDeleteVersioningEntity +{ + private String name; + private Integer age; + + @Override + public String toString() + { + return JSONObject.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/mapper/UserMapper.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/mapper/UserMapper.java new file mode 100644 index 0000000..c934b65 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/mapper/UserMapper.java @@ -0,0 +1,9 @@ +package io.github.hpsocket.demo.infra.mysql.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import io.github.hpsocket.demo.infra.mysql.entity.User; + +public interface UserMapper extends BaseMapper +{ + +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/service/impl/DemoServiceImpl.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/service/impl/DemoServiceImpl.java new file mode 100644 index 0000000..9a790b3 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/service/impl/DemoServiceImpl.java @@ -0,0 +1,41 @@ +package io.github.hpsocket.demo.infra.mysql.service.impl; + +import org.apache.dubbo.config.annotation.DubboService; +import org.springframework.cloud.context.config.annotation.RefreshScope; + +import io.github.hpsocket.demo.infra.mysql.service.DemoService; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RefreshScope +@DubboService +public class DemoServiceImpl implements DemoService +{ + @Override + public String sayHello(String name) + { + MdcAttr mdc = MdcAttr.fromMdc(); + log.info("MDC: {}", mdc.getCtxMap()); + + if(name.length() < 4) + throw new DemoException("Just test exception: name length < 4"); + + return "Hello Mr. " + name; + } + + @SuppressWarnings("serial") + public static class DemoException extends RuntimeException + { + public DemoException() + { + super(); + } + + public DemoException(String msg) + { + super(msg); + } + } +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/service/impl/UserServiceImpl.java b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..5d44b52 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/java/io/github/hpsocket/demo/infra/mysql/service/impl/UserServiceImpl.java @@ -0,0 +1,97 @@ +package io.github.hpsocket.demo.infra.mysql.service.impl; + +import org.apache.dubbo.config.annotation.DubboService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import io.github.hpsocket.demo.infra.mysql.bo.UserBo; +import io.github.hpsocket.demo.infra.mysql.converter.UserConverter; +import io.github.hpsocket.demo.infra.mysql.entity.User; +import io.github.hpsocket.demo.infra.mysql.mapper.UserMapper; +import io.github.hpsocket.demo.infra.mysql.service.UserService; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RefreshScope +@DubboService +public class UserServiceImpl extends ServiceImpl implements UserService +{ + @Autowired + private UserConverter userConverter; + + @Override + public UserBo getDefaultUser() + { + User user = getOne(Wrappers.lambdaQuery().eq(User::getId, 1L)); + log.info("return user: {}", user); + + return userConverter.toBo(user); + } + + @Override + @DS("master") + public UserBo getMasterUser() + { + User user = getOne(Wrappers.lambdaQuery().eq(User::getId, 1L)); + log.info("return user: {}", user); + + return userConverter.toBo(user); + } + + @Override + @DS("slave") + public UserBo getSlaveUser() + { + User user = getOne(Wrappers.lambdaQuery().eq(User::getId, 1L)); + log.info("return user: {}", user); + + return userConverter.toBo(user); + } + + @Override + @DS("slave_01") + public UserBo getSlave1User() + { + User user = getOne(Wrappers.lambdaQuery().eq(User::getId, 1)); + log.info("return user: {}", user); + + return userConverter.toBo(user); + } + + @Override + @DS("slave_02") + public UserBo getSlave2User() + { + User user = getOne(Wrappers.lambdaQuery().eq(User::getId, 1)); + log.info("return user: {}", user); + + return userConverter.toBo(user); + } + + @Override + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) + public boolean saveUser(UserBo userBo) + { + User user = userConverter.fromBo(userBo); + log.info("save user: ", user); + + if(user.getId() != null) + { + User user2 = getOne(Wrappers.lambdaQuery().eq(User::getId, user.getId())); + if(user2 != null) + { + user.setVersion(user2.getVersion()); + return updateById(user); + } + } + + return save(user); + } + +} diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/application.yml b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..7108ba5 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/bootstrap.yml @@ -0,0 +1,55 @@ +# app +hp.soa.web: + app: + id: "0010100010" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + # spring bean scan packages + component-scan: + base-package: ${project.groupId} + # mybatis mapper scan packages + mapper-scan: + base-package: ${project.groupId}.infra.mysql.mapper + access-verification: + enabled: false + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: mysql.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5010 + tri: + name: tri + port: 6010 + +# server +server.port: 9010 diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/log4j2.xml b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/hp-demo-infra-mysql-service/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-mysql/pom.xml b/hp-demo/hp-demo-infra-mysql/pom.xml new file mode 100644 index 0000000..eafa53c --- /dev/null +++ b/hp-demo/hp-demo-infra-mysql/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-infra-mysql + ${project.artifactId} + pom + + + + + + hp-demo-infra-mysql-api + hp-demo-infra-mysql-service + + + + + + diff --git a/hp-demo/hp-demo-infra-nacos/.gitignore b/hp-demo/hp-demo-infra-nacos/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/.gitignore b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/pom.xml b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/pom.xml new file mode 100644 index 0000000..348e22f --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-nacos + ${revision} + + hp-demo-infra-nacos-api + ${project.artifactId} + + + + + + + jakarta.validation + jakarta.validation-api + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/src/main/java/io/github/hpsocket/demo/infra/nacos/service/DemoService.java b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/src/main/java/io/github/hpsocket/demo/infra/nacos/service/DemoService.java new file mode 100644 index 0000000..6b29617 --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-api/src/main/java/io/github/hpsocket/demo/infra/nacos/service/DemoService.java @@ -0,0 +1,8 @@ +package io.github.hpsocket.demo.infra.nacos.service; + +import jakarta.validation.constraints.NotBlank; + +public interface DemoService +{ + String sayHello(@NotBlank(message="姓名不能为空") String name); +} diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/.gitignore b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/pom.xml b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/pom.xml new file mode 100644 index 0000000..85216c8 --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-nacos + ${revision} + + hp-demo-infra-nacos-service + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-nacos-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + + diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/assembly/assembly.xml b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/java/io/github/hpsocket/demo/infra/nacos/service/impl/DemoServiceImpl.java b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/java/io/github/hpsocket/demo/infra/nacos/service/impl/DemoServiceImpl.java new file mode 100644 index 0000000..3a06a4d --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/java/io/github/hpsocket/demo/infra/nacos/service/impl/DemoServiceImpl.java @@ -0,0 +1,45 @@ +package io.github.hpsocket.demo.infra.nacos.service.impl; + +import org.apache.dubbo.config.annotation.DubboService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; + +import io.github.hpsocket.demo.infra.nacos.service.DemoService; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RefreshScope +@DubboService +public class DemoServiceImpl implements DemoService +{ + @Value("${aaa.bbb}") + Integer val; + + @Override + public String sayHello(String name) + { + MdcAttr mdc = MdcAttr.fromMdc(); + log.info("MDC: {}", mdc.getCtxMap()); + + if(name.length() < 4) + throw new DemoException("Just test exception: name length < 4"); + + return "Hello Mr. " + name; + } + + @SuppressWarnings("serial") + public static class DemoException extends RuntimeException + { + public DemoException() + { + super(); + } + + public DemoException(String msg) + { + super(msg); + } + } +} diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/application.yml b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..7f52524 --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/bootstrap.yml @@ -0,0 +1,48 @@ +# app +hp.soa.web: + app: + id: "0010100004" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: false + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5004 + tri: + name: tri + port: 6004 + +# server +server.port: 9004 diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/log4j2.xml b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/hp-demo-infra-nacos-service/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-nacos/pom.xml b/hp-demo/hp-demo-infra-nacos/pom.xml new file mode 100644 index 0000000..b24f169 --- /dev/null +++ b/hp-demo/hp-demo-infra-nacos/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-infra-nacos + ${project.artifactId} + pom + + + + + + hp-demo-infra-nacos-api + hp-demo-infra-nacos-service + + + + + + diff --git a/hp-demo/hp-demo-infra-sentinel/.gitignore b/hp-demo/hp-demo-infra-sentinel/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/.gitignore b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/pom.xml b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/pom.xml new file mode 100644 index 0000000..1180eb6 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-sentinel + ${revision} + + hp-demo-infra-sentinel-api + ${project.artifactId} + + + + + + + jakarta.validation + jakarta.validation-api + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/src/main/java/io/github/hpsocket/demo/infra/sentinel/service/DemoService.java b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/src/main/java/io/github/hpsocket/demo/infra/sentinel/service/DemoService.java new file mode 100644 index 0000000..b4be2d8 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-api/src/main/java/io/github/hpsocket/demo/infra/sentinel/service/DemoService.java @@ -0,0 +1,8 @@ +package io.github.hpsocket.demo.infra.sentinel.service; + +import jakarta.validation.constraints.NotBlank; + +public interface DemoService +{ + String sayHello(@NotBlank(message="姓名不能为空") String name); +} diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/.gitignore b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/pom.xml b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/pom.xml new file mode 100644 index 0000000..8019a96 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-sentinel + ${revision} + + hp-demo-infra-sentinel-service + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-sentinel-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-sentinel + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + + diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/assembly/assembly.xml b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/java/io/github/hpsocket/demo/infra/sentinel/service/impl/DemoServiceImpl.java b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/java/io/github/hpsocket/demo/infra/sentinel/service/impl/DemoServiceImpl.java new file mode 100644 index 0000000..93de07e --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/java/io/github/hpsocket/demo/infra/sentinel/service/impl/DemoServiceImpl.java @@ -0,0 +1,45 @@ +package io.github.hpsocket.demo.infra.sentinel.service.impl; + +import org.apache.dubbo.config.annotation.DubboService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; + +import io.github.hpsocket.demo.infra.sentinel.service.DemoService; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RefreshScope +@DubboService +public class DemoServiceImpl implements DemoService +{ + @Value("${aaa.bbb}") + Integer val; + + @Override + public String sayHello(String name) + { + MdcAttr mdc = MdcAttr.fromMdc(); + log.info("MDC: {}", mdc.getCtxMap()); + + if(name.length() < 4) + throw new DemoException("Just test exception: name length < 4"); + + return "Hello Mr. " + name; + } + + @SuppressWarnings("serial") + public static class DemoException extends RuntimeException + { + public DemoException() + { + super(); + } + + public DemoException(String msg) + { + super(msg); + } + } +} diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/application.yml b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..19a24f7 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/bootstrap.yml @@ -0,0 +1,51 @@ +# app +hp.soa.web: + app: + id: "0010100006" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: false + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: sentinel.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5006 + tri: + name: tri + port: 6006 + +# server +server.port: 9006 diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/log4j2.xml b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/hp-demo-infra-sentinel-service/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-sentinel/pom.xml b/hp-demo/hp-demo-infra-sentinel/pom.xml new file mode 100644 index 0000000..033e866 --- /dev/null +++ b/hp-demo/hp-demo-infra-sentinel/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-infra-sentinel + ${project.artifactId} + pom + + + + + + hp-demo-infra-sentinel-api + hp-demo-infra-sentinel-service + + + + + + diff --git a/hp-demo/hp-demo-infra-skywalking/.gitignore b/hp-demo/hp-demo-infra-skywalking/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/.gitignore b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/pom.xml b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/pom.xml new file mode 100644 index 0000000..0a9586d --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-skywalking + ${revision} + + hp-demo-infra-skywalking-api + ${project.artifactId} + + + + + + + jakarta.validation + jakarta.validation-api + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + + diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/src/main/java/io/github/hpsocket/demo/infra/skywalking/service/DemoService.java b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/src/main/java/io/github/hpsocket/demo/infra/skywalking/service/DemoService.java new file mode 100644 index 0000000..3da21ca --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-api/src/main/java/io/github/hpsocket/demo/infra/skywalking/service/DemoService.java @@ -0,0 +1,8 @@ +package io.github.hpsocket.demo.infra.skywalking.service; + +import jakarta.validation.constraints.NotBlank; + +public interface DemoService +{ + String sayHello(@NotBlank(message="姓名不能为空") String name); +} diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/.gitignore b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/pom.xml b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/pom.xml new file mode 100644 index 0000000..0da1c25 --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + io.github.hpsocket.demo + hp-demo-infra-skywalking + ${revision} + + hp-demo-infra-skywalking-service + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-skywalking-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-sentinel + + + io.github.hpsocket + hp-soa-starter-skywalking + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + + diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/assembly/assembly.xml b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/java/io/github/hpsocket/demo/infra/skywalking/service/impl/DemoServiceImpl.java b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/java/io/github/hpsocket/demo/infra/skywalking/service/impl/DemoServiceImpl.java new file mode 100644 index 0000000..efee01d --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/java/io/github/hpsocket/demo/infra/skywalking/service/impl/DemoServiceImpl.java @@ -0,0 +1,58 @@ +package io.github.hpsocket.demo.infra.skywalking.service.impl; + +import java.util.concurrent.CompletableFuture; + +import org.apache.dubbo.config.annotation.DubboService; +import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cloud.context.config.annotation.RefreshScope; + +import io.github.hpsocket.demo.infra.skywalking.service.DemoService; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.starter.skywalking.async.TracingSupplierWrapper; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RefreshScope +@DubboService +public class DemoServiceImpl implements DemoService +{ + @Value("${aaa.bbb}") + Integer val; + + @Override + public String sayHello(String name) + { + MdcAttr mdc = MdcAttr.fromMdc(); + log.info("MDC: {}", mdc.getCtxMap()); + log.info("Trace: {}, {}, {}", TraceContext.traceId(), TraceContext.segmentId(), TraceContext.spanId()); + + if(name.length() < 4) + throw new DemoException("Just test exception: name length < 4"); + + CompletableFuture.supplyAsync(TracingSupplierWrapper.of(() -> { + GeneralHelper.waitFor(500); + log.info("Async MDC: {}", mdc.getCtxMap()); + log.info("Async Trace: {}, {}, {}", TraceContext.traceId(), TraceContext.segmentId(), TraceContext.spanId()); + return "OK"; + })); + + return "Hello Mr. " + name; + } + + @SuppressWarnings("serial") + public static class DemoException extends RuntimeException + { + public DemoException() + { + super(); + } + + public DemoException(String msg) + { + super(msg); + } + } +} diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/application.yml b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..cb2795f --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/bootstrap.yml @@ -0,0 +1,51 @@ +# app +hp.soa.web: + app: + id: "0010100008" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: false + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: sentinel.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5008 + tri: + name: tri + port: 6008 + +# server +server.port: 9008 diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/log4j2.xml b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/hp-demo-infra-skywalking-service/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-infra-skywalking/pom.xml b/hp-demo/hp-demo-infra-skywalking/pom.xml new file mode 100644 index 0000000..3f8d9b2 --- /dev/null +++ b/hp-demo/hp-demo-infra-skywalking/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-infra-skywalking + ${project.artifactId} + pom + + + + + + hp-demo-infra-skywalking-api + hp-demo-infra-skywalking-service + + + + + + diff --git a/hp-demo/hp-demo-job/.gitignore b/hp-demo/hp-demo-job/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-job/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-job/pom.xml b/hp-demo/hp-demo-job/pom.xml new file mode 100644 index 0000000..e1a7973 --- /dev/null +++ b/hp-demo/hp-demo-job/pom.xml @@ -0,0 +1,90 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-job + ${project.artifactId} + + + + + + + io.github.hpsocket.demo + hp-demo-infra-nacos-api + ${project.version} + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-skywalking + + + io.github.hpsocket + hp-soa-starter-job-exclusive + + + io.github.hpsocket + hp-soa-starter-job-xxljob + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-job/src/main/assembly/assembly.xml b/hp-demo/hp-demo-job/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/config/AppConfig.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/config/AppConfig.java new file mode 100644 index 0000000..70a3deb --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/config/AppConfig.java @@ -0,0 +1,9 @@ +package io.github.hpsocket.demo.job.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig +{ + +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/contract/req/DemoReuqest.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/contract/req/DemoReuqest.java new file mode 100644 index 0000000..bb93372 --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/contract/req/DemoReuqest.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.demo.job.contract.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "请求对象示例") +public class DemoReuqest +{ + @NotBlank(message = "name is empty") + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, minLength = 1, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/contract/resp/DemoResponse.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/contract/resp/DemoResponse.java new file mode 100644 index 0000000..eefd0cc --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/contract/resp/DemoResponse.java @@ -0,0 +1,30 @@ +package io.github.hpsocket.demo.job.contract.resp; + +import com.alibaba.fastjson2.JSON; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "响应对象示例") +public class DemoResponse +{ + @Schema(description = "ID", example = "123", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long id; + + @Schema(description = "姓名", example = "my name", requiredMode = RequiredMode.REQUIRED, nullable = false) + private String name; + + @Schema(description = "年龄", example = "23", requiredMode = RequiredMode.NOT_REQUIRED, nullable = false) + private Integer age; + + private String token; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/controller/DemoController.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/controller/DemoController.java new file mode 100644 index 0000000..475296a --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/controller/DemoController.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.demo.job.controller; + +import io.github.hpsocket.demo.job.contract.req.DemoReuqest; +import io.github.hpsocket.demo.job.contract.resp.DemoResponse; +import io.github.hpsocket.soa.framework.web.model.Response; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@RequestMapping(value = "/demo", method = {RequestMethod.POST}) +@Tag(name = "示例Demo接口") +public interface DemoController +{ + @PostMapping(value = "/queryUser") + @Operation(summary = "查询用户", description = "通过姓名查询用户") + Response queryUser(@RequestBody @Valid DemoReuqest request); + + @PostMapping(value = "/test") + Object test(@RequestBody @Valid DemoReuqest request); +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/controller/impl/DemoControllerImpl.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/controller/impl/DemoControllerImpl.java new file mode 100644 index 0000000..2381a2f --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/controller/impl/DemoControllerImpl.java @@ -0,0 +1,53 @@ +package io.github.hpsocket.demo.job.controller.impl; + +import io.github.hpsocket.demo.infra.nacos.service.DemoService; +import io.github.hpsocket.demo.job.contract.req.DemoReuqest; +import io.github.hpsocket.demo.job.contract.resp.DemoResponse; +import io.github.hpsocket.demo.job.controller.DemoController; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.model.Response; + +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +import org.apache.dubbo.config.annotation.DubboReference; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@AccessVerification(Type.NO_LOGIN) +public class DemoControllerImpl implements DemoController +{ + @DubboReference + DemoService demoService; + + @Override + @AccessVerification(Type.REQUIRE_LOGIN) + public Response queryUser(@RequestBody @Valid DemoReuqest request) + { + /** 通过 RequestContext.getXxx() 获取 Request Context 相关信息 */ + System.out.printf("HAPI-INS - clientId: %s, requestId: %s\n", RequestContext.getClientId(), RequestContext.getRequestId()); + + String name = demoService.sayHello(request.getName()); + + DemoResponse resp = new DemoResponse(); + resp.setId(1001L); + resp.setName(name); + resp.setAge(request.getAge()); + resp.setToken("41784a5039322bbe55a8bf8ce29b9280"); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } + + @Override + public Object test(@Valid DemoReuqest request) + { + return "test"; + } +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/handler/ExclusiveJobHandler.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/handler/ExclusiveJobHandler.java new file mode 100644 index 0000000..3feb3ec --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/handler/ExclusiveJobHandler.java @@ -0,0 +1,25 @@ +package io.github.hpsocket.demo.job.handler; + +import org.springframework.stereotype.Component; + +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.job.exclusive.annotation.ExclusiveJob; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class ExclusiveJobHandler +{ + int i = 0; + + //@Scheduled(cron = "*/5 * * * * ?") + @ExclusiveJob(jobName = "job1", cron = "*/5 * * * * ?") + public void job1() + { + if((++i) % 5 == 0) + throw new RuntimeException("test thow exceptions"); + + log.info("traceId: {}", WebServerHelper.getTraceId()); + } +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/handler/XxlJobHandler.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/handler/XxlJobHandler.java new file mode 100644 index 0000000..72661b3 --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/handler/XxlJobHandler.java @@ -0,0 +1,24 @@ +package io.github.hpsocket.demo.job.handler; + +import org.springframework.stereotype.Component; + +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import com.xxl.job.core.handler.annotation.XxlJob; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class XxlJobHandler +{ + private int i; + + @XxlJob("xxlJobHandler1") + public void xxlJobHandler1() + { + if((++i) % 5 == 0) + throw new RuntimeException("test thow exceptions"); + + log.info("traceId: {}", WebServerHelper.getTraceId()); + } +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/AccessVerificationServiceImpl.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/AccessVerificationServiceImpl.java new file mode 100644 index 0000000..a92b898 --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/AccessVerificationServiceImpl.java @@ -0,0 +1,28 @@ +package io.github.hpsocket.demo.job.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +@Service +public class AccessVerificationServiceImpl implements AccessVerificationService +{ + @Override + public Pair verifyUserByTokenAndGroupId(String token, Long groupId) + { + return new Pair(123L, "OK"); + } + + @Override + public Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId) + { + return new Pair(Boolean.TRUE, "ok"); + } + + @Override + public boolean verifyAppCode(String appCode) + { + return true; + } +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/ExclusiveJobExceptionHandlerImpl.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/ExclusiveJobExceptionHandlerImpl.java new file mode 100644 index 0000000..70a9fca --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/ExclusiveJobExceptionHandlerImpl.java @@ -0,0 +1,19 @@ +package io.github.hpsocket.demo.job.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.starter.job.exclusive.exception.ExclusiveJobExceptionHandler; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class ExclusiveJobExceptionHandlerImpl implements ExclusiveJobExceptionHandler +{ + @Override + public void handleException(String jobPrefix, String jobName, Exception e) + { + log.info("handle exclusive job exception : (jobPrefix: {}, jobName: {}, exception: {})", jobPrefix, jobName, e); + } + +} diff --git a/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/XxlJobExceptionHandlerImpl.java b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/XxlJobExceptionHandlerImpl.java new file mode 100644 index 0000000..331565f --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/java/io/github/hpsocket/demo/job/service/impl/XxlJobExceptionHandlerImpl.java @@ -0,0 +1,19 @@ +package io.github.hpsocket.demo.job.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.starter.job.xxljob.exception.XxlJobExceptionHandler; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class XxlJobExceptionHandlerImpl implements XxlJobExceptionHandler +{ + @Override + public void handleException(String jobName, long jobId, String param, Exception e) + { + log.info("handle xxl-job exception : (jobName: {}, jobId: {}, param: '{}', exception: {})", jobName, jobId, param, e); + } + +} diff --git a/hp-demo/hp-demo-job/src/main/resources/application.yml b/hp-demo/hp-demo-job/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-job/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-job/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..f92b159 --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/resources/bootstrap.yml @@ -0,0 +1,60 @@ +# app +hp.soa.web: + app: + id: "0010100101" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-task.yml + refresh: true + - group: GLOBAL_GROUP + data-id: xxl-job.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: redis.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5101 + tri: + name: tri + port: 6101 + +# server +server.port: 9101 + +# xxl-job +xxl.job.executor.port: 7101 diff --git a/hp-demo/hp-demo-job/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-job/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-job/src/main/resources/log4j2.xml b/hp-demo/hp-demo-job/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-job/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-mq-consumer/.gitignore b/hp-demo/hp-demo-mq-consumer/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-mq-consumer/pom.xml b/hp-demo/hp-demo-mq-consumer/pom.xml new file mode 100644 index 0000000..ac22c80 --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/pom.xml @@ -0,0 +1,81 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-mq-consumer + ${project.artifactId} + + + + + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-skywalking + + + io.github.hpsocket + hp-soa-starter-rabbitmq + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-mq-consumer/src/main/assembly/assembly.xml b/hp-demo/hp-demo-mq-consumer/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-mq-consumer/src/main/java/io/github/hpsocket/demo/mq/consumer/config/AppConfig.java b/hp-demo/hp-demo-mq-consumer/src/main/java/io/github/hpsocket/demo/mq/consumer/config/AppConfig.java new file mode 100644 index 0000000..3053ef1 --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/src/main/java/io/github/hpsocket/demo/mq/consumer/config/AppConfig.java @@ -0,0 +1,149 @@ +package io.github.hpsocket.demo.mq.consumer.config; + +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.ExchangeBuilder; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.hpsocket.soa.starter.rabbitmq.annotation.EnableSoaRabbitmqConsumer; + +@Configuration +/* enable rabbitmq consumer */ +@EnableSoaRabbitmqConsumer +public class AppConfig +{ + public static final String DOMAIN_NAME = "demo.order"; + public static final String CREATE_ORDER_EVENT_NAME = "createOrder"; + public static final String CREATE_ORDER_ROUTING_KEY = "order.create.*"; + + public static final String QUE_REGION_0 = "QUE_REGION_0"; + public static final String QUE_REGION_1 = "QUE_REGION_1"; + public static final String QUE_REGION_2 = "QUE_REGION_2"; + public static final String QUE_REGION_3 = "QUE_REGION_3"; + + public static final String STM_REGION_0 = "default-stream"; + public static final String STM_REGION_1 = "first-stream"; + public static final String STM_REGION_2 = "second-stream"; + public static final String STM_REGION_3 = "third-stream"; + + public static final String[] REGION_EXCHANGES = {"EXC_REGION_0", "EXC_REGION_1", "EXC_REGION_2", "EXC_REGION_3"}; + public static final String[] REGION_QUEUES = {QUE_REGION_0, QUE_REGION_1, QUE_REGION_2, QUE_REGION_3}; + public static final String[] REGION_STREAMS = {STM_REGION_0, STM_REGION_1, STM_REGION_2, STM_REGION_3}; + + + @Autowired + @Qualifier("defaultAmqpAdmin") + AmqpAdmin defaultAmqpAdmin; + @Autowired + @Qualifier("firstAmqpAdmin") + AmqpAdmin firstAmqpAdmin; + @Autowired + @Qualifier("secondAmqpAdmin") + AmqpAdmin secondAmqpAdmin; + @Autowired + @Qualifier("thirdAmqpAdmin") + AmqpAdmin thirdAmqpAdmin; + + @Bean + TopicExchange region0TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[0]).durable(true).admins(defaultAmqpAdmin).build(); + } + + @Bean + TopicExchange region1TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[1]).durable(true).admins(firstAmqpAdmin).build(); + } + + @Bean + TopicExchange region2TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[2]).durable(true).admins(secondAmqpAdmin).build(); + } + + @Bean + TopicExchange region3TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[3]).durable(true).admins(thirdAmqpAdmin).build(); + } + + @Bean + Queue region0Queue() + { + Queue queue = QueueBuilder.durable(REGION_QUEUES[0]).maxLength(1000000).maxLengthBytes(300485760).build(); + queue.setAdminsThatShouldDeclare(defaultAmqpAdmin); + + return queue; + } + + @Bean + Queue region1Queue() + { + Queue queue = QueueBuilder.durable(REGION_QUEUES[1]).maxLength(1000000).maxLengthBytes(300485760).build(); + queue.setAdminsThatShouldDeclare(firstAmqpAdmin); + + return queue; + } + + @Bean + Queue region2Queue() + { + Queue queue = QueueBuilder.durable(REGION_QUEUES[2]).maxLength(1000000).maxLengthBytes(300485760).build(); + queue.setAdminsThatShouldDeclare(secondAmqpAdmin); + + return queue; + } + + @Bean + Queue region3Queue() + { + Queue queue = QueueBuilder.durable(REGION_QUEUES[3]).maxLength(1000000).maxLengthBytes(300485760).build(); + queue.setAdminsThatShouldDeclare(thirdAmqpAdmin); + + return queue; + } + + @Bean + Binding region0Binding(@Qualifier("region0Queue") Queue queue, @Qualifier("region0TopicExchange") TopicExchange exchange) + { + Binding binding = BindingBuilder.bind(queue).to(exchange).with(CREATE_ORDER_ROUTING_KEY); + binding.setAdminsThatShouldDeclare(defaultAmqpAdmin); + + return binding; + } + + @Bean + Binding region1Binding(@Qualifier("region1Queue") Queue queue, @Qualifier("region1TopicExchange") TopicExchange exchange) + { + Binding binding = BindingBuilder.bind(queue).to(exchange).with(CREATE_ORDER_ROUTING_KEY); + binding.setAdminsThatShouldDeclare(firstAmqpAdmin); + + return binding; + } + + @Bean + Binding region2Binding(@Qualifier("region2Queue") Queue queue, @Qualifier("region2TopicExchange") TopicExchange exchange) + { + Binding binding = BindingBuilder.bind(queue).to(exchange).with(CREATE_ORDER_ROUTING_KEY); + binding.setAdminsThatShouldDeclare(secondAmqpAdmin); + + return binding; + } + + @Bean + Binding region3Binding(@Qualifier("region3Queue") Queue queue, @Qualifier("region3TopicExchange") TopicExchange exchange) + { + Binding binding = BindingBuilder.bind(queue).to(exchange).with(CREATE_ORDER_ROUTING_KEY); + binding.setAdminsThatShouldDeclare(thirdAmqpAdmin); + + return binding; + } +} diff --git a/hp-demo/hp-demo-mq-consumer/src/main/java/io/github/hpsocket/demo/mq/consumer/listener/MqListenerHandler.java b/hp-demo/hp-demo-mq-consumer/src/main/java/io/github/hpsocket/demo/mq/consumer/listener/MqListenerHandler.java new file mode 100644 index 0000000..753b7b3 --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/src/main/java/io/github/hpsocket/demo/mq/consumer/listener/MqListenerHandler.java @@ -0,0 +1,186 @@ +package io.github.hpsocket.demo.mq.consumer.listener; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.messaging.MessageHeaders; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.rabbitmq.client.Channel; +import com.rabbitmq.stream.MessageHandler.Context; +import com.rabbitmq.stream.Properties; + +import io.github.hpsocket.demo.mq.consumer.config.AppConfig; +import lombok.extern.slf4j.Slf4j; + +import static io.github.hpsocket.soa.starter.rabbitmq.common.util.RabbitmqConstant.*; + +import java.io.IOException; +import java.util.Map; + +@Slf4j +@Component +public class MqListenerHandler +{ + @RabbitListener(queues = {AppConfig.QUE_REGION_0}, ackMode = "MANUAL", containerFactory = "defaultSimpleRabbitListenerContainerFactory") + public void onDefaultMessage(Message message, Channel channel) throws IOException + { + MessageProperties properties = message.getMessageProperties(); + + String msgId = properties.getMessageId(); + long deliveryTag = properties.getDeliveryTag(); + String domainName = properties.getHeader(HEADER_DOMAIN_NAME); + String eventName = properties.getHeader(HEADER_EVENT_NAME); + + try + { + JSONObject msg = JSON.parseObject(message.getBody()); + + log.info("receive message -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, msg: {}", AppConfig.QUE_REGION_0, msgId, domainName, eventName, msg.toJSONString()); + channel.basicAck(deliveryTag, false); + } + catch(Exception e) + { + channel.basicNack(deliveryTag, false, false); + log.error("receive message fail -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, exception: {}", AppConfig.QUE_REGION_0, msgId, domainName, eventName, e.getMessage(), e); + } + } + + @RabbitListener(queues = {AppConfig.QUE_REGION_1}, ackMode = "AUTO", containerFactory = "firstSimpleRabbitListenerContainerFactory") + public void onFirstMessage(org.springframework.messaging.Message message, Channel channel) throws IOException + { + MessageHeaders headers = message.getHeaders(); + String msgId = (String)headers.get(HEADER_AMQP_MESSAGE_ID); + //long deliveryTag = (Long)headers.get(HEADER_AMQP_DELIVERY_TAG); + String domainName = (String)headers.get(HEADER_DOMAIN_NAME); + String eventName = (String)headers.get(HEADER_EVENT_NAME); + + try + { + JSONObject msg = message.getPayload(); + + log.info("receive message -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, msg: {}", AppConfig.QUE_REGION_1, msgId, domainName, eventName, msg.toJSONString()); + } + catch(Exception e) + { + log.error("receive message fail -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, exception: {}", AppConfig.QUE_REGION_1, msgId, domainName, eventName, e.getMessage(), e); + } + } + + /* + @RabbitListener(queues = {AppConfig.QUE_REGION_2}, ackMode = "AUTO", containerFactory = "secondSimpleRabbitListenerContainerFactory") + public void onSecondMessage(Message message, Channel channel) throws IOException + { + MessageProperties properties = message.getMessageProperties(); + + String msgId = properties.getMessageId(); + //long deliveryTag = properties.getDeliveryTag(); + String domainName = properties.getHeader(HEADER_DOMAIN_NAME); + String eventName = properties.getHeader(HEADER_EVENT_NAME); + + try + { + JSONObject msg = JSON.parseObject(message.getBody()); + + log.info("receive message -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, msg: {}", AppConfig.QUE_REGION_2, msgId, domainName, eventName, msg.toJSONString()); + } + catch(Exception e) + { + log.error("receive message fail -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, exception: {}", AppConfig.QUE_REGION_2, msgId, domainName, eventName, e.getMessage(), e); + } + } + + @RabbitListener(queues = {AppConfig.QUE_REGION_3}, ackMode = "MANUAL", containerFactory = "thirdSimpleRabbitListenerContainerFactory") + public void onThirdMessage(org.springframework.messaging.Message message, Channel channel) throws IOException + { + MessageHeaders headers = message.getHeaders(); + String msgId = (String)headers.get(HEADER_AMQP_MESSAGE_ID); + long deliveryTag = (Long)headers.get(HEADER_AMQP_DELIVERY_TAG); + String domainName = (String)headers.get(HEADER_DOMAIN_NAME); + String eventName = (String)headers.get(HEADER_EVENT_NAME); + + try + { + JSONObject msg = message.getPayload(); + + log.info("receive message -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, msg: {}", AppConfig.QUE_REGION_3, msgId, domainName, eventName, msg.toJSONString()); + channel.basicAck(deliveryTag, false); + } + catch(Exception e) + { + channel.basicNack(deliveryTag, false, false); + log.error("receive message fail -> queue: {}, msgId: {}, domainName: {}, evnetName: {}, exception: {}", AppConfig.QUE_REGION_3, msgId, domainName, eventName, e.getMessage(), e); + } + } + + @RabbitListener(queues = {AppConfig.STM_REGION_0}, containerFactory = "defaultStreamRabbitListenerContainerFactory") + public void onDefaultStreamMessage(com.rabbitmq.stream.Message message, Context context) throws IOException + { + onStreamMessage(AppConfig.STM_REGION_0, message, context); + } + + @RabbitListener(queues = {AppConfig.STM_REGION_1}, containerFactory = "firstStreamRabbitListenerContainerFactory") + public void onFirstStreamMessage(com.rabbitmq.stream.Message message, Context context) throws IOException + { + onStreamMessage(AppConfig.STM_REGION_1, message, context); + } + */ + + @RabbitListener(queues = {AppConfig.STM_REGION_2}, containerFactory = "secondStreamRabbitListenerContainerFactory") + public void onSecondStreamMessage(com.rabbitmq.stream.Message message, Context context) throws IOException + { + onStreamMessage(AppConfig.STM_REGION_2, message, context); + } + + @RabbitListener(queues = {AppConfig.STM_REGION_3}, containerFactory = "thirdStreamRabbitListenerContainerFactory", messageConverter = "messageConverter") + public void onThirdStreamMessage(org.springframework.messaging.Message message) throws IOException + { + onStreamMessage(AppConfig.STM_REGION_3, message); + } + + private void onStreamMessage(String stream, com.rabbitmq.stream.Message message, Context context) + { + Properties props = message.getProperties(); + Map appProps = message.getApplicationProperties(); + + String msgId = props.getMessageIdAsString(); + String corId = props.getCorrelationIdAsString(); + String domainName = (String)appProps.get(HEADER_DOMAIN_NAME); + String eventName = (String)appProps.get(HEADER_EVENT_NAME); + + try + { + JSONObject msg = JSON.parseObject(message.getBodyAsBinary()); + + log.info("receive message -> stream: {}, msgId: {}, corId: {}, domainName: {}, evnetName: {}, msg: {}", stream, msgId, corId, domainName, eventName, msg.toJSONString()); + } + catch(Exception e) + { + log.error("receive message fail -> stream: {}, msgId: {}, corId: {}, domainName: {}, evnetName: {}, exception: {}", stream, msgId, corId, domainName, eventName, e.getMessage(), e); + } + } + + private void onStreamMessage(String stream, org.springframework.messaging.Message message) + { + MessageHeaders headers = message.getHeaders(); + + String msgId = (String)headers.get(HEADER_MSG_ID); + String corId = (String)headers.get(HEADER_CORRELA_DATA_ID); + String domainName = (String)headers.get(HEADER_DOMAIN_NAME); + String eventName = (String)headers.get(HEADER_EVENT_NAME); + + try + { + JSONObject msg = message.getPayload(); + + log.info("receive message -> stream: {}, msgId: {}, corId: {}, domainName: {}, evnetName: {}, msg: {}", stream, msgId, corId, domainName, eventName, msg.toJSONString()); + } + catch(Exception e) + { + log.error("receive message fail -> stream: {}, msgId: {}, corId: {}, domainName: {}, evnetName: {}, exception: {}", stream, msgId, corId, domainName, eventName, e.getMessage(), e); + } + } + +} diff --git a/hp-demo/hp-demo-mq-consumer/src/main/resources/application.yml b/hp-demo/hp-demo-mq-consumer/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-mq-consumer/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-mq-consumer/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..c29ea29 --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/src/main/resources/bootstrap.yml @@ -0,0 +1,51 @@ +# app +hp.soa.web: + app: + id: "0010100012" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: false + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: rabbitmq.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5012 + tri: + name: tri + port: 6012 + +# server +server.port: 9012 diff --git a/hp-demo/hp-demo-mq-consumer/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-mq-consumer/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-mq-consumer/src/main/resources/log4j2.xml b/hp-demo/hp-demo-mq-consumer/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-mq-consumer/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-mq-producer/.gitignore b/hp-demo/hp-demo-mq-producer/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-demo/hp-demo-mq-producer/pom.xml b/hp-demo/hp-demo-mq-producer/pom.xml new file mode 100644 index 0000000..78689da --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/pom.xml @@ -0,0 +1,89 @@ + + 4.0.0 + + io.github.hpsocket.demo + hp-demo + ${revision} + + hp-demo-mq-producer + ${project.artifactId} + + + + + + + io.github.hpsocket + hp-soa-starter-web + + + io.github.hpsocket + hp-soa-starter-nacos + + + io.github.hpsocket + hp-soa-starter-skywalking + + + io.github.hpsocket + hp-soa-starter-job-exclusive + + + io.github.hpsocket + hp-soa-starter-rabbitmq + + + io.github.hpsocket + hp-soa-starter-data-mysql + + + + + + + pl.project13.maven + git-commit-id-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + + diff --git a/hp-demo/hp-demo-mq-producer/src/main/assembly/assembly.xml b/hp-demo/hp-demo-mq-producer/src/main/assembly/assembly.xml new file mode 100644 index 0000000..8629eac --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/assembly/assembly.xml @@ -0,0 +1,32 @@ + + ${git.commit.id.abbrev}-${git.commit.time} + + tar.gz + + true + ${project.artifactId}-${project.version}-${git.commit.id.abbrev}-${git.commit.time} + + + /opt/deploy/scripts + bin + 0755 + unix + true + + + + + target/classes/git.properties + deploy + 0644 + unix + + + target/${project.artifactId}-${project.version}.jar + deploy + 0644 + + + diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/config/AppConfig.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/config/AppConfig.java new file mode 100644 index 0000000..a3140fd --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/config/AppConfig.java @@ -0,0 +1,64 @@ +package io.github.hpsocket.demo.mq.producer.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.core.ExchangeBuilder; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.github.hpsocket.soa.starter.rabbitmq.annotation.EnableSoaRabbitmqProducer; + +@Configuration +/* enable rabbitmq producer */ +@EnableSoaRabbitmqProducer +/* default mybatis mapper scan package -> ${hp.soa.web.mapper-scan.base-package} */ +@MapperScan("io.github.hpsocket.demo.mq.producer.mapper") +public class AppConfig +{ + public static final String DOMAIN_NAME = "demo.order"; + public static final String CREATE_ORDER_EVENT_NAME = "createOrder"; + public static final String CREATE_ORDER_ROUTING_KEY = "order.create.*"; + + public static final String[] REGION_EXCHANGES = {"EXC_REGION_0", "EXC_REGION_1", "EXC_REGION_2", "EXC_REGION_3"}; + public static final String[] REGION_QUEUES = {"QUE_REGION_0", "QUE_REGION_1", "QUE_REGION_2", "QUE_REGION_3"}; + + @Autowired + @Qualifier("defaultAmqpAdmin") + AmqpAdmin defaultAmqpAdmin; + @Autowired + @Qualifier("firstAmqpAdmin") + AmqpAdmin firstAmqpAdmin; + @Autowired + @Qualifier("secondAmqpAdmin") + AmqpAdmin secondAmqpAdmin; + @Autowired + @Qualifier("thirdAmqpAdmin") + AmqpAdmin thirdAmqpAdmin; + + @Bean + TopicExchange region0TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[0]).durable(true).admins(defaultAmqpAdmin).build(); + } + + @Bean + TopicExchange region1TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[1]).durable(true).admins(firstAmqpAdmin).build(); + } + + @Bean + TopicExchange region2TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[2]).durable(true).admins(secondAmqpAdmin).build(); + } + + @Bean + TopicExchange region3TopicExchange() + { + return ExchangeBuilder.topicExchange(REGION_EXCHANGES[3]).durable(true).admins(thirdAmqpAdmin).build(); + } +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/contract/req/DemoCreateOrderReuqest.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/contract/req/DemoCreateOrderReuqest.java new file mode 100644 index 0000000..36dddd1 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/contract/req/DemoCreateOrderReuqest.java @@ -0,0 +1,31 @@ +package io.github.hpsocket.demo.mq.producer.contract.req; + +import org.hibernate.validator.constraints.Range; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "请求对象示例") +public class DemoCreateOrderReuqest +{ + @NotNull(message = "区域不能为空") + @Range(min = 0, max = 3, message = "区域超出范围 [0 ~ 3]") + @Schema(description = "区域", example = "1", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Integer regionId; + + @NotBlank(message = "orderNumber is empty") + @Schema(description = "订单号", example = "1234567890987654", requiredMode = RequiredMode.REQUIRED, minLength = 16, nullable = false) + private String orderNumber; + + @NotNull(message = "金额不能为空") + @PositiveOrZero(message = "金额不能为负数") + @Schema(description = "金额", example = "23000", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long price; +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/contract/resp/DemoCreateOrderResponse.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/contract/resp/DemoCreateOrderResponse.java new file mode 100644 index 0000000..2f19045 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/contract/resp/DemoCreateOrderResponse.java @@ -0,0 +1,30 @@ +package io.github.hpsocket.demo.mq.producer.contract.resp; + +import java.time.LocalDateTime; + +import com.alibaba.fastjson2.JSON; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Schema(description = "响应对象示例") +public class DemoCreateOrderResponse +{ + @Schema(description = "ID", example = "123", requiredMode = RequiredMode.REQUIRED, nullable = false) + private Long id; + + @Schema(description = "订单号", example = "1234567890987654", requiredMode = RequiredMode.REQUIRED, minLength = 16, nullable = false) + private String orderNumber; + + @Schema(description = "创建时间", example = "2023-11-22 12:34:56.789", requiredMode = RequiredMode.REQUIRED, nullable = false) + private LocalDateTime createTime; + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/controller/DemoController.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/controller/DemoController.java new file mode 100644 index 0000000..7b21a12 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/controller/DemoController.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.demo.mq.producer.controller; + +import io.github.hpsocket.demo.mq.producer.contract.req.DemoCreateOrderReuqest; +import io.github.hpsocket.demo.mq.producer.contract.resp.DemoCreateOrderResponse; +import io.github.hpsocket.soa.framework.web.model.Response; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@RequestMapping(value = "/demo", method = {RequestMethod.POST}) +@Tag(name = "示例Demo接口") +public interface DemoController +{ + @PostMapping(value = "/createOrder") + @Operation(summary = "创建订单", description = "创建订单") + Response createOrder(@RequestBody @Valid DemoCreateOrderReuqest request); + + @PostMapping(value = "/sendStream") + Response sendStream(@RequestBody @Valid DemoCreateOrderReuqest request); +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/controller/impl/DemoControllerImpl.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/controller/impl/DemoControllerImpl.java new file mode 100644 index 0000000..4a7861e --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/controller/impl/DemoControllerImpl.java @@ -0,0 +1,62 @@ +package io.github.hpsocket.demo.mq.producer.controller.impl; + +import io.github.hpsocket.demo.mq.producer.contract.req.DemoCreateOrderReuqest; +import io.github.hpsocket.demo.mq.producer.contract.resp.DemoCreateOrderResponse; +import io.github.hpsocket.demo.mq.producer.controller.DemoController; +import io.github.hpsocket.demo.mq.producer.converter.OrderConverter; +import io.github.hpsocket.demo.mq.producer.entity.Order; +import io.github.hpsocket.demo.mq.producer.sender.StreamSender; +import io.github.hpsocket.demo.mq.producer.service.OrderService; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.model.Response; + +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@AccessVerification(Type.NO_LOGIN) +public class DemoControllerImpl implements DemoController +{ + @Autowired + private OrderService orderService; + + @Autowired + StreamSender streamSender; + + @Autowired + private OrderConverter orderConverter; + + @Override + @AccessVerification(Type.REQUIRE_LOGIN) + public Response createOrder(@RequestBody @Valid DemoCreateOrderReuqest request) + { + System.out.printf("HAPI-INS - clientId: %s, requestId: %s\n", RequestContext.getClientId(), RequestContext.getRequestId()); + + Order order = orderService.createOrder(orderConverter.fromRequest(request)); + DemoCreateOrderResponse resp = orderConverter.toResponse(order); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } + + @Override + public Response sendStream(@Valid DemoCreateOrderReuqest request) + { + Order order = streamSender.sendOrder(orderConverter.fromRequest(request)); + DemoCreateOrderResponse resp = orderConverter.toResponse(order); + + log.debug(resp.toString()); + + Response response = new Response<>(resp); + return response; + } +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/converter/OrderConverter.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/converter/OrderConverter.java new file mode 100644 index 0000000..5e1f670 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/converter/OrderConverter.java @@ -0,0 +1,18 @@ +package io.github.hpsocket.demo.mq.producer.converter; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.factory.Mappers; + +import io.github.hpsocket.demo.mq.producer.contract.req.DemoCreateOrderReuqest; +import io.github.hpsocket.demo.mq.producer.contract.resp.DemoCreateOrderResponse; +import io.github.hpsocket.demo.mq.producer.entity.Order; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public interface OrderConverter +{ + OrderConverter INSTANCE = Mappers.getMapper(OrderConverter.class); + + DemoCreateOrderResponse toResponse(Order order); + Order fromRequest(DemoCreateOrderReuqest req); +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/entity/DemoEvent.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/entity/DemoEvent.java new file mode 100644 index 0000000..1f3ea8f --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/entity/DemoEvent.java @@ -0,0 +1,27 @@ +package io.github.hpsocket.demo.mq.producer.entity; + +import io.github.hpsocket.soa.starter.rabbitmq.producer.entity.DomainEvent; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@SuppressWarnings("serial") +public class DemoEvent extends DomainEvent +{ + private Long bizId; + private Integer regionId; + + public DemoEvent(Long bizId) + { + this(bizId, 0); + } + + public DemoEvent(Long bizId, Integer regionId) + { + this.bizId = bizId; + this.regionId = regionId; + } +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/entity/Order.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/entity/Order.java new file mode 100644 index 0000000..3f2f807 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/entity/Order.java @@ -0,0 +1,15 @@ +package io.github.hpsocket.demo.mq.producer.entity; + +import io.github.hpsocket.soa.starter.data.mysql.entity.BaseLogicDeleteEntity; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@SuppressWarnings("serial") +public class Order extends BaseLogicDeleteEntity +{ + private Integer regionId; + private String orderNumber; + private Long price; +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/handler/DomainEventSenderJobHandler.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/handler/DomainEventSenderJobHandler.java new file mode 100644 index 0000000..cfcb820 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/handler/DomainEventSenderJobHandler.java @@ -0,0 +1,77 @@ +package io.github.hpsocket.demo.mq.producer.handler; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import io.github.hpsocket.demo.mq.producer.entity.DemoEvent; +import io.github.hpsocket.soa.starter.job.exclusive.annotation.ExclusiveJob; +import io.github.hpsocket.soa.starter.rabbitmq.producer.sender.AbstractRabbitmqDomainEventSender; +import io.github.hpsocket.soa.starter.rabbitmq.producer.service.DomainEventService; +import jakarta.annotation.PostConstruct; + +@Component +public class DomainEventSenderJobHandler extends AbstractRabbitmqDomainEventSender +{ + @Autowired + private DomainEventService demoEventService; + + @Autowired(required = false) + @Qualifier("defaultRabbitTemplate") + private RabbitTemplate defaultRabbitTemplate; + + @Autowired(required = false) + @Qualifier("firstRabbitTemplate") + private RabbitTemplate firstRabbitTemplate; + + @Autowired(required = false) + @Qualifier("secondRabbitTemplate") + private RabbitTemplate secondRabbitTemplate; + + @Autowired(required = false) + @Qualifier("thirdRabbitTemplate") + private RabbitTemplate thirdRabbitTemplate; + + private RabbitTemplate[] rabbitTemplates; + + @PostConstruct + public void postConstruct() + { + rabbitTemplates = new RabbitTemplate[] + { + defaultRabbitTemplate, + firstRabbitTemplate, + secondRabbitTemplate, + thirdRabbitTemplate + }; + } + + /** MQ 消息发送 JOB */ + @Override + @ExclusiveJob(jobName = "sendMqEvent", cron = "*/3 * * * * ?") + public void sendMqEvent() + { + super.sendMqEvent(); + } + + /** MQ 消息状态重置补偿 JOB */ + @Override + @ExclusiveJob(jobName = "compensateMqEvent", cron = "15 */1 * * * ?") + public void compensateMqEvent() + { + super.compensateMqEvent(); + } + + @Override + protected RabbitTemplate getRabbitTemplate(DemoEvent event) + { + return rabbitTemplates[event.getRegionId()]; + } + + @Override + protected DomainEventService getDomainEventService() + { + return demoEventService; + } +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/mapper/DemoEventMapper.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/mapper/DemoEventMapper.java new file mode 100644 index 0000000..23fb6db --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/mapper/DemoEventMapper.java @@ -0,0 +1,10 @@ +package io.github.hpsocket.demo.mq.producer.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import io.github.hpsocket.demo.mq.producer.entity.DemoEvent; + +public interface DemoEventMapper extends BaseMapper +{ + +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/mapper/OrderMapper.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/mapper/OrderMapper.java new file mode 100644 index 0000000..2d0fa14 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/mapper/OrderMapper.java @@ -0,0 +1,10 @@ +package io.github.hpsocket.demo.mq.producer.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import io.github.hpsocket.demo.mq.producer.entity.Order; + +public interface OrderMapper extends BaseMapper +{ + +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/sender/StreamSender.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/sender/StreamSender.java new file mode 100644 index 0000000..24b2757 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/sender/StreamSender.java @@ -0,0 +1,85 @@ +package io.github.hpsocket.demo.mq.producer.sender; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSONObject; + +import io.github.hpsocket.demo.mq.producer.config.AppConfig; +import io.github.hpsocket.demo.mq.producer.entity.DemoEvent; +import io.github.hpsocket.demo.mq.producer.entity.Order; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class StreamSender +{ + @Autowired(required = false) + @Qualifier("defaultRabbitStreamTemplate") + private RabbitStreamTemplate defaultRabbitStreamTemplate; + + @Autowired(required = false) + @Qualifier("firstRabbitStreamTemplate") + private RabbitStreamTemplate firstRabbitStreamTemplate; + + @Autowired(required = false) + @Qualifier("secondRabbitStreamTemplate") + private RabbitStreamTemplate secondRabbitStreamTemplate; + + @Autowired(required = false) + @Qualifier("thirdRabbitStreamTemplate") + private RabbitStreamTemplate thirdRabbitStreamTemplate; + + private RabbitStreamTemplate[] rabbitStreamTemplates; + + private AtomicInteger seq = new AtomicInteger(0); + + @PostConstruct + public void postConstruct() + { + rabbitStreamTemplates = new RabbitStreamTemplate[] + { + defaultRabbitStreamTemplate, + firstRabbitStreamTemplate, + secondRabbitStreamTemplate, + thirdRabbitStreamTemplate + }; + } + + + public Order sendOrder(Order order) + { + Integer regionId = order.getRegionId(); + RabbitStreamTemplate streamTemplate = rabbitStreamTemplates[regionId]; + + DemoEvent event = new DemoEvent(order.getId(), order.getRegionId()); + String corId = new StringBuilder(event.getMsgId()).append('#').append((seq.incrementAndGet()) % 100).toString(); + + event.setDomainName(AppConfig.DOMAIN_NAME); + event.setEventName(AppConfig.CREATE_ORDER_EVENT_NAME); + event.setExchange(AppConfig.REGION_EXCHANGES[event.getRegionId()]); + event.setRoutingKey(AppConfig.CREATE_ORDER_ROUTING_KEY); + event.setMsg(JSONObject.toJSONString(order)); + + try + { + CompletableFuture future = streamTemplate.send(event.toStreamMessage(corId)); + Boolean ack = future.get(); + + log.info("send MQ message -> (regionId: {}, msgId: {}, ack: {})", regionId, event.getMsgId(), ack); + } + catch(Exception e) + { + log.error("send MQ message fail -> (regionId: {}, msgId: {}) : {}", regionId, event.getMsgId(), e.getMessage(), e); + } + + return order; + } + +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/OrderService.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/OrderService.java new file mode 100644 index 0000000..619922f --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/OrderService.java @@ -0,0 +1,8 @@ +package io.github.hpsocket.demo.mq.producer.service; + +import io.github.hpsocket.demo.mq.producer.entity.Order; + +public interface OrderService +{ + Order createOrder(Order order); +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/AccessVerificationServiceImpl.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/AccessVerificationServiceImpl.java new file mode 100644 index 0000000..8bb283e --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/AccessVerificationServiceImpl.java @@ -0,0 +1,28 @@ +package io.github.hpsocket.demo.mq.producer.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +@Service +public class AccessVerificationServiceImpl implements AccessVerificationService +{ + @Override + public Pair verifyUserByTokenAndGroupId(String token, Long groupId) + { + return new Pair(123L, "OK"); + } + + @Override + public Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId) + { + return new Pair(Boolean.TRUE, "ok"); + } + + @Override + public boolean verifyAppCode(String appCode) + { + return true; + } +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/DemoEventServiceImpl.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/DemoEventServiceImpl.java new file mode 100644 index 0000000..3688a08 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/DemoEventServiceImpl.java @@ -0,0 +1,13 @@ +package io.github.hpsocket.demo.mq.producer.service.impl; + +import org.springframework.stereotype.Service; + +import io.github.hpsocket.demo.mq.producer.entity.DemoEvent; +import io.github.hpsocket.demo.mq.producer.mapper.DemoEventMapper; +import io.github.hpsocket.soa.starter.rabbitmq.producer.service.impl.DomainEventServiceImpl; + +@Service +public class DemoEventServiceImpl extends DomainEventServiceImpl +{ + +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/OrderServiceImpl.java b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..56c1de6 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/java/io/github/hpsocket/demo/mq/producer/service/impl/OrderServiceImpl.java @@ -0,0 +1,44 @@ +package io.github.hpsocket.demo.mq.producer.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import io.github.hpsocket.demo.mq.producer.config.AppConfig; +import io.github.hpsocket.demo.mq.producer.entity.DemoEvent; +import io.github.hpsocket.demo.mq.producer.entity.Order; +import io.github.hpsocket.demo.mq.producer.mapper.OrderMapper; +import io.github.hpsocket.demo.mq.producer.service.OrderService; +import io.github.hpsocket.soa.starter.rabbitmq.producer.service.DomainEventService; + +@Service +public class OrderServiceImpl extends ServiceImpl implements OrderService +{ + @Autowired + private DomainEventService demoEventService; + + @Override + public Order createOrder(Order order) + { + save(order); + raiseCreateOrderEvent(order); + + return order; + } + + private void raiseCreateOrderEvent(Order order) + { + DemoEvent event = new DemoEvent(order.getId(), order.getRegionId()); + + event.setDomainName(AppConfig.DOMAIN_NAME); + event.setEventName(AppConfig.CREATE_ORDER_EVENT_NAME); + event.setExchange(AppConfig.REGION_EXCHANGES[event.getRegionId()]); + event.setRoutingKey(AppConfig.CREATE_ORDER_ROUTING_KEY); + event.setMsg(JSONObject.toJSONString(order)); + + demoEventService.save(event); + } + +} diff --git a/hp-demo/hp-demo-mq-producer/src/main/resources/application.yml b/hp-demo/hp-demo-mq-producer/src/main/resources/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/resources/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/hp-demo/hp-demo-mq-producer/src/main/resources/bootstrap.yml b/hp-demo/hp-demo-mq-producer/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..558370a --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/resources/bootstrap.yml @@ -0,0 +1,61 @@ +# app +hp.soa.web: + app: + id: "0010100011" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +spring.cloud.nacos.config: + #server-addr: 192.168.56.23:8848 + #username: nacos + #password: 123456 + #namespace: DEV + group: DEMO_GROUP + name: ${hp.soa.web.app.name} + file-extension: yml + refresh-enabled: true + shared-configs: + - group: GLOBAL_GROUP + data-id: hp-soa-web.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-boot.yml + refresh: true + - group: GLOBAL_GROUP + data-id: spring-task.yml + refresh: true + - group: GLOBAL_GROUP + data-id: rabbitmq.yml + refresh: true + - group: GLOBAL_GROUP + data-id: dubbo.yml + refresh: true + - group: GLOBAL_GROUP + data-id: mysql.yml + refresh: true + - group: GLOBAL_GROUP + data-id: redis.yml + refresh: true + +# dubbo +dubbo.protocols: + #dubbo: + # name: dubbo + # port: 5011 + tri: + name: tri + port: 6011 + +# server +server.port: 9011 + diff --git a/hp-demo/hp-demo-mq-producer/src/main/resources/log4j2-kafka.xml b/hp-demo/hp-demo-mq-producer/src/main/resources/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/resources/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/hp-demo-mq-producer/src/main/resources/log4j2.xml b/hp-demo/hp-demo-mq-producer/src/main/resources/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/hp-demo/hp-demo-mq-producer/src/main/resources/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hp-demo/pom.xml b/hp-demo/pom.xml new file mode 100644 index 0000000..95e362f --- /dev/null +++ b/hp-demo/pom.xml @@ -0,0 +1,84 @@ + + 4.0.0 + + io.github.hpsocket + hp-soa-parent + ${revision} + + io.github.hpsocket.demo + hp-demo + ${project.artifactId} + pom + + + + + + hp-demo-infra-basic + hp-demo-infra-nacos + hp-demo-infra-mysql + hp-demo-infra-sentinel + hp-demo-infra-skywalking + hp-demo-bff-basic + hp-demo-bff-nacos + hp-demo-bff-mysql + hp-demo-bff-sentinel + hp-demo-bff-skywalking + hp-demo-job + hp-demo-mq-producer + hp-demo-mq-consumer + + + + + + io.github.hpsocket + hp-soa-dependencies + ${project.parent.version} + pom + import + + + + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + + org.apache.maven.plugins + maven-install-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + + true + + + + + diff --git a/hp-soa-dependencies/.gitignore b/hp-soa-dependencies/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-dependencies/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-dependencies/pom.xml b/hp-soa-dependencies/pom.xml new file mode 100644 index 0000000..3e04e9f --- /dev/null +++ b/hp-soa-dependencies/pom.xml @@ -0,0 +1,1398 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-parent + ${revision} + + hp-soa-dependencies + ${project.artifactId} + pom + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + 2.2.4 + 0.2.12 + 3.2.5 + 4.1.97.Final + 3.9.0 + 0.11 + 5.5.0 + 3.0.4-jdk17 + 2.4.0 + 1.8.6 + + + + 6.0.11 + 3.1.3 + 2022.0.4 + 2022.0.0.0 + 2.5.2.RELEASE + 1.1.1.RELEASE + 3.1.3 + 3.1.3 + 3.1.3 + 3.1.3 + 4.1.3 + 3.0.8 + 3.0.8 + 3.0.8 + 3.0.11 + 3.5.1 + 1.2.19 + 4.4.4 + 6.2.6.RELEASE + 3.23.4 + 5.18.0 + 0.12.0 + 3.12.14 + 8.1.0 + 3.0.2 + 3.5.13 + 3.0.2 + 3.5.3.2 + 4.1.3 + 5.4.0 + 5.2.1 + 3.10.8 + + 2.13.0 + 3.9.0 + 2.6 + 3.13.0 + 1.9.4 + 3.2.2 + 4.4 + 1.16.0 + 1.5.4.0 + 2.11.1 + 3.1 + + 6.0.0 + 3.0.2 + 5.0.1 + 2.1.1 + 2.1.2 + 4.0.0 + 3.1.0 + + 8.0.1 + 4.0.1 + 3.0.1-b06 + 3.0.1-b12 + 2.0.1.Final + 1.6.2 + + 2.0.9 + 2.20.0 + 3.4.4 + 8.16.0 + 4.13.2 + 5.10.0 + + 2.0.40 + 2.0.40 + 3.24.3 + 2.3.2 + 4.5.14 + 5.2.1 + 1.4.1 + 1.4.20 + 8.0.1.Final + 2.3 + 2.3.32 + 3.15.8.RELEASE + + 4.1.0 + 2.10.1 + 2.15.2 + 4.0.66 + 3.5.2 + 2.7.5 + 4.11.0 + 32.1.2-jre + + 1.16.1 + 4.12.1 + + 5.2.3 + 3.3.2 + 2.12.2 + 5.3.3 + 2.2.0 + + 5.5.0 + 5.8.21 + 0.11.5 + + 1.7.1 + + 0.2.2 + 1.11.3 + + 5.16.0 + 2.16.4 + + + + + + io.github.hpsocket + hp-soa-framework-core + ${project.parent.version} + + + io.github.hpsocket + hp-soa-framework-web + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-web + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-task + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-nacos + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-sentinel + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-skywalking + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-data-redis + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-data-mysql + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-job-xxljob + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-job-exclusive + ${project.parent.version} + + + io.github.hpsocket + hp-soa-starter-rabbitmq + ${project.parent.version} + + + + org.apache.dubbo + dubbo + ${dubbo.version} + + + org.apache.dubbo + dubbo-qos + ${dubbo.version} + + + org.apache.dubbo + dubbo-spring-boot-starter + ${dubbo.version} + + + org.apache.dubbo + dubbo-spring-boot-observability-starter + ${dubbo.version} + + + org.apache.dubbo + dubbo-spring-boot-tracing-otel-zipkin-starter + ${dubbo.version} + + + org.apache.dubbo + dubbo-spring-boot-actuator + ${dubbo.version} + + + org.apache.dubbo + dubbo-registry-nacos + ${dubbo.version} + + + org.apache.dubbo + dubbo-configcenter-nacos + ${dubbo.version} + + + org.apache.dubbo + dubbo-metadata-report-nacos + ${dubbo.version} + + + org.apache.dubbo + dubbo-registry-zookeeper + ${dubbo.version} + + + org.apache.dubbo + dubbo-configcenter-zookeeper + ${dubbo.version} + + + org.apache.dubbo + dubbo-metadata-report-zookeeper + ${dubbo.version} + + + + com.alibaba.nacos + nacos-client + ${nacos-client.version} + + + + com.alibaba.boot + nacos-config-spring-boot-starter + ${nacos-config-spring-boot-starter.version} + + + + org.apache.curator + curator-recipes + ${curator.version} + + + org.apache.curator + curator-framework + ${curator.version} + + + org.apache.curator + curator-client + ${curator.version} + + + + org.apache.zookeeper + zookeeper + ${zookeeper.version} + + + com.101tec + zkclient + ${zkclient.version} + + + + io.netty + netty-all + ${netty.version} + + + + jakarta.validation + jakarta.validation-api + ${jakarta.validation-api.version} + + + + jakarta.servlet + jakarta.servlet-api + ${jakarta.servlet-api.version} + + + + jakarta.el + jakarta.el-api + ${jakarta.el-api.version} + + + + jakarta.annotation + jakarta.annotation-api + ${jakarta.annotation-api.version} + + + + jakarta.activation + jakarta.activation-api + ${jakarta.activation-api.version} + + + + jakarta.persistence + jakarta.persistence-api + ${jakarta.persistence-api.version} + + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jakarta.xml.bind-api.version} + + + + javax + javaee-api + ${javax-javaee-api.version} + + + javax.servlet + javax.servlet-api + ${javax.servlet-api.version} + + + javax.el + javax.el-api + ${javax-el-api.version} + + + org.glassfish + javax.el + ${javax-el.version} + + + + javax.validation + validation-api + ${javax-validation-api.version} + + + + javax.mail + mail + ${javax-mail.version} + + + + org.hibernate.validator + hibernate-validator + ${hibernate-validator.version} + + + + com.google.code.gson + gson + ${gson.version} + + + + org.ehcache + ehcache + ${ehcache.version} + + + + com.esotericsoftware + kryo + ${kryo.version} + + + + de.ruedigermoeller + fst + ${fst.version} + + + + com.xuxueli + xxl-job-core + ${xxl-job.version} + + + + com.alibaba.csp + sentinel-core + ${sentinel.version} + + + com.alibaba.csp + sentinel-annotation-aspectj + ${sentinel.version} + + + com.alibaba.csp + sentinel-datasource-nacos + ${sentinel.version} + + + com.alibaba.csp + sentinel-transport-simple-http + ${sentinel.version} + + + com.alibaba.csp + sentinel-datasource-extension + ${sentinel.version} + + + com.alibaba.csp + sentinel-parameter-flow-control + ${sentinel.version} + + + com.alibaba.csp + sentinel-apache-dubbo3-adapter + ${sentinel.version} + + + com.alibaba.csp + sentinel-spring-webmvc-adapter + ${sentinel.version} + + + com.alibaba.csp + sentinel-web-servlet + ${sentinel.version} + + + + + + com.google.zxing + javase + ${zxing-javase.version} + + + + com.squareup.okhttp + okhttp + ${okhttp2.version} + + + com.squareup.okhttp3 + okhttp + ${okhttp3.version} + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + org.freemarker + freemarker + ${freemarker.version} + + + com.ibeetl + beetl + ${beetl.version} + + + + com.rabbitmq + amqp-client + ${amqp-client.version} + + + org.springframework.amqp + spring-rabbit + ${spring-rabbit.version} + + + com.rabbitmq + stream-client + ${stream-client.version} + + + org.springframework.amqp + spring-rabbit-stream + ${spring-rabbit-stream.version} + + + + + org.springframework.data + spring-data-commons + ${spring-data.version} + + + org.springframework.amqp + spring-amqp + ${spring-amqp.version} + + + org.springframework.data + spring-data-redis + ${spring-data-redis.version} + + + + org.springframework.boot + spring-boot-starter-data-redis + ${spring-boot-starter-data-redis.version} + + + + org.springframework.data + spring-data-mongodb + ${spring-data-mongodb.version} + + + + org.springframework.boot + spring-boot-starter-data-mongodb + ${spring-boot-starter-data-mongodb.version} + + + + org.springframework.kafka + spring-kafka + ${spring-kafka.version} + + + org.apache.kafka + kafka-clients + ${kafka-clients.version} + + + org.apache.kafka + kafka-log4j-appender + ${kafka-clients.version} + + + + org.mybatis + mybatis + ${mybatis.version} + + + org.mybatis + mybatis-spring + ${mybatis-spring.version} + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot-starter.version} + + + + com.baomidou + mybatis-plus-core + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-annotation + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus-generator.version} + + + com.baomidou + mybatis-plus-extension + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + com.baomidou + dynamic-datasource-spring-boot3-starter + ${dynamic-datasource-spring-boot3-starter.version} + + + + org.apache.shardingsphere + shardingsphere-jdbc-core + ${shardingsphere-jdbc-core.version} + + + org.apache.shardingsphere + shardingsphere-jdbc-core-spring-boot-starter + ${shardingsphere-jdbc-core-spring-boot-starter.version} + + + + org.springframework.security.oauth + spring-security-oauth2 + ${spring-security-oauth.version} + + + org.springframework.security + spring-security-jwt + ${spring-security-jwt.version} + + + + com.mysql + mysql-connector-j + ${mysql-connector-j.version} + + + + org.mongodb + mongo-java-driver + ${mongo-java-driver.version} + + + + redis.clients + jedis + ${jedis.version} + + + io.lettuce + lettuce-core + ${lettuce.version} + + + org.redisson + redisson + ${redisson.version} + + + org.redisson + redisson-spring-boot-starter + ${redisson.version} + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + + org.apache.logging.log4j + log4j-layout-template-json + ${log4j.version} + + + + org.apache.logging.log4j + log4j-spring-boot + ${log4j.version} + + + + com.lmax + disruptor + ${disruptor.version} + + + + org.apache.skywalking + apm-toolkit-log4j-2.x + ${skywalking.apm-toolkit.version} + + + org.apache.skywalking + apm-toolkit-trace + ${skywalking.apm-toolkit.version} + + + org.apache.skywalking + apm-toolkit-micrometer-registry + ${skywalking.apm-toolkit.version} + + + org.apache.skywalking + apm-toolkit-micrometer-1.10 + ${skywalking.apm-toolkit.version} + + + + org.quartz-scheduler + quartz + ${quartz.version} + + + + com.thoughtworks.xstream + xstream + ${xstream.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + commons-net + commons-net + ${commons-net.version} + + + + commons-lang + commons-lang + ${commons-lang.version} + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + commons-beanutils + commons-beanutils + ${commons-beanutils.version} + + + + commons-collections + commons-collections + ${commons-collections.version} + + + + org.apache.commons + commons-collections4 + ${commons-collections4.version} + + + + commons-codec + commons-codec + ${commons-codec.version} + + + + org.apache.commons + commons-pool + ${commons-pool.version} + + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + + commons-httpclient + commons-httpclient + ${commons-httpclient.version} + + + + org.jsoup + jsoup + ${jsoup.version} + + + + com.maxmind.geoip2 + geoip2 + ${geoip2.version} + + + com.fasterxml.jackson.core + jackson-databind + + + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-dataformat-xaml + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-dataformat-yaml + ${jackson.version} + + + + org.codehaus.jackson + jackson-all + ${jackson-all.version} + + + + com.alibaba + fastjson + ${fastjson.version} + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + + + com.alibaba.fastjson2 + fastjson2-extension-spring6 + ${fastjson2.version} + + + + com.google.protobuf + protobuf-java + ${protobuf-java.version} + + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.apache.httpcomponents + httpmime + ${httpclient.version} + + + + org.apache.httpcomponents.client5 + httpclient5 + ${httpclient5.version} + + + + com.blueconic + browscap-java + ${browscap-java.version} + + + + com.alibaba + druid + ${druid.version} + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + com.alibaba + druid-spring-boot-3-starter + ${druid.version} + + + + com.caucho + hessian + ${hessian.version} + + + + org.springframework + spring-core + ${spring.version} + + + + org.springframework + spring-test + ${spring.version} + test + + + + org.springframework + spring-oxm + ${spring.version} + + + + org.springframework + spring-orm + ${spring.version} + + + + org.springframework + spring-aspects + ${spring.version} + + + + org.springframework + spring-messaging + ${spring.version} + + + + org.springframework + spring-instrument + ${spring.version} + + + + org.springframework + spring-jdbc + ${spring.version} + + + + org.springframework + spring-context-support + ${spring.version} + + + + org.springframework + spring-context + ${spring.version} + + + + org.springframework + spring-aop + ${spring.version} + + + + org.springframework + spring-beans + ${spring.version} + + + + org.springframework + spring-expression + ${spring.version} + + + + org.springframework + spring-web + ${spring.version} + + + + org.springframework + spring-webmvc + ${spring.version} + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + ${spring-cloud-alibaba.version} + pom + import + + + + junit + junit + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit5.version} + test + + + + + com.google.guava + guava + ${guava.version} + + + + + cn.hutool + hutool-all + ${hutool.version} + + + cn.hutool + hutool-core + ${hutool.version} + + + cn.hutool + hutool-http + ${hutool.version} + + + cn.hutool + hutool-crypto + ${hutool.version} + + + cn.hutool + hutool-json + ${hutool.version} + + + cn.hutool + hutool-log + ${hutool.version} + + + cn.hutool + hutool-extra + ${hutool.version} + + + cn.hutool + hutool-cache + ${hutool.version} + + + cn.hutool + hutool-setting + ${hutool.version} + + + cn.hutool + hutool-db + ${hutool.version} + + + cn.hutool + hutool-captcha + ${hutool.version} + + + cn.hutool + hutool-system + ${hutool.version} + + + cn.hutool + hutool-jwt + ${hutool.version} + + + cn.hutool + hutool-poi + ${hutool.version} + + + cn.hutool + hutool-cron + ${hutool.version} + + + cn.hutool + hutool-bom + ${hutool.version} + pom + + + cn.hutool + hutool-script + ${hutool.version} + + + cn.hutool + hutool-socket + ${hutool.version} + + + cn.hutool + hutool-dfa + ${hutool.version} + + + cn.hutool + hutool-bloomFilter + ${hutool.version} + + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + + org.projectlombok + lombok + ${lombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-jdk8 + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + stax-api + stax + + + commons-codec + commons-codec + + + + + + com.alibaba + easyexcel + ${easyexcel.version} + + + + xerces + xercesImpl + ${xerces.version} + + + + com.googlecode.aviator + aviator + ${aviator.version} + + + + org.seleniumhq.selenium + selenium-java + ${selenium.version} + + + org.seleniumhq.selenium + selenium-api + ${selenium.version} + + + org.seleniumhq.selenium + selenium-chrome-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-edge-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-firefox-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-ie-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-opera-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-remote-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-safari-driver + ${selenium.version} + + + org.seleniumhq.selenium + selenium-support + ${selenium.version} + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-openapi.version} + + + + + io.seata + seata-all + ${seata.version} + + + io.seata + seata-spring-boot-starter + ${seata.version} + + + + + io.github.mweirauch + micrometer-jvm-extras + ${micrometer-jvm-extras.version} + + + io.micrometer + micrometer-core + ${micrometer-core.version} + + + + + io.zipkin.brave + brave + ${zipkin-brave.version} + + + io.zipkin.brave + brave-context-slf4j + ${zipkin-brave.version} + + + io.zipkin.brave + brave-spring-beans + ${zipkin-brave.version} + + + io.zipkin.brave + brave-instrumentation-spring-rabbit + ${zipkin-brave.version} + + + io.zipkin.brave + brave-instrumentation-http + ${zipkin-brave.version} + + + io.zipkin.brave + brave-instrumentation-servlet + ${zipkin-brave.version} + + + io.zipkin.brave + brave-instrumentation-mysql + ${zipkin-brave.version} + + + io.zipkin.reporter2 + zipkin-sender-urlconnection + ${zipkin-reporter.version} + + + io.zipkin.reporter2 + zipkin-sender-okhttp3 + ${zipkin-reporter.version} + + + io.zipkin.reporter2 + zipkin-sender-amqp-client + ${zipkin-reporter.version} + + + io.zipkin.reporter2 + zipkin-reporter-spring-beans + ${zipkin-reporter.version} + + + + + diff --git a/hp-soa-framework/.gitignore b/hp-soa-framework/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-framework/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-framework/hp-soa-framework-core/.gitignore b/hp-soa-framework/hp-soa-framework-core/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-framework/hp-soa-framework-core/pom.xml b/hp-soa-framework/hp-soa-framework-core/pom.xml new file mode 100644 index 0000000..a0357fc --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-framework + ${revision} + + hp-soa-framework-core + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + org.slf4j + slf4j-api + + + + org.apache.logging.log4j + log4j-core + + + + org.apache.logging.log4j + log4j-slf4j2-impl + + + + org.apache.logging.log4j + log4j-api + + + + org.apache.logging.log4j + log4j-layout-template-json + + + + com.lmax + disruptor + + + + com.google.guava + guava + + + + org.apache.commons + commons-lang3 + + + + cn.hutool + hutool-core + + + + org.projectlombok + lombok + + + org.projectlombok + lombok-mapstruct-binding + + + + org.mapstruct + mapstruct + + + org.mapstruct + mapstruct-processor + + + diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/exception/ServiceException.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/exception/ServiceException.java new file mode 100644 index 0000000..7f6518d --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/exception/ServiceException.java @@ -0,0 +1,281 @@ +package io.github.hpsocket.soa.framework.core.exception; + +import org.slf4j.Logger; + +/** HP-SOA 统一异常 */ +@SuppressWarnings("serial") +public class ServiceException extends RuntimeException +{ + /** 成功 */ + public static final int OK = 0; + /** 已接受 */ + public static final int ACCEPTED = 202; + /** 空内容 */ + public static final int NO_CONTENT = 204; + /** 部分成功 */ + public static final int PARTIAL_OK = 206; + /** 非法请求 */ + public static final int BAD_REQUEST = 400; + /** 目标不存在 */ + public static final int NOT_EXIST = 404; + /** 参数校验失败 */ + public static final int PARAM_VERIFY_ERROR = 409; + /** 服务器内部错误 */ + public static final int GENERAL_ERROR = 500; + /** 参数验证错误 */ + public static final int PARAM_VALIDATION_ERROR = 501; + /** 接口未实现 */ + public static final int NOT_IMPLEMENTED = 502; + /** 频次超限 */ + public static final int FREQUENCY_LIMIT_ERROR = 503; + /** 拒绝访问 */ + public static final int FORBID_ERROR = 504; + /** 请求参数非法 */ + public static final int PARAMS_ERROR = 505; + /** 重复请求 */ + public static final int REPEATED_REQ_ERROR = 506; + /** 访问被限流 */ + public static final int TRAFFIC_LIMIT_ERROR = 507; + /** 接口不支持 */ + public static final int NOT_SUPPORTED = 508; + /** 禁止更新 */ + public static final int FORBID_UPDATE_ERROR = 509; + /** 调用超时 */ + public static final int TIMEOUT_ERROR = 511; + /** APP CODE 不存在 */ + public static final int APPCODE_NOT_EXIST_ERROR = 601; + /** APP CODE 已存在 */ + public static final int APPCODE_EXIST_ERROR = 602; + /** 用户验证错误 */ + public static final int AUTHEN_ERROR = 701; + /** 用户授权错误 */ + public static final int AUTHOR_ERROR = 702; + /** 网络错误 */ + public static final int NETWORK_ERROR = 801; + /** 外部服务调用失败 */ + public static final int OUTER_API_CALL_FAIL = 802; + /** 内部服务调用失败 */ + public static final int INNER_API_CALL_FAIL = 803; + /** 签名验证失败 */ + public static final int SIGN_VERIFY_ERROR = 901; + /** 登录已失效 */ + public static final int LOGIN_INVALID = 904; + /** 未登录 */ + public static final int NOT_LOGGED_IN = 907; + + public static final ServiceException BAD_REQUEST_EXCEPTION = new ServiceException("非法请求", BAD_REQUEST); + public static final ServiceException NOT_EXIST_EXCEPTION = new ServiceException("目标不存在", NOT_EXIST); + public static final ServiceException PARAM_VERIFY_EXCEPTION = new ServiceException("参数校验失败", PARAM_VERIFY_ERROR); + public static final ServiceException GENERAL_EXCEPTION = new ServiceException("服务器内部错误", GENERAL_ERROR); + public static final ServiceException PARAM_VALIDATION_EXCEPTION = new ServiceException("参数验证错误", PARAM_VALIDATION_ERROR); + public static final ServiceException NOT_IMPLEMENTED_EXCEPTION = new ServiceException("接口未实现", NOT_IMPLEMENTED); + public static final ServiceException NOT_SUPPORTED_EXCEPTION = new ServiceException("接口不支持", NOT_SUPPORTED); + public static final ServiceException FORBID_SERVICE_EXCEPTION = new ServiceException("禁止更新", FORBID_UPDATE_ERROR); + public static final ServiceException APPCODE_NOT_EXIST_EXCEPTION= new ServiceException("应用程序编号不存在", APPCODE_NOT_EXIST_ERROR); + public static final ServiceException APPCODE_EXIST_EXCEPTION = new ServiceException("应用程序编号已存在", APPCODE_EXIST_ERROR); + public static final ServiceException AUTHEN_EXCEPTION = new ServiceException("用户认证失败", AUTHEN_ERROR); + public static final ServiceException AUTHOR_EXCEPTION = new ServiceException("授权验证失败", AUTHOR_ERROR); + public static final ServiceException NETWORK_EXCEPTION = new ServiceException("网络错误", NETWORK_ERROR); + public static final ServiceException PARAMS_EXCEPTION = new ServiceException("请求参数非法", PARAMS_ERROR); + public static final ServiceException REPEATED_REQ_EXCEPTION = new ServiceException("请勿重复请求", REPEATED_REQ_ERROR); + public static final ServiceException TIMEOUT_EXCEPTION = new ServiceException("调用超时", TIMEOUT_ERROR); + public static final ServiceException OUTER_API_CALL_EXCEPTION = new ServiceException("外部服务调用失败", OUTER_API_CALL_FAIL); + public static final ServiceException INNER_API_CALL_EXCEPTION = new ServiceException("内部服务调用失败", INNER_API_CALL_FAIL); + public static final ServiceException SIGN_VERIFY_EXCEPTION = new ServiceException("签名验证失败", SIGN_VERIFY_ERROR); + + public static final ServiceException FREQUENCY_LIMIT_EXCEPTION = new UnimportantException("系统繁忙", FREQUENCY_LIMIT_ERROR); + public static final ServiceException FORBID_EXCEPTION = new UnimportantException("拒绝访问", FORBID_ERROR); + public static final ServiceException TRAFFIC_LIMIT_EXCEPTION = new UnimportantException("系统繁忙", TRAFFIC_LIMIT_ERROR); + public static final ServiceException LOGIN_INVALID_EXCEPTION = new UnimportantException("登录已失效", LOGIN_INVALID); + public static final ServiceException NOT_LOGGED_IN_EXCEPTION = new UnimportantException("未登录", NOT_LOGGED_IN); + + /** 状态码:{@linkplain ServiceException#OK OK} - 成功,其它 - 失败 */ + private Integer statusCode; + /** 结果码:用于服务内部监控、统计,不暴露到调用方,{@linkplain ServiceException#OK OK} - 成功,其它 - 失败 */ + private transient Integer resultCode; + + public ServiceException() + { + } + + public ServiceException(String message) + { + this(message, GENERAL_ERROR); + } + + public ServiceException(String message, Integer statusCode) + { + this(message, statusCode, statusCode); + } + + public ServiceException(String message, Integer statusCode, Integer resultCode) + { + super(message); + setStatusCode(statusCode); + setResultCode(resultCode); + } + + public ServiceException(String message, Throwable cause) + { + super(message, cause); + if(cause instanceof ServiceException) + { + setStatusCode(((ServiceException)cause).getStatusCode()); + setResultCode(((ServiceException)cause).getResultCode()); + } + } + + public ServiceException(String message, Integer statusCode, Throwable cause) + { + super(message, cause); + setStatusCode(statusCode); + setResultCode(statusCode); + } + + public Integer getResultCode() + { + return resultCode; + } + + public void setResultCode(Integer resultCode) + { + this.resultCode = resultCode; + } + + public Integer getStatusCode() + { + return statusCode; + } + + public void setStatusCode(Integer statusCode) + { + this.statusCode = statusCode; + } + + public static final ServiceException wrapServiceException(Throwable e) + { + return wrapServiceException(GENERAL_EXCEPTION.getMessage(), GENERAL_EXCEPTION.statusCode, e); + } + + public static final ServiceException wrapServiceException(ServiceException se, Throwable e) + { + return wrapServiceException(se.getMessage(), se.getStatusCode(), e); + } + + public static final ServiceException wrapServiceException(String message, Integer statusCode, Throwable e) + { + if(!(e instanceof ServiceException)) + e = new ServiceException(message, statusCode, e); + + return (ServiceException)e; + } + + public static final void throwServiceException(Throwable e) + { + throw wrapServiceException(e); + } + + public static final void throwServiceException(Throwable e, String message) + { + throw wrapServiceException(message, GENERAL_ERROR, e); + } + + public static final void throwServiceException(Throwable e, String format, Object... args) + { + String message = String.format(format, args); + throw wrapServiceException(message, GENERAL_ERROR, e); + } + + public static final void throwServiceException(String message, Integer statusCode) + { + throw new ServiceException(message, statusCode); + } + + public static final void throwServiceException(String message, Integer statusCode, Throwable e) + { + throw new ServiceException(message, statusCode, e); + } + + public static final void throwServiceException(ServiceException e, Object... args) + { + throwFormattedServiceException(e, e.getMessage(), args); + } + + public static final void throwFormattedServiceException(Integer statusCode, String format, Object... args) + { + throw wrapFormattedServiceException(statusCode, format, args); + } + + public static final ServiceException wrapFormattedServiceException(Integer statusCode, String format, Object... args) + { + String message = String.format(format, args); + return new ServiceException(message, statusCode); + } + + public static final void throwFormattedServiceException(ServiceException e, String format, Object... args) + { + throw wrapFormattedServiceException(e, format, args); + } + + public static final ServiceException wrapFormattedServiceException(ServiceException e, String format, Object... args) + { + String message = String.format(format, args); + return new ServiceException(message, e.getStatusCode(), e.getCause()); + } + + public static final void throwValidateException(String message) + { + throw wrapValidateException(message); + } + + public static final ServiceException wrapValidateException(String message) + { + return new ServiceException(message, PARAM_VALIDATION_ERROR); + } + + public static final void throwValidateException(String format, Object... args) + { + throwFormattedServiceException(PARAM_VALIDATION_EXCEPTION, format, args); + } + + public static final ServiceException wrapValidateException(String format, Object... args) + { + return wrapFormattedServiceException(PARAM_VALIDATION_EXCEPTION, format, args); + } + + public static void logException(Logger logger, Throwable e) + { + if(e instanceof ServiceException) + logServiceException(logger, (ServiceException)e); + else + logException(logger, GENERAL_ERROR, e.getMessage(), e); + } + + public static void logException(Logger logger, Integer code, String msg, Throwable e) + { + logServiceException(logger, new ServiceException(msg, code, e)); + } + + public static void logServiceException(Logger logger, ServiceException e) + { + logServiceException(logger, e.getMessage(), e); + } + + public static void logServiceException(Logger logger, String msg, ServiceException e) + { + final String FORMAT = "(SERVICE EXCEPTION - SC: {}, RC: {}) -> {}"; + final Integer statusCode = e.getStatusCode(); + final Integer resultCode = e.getResultCode(); + + if(GENERAL_ERROR == statusCode) + logger.error(FORMAT, statusCode, resultCode, msg, e); + else + { + if(e instanceof UnimportantException) + logger.info(FORMAT, statusCode, resultCode, msg); + else + logger.warn(FORMAT, statusCode, resultCode, msg, e); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/exception/UnimportantException.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/exception/UnimportantException.java new file mode 100644 index 0000000..ef361a4 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/exception/UnimportantException.java @@ -0,0 +1,39 @@ + +package io.github.hpsocket.soa.framework.core.exception; + +/** HP-SOA 统一非严重异常,即使引发异常也认为调用成功
+ * {@linkplain UnimportantException#getResultCode() getResultCode()} 总是返回 {@linkplain ServiceException#OK OK}
+ * 如:用户输错密码导致登录失败,触发 {@linkplain ServiceException#LOGIN_INVALID_EXCEPTION LOGIN_INVALID_EXCEPTION},用户调用是失败的,但服务内部处理是成功的。因此监控系统也会把该请求看作成功请求 + */ +@SuppressWarnings("serial") +public class UnimportantException extends ServiceException +{ + public UnimportantException() + { + setResultCode(OK); + } + + public UnimportantException(String message) + { + super(message); + setResultCode(OK); + } + + public UnimportantException(String message, Integer statusCode) + { + super(message, statusCode); + setResultCode(OK); + } + + public UnimportantException(String message, Throwable cause) + { + super(message, cause); + setResultCode(OK); + } + + public UnimportantException(String message, Integer statusCode, Throwable cause) + { + super(message, statusCode, cause); + setResultCode(OK); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/id/IdGenerator.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/id/IdGenerator.java new file mode 100644 index 0000000..b9d93f6 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/id/IdGenerator.java @@ -0,0 +1,55 @@ +package io.github.hpsocket.soa.framework.core.id; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +/** ID 生成器 */ +public class IdGenerator +{ + private static final DateTimeFormatter DATE_TIME_PATTERN = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); + + private static Sequence SEQUENCE = new Sequence(); + + /** + * 重置雪花 ID 生成器的 workerId 和 datacenterId
+ * @param workerId :参考 {@linkplain Sequence#workerId} + * @param datacenterId :参考 {@linkplain Sequence#datacenterId} + */ + public static final void resetSequence(long workerId, long datacenterId) + { + SEQUENCE = new Sequence(workerId, datacenterId); + } + + /** 获取新雪花 ID */ + public static final long nextId() + { + return SEQUENCE.nextId(); + } + + /** 获取新雪花 ID */ + public static final String nextIdStr() + { + return String.valueOf(nextId()); + } + + /** 获取新雪花 UUID */ + public static final String nextUUID() + { + ThreadLocalRandom random = ThreadLocalRandom.current(); + return new UUID(random.nextLong(), random.nextLong()).toString(); + } + + /** 获取新雪花 UUID,并移除 UUID 中的 '-' */ + public static final String nextCompactUUID() + { + return nextUUID().replaceAll("-", ""); + } + + /** 获取当前时间并格式化为字符串(精确到毫秒) */ + public static final String curMillisecondStr() + { + return LocalDateTime.now().format(DATE_TIME_PATTERN); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/id/Sequence.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/id/Sequence.java new file mode 100644 index 0000000..453a5d4 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/id/Sequence.java @@ -0,0 +1,325 @@ + +package io.github.hpsocket.soa.framework.core.id; + +import lombok.extern.slf4j.Slf4j; + +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; +import java.util.concurrent.ThreadLocalRandom; + +/** 雪花 ID 生成器 */ +@Slf4j +public class Sequence +{ + /** + * 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动) + */ + private final long twepoch = 1688140800000L; + + /** + * 5位的数据中心id + */ + private final long datacenterIdBits = 5L; + /** + * 5位的机器id + */ + private final long workerIdBits = 5L; + /** + * 每毫秒内产生的id数: 2的12次方个 + */ + private final long sequenceBits = 12L; + + protected final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + protected final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + private final long workerIdShift = sequenceBits; + private final long datacenterIdShift = sequenceBits + workerIdBits; + + /** + * 时间戳左移动位 + */ + private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + private final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** + * 所属数据中心id + */ + private final long datacenterId; + /** + * 所属机器id + */ + private final long workerId; + /** + * 并发控制序列 + */ + private long sequence = 0L; + + /** + * 上次生产 ID 时间戳 + */ + private long lastTimestamp = -1L; + + private static volatile InetAddress LOCAL_ADDRESS = null; + + public Sequence() + { + this.datacenterId = calcDatacenterId(); + this.workerId = getMaxWorkerId(datacenterId); + } + + /** + * 有参构造器 + * + * @param workerId + * 工作机器 ID + * @param datacenterId + * 序列号 + */ + public Sequence(long workerId, long datacenterId) + { + if(workerId > maxWorkerId || workerId < 0) + { + throw new IllegalArgumentException(String.format("Worker Id can't be greater than %d or less than 0", maxWorkerId)); + } + if(datacenterId > maxDatacenterId || datacenterId < 0) + { + throw new IllegalArgumentException(String.format("Datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + } + + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + /** + * 基于网卡MAC地址计算余数作为数据中心 + *

+ * 可自定扩展 + */ + protected long calcDatacenterId() + { + long id = 0L; + + try + { + NetworkInterface nic = NetworkInterface.getByInetAddress(getLocalAddress()); + + if(null == nic) + { + id = 1L; + } + else + { + byte[] mac = nic.getHardwareAddress(); + + if(null != mac) + { + id = ((0x000000FF & (long)mac[mac.length - 2]) | (0x0000FF00 & (((long)mac[mac.length - 1]) << 8))) >> 6; + id = id % (maxDatacenterId + 1); + } + } + } + catch(Exception e) + { + log.warn(" calcDatacenterId: " + e.getMessage()); + } + + return id; + } + + /** + * 基于 MAC + PID 的 hashcode 获取16个低位 + *

+ * 可自定扩展 + */ + protected long getMaxWorkerId(long datacenterId) + { + StringBuilder mpId = new StringBuilder(); + mpId.append(datacenterId); + + String name = ManagementFactory.getRuntimeMXBean().getName(); + + if(name != null && name.length() > 0) + { + // GET jvmPid + mpId.append(name.split("@")[0]); + } + + // MAC + PID 的 hashcode 获取16个低位 + return (mpId.toString().hashCode() & 0xffff) % (maxWorkerId + 1); + } + + /** + * 获取下一个 ID + */ + public synchronized long nextId() + { + long timestamp = timeGen(); + + // 闰秒 + if(timestamp < lastTimestamp) + { + long offset = lastTimestamp - timestamp; + + if(offset <= 5) + { + try + { + // 休眠双倍差值后重新获取,再次校验 + wait(offset << 1); + timestamp = timeGen(); + + if(timestamp < lastTimestamp) + { + throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset)); + } + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + else + { + throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset)); + } + } + + if(lastTimestamp == timestamp) + { + // 相同毫秒内,序列号自增 + sequence = (sequence + 1) & sequenceMask; + + if(sequence == 0) + { + // 同一毫秒的序列数已经达到最大 + timestamp = tilNextMillis(lastTimestamp); + } + } + else + { + // 不同毫秒内,序列号置为 1 - 3 随机数 + sequence = ThreadLocalRandom.current().nextLong(1, 3); + } + + lastTimestamp = timestamp; + + // 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分 + return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; + } + + protected long tilNextMillis(long lastTimestamp) + { + long timestamp = timeGen(); + + while(timestamp <= lastTimestamp) + { + timestamp = timeGen(); + } + + return timestamp; + } + + protected long timeGen() + { + return System.currentTimeMillis(); + } + + /** + * 从网卡获取第一个 + * + * @return first valid local IP + */ + protected static InetAddress getLocalAddress() + { + if(LOCAL_ADDRESS == null) + { + synchronized(Sequence.class) + { + if(LOCAL_ADDRESS == null) + { + LOCAL_ADDRESS = getLocalAddress0(); + } + } + } + + return LOCAL_ADDRESS; + } + + private static InetAddress getLocalAddress0() + { + InetAddress localAddress = null; + + try + { + localAddress = InetAddress.getLocalHost(); + + if(isValidAddress(localAddress)) + { + return localAddress; + } + } + catch(Throwable e) + { + log.warn("Failed to retrieving ip address, " + e.getMessage(), e); + } + + try + { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + + if(interfaces != null) + { + while(interfaces.hasMoreElements()) + { + try + { + NetworkInterface network = interfaces.nextElement(); + Enumeration addresses = network.getInetAddresses(); + + while(addresses.hasMoreElements()) + { + try + { + InetAddress address = addresses.nextElement(); + + if(isValidAddress(address)) + { + return address; + } + } + catch(Throwable e) + { + log.warn("Failed to retrieving ip address, " + e.getMessage(), e); + } + } + } + catch(Throwable e) + { + log.warn("Failed to retrieving ip address, " + e.getMessage(), e); + } + } + } + } + catch(Throwable e) + { + log.warn("Failed to retrieving ip address, " + e.getMessage(), e); + } + + log.error("Could not get local host ip address, will use 127.0.0.1 instead."); + + return localAddress; + } + + private static boolean isValidAddress(InetAddress address) + { + if(address == null || address.isLoopbackAddress() || address.isAnyLocalAddress() || address.isMulticastAddress()) + { + return false; + } + + return true; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/log/LoggerNameFilter.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/log/LoggerNameFilter.java new file mode 100644 index 0000000..499a994 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/log/LoggerNameFilter.java @@ -0,0 +1,108 @@ + +package io.github.hpsocket.soa.framework.core.log; + +import java.util.regex.Pattern; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.filter.AbstractFilter; +import org.apache.logging.log4j.message.Message; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; + +/** log4j2 logger 日志过滤器
+ * 用于调整 logger 名称匹配特定正则表达式的 logger 的日志级别 + */ +@Plugin(name = "LoggerNameFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +public class LoggerNameFilter extends AbstractFilter +{ + private final Level level; + private final Pattern name; + + private LoggerNameFilter(final Pattern name, final Level level, final Result onMatch, final Result onMismatch) + { + super(onMatch, onMismatch); + this.level = level; + this.name = name; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) + { + return filter(logger.getName(), level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) + { + return filter(logger.getName(), level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) + { + return filter(logger.getName(), level); + } + + @Override + public Result filter(final LogEvent event) + { + return filter(event.getLoggerName(), event.getLevel()); + } + + private Result filter(String name, final Level level) + { + if(level.isLessSpecificThan(this.level) && this.name.matcher(name).matches()) + return onMismatch; + + return onMatch; + } + + public Level getLevel() + { + return level; + } + + public String getName() + { + return name.toString(); + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(); + + sb.append("level=").append(level); + sb.append(", name=").append(name); + + return sb.toString(); + } + + @PluginFactory + public static LoggerNameFilter createFilter( + @PluginAttribute("name") final String name, + @PluginAttribute("level") final Level level, + @PluginAttribute("onMatch") final Result match, + @PluginAttribute("onMismatch") final Result mismatch) + { + if(GeneralHelper.isStrEmpty(name)) + { + LOGGER.error("'name' attribute must be provided for LoggerNameFilter"); + return null; + } + + final Level actualLevel = level == null ? Level.ERROR : level; + final Result onMatch = match == null ? Result.NEUTRAL : match; + final Result onMismatch = mismatch == null ? Result.DENY : mismatch; + + return new LoggerNameFilter(Pattern.compile(name), actualLevel, onMatch, onMismatch); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcAttr.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcAttr.java new file mode 100644 index 0000000..493291b --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcAttr.java @@ -0,0 +1,354 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.MDC; + +/** 调用链跟踪 {@linkplain MDC} 属性 */ +public class MdcAttr +{ + public static final String MDC_TRACE_ID_KEY = "__traceId"; + public static final String MDC_REQUEST_ID_KEY = "__requestId"; + public static final String MDC_CLIENT_ID_KEY = "__clientId"; + public static final String MDC_SESSION_ID_KEY = "__sessionId"; + public static final String MDC_APP_CODE_KEY = "__appCode"; + public static final String MDC_SRC_APP_CODE_KEY = "__srcAppCode"; + public static final String MDC_TOKEN_KEY = "__token"; + public static final String MDC_USER_ID_KEY = "__userId"; + public static final String MDC_GROUP_ID_KEY = "__groupId"; + public static final String MDC_EXTRA_KEY = "__extra"; + public static final String MDC_APP_ID_KEY = "__appId"; + public static final String MDC_APP_NAME_KEY = "__appName"; + public static final String MDC_SERVICE_ID_KEY = "__serviceId"; + public static final String MDC_SERVICE_NAME_KEY = "__serviceName"; + public static final String MDC_SERVICE_ADDR_KEY = "__serviceAddr"; + public static final String MDC_ORG_KEY = "__organization"; + public static final String MDC_OWNER_KEY = "__owner"; + + public static final String MDC_MESSAGE_ID_KEY = "__messageId"; + public static final String MDC_SOURCE_REQUEST_ID_KEY = "__sourceRequestId"; + + public static final String MDC_FROM_SERVICE_ID_KEY = "__fromServiceId"; + public static final String MDC_FROM_SERVICE_NAME_KEY = "__fromServiceName"; + public static final String MDC_FROM_SERVICE_ADDR_KEY = "__fromServiceAddr"; + + public static final String TRANSFER_MDC_KEYS[] = { + MDC_TRACE_ID_KEY, + MDC_REQUEST_ID_KEY, + MDC_CLIENT_ID_KEY, + MDC_SESSION_ID_KEY, + MDC_MESSAGE_ID_KEY, + MDC_SOURCE_REQUEST_ID_KEY, + MDC_APP_CODE_KEY, + MDC_SRC_APP_CODE_KEY, + MDC_TOKEN_KEY, + MDC_USER_ID_KEY, + MDC_GROUP_ID_KEY, + MDC_EXTRA_KEY, + MDC_APP_ID_KEY, + MDC_APP_NAME_KEY + }; + + public static final String TRANSFER_MDC_ALL_KEYS[] = { + MDC_TRACE_ID_KEY, + MDC_REQUEST_ID_KEY, + MDC_CLIENT_ID_KEY, + MDC_SESSION_ID_KEY, + MDC_MESSAGE_ID_KEY, + MDC_SOURCE_REQUEST_ID_KEY, + MDC_APP_CODE_KEY, + MDC_SRC_APP_CODE_KEY, + MDC_TOKEN_KEY, + MDC_USER_ID_KEY, + MDC_GROUP_ID_KEY, + MDC_EXTRA_KEY, + MDC_APP_ID_KEY, + MDC_APP_NAME_KEY, + MDC_FROM_SERVICE_ID_KEY, + MDC_FROM_SERVICE_NAME_KEY, + MDC_FROM_SERVICE_ADDR_KEY + }; + + Map ctxMap; + + public MdcAttr() + { + ctxMap = new HashMap<>(); + } + + public MdcAttr(Map mdcMap) + { + this.ctxMap = mdcMap; + } + + public static MdcAttr from(Map mdcMap) + { + return new MdcAttr(mdcMap); + } + + public static MdcAttr fromMdc() + { + return new MdcAttr(MDC.getCopyOfContextMap()); + } + + public void putMdc() + { + ctxMap.forEach((k, v) -> MDC.put(k, v)); + } + + public void removeMdc() + { + removeMdc(true); + } + + public void removeMdc(boolean clearMdcMap) + { + ctxMap.forEach((k, v) -> MDC.remove(k)); + + if(clearMdcMap) + ctxMap.clear(); + } + + public void set(String key, String val) + { + ctxMap.put(key, val); + } + + public String remove(String key) + { + return ctxMap.remove(key); + } + + public String get(String key) + { + return ctxMap.get(key); + } + + public Map getCtxMap() + { + return ctxMap; + } + + public void setCtxMap(Map ctxMap) + { + this.ctxMap = ctxMap; + } + + public String getTraceId() + { + return get(MDC_TRACE_ID_KEY); + } + + public void setTraceId(String traceId) + { + set(MDC_TRACE_ID_KEY, traceId); + } + + public String getClientId() + { + return get(MDC_CLIENT_ID_KEY); + } + + public void setClientId(String clientId) + { + set(MDC_CLIENT_ID_KEY, clientId); + } + + public String getRequestId() + { + return get(MDC_REQUEST_ID_KEY); + } + + public void setRequestId(String requestId) + { + set(MDC_REQUEST_ID_KEY, requestId); + } + + public String getSessionId() + { + return get(MDC_SESSION_ID_KEY); + } + + public void setSessionId(String sessionId) + { + set(MDC_SESSION_ID_KEY, sessionId); + } + + public String getMessageId() + { + return get(MDC_MESSAGE_ID_KEY); + } + + public void setMessageId(String messageId) + { + set(MDC_MESSAGE_ID_KEY, messageId); + } + + public String getSourceRequestId() + { + return get(MDC_SOURCE_REQUEST_ID_KEY); + } + + public void setSourceRequestId(String sourceRequestId) + { + set(MDC_SOURCE_REQUEST_ID_KEY, sourceRequestId); + } + + public String getToken() + { + return get(MDC_TOKEN_KEY); + } + + public void setToken(String token) + { + set(MDC_TOKEN_KEY, token); + } + + public String getUserId() + { + return get(MDC_USER_ID_KEY); + } + + public void setUserId(String userId) + { + set(MDC_USER_ID_KEY, userId); + } + + public String getGroupId() + { + return get(MDC_GROUP_ID_KEY); + } + + public void setGroupId(String groupId) + { + set(MDC_GROUP_ID_KEY, groupId); + } + + public String getExtra() + { + return get(MDC_EXTRA_KEY); + } + + public void setExtra(String extra) + { + set(MDC_EXTRA_KEY, extra); + } + + public String getAppCode() + { + return get(MDC_APP_CODE_KEY); + } + + public void setAppCode(String appCode) + { + set(MDC_APP_CODE_KEY, appCode); + } + + public void setSrcAppCode(String srcAppCode) + { + set(MDC_SRC_APP_CODE_KEY, srcAppCode); + } + + public String getAppName() + { + return get(MDC_APP_NAME_KEY); + } + + public void setAppName(String appName) + { + set(MDC_APP_NAME_KEY, appName); + } + + public String getAppId() + { + return get(MDC_APP_ID_KEY); + } + + public void setAppId(String appId) + { + set(MDC_APP_ID_KEY, appId); + } + + public String getServiceId() + { + return get(MDC_SERVICE_ID_KEY); + } + + public void setServiceId(String serviceId) + { + set(MDC_SERVICE_ID_KEY, serviceId); + } + + public String getServiceName() + { + return get(MDC_SERVICE_NAME_KEY); + } + + public void setServiceName(String serviceName) + { + set(MDC_SERVICE_NAME_KEY, serviceName); + } + + public String getServiceAddr() + { + return get(MDC_SERVICE_ADDR_KEY); + } + + public void setServiceAddr(String serviceAddr) + { + set(MDC_SERVICE_ADDR_KEY, serviceAddr); + } + + public String getFromServiceId() + { + return get(MDC_FROM_SERVICE_ID_KEY); + } + + public void setFromServiceId(String fromServiceId) + { + set(MDC_FROM_SERVICE_ID_KEY, fromServiceId); + } + + public String getFromServiceName() + { + return get(MDC_FROM_SERVICE_NAME_KEY); + } + + public void setFromServiceName(String fromServiceName) + { + set(MDC_FROM_SERVICE_NAME_KEY, fromServiceName); + } + + public String getFromServiceAddr() + { + return get(MDC_FROM_SERVICE_ADDR_KEY); + } + + public void setFromServiceAddr(String fromServiceAddr) + { + set(MDC_FROM_SERVICE_ADDR_KEY, fromServiceAddr); + } + + public String getOrganization() + { + return get(MDC_ORG_KEY); + } + + public void setOrganization(String orgName) + { + set(MDC_ORG_KEY, orgName); + } + + public String getOwner() + { + return get(MDC_OWNER_KEY); + } + + public void setOwner(String ownerName) + { + set(MDC_OWNER_KEY, ownerName); + } + +} + diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcCallable.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcCallable.java new file mode 100644 index 0000000..fbf08c3 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcCallable.java @@ -0,0 +1,40 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.concurrent.Callable; + +import lombok.Getter; + +/** {@linkplain Callable} 基类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +abstract public class MdcCallable implements Callable +{ + private MdcAttr mdcAttr; + + abstract protected T doCall() throws Exception; + + public MdcCallable() + { + this(MdcAttr.fromMdc()); + } + + public MdcCallable(MdcAttr mdcAttr) + { + this.mdcAttr = mdcAttr; + } + + @Override + public T call() throws Exception + { + try + { + mdcAttr.putMdc(); + + return doCall(); + } + finally + { + mdcAttr.removeMdc(); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcCallableWrapper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcCallableWrapper.java new file mode 100644 index 0000000..804a38a --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcCallableWrapper.java @@ -0,0 +1,34 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.concurrent.Callable; + +import lombok.Getter; + +/** {@linkplain Callable} 包装类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +public class MdcCallableWrapper extends MdcCallable +{ + private Callable c; + + public MdcCallableWrapper(Callable c) + { + this.c = c; + } + + public MdcCallableWrapper(Callable c, MdcAttr mdcAttr) + { + super(mdcAttr); + this.c = c; + } + + @Override + public T doCall() throws Exception + { + return c.call(); + } + + public static MdcCallableWrapper of(Callable c) + { + return new MdcCallableWrapper(c); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcConsumer.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcConsumer.java new file mode 100644 index 0000000..6bac1ee --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcConsumer.java @@ -0,0 +1,39 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.function.Consumer; + +import lombok.Getter; +/** {@linkplain Consumer} 基类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +abstract public class MdcConsumer implements Consumer +{ + private final MdcAttr mdcAttr; + + abstract protected void doAccept(T t); + + public MdcConsumer() + { + this(MdcAttr.fromMdc()); + } + + public MdcConsumer(MdcAttr mdcAttr) + { + this.mdcAttr = mdcAttr; + } + + @Override + public void accept(T t) + { + try + { + mdcAttr.putMdc(); + + doAccept(t); + } + finally + { + mdcAttr.removeMdc(); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcConsumerWrapper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcConsumerWrapper.java new file mode 100644 index 0000000..6aafc0e --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcConsumerWrapper.java @@ -0,0 +1,34 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.function.Consumer; + +import lombok.Getter; + +/** {@linkplain Consumer} 包装类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +public class MdcConsumerWrapper extends MdcConsumer +{ + private Consumer c; + + public MdcConsumerWrapper(Consumer c) + { + this.c = c; + } + + public MdcConsumerWrapper(Consumer c, MdcAttr mdcAttr) + { + super(mdcAttr); + this.c = c; + } + + @Override + public void doAccept(T t) + { + c.accept(t); + } + + public static MdcConsumerWrapper of(Consumer c) + { + return new MdcConsumerWrapper(c); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcFunction.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcFunction.java new file mode 100644 index 0000000..20b44cb --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcFunction.java @@ -0,0 +1,39 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.function.Function; + +import lombok.Getter; + +/** {@linkplain Function} 基类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +abstract public class MdcFunction implements Function +{ + private final MdcAttr mdcAttr; + + abstract protected R doApply(T t); + + public MdcFunction() + { + this(MdcAttr.fromMdc()); + } + + public MdcFunction(MdcAttr mdcAttr) + { + this.mdcAttr = mdcAttr; + } + + @Override + public R apply(T t) + { + try + { + mdcAttr.putMdc(); + + return doApply(t); + } + finally + { + mdcAttr.removeMdc(); + } + } +} \ No newline at end of file diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcFunctionWrapper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcFunctionWrapper.java new file mode 100644 index 0000000..5c53f28 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcFunctionWrapper.java @@ -0,0 +1,34 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.function.Function; + +import lombok.Getter; + +/** {@linkplain Function} 包装类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +public class MdcFunctionWrapper extends MdcFunction +{ + private Function f; + + public MdcFunctionWrapper(Function f) + { + this.f = f; + } + + public MdcFunctionWrapper(Function f, MdcAttr mdcAttr) + { + super(mdcAttr); + this.f = f; + } + + @Override + public R doApply(T t) + { + return f.apply(t); + } + + public static MdcFunctionWrapper of(Function f) + { + return new MdcFunctionWrapper(f); + } +} \ No newline at end of file diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRecursiveAction.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRecursiveAction.java new file mode 100644 index 0000000..71e3ec0 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRecursiveAction.java @@ -0,0 +1,41 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.concurrent.RecursiveAction; + +import lombok.Getter; + +/** {@linkplain RecursiveAction} 基类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +@SuppressWarnings("serial") +abstract public class MdcRecursiveAction extends RecursiveAction +{ + private final MdcAttr mdcAttr; + + abstract protected void doCompute(); + + public MdcRecursiveAction() + { + this(MdcAttr.fromMdc()); + } + + public MdcRecursiveAction(MdcAttr mdcAttr) + { + this.mdcAttr = mdcAttr; + } + + @Override + protected void compute() + { + try + { + mdcAttr.putMdc(); + + doCompute(); + } + finally + { + mdcAttr.removeMdc(); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRecursiveTask.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRecursiveTask.java new file mode 100644 index 0000000..0113c51 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRecursiveTask.java @@ -0,0 +1,41 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.concurrent.RecursiveTask; + +import lombok.Getter; + +/** {@linkplain RecursiveTask} 基类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +@SuppressWarnings("serial") +abstract public class MdcRecursiveTask extends RecursiveTask +{ + private final MdcAttr mdcAttr; + + abstract protected T doCompute(); + + public MdcRecursiveTask() + { + this(MdcAttr.fromMdc()); + } + + public MdcRecursiveTask(MdcAttr mdcAttr) + { + this.mdcAttr = mdcAttr; + } + + @Override + protected T compute() + { + try + { + mdcAttr.putMdc(); + + return doCompute(); + } + finally + { + mdcAttr.removeMdc(); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRunnable.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRunnable.java new file mode 100644 index 0000000..6291bdf --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRunnable.java @@ -0,0 +1,38 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import lombok.Getter; + +/** {@linkplain Runnable} 基类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +abstract public class MdcRunnable implements Runnable +{ + private final MdcAttr mdcAttr; + + abstract protected void doRun(); + + public MdcRunnable() + { + this(MdcAttr.fromMdc()); + } + + public MdcRunnable(MdcAttr mdcAttr) + { + this.mdcAttr = mdcAttr; + } + + @Override + public void run() + { + try + { + mdcAttr.putMdc(); + + doRun(); + } + finally + { + mdcAttr.removeMdc(); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRunnableWrapper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRunnableWrapper.java new file mode 100644 index 0000000..194912c --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcRunnableWrapper.java @@ -0,0 +1,32 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import lombok.Getter; + +/** {@linkplain Runnable} 包装类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +public class MdcRunnableWrapper extends MdcRunnable +{ + private Runnable r; + + public MdcRunnableWrapper(Runnable r) + { + this.r = r; + } + + public MdcRunnableWrapper(Runnable r, MdcAttr mdcAttr) + { + super(mdcAttr); + this.r = r; + } + + @Override + public void doRun() + { + r.run(); + } + + public static MdcRunnableWrapper of(Runnable r) + { + return new MdcRunnableWrapper(r); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcSupplier.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcSupplier.java new file mode 100644 index 0000000..83b209a --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcSupplier.java @@ -0,0 +1,40 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.function.Supplier; + +import lombok.Getter; + +/** {@linkplain Supplier} 基类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +abstract public class MdcSupplier implements Supplier +{ + private final MdcAttr mdcAttr; + + abstract protected T doGet(); + + public MdcSupplier() + { + this(MdcAttr.fromMdc()); + } + + public MdcSupplier(MdcAttr mdcAttr) + { + this.mdcAttr = mdcAttr; + } + + @Override + public T get() + { + try + { + mdcAttr.putMdc(); + + return doGet(); + } + finally + { + mdcAttr.removeMdc(); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcSupplierWrapper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcSupplierWrapper.java new file mode 100644 index 0000000..2921953 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/mdc/MdcSupplierWrapper.java @@ -0,0 +1,35 @@ +package io.github.hpsocket.soa.framework.core.mdc; + +import java.util.function.Supplier; + +import lombok.Getter; + +/** {@linkplain Supplier} 包装类(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +public class MdcSupplierWrapper extends MdcSupplier +{ + private Supplier s; + + public MdcSupplierWrapper(Supplier s) + { + this.s = s; + } + + public MdcSupplierWrapper(Supplier s, MdcAttr mdcAttr) + { + super(mdcAttr); + this.s = s; + } + + @Override + public T doGet() + { + return s.get(); + } + + public static MdcSupplierWrapper of(Supplier s) + { + return new MdcSupplierWrapper(s); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageInfo.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageInfo.java new file mode 100644 index 0000000..d791a45 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageInfo.java @@ -0,0 +1,47 @@ + +package io.github.hpsocket.soa.framework.core.paging; + +import java.io.Serializable; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** 通用分页信息 */ +@Getter +@Setter +@NoArgsConstructor +@SuppressWarnings("serial") +public class PageInfo implements Serializable +{ + public static final int DEFAULT_PAGE_SIZE = 20; + + private int pageRows = 0; + private int pageNumber = 1; + private int pageSize = DEFAULT_PAGE_SIZE; + private int pageCount = 0; + private int totalRows = 0; + + public int calculatePageCount() + { + pageCount = totalRows / pageSize + ((totalRows % pageSize == 0) ? 0 : 1); + + if(pageNumber < pageCount) + pageRows = pageSize; + else if(pageNumber == pageCount) + pageRows = totalRows - (pageCount - 1) * pageSize; + + return pageCount; + } + + public boolean outofBounds() + { + return pageNumber > pageCount || pageNumber < 1; + } + + public int limitStart() + { + return (pageNumber - 1) * pageSize; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageRequest.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageRequest.java new file mode 100644 index 0000000..912d34a --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageRequest.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.soa.framework.core.paging; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** 通用分页请求 */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PageRequest +{ + private int pageNumber = 1; + private int pageSize = PageInfo.DEFAULT_PAGE_SIZE; + private String orderBy; + + private T param; +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageResult.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageResult.java new file mode 100644 index 0000000..b999fec --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/paging/PageResult.java @@ -0,0 +1,19 @@ +package io.github.hpsocket.soa.framework.core.paging; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** 通用分页结果 */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PageResult +{ + private PageInfo page; + private List data; +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/AsyncThreadPoolExecutor.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/AsyncThreadPoolExecutor.java new file mode 100644 index 0000000..6693efa --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/AsyncThreadPoolExecutor.java @@ -0,0 +1,48 @@ +package io.github.hpsocket.soa.framework.core.thread; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.github.hpsocket.soa.framework.core.mdc.MdcRunnableWrapper; + +import lombok.Getter; + +/** 异步线程池(支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) */ +@Getter +public class AsyncThreadPoolExecutor extends ThreadPoolExecutor +{ + public AsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); + } + + public AsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); + } + + public AsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); + } + + public AsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); + } + + @Override + public void execute(Runnable task) + { + super.execute(decorate(task)); + } + + protected Runnable decorate(Runnable task) + { + return MdcRunnableWrapper.of(task); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/LocalExecutorServiceFactory.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/LocalExecutorServiceFactory.java new file mode 100644 index 0000000..ab71cc2 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/LocalExecutorServiceFactory.java @@ -0,0 +1,81 @@ +package io.github.hpsocket.soa.framework.core.thread; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** 线程局部线程池工厂:每个线程拥有独立线程池

+ * (支持 {@linkplain org.slf4j.MDC MDC} 调用链跟踪) + */ + +public class LocalExecutorServiceFactory +{ + public static final LocalExecutorServiceFactory DEFAULT_FACTORY = new LocalExecutorServiceFactory(); + private static final RejectedExecutionHandler DEFAULT_REJECT_HANDLER = new SynchronousRejectedExecutionHandler(); + + private ThreadLocal local = new ThreadLocal(); + + public LocalExecutorServiceFactory() + { + + } + + public ExecutorService get() + { + return get(Integer.MAX_VALUE, 60L, TimeUnit.SECONDS); + } + + public ExecutorService get(int maximumPoolSize) + { + return get(maximumPoolSize, 60L, TimeUnit.SECONDS); + } + + public ExecutorService get(long keepAliveTime, TimeUnit unit) + { + return get(Integer.MAX_VALUE, keepAliveTime, unit); + } + + public ExecutorService get(int maximumPoolSize, long keepAliveTime, TimeUnit unit) + { + return get(maximumPoolSize, keepAliveTime, unit, null); + } + + public ExecutorService get(int maximumPoolSize, long keepAliveTime, TimeUnit unit, RejectedExecutionHandler rejectHandler) + { + ThreadPoolExecutor executor = local.get(); + + if(rejectHandler == null) + rejectHandler = DEFAULT_REJECT_HANDLER; + + if(executor == null) + { + executor = createExecutor(0, maximumPoolSize, keepAliveTime, unit, new SynchronousQueue(), rejectHandler); + local.set(executor); + } + else + { + if(executor.getMaximumPoolSize() != maximumPoolSize) + executor.setMaximumPoolSize(maximumPoolSize); + if(executor.getKeepAliveTime(TimeUnit.NANOSECONDS) != unit.toNanos(keepAliveTime)) + executor.setKeepAliveTime(keepAliveTime, unit); + if(executor.getRejectedExecutionHandler() != rejectHandler) + executor.setRejectedExecutionHandler(rejectHandler); + } + + return executor; + } + + public void remove() + { + local.remove(); + } + + private ThreadPoolExecutor createExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue synchronousQueue, RejectedExecutionHandler rejectHandler) + { + return new AsyncThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, synchronousQueue, rejectHandler); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/SynchronousRejectedExecutionHandler.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/SynchronousRejectedExecutionHandler.java new file mode 100644 index 0000000..0b0f3fe --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/thread/SynchronousRejectedExecutionHandler.java @@ -0,0 +1,30 @@ +package io.github.hpsocket.soa.framework.core.thread; + +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; + +import lombok.extern.slf4j.Slf4j; + +/** 阻塞式 {@linkplain RejectedExecutionHandler}
+ * 当线程池的工作队列已满时,阻塞任务提交并等待。 + */ +@Slf4j +public class SynchronousRejectedExecutionHandler implements RejectedExecutionHandler +{ + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) + { + if(!executor.isShutdown()) + { + try + { + executor.getQueue().put(r); + } + catch(InterruptedException e) + { + log.error("submit task {} interrupted", r, e); + } + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/BeanHelper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/BeanHelper.java new file mode 100644 index 0000000..d2ba8f4 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/BeanHelper.java @@ -0,0 +1,1654 @@ +package io.github.hpsocket.soa.framework.core.util; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.TreeSet; + +/** Java Bean 帮助类,执行 Java Bean 反射和内省等相关操作 */ +public class BeanHelper +{ + /** 原生数据类型集合 */ + public static final Set> NATIVE_CLASS_SET = new HashSet>(8); + /** 简单数据类型集合 */ + public static final Set> SMIPLE_CLASS_SET = new HashSet>(18); + /** 基本类型包装类集合 */ + public static final Set> WRAPPER_CLASS_SET = new HashSet>(8); + + private static final String STRING_DELIMITERS = " ,;|\t\n\r\f"; + private static final char ATTR_SEP_CHAR = '.'; + + static + { + NATIVE_CLASS_SET.add(int.class); + NATIVE_CLASS_SET.add(long.class); + NATIVE_CLASS_SET.add(float.class); + NATIVE_CLASS_SET.add(double.class); + NATIVE_CLASS_SET.add(byte.class); + NATIVE_CLASS_SET.add(char.class); + NATIVE_CLASS_SET.add(short.class); + NATIVE_CLASS_SET.add(boolean.class); + + SMIPLE_CLASS_SET.add(int.class); + SMIPLE_CLASS_SET.add(long.class); + SMIPLE_CLASS_SET.add(float.class); + SMIPLE_CLASS_SET.add(double.class); + SMIPLE_CLASS_SET.add(byte.class); + SMIPLE_CLASS_SET.add(char.class); + SMIPLE_CLASS_SET.add(short.class); + SMIPLE_CLASS_SET.add(boolean.class); + SMIPLE_CLASS_SET.add(Integer.class); + SMIPLE_CLASS_SET.add(Long.class); + SMIPLE_CLASS_SET.add(Float.class); + SMIPLE_CLASS_SET.add(Double.class); + SMIPLE_CLASS_SET.add(Byte.class); + SMIPLE_CLASS_SET.add(Character.class); + SMIPLE_CLASS_SET.add(Short.class); + SMIPLE_CLASS_SET.add(Boolean.class); + SMIPLE_CLASS_SET.add(String.class); + SMIPLE_CLASS_SET.add(Date.class); + + WRAPPER_CLASS_SET.add(Integer.class); + WRAPPER_CLASS_SET.add(Long.class); + WRAPPER_CLASS_SET.add(Float.class); + WRAPPER_CLASS_SET.add(Double.class); + WRAPPER_CLASS_SET.add(Byte.class); + WRAPPER_CLASS_SET.add(Character.class); + WRAPPER_CLASS_SET.add(Short.class); + WRAPPER_CLASS_SET.add(Boolean.class); + } + + /** 检查是否为非抽象公共实例方法 */ + public static final boolean isInstanceField(Field field) + { + int flag = field.getModifiers(); + return (!Modifier.isStatic(flag)); + } + + /** 检查是否为非抽象公共实例方法 */ + public static final boolean isInstanceNotFinalField(Field field) + { + int flag = field.getModifiers(); + return (!Modifier.isStatic(flag) && !Modifier.isFinal(flag)); + } + + /** 检查是否为非抽象公共实例方法 */ + public static final boolean isPublicInstanceMethod(Method method) + { + int flag = method.getModifiers(); + return (!Modifier.isStatic(flag) && !Modifier.isAbstract(flag) && Modifier.isPublic(flag)); + } + + /** 检查是否为公共接口 */ + public static final boolean isPublicInterface(Class clazz) + { + int flag = clazz.getModifiers(); + return (Modifier.isInterface(flag) && Modifier.isPublic(flag)); + } + + /** 检查是否为公共类 */ + public static final boolean isPublicClass(Class clazz) + { + int flag = clazz.getModifiers(); + return (!Modifier.isInterface(flag) && Modifier.isPublic(flag)); + } + + /** 检查是否为非接口非抽象公共类 */ + public static final boolean isPublicNotAbstractClass(Class clazz) + { + int flag = clazz.getModifiers(); + return (!Modifier.isInterface(flag) && !Modifier.isAbstract(flag) && Modifier.isPublic(flag)); + } + + /** 检查 clazz 是否为原生数据类型 */ + public final static boolean isNativeType(Class clazz) + { + return NATIVE_CLASS_SET.contains(clazz); + } + + /** 检查 clazz 是否为简单数据类型 */ + public final static boolean isSimpleType(Class clazz) + { + return SMIPLE_CLASS_SET.contains(clazz); + } + + /** 检查 clazz 是否为基础类型包装类 */ + public final static boolean isWrapperType(Class clazz) + { + return WRAPPER_CLASS_SET.contains(clazz); + } + + /** 检查包装类和基础类型是否匹配 */ + public final static boolean isWrapperAndPrimitiveMatch(Class wrapperClazz, Class primitiveClass) + { + if(!primitiveClass.isPrimitive()) return false; + if(!isWrapperType(wrapperClazz)) return false; + + try + { + Field f = wrapperClazz.getDeclaredField("TYPE"); + return f.get(null) == primitiveClass; + } + catch(Exception e) + { + + } + + return false; + } + + /** 检查源类型是否兼容目标类型 */ + public static final boolean isCompatibleType(Class srcClazz,Class destClazz) + { + return ( + destClazz.isAssignableFrom(srcClazz) || + isWrapperAndPrimitiveMatch(destClazz, srcClazz) || + isWrapperAndPrimitiveMatch(srcClazz, destClazz) + ); + } + + /** 检查源数组的元素类型是否兼容目标数组的元素类型 */ + public static final boolean isCompatibleArray(Class srcClazz, Class destClazz) + { + if(srcClazz.isArray() && destClazz.isArray()) + { + Class srcComponentType = srcClazz.getComponentType(); + Class destComponentType = destClazz.getComponentType(); + + return isCompatibleType(srcComponentType, destComponentType); + } + + return false; + } + + /** 检查属性是否可以联级装配 */ + public static final boolean isCascadableProperty(PropertyDescriptor pd) + { + return (pd != null && getPropertyWriteMethod(pd) != null && isCascadable(pd.getPropertyType())); + } + + /** 检查成员变量是否可以联级装配 */ + public static final boolean isCascadableField(Field f) + { + return (f != null && isInstanceNotFinalField(f) && isCascadable(f.getType())); + } + + /** 检查类是否可以联级装配 */ + public static final boolean isCascadable(Class clazz) + { + return isPublicNotAbstractClass(clazz) && + !isSimpleType(clazz) && + !clazz.isArray() && + !Collection.class.isAssignableFrom(clazz) && + !Map.class.isAssignableFrom(clazz) ; + } + + /** 创建指定类型的 Java Bean,并设置相关属性或成员变量 + * + * @param clazz : Bean 类型
+ * @param properties : 属性或成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性或成员变量名称一致
+ * 属性或成员变量值可能为以下 3 中类型:
+ *     1) 属性或成员变量的实际类型:直接对属性或成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性或成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性或成员变量赋值
+ * @return : 生成的 Bean 实例 + */ + public static final B createBean(Class clazz, Map properties) + { + return createBean(clazz, properties, null); + } + + /** 创建指定类型的 Java Bean,并设置相关属性或成员变量 + * + * @param clazz : Bean 类型
+ * @param valueMap : 属性或成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性或成员变量名称可能一直也可能不一致
+ * 属性或成员变量值可能为以下 3 中类型:
+ *     1) 属性的实际类型:直接对属性或成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性或成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性或成员变量赋值
+ * @param keyMap : properties.key / Bean 属性或成员变量名映射,当 properties 的 key 与属性或成员变量名不对应时, + * 用 keyMap 把它们关联起来 + * @return 生成的 Bean 实例 + */ + public static final B createBean(Class clazz, Map valueMap, Map keyMap) + { + B bean = null; + + try + { + bean = clazz.getDeclaredConstructor().newInstance(); + setPropertiesOrFieldValues(bean, valueMap, keyMap); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + + return bean; + } + + /** 创建指定类型的 Java Bean,并设置相关属性 + * + * @param clazz : Bean 类型
+ * @param properties : 属性名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性名称一致
+ * 属性值可能为以下 3 中类型:
+ *     1) 属性的实际类型:直接对属性赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性赋值
+ * @return : 生成的 Bean 实例 + */ + public static final B createBeanByProperties(Class clazz, Map properties) + { + return createBeanByProperties(clazz, properties, null); + } + + /** 创建指定类型的 Java Bean,并设置相关属性 + * + * @param clazz : Bean 类型
+ * @param properties : 属性名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性名称可能一直也可能不一致
+ * 属性值可能为以下 3 中类型:
+ *     1) 属性的实际类型:直接对属性赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性赋值
+ * @param keyMap : properties.key / Bean 属性名映射,当 properties 的 key 与属性名不对应时, + * 用 keyMap 把它们关联起来 + * @return 生成的 Bean 实例 + */ + public static final B createBeanByProperties(Class clazz, Map properties, Map keyMap) + { + B bean = null; + + try + { + bean = clazz.getDeclaredConstructor().newInstance(); + setProperties(bean, properties, keyMap); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + + return bean; + } + + /** 创建指定类型的 Java Bean,并设置相关属性 + * + * @param clazz : Bean 类型
+ * @param values : 成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与成员变量名称可能一直也可能不一致
+ * 成员变量值可能为以下 3 中类型:
+ *     1) 成员变量的实际类型:直接对成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对成员变量值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对成员变量值
+ * @return 生成的 Bean 实例 + */ + public static final B createBeanByFieldValues(Class clazz, Map values) + { + return createBeanByFieldValues(clazz, values, null); + } + + /** 创建指定类型的 Java Bean,并设置相关属性 + * + * @param clazz : Bean 类型
+ * @param values : 成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与成员变量名称可能一直也可能不一致
+ * 成员变量值可能为以下 3 中类型:
+ *     1) 成员变量的实际类型:直接对成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对成员变量值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对成员变量值
+ * @param keyMap : values.key / Bean 成员变量名映射,当 values 的 key 与成员变量名不对应时, + * 用 keyMap 把它们关联起来 + * @return 生成的 Bean 实例 + */ + public static final B createBeanByFieldValues(Class clazz, Map values, Map keyMap) + { + B bean = null; + + try + { + bean = clazz.getDeclaredConstructor().newInstance(); + setFieldValues(bean, values, keyMap); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + + return bean; + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param properties : 属性名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性名称一致
+ * 属性值可能为以下 3 中类型:
+ *     1) 属性的实际类型:直接对属性赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性赋值
+ */ + public static final void setProperties(Object bean, Map properties) + { + setProperties(bean, properties, null); + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param properties : 属性名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性名称可能一直也可能不一致
+ * 属性值可能为以下 3 中类型:
+ *     1) 属性的实际类型:直接对属性赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性赋值
+ * @param keyMap : properties.key / Bean 属性名映射,当 properties 的 key 与属性名不对应时, + * 用 keyMap 把它们关联起来 + */ + public static final void setProperties(Object bean, Map properties, Map keyMap) + { + if(properties == null || properties.isEmpty()) + return; + + Map> subs = new HashMap>(); + Map pps = getPropDescMap(bean.getClass()); + Map params = translateKVMap(properties, keyMap); + + parseCascadeProperties(bean, subs, pps, params, null); + + if(!subs.isEmpty()) + { + Set>> sset = subs.entrySet(); + for(Map.Entry> e : sset) + { + try + { + PropertyDescriptor key = (PropertyDescriptor)e.getKey(); + Object o = key.getPropertyType().getDeclaredConstructor().newInstance(); + + setProperties(o, e.getValue()); + setProperty(bean, key, o); + } + catch(Exception ex) + { + throw new RuntimeException(ex); + } + } + } + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param values : 成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与成员变量名称可能一直也可能不一致
+ * 成员变量值可能为以下 3 中类型:
+ *     1) 成员变量的实际类型:直接对成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再成员变量赋值
+ */ + public static final void setFieldValues(Object bean, Map values) + { + setFieldValues(bean, values, null); + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param values : 成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与成员变量名称可能一直也可能不一致
+ * 成员变量值可能为以下 3 中类型:
+ *     1) 成员变量的实际类型:直接对成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再成员变量赋值
+ * @param keyMap : properties.key / Bean 成员变量名映射,当 properties 的 key 与成员变量名不对应时, + * 用 keyMap 把它们关联起来 + */ + public static final void setFieldValues(Object bean, Map values, Map keyMap) + { + if(values == null || values.isEmpty()) + return; + + Map> subs = new HashMap>(); + Map fms = getInstanceFieldMap(bean.getClass()); + Map params = translateKVMap(values, keyMap); + + parseCascadeFields(bean, subs, fms, params); + + if(!subs.isEmpty()) + { + Set>> sset = subs.entrySet(); + for(Map.Entry> e : sset) + { + try + { + Field key = (Field)e.getKey(); + Object o = key.getType().getDeclaredConstructor().newInstance(); + + setFieldValues(o, e.getValue()); + setFieldValue(bean, key, o); + } + catch(Exception ex) + { + throw new RuntimeException(ex); + } + } + } + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param valueMap : 属性或成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性或成员变量名称可能一直也可能不一致
+ * 属性或成员变量值可能为以下 3 中类型:
+ *     1) 属性或成员变量的实际类型:直接对属性或成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属或成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属赋或成员变量值
+ */ + public static final void setPropertiesOrFieldValues(Object bean, Map valueMap) + { + setPropertiesOrFieldValues(bean, valueMap, null); + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param valueMap : 属性或成员变量名 / 值映射
+ * 其中名称为 {@link String} 类型,与属性或成员变量名称可能一直也可能不一致
+ * 属性或成员变量值可能为以下 3 中类型:
+ *     1) 属性或成员变量的实际类型:直接对属性或成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属或成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属赋或成员变量值
+ * @param keyMap : properties.key / Bean 属性或成员变量名映射,当 properties 的 key 与属性或成员变量名不对应时, + * 用 keyMap 把它们关联起来 + */ + public static final void setPropertiesOrFieldValues(Object bean, Map valueMap, Map keyMap) + { + if(valueMap == null || valueMap.isEmpty()) + return; + + Map> subs = new HashMap>(); + Map pps = getPropDescMap(bean.getClass()); + Map params = translateKVMap(valueMap, keyMap); + Map failParams = new HashMap(); + + parseCascadeProperties(bean, subs, pps, params, failParams); + + if(!failParams.isEmpty()) + { + Map fms = getInstanceFieldMap(bean.getClass()); + parseCascadeFields(bean, subs, fms, failParams); + } + + if(!subs.isEmpty()) + { + Set>> sset = subs.entrySet(); + for(Map.Entry> e : sset) + { + Object key = e.getKey(); + Map value = e.getValue(); + + try + { + if(key instanceof PropertyDescriptor) + { + PropertyDescriptor pd = (PropertyDescriptor)key; + Object o = pd.getPropertyType().getDeclaredConstructor().newInstance(); + + setPropertiesOrFieldValues(o, value); + setProperty(bean, pd, o); + } + else + { + Field f = (Field)key; + Object o = f.getType().getDeclaredConstructor().newInstance(); + + setPropertiesOrFieldValues(o, value); + setFieldValue(bean, f, o); + } + } + catch(Exception ex) + { + throw new RuntimeException(ex); + } + } + } + } + + private static void parseCascadeProperties(Object bean, Map> subs, Map pps, Map params, Map failParams) + { + Set> set = params.entrySet(); + + for(Map.Entry e : set) + { + String key = e.getKey(); + T value = e.getValue(); + int index = key.indexOf(ATTR_SEP_CHAR); + + if(index == -1) + { + PropertyDescriptor pd = pps.get(key); + + if(getPropertyWriteMethod(pd) != null) + setProperty(bean, pd, value); + else if(failParams != null) + failParams.put(key, value); + } + else + { + String skey = key.substring(0, index); + PropertyDescriptor pd = pps.get(skey); + + if(getPropertyWriteMethod(pd) == null) + { + if(failParams != null) + failParams.put(key, value); + } + else if(isCascadableProperty(pd)) + { + if(!subs.containsKey(pd)) + subs.put(pd, new HashMap()); + + subs.get(pd).put(key.substring(index + 1), value); + } + } + } + } + + private static void parseCascadeFields(Object bean, Map> subs, Map fms, Map params) + { + Set> set = params.entrySet(); + + for(Map.Entry e : set) + { + String key = e.getKey(); + T value = e.getValue(); + int index = key.indexOf(ATTR_SEP_CHAR); + + if(index == -1) + { + Field f = fms.get(key); + setFieldValue(bean, f, value); + } + else + { + String skey = key.substring(0, index); + Field f = fms.get(skey); + + if(isCascadableField(f)) + { + if(!subs.containsKey(f)) + subs.put(f, new HashMap()); + + subs.get(f).put(key.substring(index + 1), value); + } + } + } + } + + private static final Map translateKVMap(Map valueMap, Map keyMap) + { + if(keyMap == null || keyMap.isEmpty()) + return valueMap; + + Map resultMap = new HashMap(); + Set> set = valueMap.entrySet(); + + for(Map.Entry e : set) + { + String key = e.getKey(); + String name = key; + + if(keyMap.containsKey(key)) + name = keyMap.get(key); + + resultMap.put(name, e.getValue()); + } + + return resultMap; + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param pd : 属性描述符
+ * @param value : 属性值,可能为以下 3 中类型:
+ *     1) 属性的实际类型:直接对属性赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性赋值
+ */ + public static final boolean setProperty(Object bean, PropertyDescriptor pd, T value) + { + if(pd != null) + { + Method method = getPropertyWriteMethod(pd); + + if(method != null) + { + Type genericType = null; + Class clazz = pd.getPropertyType(); + + if ( + Collection.class.isAssignableFrom(clazz) && + value != null && + !clazz.isAssignableFrom(value.getClass()) + ) + { + Type[] types = method.getGenericParameterTypes(); + + if(types.length > 0) + genericType = types[0]; + } + + Result result = parseValue(value, clazz, genericType); + + if(result.getFlag()) + { + invokeMethod(bean, method, result.getValue()); + return true; + } + } + } + + return false; + } + + /** 设置 Java Bean 的属性 + * + * @param bean : Bean 实例
+ * @param field : 成员变量 {@link Field} 对象
+ * @param value : 成员变量值,可能为以下 3 中类型:
+ *     1) 成员变量的实际类型:直接对成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对成员变量值
+ *     3) {@link String}[] 类型:先执行自动类型转换再成员变量赋值
+ */ + public static final boolean setFieldValue(Object bean, Field field, T value) + { + if(field != null && isInstanceNotFinalField(field)) + { + Class clazz = field.getType(); + Type genericType = field.getGenericType(); + + Result result = parseValue(value, clazz, genericType); + + if(result.getFlag()) + { + invokeSetFieldValue(bean, field, result.getValue()); + return true; + } + } + + return false; + } + + /** 把对象转换为目标类型 + * + * @param value : 待转换的参数,可能为以下 3 中类型:
+ *     1) 参数的类型与目标类型兼容:不作转换
+ *     2) {@link String} 类型:尝试执行自动类型转换
+ *     3) {@link String}[] 类型:尝试执行自动类型转换
+ * @param clazz : 目标类型
+ * @param genericType : 目标类型的元素类型 (只当目标类型是 {@link List} 或 {@link Set} 等集合类型时才需要)
+ */ + @SuppressWarnings("unchecked") + public static final Result parseValue(T value, Class clazz, Type genericType) + { + Result result = Result.initialBoolean(); + + if(value == null) + { + if(clazz.isPrimitive()) + result.set(Boolean.TRUE, GeneralHelper.str2Object(clazz, (String)value)); + else + result.set(Boolean.TRUE, value); + } + else if(clazz.isAssignableFrom(value.getClass())) + result.set(Boolean.TRUE, value); + else if(isWrapperAndPrimitiveMatch(value.getClass(), clazz)) + result.set(Boolean.TRUE, value); + else if(isCompatibleArray(value.getClass(), clazz)) + getArrayValue(value, clazz.getComponentType(), result); + else if(Collection.class.isAssignableFrom(clazz)) + getCollectionValue(value, (Class>)clazz, genericType, result); + else + getSimpleValue(value, clazz, result); + + return result; + } + + private static final void getArrayValue( T value, Class clazz, Result result) + { + int length = Array.getLength(value); + Object array = Array.newInstance(clazz, length); + + System.arraycopy(value, 0, array, 0, length); + + result.set(Boolean.TRUE, array); + } + + private static final void getCollectionValue(T value, Class> colClazz, Type genericType, Result result) + { + if(genericType instanceof ParameterizedType) + { + Class paramClazz = (Class)(((ParameterizedType)genericType).getActualTypeArguments()[0]); + getCollectionValue(value, colClazz, paramClazz, result); + } + } + + private static final void getCollectionValue(T value, Class> clazz, Class paramClazz, Result result) + { + Class valueType = value.getClass(); + Class valueComType = valueType.getComponentType(); + + if ( + isSimpleType(paramClazz) && + ( + (valueType.equals(String.class)) || + (valueType.isArray() && valueComType.equals(String.class)) + ) + + ) + { + Collection col = parseCollectionParameter(clazz, paramClazz, value); + result.set(Boolean.TRUE, col); + } + } + + private static final void getSimpleValue(T value, Class clazz, Result result) + { + Class valueType = value.getClass(); + Class valueComType = valueType.getComponentType(); + Class clazzComType = clazz.getComponentType(); + + if ( + ( + (valueType.equals(String.class)) || + (valueType.isArray() && valueComType.equals(String.class)) + ) + && + ( + (isSimpleType(clazz)) || + (clazz.isArray() && isSimpleType(clazzComType)) + ) + ) + { + Object param = parseParameter(clazz, value); + result.set(Boolean.TRUE, param); + } + } + + private static final Collection parseCollectionParameter(Class> clazz, Class paramClazz, T obj) + { + Collection col = getRealCollectionClass(clazz); + + if(col != null) + { + Class valueType = obj.getClass(); + String[] value = null; + + if(valueType.isArray()) + value = (String[])obj; + else + { + String str = (String)obj; + StringTokenizer st = new StringTokenizer(str, STRING_DELIMITERS); + value = new String[st.countTokens()]; + + for(int i = 0; st.hasMoreTokens(); i++) + value[i] = st.nextToken(); + } + + for(int i = 0; i < value.length; i++) + { + String v = value[i]; + Object p = GeneralHelper.str2Object(paramClazz, v); + col.add(p); + } + } + + return col; + } + + @SuppressWarnings("unchecked") + public static final Collection getRealCollectionClass(Class> clazz) + { + Class realClazz = null; + + if(isPublicNotAbstractClass(clazz)) + realClazz = clazz; + else if(SortedSet.class.isAssignableFrom(clazz)) + realClazz = TreeSet.class; + else if(Set.class.isAssignableFrom(clazz)) + realClazz = HashSet.class; + else if(Collection.class.isAssignableFrom(clazz)) + realClazz = ArrayList.class; + + if(realClazz == null) + throw new RuntimeException(clazz.getName() + "is not Collection class"); + + try + { + return (Collection)realClazz.getDeclaredConstructor().newInstance(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + public static final Map getRealMapClass(Class> clazz) + { + Class realClazz = null; + + if(isPublicNotAbstractClass(clazz)) + realClazz = clazz; + else if(SortedMap.class.isAssignableFrom(clazz)) + realClazz = TreeMap.class; + else if(Map.class.isAssignableFrom(clazz)) + realClazz = HashMap.class; + + if(realClazz == null) + throw new RuntimeException(clazz.getName() + "is not Map class"); + + try + { + return (Map)realClazz.getDeclaredConstructor().newInstance(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + /** 通过反射机制调用方法,失败则抛出异常 */ + @SuppressWarnings("unchecked") + public static final T invokeMethod(Object bean, Method method, Object ... param) + { + try + { + method.setAccessible(true); + return (T)method.invoke(bean, param); + } + catch(Exception e) + { + if(e instanceof InvocationTargetException) + { + Exception cause = (Exception)e.getCause(); + if(cause != null) e = cause; + } + + throw new RuntimeException(e); + } + } + + /** 通过反射机制调用方法,执行结果由 {@link InternalTestResult} 标识,不抛出异常 */ + public static final Result tryInvokeMethod(Object bean, Method method, Object ... param) + { + Result result = Result.initialBoolean(); + + try + { + T value = invokeMethod(bean, method, param); + result.set(Boolean.TRUE, value); + } + catch (Exception e) + { + } + + return result; + } + + /** 通过反射机制获取成员变量值,失败则抛出异常 */ + @SuppressWarnings("unchecked") + public static final T invokeGetFieldValue(Object bean, Field field) + { + try + { + field.setAccessible(true); + return (T)field.get(bean); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** 通过反射机制获取成员变量值,执行结果由 {@link InternalTestResult} 标识,不抛出异常 */ + public static final Result tryInvokeGetFieldValue(Object bean, Field field) + { + Result result = Result.initialBoolean(); + + try + { + T value = invokeGetFieldValue(bean, field); + result.set(Boolean.TRUE, value); + } + catch (Exception e) + { + } + + return result; + } + + /** 通过反射机制设置成员变量值,失败则抛出异常 */ + public static final void invokeSetFieldValue(Object bean, Field field, Object value) + { + try + { + field.setAccessible(true); + field.set(bean, value); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** 通过反射机制设置成员变量值,失败则返回 false,不抛出异常 */ + public static final boolean tryInvokeSetFieldValue(Object bean, Field field, Object value) + { + boolean isOK = true; + + try + { + invokeSetFieldValue(bean, field, value); + } + catch (Exception e) + { + isOK = false; + } + + return isOK; + } + + private static final Object parseParameter(Class clazz, T obj) + { + Object param = null; + Class valueType = obj.getClass(); + + if(clazz.isArray()) + { + String[] value = null; + + if(valueType.isArray()) + value = (String[])obj; + else + { + String str = (String)obj; + StringTokenizer st = new StringTokenizer(str, STRING_DELIMITERS); + value = new String[st.countTokens()]; + + for(int i = 0; st.hasMoreTokens(); i++) + value[i] = st.nextToken(); + } + + int length = value.length; + Class type = clazz.getComponentType(); + param = Array.newInstance(type, length); + + for(int i = 0; i < length; i++) + { + String v = value[i]; + Object p = GeneralHelper.str2Object(type, v); + Array.set(param, i, p); + } + } + else + { + String value = null; + + if(valueType.isArray()) + { + String[] array = (String[])obj; + if(array.length > 0) + value = array[0]; + } + else + value = (String)obj; + + param = GeneralHelper.str2Object(clazz, value); + } + + return param; + } + + /** 获取指定类型 Java Bean 的所有属性描述(包括 Object 类除外的所有父类定义的属性) + * + * @param startClass : Bean 类型 + * @return 属性名 / 描述对象映射 + */ + public static final Map getPropDescMap(Class startClass) + { + return getPropDescMap(startClass, Object.class); + } + + /** 获取指定类型 Java Bean 的所有属性描述(包括 stopClass 及更高层父类除外的所有父类定义的属性) + * + * @param startClass : Bean 类型 + * @param stopClass : 截止查找的父类类型 + * @return 属性名 / 描述对象映射 + */ + public static final Map getPropDescMap(Class startClass, Class stopClass) + { + Map map = new HashMap(); + + try + { + BeanInfo info = Introspector.getBeanInfo(startClass, stopClass); + PropertyDescriptor[] pds = info.getPropertyDescriptors(); + + for(PropertyDescriptor pd : pds) + map.put(pd.getName(), pd); + } + catch(IntrospectionException e) + { + throw new RuntimeException(e); + } + + return map; + } + + /** 获取 Java Bean 的属性 + * + * @param bean : Bean 实例 + * @return : Bean 属性名 / 值映射 + */ + public static final Map getProperties(Object bean) + { + Map result = new HashMap(); + Map pps = getPropDescMap(bean.getClass()); + Set> set = pps.entrySet(); + + for(Map.Entry o : set) + { + String key = o.getKey(); + PropertyDescriptor pd = o.getValue(); + Method method = getPropertyReadMethod(pd); + + if(method != null) + { + Object obj = invokeMethod(bean, method); + result.put(key, obj); + } + } + + return result; + } + + /** 获取指定类型 Java Bean 的名称为 name 的属性描述对象 + * + * @param startClass : Bean 类型 + * @param name : 属性名称 + * @return 描述对象映射,找不到属性则返回 null + */ + public static final PropertyDescriptor getPropDescByName(Class startClass, String name) + { + return getPropDescByName(startClass, Object.class, name); + } + + /** 获取指定类型 Java Bean 的名称为 name 的属性描述对象 + * + * @param startClass : Bean 类型 + * @param stopClass : 截止查找的父类类型 + * @param name : 属性名称 + * @return 描述对象映射,找不到属性则返回 null + */ + public static final PropertyDescriptor getPropDescByName(Class startClass, Class stopClass, String name) + { + try + { + BeanInfo info = Introspector.getBeanInfo(startClass, stopClass); + PropertyDescriptor[] pds = info.getPropertyDescriptors(); + + for(PropertyDescriptor pd : pds) + { + if(pd.getName().equals(name)) + return pd; + } + } + catch(IntrospectionException e) + { + throw new RuntimeException(e); + } + + return null; + } + + /** 获取属性的 getter 方法的 {@link Method} 对象 + * + * @param startClass : Bean 类型 + * @param property : 属性名称 + * @return 描述对象映射,找不到属性则返回 null + */ + public static final Method getPropertyReadMethod(Class startClass, String property) + { + return getPropertyReadMethod(startClass, null, property); + } + + /** 获取属性的 getter 方法的 {@link Method} 对象 + * + * @param startClass : Bean 类型 + * @param stopClass : 截止查找的父类类型 + * @param property : 属性名称 + * @return 描述对象映射,找不到属性则返回 null + */ + public static final Method getPropertyReadMethod(Class startClass, Class stopClass, String property) + { + PropertyDescriptor pd = getPropDescByName(startClass, stopClass, property); + return getPropertyReadMethod(pd); + } + + /** 获取属性的 getter 方法的 {@link Method} 对象 + * + * @param pd : 属性的 {@link PropertyDescriptor} 描述符 + * @return {@link Method} 对象,找不到则返回 null + */ + public static final Method getPropertyReadMethod(PropertyDescriptor pd) + { + return getPropertyMethod(pd, true); + } + + /** 获取属性的 setter 方法的 {@link Method} 对象 + * + * @param startClass : Bean 类型 + * @param property : 属性名称 + * @return 描述对象映射,找不到属性则返回 null + */ + public static final Method getPropertyWriteMethod(Class startClass, String property) + { + return getPropertyWriteMethod(startClass, null, property); + } + + /** 获取属性的 setter 方法的 {@link Method} 对象 + * + * @param startClass : Bean 类型 + * @param stopClass : 截止查找的父类类型 + * @param property : 属性名称 + * @return 描述对象映射,找不到属性则返回 null + */ + public static final Method getPropertyWriteMethod(Class startClass, Class stopClass, String property) + { + PropertyDescriptor pd = getPropDescByName(startClass, stopClass, property); + return getPropertyWriteMethod(pd); + } + + /** 获取属性的 setter 方法的 {@link Method} 对象 + * + * @param pd : 属性的 {@link PropertyDescriptor} 描述符 + * @return {@link Method} 对象,找不到则返回 null + */ + public static final Method getPropertyWriteMethod(PropertyDescriptor pd) + { + return getPropertyMethod(pd, false); + } + + /** 获取属性的 getter 或 setter 方法的 {@link Method} 对象 + * + * @param pd : 属性的 {@link PropertyDescriptor} 描述符 + * @param readOrWrite : 标识:true -> getter, false -> setter + * @return {@link Method} 对象,找不到则返回 null + */ + public static final Method getPropertyMethod(PropertyDescriptor pd, boolean readOrWrite) + { + if(pd != null) + { + Method method = readOrWrite ? pd.getReadMethod() : pd.getWriteMethod(); + if(method != null && isPublicInstanceMethod(method)) + return method; + } + + return null; + } + + /** 设置 Java Bean 的名称为 name 的属性值 + * + * @param bean : Bean 实例 + * @param name : 属性名称 + * @param value : 属性值可能为以下 3 中类型:
+ *     1) 属性的实际类型:直接对属性赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性赋值
+ */ + public static final boolean setProperty(Object bean, String name, T value) + { + PropertyDescriptor pd = getPropDescByName(bean.getClass(), name); + return setProperty(bean, pd, value); + } + + /** 获取 Java Bean 的名称为 name 的属性值 + * + * @param bean : Bean 实例 + * @param name : 属性名称 + * @throws RuntimeException 失败则抛出相应的运行期异常 + */ + public static final Result getProperty(Object bean, String name) + { + PropertyDescriptor pd = getPropDescByName(bean.getClass(), name); + return getProperty(bean, pd); + } + + /** 获取 Java Bean 的名称为 name 的属性值 + * + * @param bean : Bean 实例 + * @param pd : 属性描述符 + * @throws RuntimeException 失败则抛出相应的运行期异常 + */ + public static final Result getProperty(Object bean, PropertyDescriptor pd) + { + Result result = Result.initialBoolean(); + Method method = getPropertyReadMethod(pd); + + if(method != null) + { + T value = invokeMethod(bean, method); + result.set(Boolean.TRUE, value); + } + + return result; + } + + /** 设置 Java Bean 的名称为 name 的成员变量值 + * + * @param bean : Bean 实例 + * @param name : 成员变量名称 + * @param value : 成员变量值可能为以下 3 中类型:
+ *     1) 成员变量的实际类型:直接对成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对成员变量赋值
+ */ + public static boolean setFieldValue(Object bean, String name, T value) + { + Field field = getInstanceFiledByName(bean.getClass(), name); + return setFieldValue(bean, field, value); + } + + /** 获取 Java Bean 的名称为 name 的成员值 + * + * @param bean : Bean 实例 + * @param name : 成员变量名称 + * @throws RuntimeException 失败则抛出相应的运行期异常 + */ + public static final Result getFieldValue(Object bean, String name) + { + Field field = getInstanceFiledByName(bean.getClass(), name); + return getFieldValue(bean, field); + } + + /** 获取 Java Bean 的名称为 name 的成员值 + * + * @param bean : Bean 实例 + * @param field : 成员变量 {@link Field} 对象 + * @throws RuntimeException 失败则抛出相应的运行期异常 + */ + public static final Result getFieldValue(Object bean, Field field) + { + Result result = Result.initialBoolean(); + + if(field != null && isInstanceField(field)) + { + T value = invokeGetFieldValue(bean, field); + result.set(Boolean.TRUE, value); + } + + return result; + } + + /** 设置 Java Bean 的名称为 name 的属性或成员变量值,如果 setter 方法不存在,则尝试直接修改成员变量 + * + * @param bean : Bean 实例 + * @param name : 属性名称或成员变量名称 + * @param value : 属性值或成员变量可能为以下 3 中类型:
+ *     1) 属性或成员变量的实际类型:直接对属性或成员变量赋值
+ *     2) {@link String} 类型:先执行自动类型转换再对属性或成员变量赋值
+ *     3) {@link String}[] 类型:先执行自动类型转换再对属性或成员变量赋值
+ */ + public static final boolean setPropertyOrFieldValue(Object bean, String name, T value) + { + return setProperty(bean, name, value) || setFieldValue(bean, name, value); + } + + /** 设置 Java Bean 的名称为 name 的属性值,如果 getter 方法不存在,则尝试直接获取成员变量的值 + * + * @param bean : Bean 实例 + * @param name : 属性名称或成员变量名称 + */ + public static final Result getPropertyOrFieldValue(Object bean, String name) + { + Result result = getProperty(bean, name); + + if(!result.getFlag()) + result = getFieldValue(bean, name); + + return result; + } + + /** 获取某个类所有成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @return : 失败返回空集合 + */ + public static final Set getAllFields(Class clazz) + { + return getAllFields(clazz, null); + } + + /** 获取某个类所有成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @param stopClazz : 终止查找的类(这个类的成员变量也不被获取) + * @return : 失败返回空集合 + */ + public static final Set getAllFields(Class clazz, Class stopClazz) + { + Set fields = new HashSet(); + + while(clazz != null && clazz != stopClazz) + { + Field[] fs = clazz.getDeclaredFields(); + Collections.addAll(fields, fs); + + clazz = clazz.getSuperclass(); + + } + + return fields; + } + + /** 获取某个类所有成员成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @return : 失败返回空集合 + */ + public static final Set getInstanceFields(Class clazz) + { + return getInstanceFields(clazz, null); + } + + /** 获取某个类所有成员成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @param stopClazz : 终止查找的类(这个类的成员变量也不被获取) + * @return : 失败返回空集合 + */ + public static final Set getInstanceFields(Class clazz, Class stopClazz) + { + Set fields = new HashSet(); + + while(clazz != null && clazz != stopClazz) + { + Field[] fs = clazz.getDeclaredFields(); + for(Field f : fs) + { + if(isInstanceField(f)) + fields.add(f); + } + + clazz = clazz.getSuperclass(); + } + + return fields; + } + + /** 获取指定类型 Java Bean 的所有成员成员变量的 {@link Field} 对象(包括 stopClass 及更高层父类除外的所有父类定义的成员变量) + * ,该方法会屏蔽父类的同名成员变量 + * @return 成员变量名 / 描述对象映射 + */ + public static final Map getInstanceFieldMap(Class clazz) + { + return getInstanceFieldMap(clazz, null); + } + + /** 获取指定类型 Java Bean 的所有成员变量的 {@link Field} 对象(包括 stopClass 及更高层父类除外的所有父类定义的成员变量) + * ,该方法会屏蔽父类的同名成员变量 + * @param clazz : Bean 类型 + * @param stopClazz : 截止查找的父类类型 + * @return 成员变量名 / 描述对象映射 + */ + public static final Map getInstanceFieldMap(Class clazz, Class stopClazz) + { + Map map = new HashMap(); + + while(clazz != null && clazz != stopClazz) + { + Field[] fs = clazz.getDeclaredFields(); + for(Field f : fs) + { + String name = f.getName(); + if(isInstanceField(f) && !map.containsKey(name)) + map.put(name, f); + } + + clazz = clazz.getSuperclass(); + } + + return map; + } + + /** 获取 Java Bean 的成员变量值 + * + * @param bean : Bean 实例 + * @return : Bean 成员变量名 / 值映射 + */ + public static final Map getFieldValues(Object bean) + { + Map result = new HashMap(); + Map fms = getInstanceFieldMap(bean.getClass()); + Set> set = fms.entrySet(); + + for(Map.Entry o : set) + { + String key = o.getKey(); + Field field = o.getValue(); + + if(field != null && isInstanceField(field)) + { + Object obj = invokeGetFieldValue(bean, field); + result.put(key, obj); + } + } + + return result; + } + + /** 获取某个类中名称为 name 的成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @param name : 方法名称 + * @return : 失败返回 null + */ + public static final Field getFiledByName(Class clazz,String name) + { + return getFiledByName(clazz, null, name); + } + + /** 获取某个类中名称为 name 的成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @param stopClazz : 终止查找的类(这个类的成员变量也不被获取) + * @param name : 方法名称 + * @return : 失败返回 null + */ + public static final Field getFiledByName(Class clazz, Class stopClazz, String name) + { + Field f = null; + + do + { + try + { + f = clazz.getDeclaredField(name); + } + catch(NoSuchFieldException e) + { + clazz = clazz.getSuperclass(); + } + } while(f == null && clazz != null && clazz != stopClazz); + + return f; + } + + /** 获取某个类中名称为 name 的成员成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @param name : 方法名称 + * @return : 失败返回 null + */ + public static final Field getInstanceFiledByName(Class clazz,String name) + { + return getInstanceFiledByName(clazz, null, name); + } + + /** 获取某个类中名称为 name 的成员变量的 {@link Field} 对象 + * + * @param clazz : 要查找的类 + * @param stopClazz : 终止查找的类(这个类的成员变量也不被获取) + * @param name : 方法名称 + * @return : 失败返回 null + */ + public static final Field getInstanceFiledByName(Class clazz, Class stopClazz, String name) + { + Field f = null; + + do + { + try + { + Field f2 = clazz.getDeclaredField(name); + + if(isInstanceField(f2)) + { + f = f2; + break; + } + else + clazz = clazz.getSuperclass(); + + } + catch(NoSuchFieldException e) + { + clazz = clazz.getSuperclass(); + } + } while(clazz != null && clazz != stopClazz); + + return f; + } + + /** 获取某个类所有方法的 {@link Method} 对象 + * + * @param clazz : 要查找的类 + * @return : 失败返回空集合 + */ + public static final Set getAllMethods(Class clazz) + { + return getAllMethods(clazz, null); + } + + /** 获取某个类所有方法的 {@link Method} 对象 + * + * @param clazz : 要查找的类 + * @param stopClazz : 终止查找的类(这个类的方法也不被获取) + * @return : 失败返回空集合 + */ + public static final Set getAllMethods(Class clazz, Class stopClazz) + { + Set methods = new HashSet(); + + while(clazz != null && clazz != stopClazz) + { + Method[] m = clazz.getDeclaredMethods(); + Collections.addAll(methods, m); + + clazz = clazz.getSuperclass(); + + } + + return methods; + } + + /** 获取某个类中名称为 name,参数为 parameterTypes 的方法的 {@link Method} 对象 + * + * @param clazz : 要查找的类 + * @param name : 方法名称 + * @param parameterTypes : 参数类型 + * @return : 失败返回 null + */ + public static final Method getMethodByName(Class clazz, String name, Class... parameterTypes) + { + return getMethodByName(clazz, null, name, parameterTypes); + } + + /** 获取某个类中名称为 name,参数为 parameterTypes 的方法的 {@link Method} 对象 + * + * @param clazz : 要查找的类 + * @param stopClazz : 终止查找的类(这个类的方法也不被获取) + * @param name : 方法名称 + * @param parameterTypes : 参数类型 + * @return : 失败返回 null + */ + public static final Method getMethodByName(Class clazz, Class stopClazz, String name, Class... parameterTypes) + { + Method m = null; + + do + { + try + { + m = clazz.getDeclaredMethod(name, parameterTypes); + } + catch(NoSuchMethodException e) + { + clazz = clazz.getSuperclass(); + } + } while(m == null && clazz != null && clazz != stopClazz); + + return m; + } + + /** 用 {@linkplain Class#getMethod(String, Class...)} 获取 {@link Method} 对象 + * + * @param clazz : 要查找的类 + * @param name : 方法名称 + * @param parameterTypes : 参数类型 + * @return : 失败返回 null + */ + public static final Method getMethod(Class clazz, String name, Class... parameterTypes) + { + Method m = null; + + try + { + m = clazz.getMethod(name, parameterTypes); + } + catch(NoSuchMethodException e) + { + } + + return m; + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Constant.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Constant.java new file mode 100644 index 0000000..2342a32 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Constant.java @@ -0,0 +1,169 @@ +package io.github.hpsocket.soa.framework.core.util; + +/** 通用常量 */ +public class Constant implements Comparable +{ + /** 代码 */ + public final int CODE; + /** 描述 */ + public final String DESC; + + public Constant() + { + this(0); + } + + public Constant(int code) + { + this(code, ""); + } + + public Constant(int code, String desc) + { + CODE = code; + DESC = desc; + } + + public Constant(Number code) + { + this(code.intValue()); + } + + public Constant(Number code, String desc) + { + this(code.intValue(), desc); + } + + public final int INT_VAL() + { + return CODE; + } + + public final short SHORT_VAL() + { + return (short)CODE; + } + + public final byte BYTE_VAL() + { + return (byte)CODE; + } + + public final String STR_VAL() + { + return Integer.toString(CODE); + } + + public boolean equals(Number value) + { + return GeneralHelper.equals(value, CODE); + } + + public boolean notEquals(Number value) + { + return !equals(value); + } + + @Override + public int compareTo(Constant other) + { + return Integer.compare(CODE, other.CODE); + } + + @Override + public boolean equals(Object obj) + { + if(this == obj) + return true; + else if(obj instanceof Constant) + return CODE == ((Constant)obj).CODE; + else if(obj instanceof Number) + return equals((Number)obj); + + return false; + } + + @Override + public int hashCode() + { + return CODE; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append('{').append(CODE).append(", ").append("\"").append(DESC).append("\"}"); + + return sb.toString(); + } + + /* *********************************************************************************************************************************** */ + /* ****************************************************** Begin: Common Constant ***************************************************** */ + /* *********************************************************************************************************************************** */ + + /** + * 通用激活标识 + */ + public static class ActivatedFlag + { + public static final Constant AF_INACTIVATED = new Constant(0, "未激活"); + public static final Constant AF_ACTIVATED = new Constant(1, "已激活"); + } + + /** + * 通用允许标识 + */ + public static class PermitFlag + { + public static final Constant PF_FORBID = new Constant(0, "禁止"); + public static final Constant PF_PERMIT = new Constant(1, "允许"); + } + + /** + * 通用成功标识 + */ + public static class SuccessFlag + { + public static final Constant SF_FAIL = new Constant(0, "失败"); + public static final Constant SF_SUCCESS = new Constant(1, "成功"); + public static final Constant SF_EXCEPTION = new Constant(-1, "异常"); + } + + /** + * 通用存在标识 + */ + public static class ExistFlag + { + public static final Constant EF_NOT_EXIST = new Constant(0, "不存在"); + public static final Constant EF_EXIST = new Constant(1, "存在"); + } + + /** + * 通用是否标识 + */ + public static class YesNoFlag + { + public static final Constant YNF_NO = new Constant(0, "否"); + public static final Constant YNF_YES = new Constant(1, "是"); + } + + /** + * 通用删除标识 + */ + public static class DeleteFlag + { + public static final Constant DF_NOT_DELETED = new Constant(0, "未删除"); + public static final Constant DF_HAD_DELETED = new Constant(1, "已删除"); + } + + /** + * 通用状态标识 + */ + public static class StatusFlag + { + public static final Constant STF_DISABLED = new Constant(0, "未开启"); + public static final Constant STF_ENABLED = new Constant(1, "已开启"); + public static final Constant STF_DELETED = new Constant(-1, "已删除"); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/CoupleKey.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/CoupleKey.java new file mode 100644 index 0000000..cdb61f0 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/CoupleKey.java @@ -0,0 +1,76 @@ +package io.github.hpsocket.soa.framework.core.util; + +/** 两个元素组成的 {@linkplain java.util.Map Map}/{@linkplain java.util.Set Set} 通用 Key */ +public class CoupleKey +{ + private K1 key1; + private K2 key2; + + public CoupleKey() + { + } + + public CoupleKey(K1 key1, K2 key2) + { + this.key1 = key1; + this.key2 = key2; + } + + @Override + public int hashCode() + { + int c1 = key1 != null ? key1.hashCode() : 0; + int c2 = key2 != null ? key2.hashCode() : 0; + + return c1 ^ c2; + } + + @Override + @SuppressWarnings("rawtypes") + public boolean equals(Object obj) + { + if(this == obj) + return true; + + if(obj instanceof CoupleKey) + { + CoupleKey other = (CoupleKey)obj; + + if(key1 != null) + { + if(!key1.equals(other.key1)) + return false; + } + else if(other.key1 != null) + return false; + + if(key2 != null) + return key2.equals(other.key2); + else + return other.key2 == null; + } + + return false; + } + + public K1 getKey1() + { + return key1; + } + + public void setKey1(K1 key1) + { + this.key1 = key1; + } + + public K2 getKey2() + { + return key2; + } + + public void setKey2(K2 key2) + { + this.key2 = key2; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/CryptHelper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/CryptHelper.java new file mode 100644 index 0000000..05696a1 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/CryptHelper.java @@ -0,0 +1,567 @@ +package io.github.hpsocket.soa.framework.core.util; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +/** 编解码、加解密帮助类 */ +public class CryptHelper +{ + /** AES 密匙长度 */ + public static final int AES_KEY_SIZE = 128; + /** DES 密匙长度 */ + public static final int DES_KEY_SIZE = 56; + /** 加密模式 */ + public static final int ENCRYPT_MODE = Cipher.ENCRYPT_MODE; + /** 解密模式 */ + public static final int DECRYPT_MODE = Cipher.DECRYPT_MODE; + /** 默认字符集(UTF-8) */ + public static final String DEFAULT_ENCODING = GeneralHelper.DEFAULT_ENCODING; + /** 加密方法:MD5 */ + public static final String MD5 = "MD5"; + /** 加密方法:SHA */ + public static final String SHA = "SHA"; + /** 加密方法:SHA-1 */ + public static final String SHA_1 = "SHA-1"; + /** 加密方法:SHA-256 */ + public static final String SHA_256 = "SHA-256"; + /** 加密方法:AES */ + public static final String AES = "AES"; + /** 加密方法:DES */ + public static final String DES = "DES"; + + private static final String SEC_RAN_ALG = "SHA1PRNG"; + + private static final String MYSQL_CIPHER = "AES/ECB/PKCS5Padding"; + + /** byte[] -> 十六进制字符串 (小写) */ + public final static String bytes2HexStr(byte[] bytes) + { + return bytes2HexStr(bytes, false); + } + + /** byte[] -> 十六进制字符串 */ + public final static String bytes2HexStr(byte[] bytes, boolean capital) + { + StringBuilder sb = new StringBuilder(); + + for(byte b : bytes) + sb.append(byte2Hex(b, capital)); + + return sb.toString(); + } + + /** byte -> 十六进制双字符 (小写) */ + public final static char[] byte2Hex(byte b) + { + return byte2Hex(b, false); + } + + /** byte -> 十六进制双字符 */ + public final static char[] byte2Hex(byte b, boolean capital) + { + byte bh = (byte)(b >>> 4 & 0xF); + byte bl = (byte)(b & 0xF); + + return new char[] {halfByte2Hex(bh, capital), halfByte2Hex(bl, capital)}; + } + + /** 半 byte -> 十六进制单字符 (小写) */ + public final static char halfByte2Hex(byte b) + { + return halfByte2Hex(b, false); + } + + /** 半 byte -> 十六进制单字符 */ + public final static char halfByte2Hex(byte b, boolean capital) + { + return (char)(b <= 9 ? b + '0' : (capital ? b + 'A' - 0xA : b + 'a' - 0xA)); + } + + /** 十六进制字符串 -> byte[] */ + public final static byte[] hexStr2Bytes(String str) + { + int length = str.length(); + + if(length % 2 != 0) + { + str = "0" + str; + length = str.length(); + } + + byte[] bytes = new byte[length / 2]; + + for(int i = 0; i < bytes.length; i++) + bytes[i] = hex2Byte(str.charAt(2 * i), str.charAt(2 * i + 1)); + + return bytes; + } + + /** 十六进制双字符 -> byte */ + public final static byte hex2Byte(char ch, char cl) + { + byte bh = hex2HalfByte(ch); + byte bl = hex2HalfByte(cl); + + return (byte)((bh << 4) + bl); + } + + /** 十六进制单字符 -> 半 byte */ + public final static byte hex2HalfByte(char c) + { + return (byte)(c <= '9' ? c - '0' : (c <= 'F' ? c - 'A' + 0xA : c - 'a' + 0xA)); + } + + /** 使用默认字符集对字符串编码后再进行 MD5 加密 */ + public final static String md5(String input) + { + return md5(input, null); + } + + /** 使用指定字符集对字符串编码后再进行 MD5 加密 */ + public final static String md5(String input, String charset) + { + return encode(getMd5Digest(), input, charset); + } + + /** MD5 加密 */ + public final static byte[] md5(byte[] input) + { + MessageDigest algorithm = getMd5Digest(); + return encode(algorithm, input); + } + + /** 使用默认字符集对字符串编码后再进行 SHA 加密 */ + public final static String sha(String input) + { + return sha(input, null); + } + + /** 使用指定字符集对字符串编码后再进行 SHA 加密 */ + public final static String sha(String input, String charset) + { + return encode(getShaDigest(), input, charset); + } + + /** 使用默认字符集对字符串编码后再进行 SHA-{X} 加密,其中 {X} 由 version 参数指定 */ + public final static String sha(String input, int version) + { + return sha(input, null, version); + } + + /** 使用指定字符集对字符串编码后再进行 SHA-{X} 加密,其中 {X} 由 version 参数指定 */ + public final static String sha(String input, String charset, int version) + { + return encode(getShaDigest(version), input, charset); + } + + /** SHA加密 */ + public final static byte[] sha(byte[] input) + { + MessageDigest algorithm = getShaDigest(); + return encode(algorithm, input); + } + + /** SHA-{X} 加密,其中 {X} 由 version 参数指定 */ + public final static byte[] sha(byte[] input, int version) + { + MessageDigest algorithm = getShaDigest(version); + return encode(algorithm, input); + } + + /** 使用指定算法对字符串加密 */ + public final static String encode(MessageDigest algorithm, String input) + { + return encode(algorithm, input, null); + } + + /** 使用指定字符集对字符串编码后再进行 SHA-{X} 加密,字符串的编码由 charset 参数指定 */ + public final static String encode(MessageDigest algorithm, String input, String charset) + { + try + { + byte[] bytes = input.getBytes(safeCharset(charset)); + byte[] output = encode(algorithm, bytes); + + return bytes2HexStr(output); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** 使用指定算法对 byte[] 加密 */ + public final static byte[] encode(MessageDigest algorithm, byte[] input) + { + return algorithm.digest(input); + } + + /** 获取 MD5 加密摘要对象 */ + public final static MessageDigest getMd5Digest() + { + return getDigest(MD5); + } + + /** 获取 SHA 加密摘要对象 */ + public final static MessageDigest getShaDigest() + { + return getDigest(SHA); + } + + /** 获取 SHA-1 加密摘要对象 */ + public final static MessageDigest getSha1Digest() + { + return getDigest(SHA_1); + } + + /** 获取 SHA-256 加密摘要对象 */ + public final static MessageDigest getSha256Digest() + { + return getDigest(SHA_256); + } + + /** 获取 SHA-{X} 加密摘要对象,其中 {X} 由 version 参数指定 */ + public final static MessageDigest getShaDigest(int version) + { + String algorithm = String.format("%s-%d", SHA, version); + return getDigest(algorithm); + } + + /** 根据加密方法名称获取加密摘要对象 */ + public final static MessageDigest getDigest(String algorithm) + { + try + { + return MessageDigest.getInstance(algorithm); + } + catch(NoSuchAlgorithmException e) + { + throw new RuntimeException(e); + } + } + + /** 根据加密方法名称和提供者获取加密摘要对象 */ + public final static MessageDigest getDigest(String algorithm, String provider) + { + try + { + return MessageDigest.getInstance(algorithm, provider); + } + catch(NoSuchAlgorithmException e) + { + throw new RuntimeException(e); + } + catch(NoSuchProviderException e) + { + throw new RuntimeException(e); + } + } + + /** 根据加密方法名称和提供者获取加密摘要对象 */ + public final static MessageDigest getDigest(String algorithm, Provider provider) + { + try + { + return MessageDigest.getInstance(algorithm, provider); + } + catch(NoSuchAlgorithmException e) + { + throw new RuntimeException(e); + } + } + + /** URL编码 (使用默认字符集) */ + public final static String urlEncode(String url) + { + return urlEncode(url, null); + } + + /** URL编码 (使用指定字符集) */ + public final static String urlEncode(String url, String charset) + { + try + { + return URLEncoder.encode(url, safeCharset(charset)); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** URL解码 (使用默认字符集) */ + public final static String urlDecode(String url) + { + return urlDecode(url, null); + } + + /** URL解码 (使用指定字符集) */ + public final static String urlDecode(String url, String enc) + { + try + { + return URLDecoder.decode(url, safeCharset(enc)); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** 使用默认字符集对字符串编码后再进行 AES 加密 */ + public final static String aesEncrypt(String content, String password) + { + return aesEncrypt(content, null, password); + } + + /** 使用指定字符集对字符串编码后再进行 AES 加密,字符串的编码由 charset 参数指定 */ + public final static String aesEncrypt(String content, String charset, String password) + { + return encrypt(AES, AES_KEY_SIZE, content, charset, password); + } + + /** AES 加密 */ + public final static byte[] aesEncrypt(byte[] content, String password) + { + return crypt(AES, ENCRYPT_MODE, AES_KEY_SIZE, content, password); + } + + /** AES 解密,并使用默认字符集生成解密后的字符串 */ + public final static String aesDecrypt(String content, String password) + { + return aesDecrypt(content, null, password); + } + + /** AES 解密,并使用指定字符集生成解密后的字符串,字符串的编码由 charset 参数指定 */ + public final static String aesDecrypt(String content, String charset, String password) + { + return decrypt(AES, AES_KEY_SIZE, content, charset, password); + } + + /** AES 解密 */ + public final static byte[] aesDecrypt(byte[] content, String password) + { + return crypt(AES, DECRYPT_MODE, AES_KEY_SIZE, content, password); + } + + /** 使用默认字符集对字符串编码后再进行 DES 加密 */ + public final static String desEncrypt(String content, String password) + { + return desEncrypt(content, null, password); + } + + /** 使用指定字符集对字符串编码后再进行 DES 加密,字符串的编码由 charset 参数指定 */ + public final static String desEncrypt(String content, String charset, String password) + { + return encrypt(DES, DES_KEY_SIZE, content, charset, password); + } + + /** DES 加密 */ + public final static byte[] desEncrypt(byte[] content, String password) + { + return crypt(DES, ENCRYPT_MODE, DES_KEY_SIZE, content, password); + } + + /** DES 解密,并使用默认字符集生成解密后的字符串 */ + public final static String desDecrypt(String content, String password) + { + return desDecrypt(content, null, password); + } + + /** DES 解密,并使用指定字符集生成解密后的字符串,字符串的编码由 charset 参数指定 */ + public final static String desDecrypt(String content, String charset, String password) + { + return decrypt(DES, DES_KEY_SIZE, content, charset, password); + } + + /** DES 解密 */ + public final static byte[] desDecrypt(byte[] content, String password) + { + return crypt(DES, DECRYPT_MODE, DES_KEY_SIZE, content, password); + } + + /** + * 加密字符串 + * + * @param method :加密方法(AES、DES) + * @param keysize :密匙长度 + * @param content :要加密的内容 + * @param charset :加密内容的编码字符集 + * @param password :密码 + * @return :加解密结果 + * @throws GeneralSecurityException 加密失败抛出异常 + */ + public final static String encrypt(String method, int keysize, String content, String charset, String password) + { + try + { + byte[] bytes = content.getBytes(safeCharset(charset)); + byte[] output = crypt(method, ENCRYPT_MODE, keysize, bytes, password); + + return bytes2HexStr(output); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** + * 解密字符串 + * + * @param method :解密方法(AES、DES) + * @param keysize :密匙长度 + * @param content :要解密的内容 + * @param charset :解密结果的编码字符集 + * @param password :密码 + * @return :加解密结果 + * @throws GeneralSecurityException 解密失败抛出异常 + */ + public final static String decrypt(String method, int keysize, String content, String charset, String password) + { + try + { + byte[] bytes = hexStr2Bytes(content); + byte[] output = crypt(method, DECRYPT_MODE, keysize, bytes, password); + + return new String(output, safeCharset(charset)); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** + * 加/解密 + * + * @param method :加/解密方法(AES、DES) + * @param mode :模式(加密/解密) + * @param keysize :密匙长度 + * @param content :要加/解密的内容 + * @param password :密码 + * @return :加解密结果 + * @throws GeneralSecurityException 解密失败抛出异常 + */ + public final static byte[] crypt(String method, int mode, int keysize, byte[] content, String password) + { + try + { + KeyGenerator kgen = KeyGenerator.getInstance(method); + SecureRandom secure = SecureRandom.getInstance(SEC_RAN_ALG); + String seed = GeneralHelper.safeString(password); + + secure.setSeed(seed.getBytes()); + kgen.init(keysize, secure); + + SecretKey secretKey = kgen.generateKey(); + byte[] enCodeFormat = secretKey.getEncoded(); + SecretKeySpec key = new SecretKeySpec(enCodeFormat, method); + Cipher cipher = Cipher.getInstance(method); + + cipher.init(mode, key); + return cipher.doFinal(content); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + private final static String safeCharset(String charset) + { + if(GeneralHelper.isStrEmpty(charset)) + charset = DEFAULT_ENCODING; + + return charset; + } + + public static String mysqlAESEncrypt(String content, String password) + { + return mysqlAESEncrypt(content, password, DEFAULT_ENCODING); + } + + public static final String mysqlAESEncrypt(String content, String password, String charset) + { + try + { + charset = safeCharset(charset); + password = GeneralHelper.safeString(password); + + SecureRandom secure = new SecureRandom(); + secure.setSeed(password.getBytes()); + + SecretKeySpec key = genMySQLAESKey(password, charset); + byte[] bytes = content.getBytes(charset); + + Cipher cipher = Cipher.getInstance(MYSQL_CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, key); + + return bytes2HexStr(cipher.doFinal(bytes)); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + public static final String mysqlAESDecrypt(String content, String password) + { + return mysqlAESDecrypt(content, password, DEFAULT_ENCODING); + } + + public static final String mysqlAESDecrypt(String content, String password, String charset) + { + try + { + charset = safeCharset(charset); + password = GeneralHelper.safeString(password); + + SecureRandom secure = new SecureRandom(); + secure.setSeed(password.getBytes()); + + SecretKeySpec key = genMySQLAESKey(password, charset); + byte[] bytes = hexStr2Bytes(content); + + Cipher cipher = Cipher.getInstance(MYSQL_CIPHER); + cipher.init(Cipher.DECRYPT_MODE, key); + + return new String(cipher.doFinal(bytes), charset); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + private static final SecretKeySpec genMySQLAESKey(final String key, final String charset) + { + try + { + final byte[] finalKey = new byte[16]; + byte[] bytes = key.getBytes(charset); + + for(int i = 0; i < bytes.length; i++) + finalKey[i % 16] ^= bytes[i]; + + return new SecretKeySpec(finalKey, AES); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/GeneralHelper.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/GeneralHelper.java new file mode 100644 index 0000000..ec06a50 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/GeneralHelper.java @@ -0,0 +1,1812 @@ +package io.github.hpsocket.soa.framework.core.util; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.lang.management.ManagementFactory; +import java.lang.reflect.Array; +import java.net.URL; +import java.net.URLDecoder; +import java.text.DateFormat; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** 通用方法帮助类 */ +public class GeneralHelper +{ + private static final String DELIMITERR_CHARS = " ,\t\r\n\f"; + + + private static final Pattern PATTERN_NUMERIC = Pattern.compile("^0$|^\\-?[1-9]+[0-9]*$"); + private static final Pattern PATTERN_EMAIL_ADDR = Pattern.compile("^[a-z0-9_\\-]+(\\.[_a-z0-9\\-]+)*@([_a-z0-9\\-]+\\.)+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel)$"); + private static final Pattern PATTERN_IP_ADDR = Pattern.compile("^([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])){3}$"); + private static final Pattern PATTERN_LINK = Pattern.compile("]*href=\\\"[^\\s\\\"]+\\\"[^>]*>[^<]*<\\/a>"); + private static final Pattern PATTERN_HTTP_URL = Pattern.compile("^(https?:\\/\\/)?([a-z]([a-z0-9\\-]*\\.)+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel)|(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(\\/[a-z0-9_\\-\\.~]+)*(\\/([a-z0-9_\\-\\.]*)(\\?[a-z0-9+_\\-\\.%=&]*)?)?(#[a-z][a-z0-9_]*)?$"); + private static final Pattern PATTERN_XML_ESCAPES= Pattern.compile(".*[&|\"|\'|<|>].*"); + + private static final String[] SHORT_DATE_PATTERN = {"yyyy-MM-dd", "yyyy/MM/dd", "yyyy\\MM\\dd", "yyyyMMdd"}; + private static final String[] LONG_DATE_PATTERN = {"yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss", "yyyy\\MM\\dd HH:mm:ss", "yyyyMMddHHmmss"}; + private static final String[] LONG_DATE_PATTERN_WITH_MILSEC = {"yyyy-MM-dd HH:mm:ss.SSS", "yyyy/MM/dd HH:mm:ss.SSS", "yyyy\\MM\\dd HH:mm:ss.SSS", "yyyyMMddHHmmssSSS"}; + + private static final Map AVAILABLE_LOCALES = new HashMap(); + private static final char[][] XML_ESCAPE_CHARS = new char[63][]; + + /** 空字符串 */ + public static final String EMPTY_STRING = ""; + /** 默认字符编码 */ + public static final String DEFAULT_ENCODING = "UTF-8"; + /** 当前操作系统平台 */ + public static final String OS_PLATFORM = getOSName(); + /** 当前操作系统平台是否为 Windows */ + public static final boolean IS_WINDOWS_PLATFORM = isWindowsPlatform(); + /** 当前操作系统平台的换行符 */ + public static final String NEWLINE_CHAR = IS_WINDOWS_PLATFORM ? "\r\n" : "\n"; + + static + { + Locale[] locales = Locale.getAvailableLocales(); + for(Locale locale : locales) + AVAILABLE_LOCALES.put(locale.toString(), locale); + + XML_ESCAPE_CHARS[38] = "&" .toCharArray(); + XML_ESCAPE_CHARS[60] = "<" .toCharArray(); + XML_ESCAPE_CHARS[62] = ">" .toCharArray(); + XML_ESCAPE_CHARS[34] = """ .toCharArray(); + XML_ESCAPE_CHARS[39] = "'" .toCharArray(); + } + + /** 字符串转换为字节数组 */ + public static byte[] strGetBytes(String content, String charset) + { + try + { + return content.getBytes(charset); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** 字节数组转换为字符串 */ + public static String strFromBytes(byte[] content, String charset) + { + try + { + return new String(content, charset); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + /** 获取系统支持的所有 {@link Locale} */ + public final static Map getAvailableLocales() + { + return AVAILABLE_LOCALES; + } + + /** 获取系统支持的指定名称的 {@link Locale} */ + public final static Locale getAvailableLocale(String locale) + { + return AVAILABLE_LOCALES.get(locale); + } + + /** 检查字符串不为 null 或空字符串 */ + public final static boolean isStrNotEmpty(String str) + { + return str != null && str.length() != 0; + } + + /** 检查字符串不为 null 、空字符串或只包含空格 */ + public final static boolean isTrimStrNotEmpty(String str) + { + boolean result = isStrNotEmpty(str); + return result ? isStrNotEmpty(str.trim()) : result; + } + + /** 检查字符串为 null 或空字符串 */ + public final static boolean isStrEmpty(String str) + { + return !isStrNotEmpty(str); + } + + /** 检查字符串为 null 、空字符串或只包含空格 */ + public final static boolean isTrimStrEmpty(String str) + { + boolean result = isStrEmpty(str); + return result ? result : isStrEmpty(str.trim()); + } + + /** 把参数 str 转换为安全字符串:如果 str = null,则把它转换为空字符串 */ + public final static String safeString(String str) + { + if(str == null) + str = ""; + + return str; + } + + /** 把参数 obj 转换为安全字符串:如果 obj = null,则把它转换为空字符串 */ + public final static String safeString(Object obj) + { + if(obj == null) + return ""; + + return obj.toString(); + } + + /** 把参数 str 转换为安全字符串并执行去除前后空格:如果 str = null,则把它转换为空字符串 */ + public final static String safeTrimString(String str) + { + return safeString(str).trim(); + } + + /** 消除字符串 str 左侧的子字符串 word */ + public static final String trimLeft(String str, String word) + { + if(word != null && word.length() > 0) + { + int index = -1; + int length = word.length(); + + int i = 0; + while(str.indexOf(word, i) == i) + { + index = i; + i += length; + } + + if(index != -1) + { + index += length; + str = str.substring(index); + } + } + + return str; + } + + /** 消除字符串 str 右侧的子字符串 word */ + public static final String trimRight(String str, String word) + { + if(word != null && word.length() > 0) + { + int index = -1; + int length = word.length(); + + int j = -1; + int i = str.length() - 1; + while((j = i - (length - 1)) >= 0 && str.lastIndexOf(word, i) == j) + { + index = i; + i -= length; + } + + if(index != -1) + { + index -= (length - 1); + str = str.substring(0, index); + } + } + + return str; + } + + /** 消除字符串 str 左右两侧的子字符串 word */ + public static final String trim(String str, String word) + { + str = trimLeft(str, word); + str = trimRight(str, word); + + return str; + } + + /** 消除字符串 str 左右两侧的指定字符 */ + public static String trimChars(String str, String chars) + { + if(isStrEmpty(str)) + return safeString(str); + if(chars == null) + chars = ""; + + int len = str.length(); + int st = 0; + + while(st < len) + { + char c = str.charAt(st); + + if(c <= ' ' || chars.indexOf(c) >= 0) + ++st; + else + break; + } + + while(st < len) + { + char c = str.charAt(len - 1); + + if(c <= ' ' || chars.indexOf(c) >= 0) + --len; + else + break; + } + + return ((st > 0) || (len < str.length())) ? str.substring(st, len) : str; + } + + /** 检查字符串是否符合整数格式 */ + public final static boolean isStrNumeric(String str) + { + return PATTERN_NUMERIC.matcher(safeString(str)).matches(); + } + + /** 检查字符串是否符合电子邮件格式 */ + public final static boolean isStrEmailAddress(String str) + { + return PATTERN_EMAIL_ADDR.matcher(safeString(str)).matches(); + } + + /** 检查字符串是否符合 IP 地址格式 */ + public final static boolean isStrIPAddress(String str) + { + return PATTERN_IP_ADDR.matcher(safeString(str)).matches(); + } + + /** 检查字符串是否符合 HTML 超链接元素格式 */ + public final static boolean isStrLink(String str) + { + return PATTERN_LINK.matcher(safeString(str)).matches(); + } + + /** 检查字符串是否符合 URL 格式 */ + public final static boolean isStrURL(String str) + { + return PATTERN_HTTP_URL.matcher(safeString(str)).matches(); + } + + /** 置换常见的 XML 特殊字符 */ + public final static String escapeXML(String str) + { + if(!PATTERN_XML_ESCAPES.matcher(str).matches()) + return str; + + char[] src = str.toCharArray(); + StringBuilder sb = new StringBuilder(src.length); + + for(char c : src) + { + if(c > '>' || c < '"') + sb.append(c); + else + { + char[] dest = XML_ESCAPE_CHARS[c]; + + if(dest == null) + sb.append(c); + else + sb.append(dest); + } + } + + return sb.toString(); + } + + /** 屏蔽正则表达式的转义字符(但不屏蔽 ignores 参数中包含的字符) */ + public static final String escapeRegexChars(String str, char ... ignores) + { + final char ESCAPE_CHAR = '\\'; + final char[] REGEX_CHARS = {'.', ',', '?', '+', '-', '*', '^', '$', '|', '&', '{', '}', '[', ']', '(', ')', '\\'}; + + char[] regex_chars = REGEX_CHARS; + + if(ignores.length > 0) + { + Set cs = new HashSet(REGEX_CHARS.length); + + for(int i = 0; i < REGEX_CHARS.length; i++) + cs.add(REGEX_CHARS[i]); + for(int i = 0; i < ignores.length; i++) + cs.remove(ignores[i]); + + int i = 0; + regex_chars = new char[cs.size()]; + Iterator it = cs.iterator(); + + while(it.hasNext()) + regex_chars[i++] = it.next(); + } + + StringBuilder sb = new StringBuilder(); + + for(int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + + for(int j = 0; j < regex_chars.length; j++) + { + if(c == regex_chars[j]) + { + sb.append(ESCAPE_CHAR); + break; + } + } + + sb.append(c); + } + + return sb.toString(); + } + + /** 符分割字符串(分割符:" \t\n\r\f,;") */ + public final static String[] splitStr(String str) + { + return splitStr(str, " \t\n\r\f,;"); + } + + /** 符分割字符串(分割符:由 delim 参数指定) */ + public final static String[] splitStr(String str, String delim) + { + StringTokenizer st = new StringTokenizer(str, delim); + String[] array = new String[st.countTokens()]; + + int i = 0; + while(st.hasMoreTokens()) + array[i++] = st.nextToken(); + + return array; + } + + /** 调用 {@linkplain Thread#sleep(long)} 方法使当前线程睡眠 period 毫秒
+ * + * 如果 {@linkplain Thread#sleep(long)} 方法被中断则返回 false + * + */ + public final static boolean waitFor(long period) + { + if(period > 0) + { + try + { + Thread.sleep(period); + } + catch(Exception e) + { + return false; + } + } + else + Thread.yield(); + + return true; + } + + /** 调用 {@linkplain Thread#sleep(long)} 方法使当前线程睡眠 period 个 unit 时间单元
+ * + * 如果 {@linkplain Thread#sleep(long)} 方法被中断则返回 false + * + */ + public final static boolean waitFor(long period, TimeUnit unit) + { + return waitFor(unit.toMillis(period)); + } + + /** String -> Integer,如果转换不成功则返回 null */ + public final static Integer str2Int(String s) + { + Integer returnVal; + try { + returnVal = Integer.decode(safeTrimString(s)); + } catch(Exception e) { + returnVal = null; + } + return returnVal; + } + + /** String -> int,如果转换不成功则返回默认值 d */ + public final static int str2Int(String s, int d) + { + int returnVal; + try { + returnVal = Integer.parseInt(safeTrimString(s)); + } catch(Exception e) { + returnVal = d; + } + return returnVal; + } + + /** String -> int,如果转换不成功则返回 0 */ + public final static int str2Int_0(String s) + { + return str2Int(s, 0); + } + + /** String -> Short,如果转换不成功则返回 null */ + public final static Short str2Short(String s) + { + Short returnVal; + try { + returnVal = Short.decode(safeTrimString(s)); + } catch(Exception e) { + returnVal = null; + } + return returnVal; + } + + /** String -> short,如果转换不成功则返回默认值 d */ + public final static short str2Short(String s, short d) + { + short returnVal; + try { + returnVal = Short.parseShort(safeTrimString(s)); + } catch(Exception e) { + returnVal = d; + } + return returnVal; + } + + /** String -> short,如果转换不成功则返回 0 */ + public final static short str2Short_0(String s) + { + return str2Short(s, (short)0); + } + + /** String -> Long,如果转换不成功则返回 null */ + public final static Long str2Long(String s) + { + Long returnVal; + try { + returnVal = Long.decode(safeTrimString(s)); + } catch(Exception e) { + returnVal = null; + } + return returnVal; + } + + /** String -> long,如果转换不成功则返回默认值 d */ + public final static long str2Long(String s, long d) + { + long returnVal; + try { + returnVal = Long.parseLong(safeTrimString(s)); + } catch(Exception e) { + returnVal = d; + } + return returnVal; + } + + /** String -> long,如果转换不成功则返回 0 */ + public final static long str2Long_0(String s) + { + return str2Long(s, 0L); + } + + /** String -> Float,如果转换不成功则返回 null */ + public final static Float str2Float(String s) + { + Float returnVal; + try { + returnVal = Float.valueOf(safeTrimString(s)); + } catch(Exception e) { + returnVal = null; + } + return returnVal; + } + + /** String -> float,如果转换不成功则返回默认值 d */ + public final static float str2Float(String s, float d) + { + float returnVal; + try { + returnVal = Float.parseFloat(safeTrimString(s)); + } catch(Exception e) { + returnVal = d; + } + return returnVal; + } + + /** String -> float,如果转换不成功则返回 0 */ + public final static float str2Float_0(String s) + { + return str2Float(s, 0F); + } + + /** String -> Double,如果转换不成功则返回 null */ + public final static Double str2Double(String s) + { + Double returnVal; + try { + returnVal = Double.valueOf(safeTrimString(s)); + } catch(Exception e) { + returnVal = null; + } + return returnVal; + } + + /** String -> double,如果转换不成功则返回默认值 d */ + public final static double str2Double(String s, double d) + { + double returnVal; + try { + returnVal = Double.parseDouble(safeTrimString(s)); + } catch(Exception e) { + returnVal = d; + } + return returnVal; + } + + /** String -> double,如果转换不成功则返回 0.0 */ + public final static double str2Double_0(String s) + { + return str2Double(s, 0D); + } + + /** String -> Byte,如果转换不成功则返回 null */ + public final static Byte str2Byte(String s) + { + Byte returnVal; + try { + returnVal = Byte.decode(safeTrimString(s)); + } catch(Exception e) { + returnVal = null; + } + return returnVal; + } + + /** String -> byte,如果转换不成功则返回默认值 d */ + public final static byte str2Byte(String s, byte d) + { + byte returnVal; + try { + returnVal = Byte.parseByte(safeTrimString(s)); + } catch(Exception e) { + returnVal = d; + } + return returnVal; + } + + /** String -> byte,如果转换不成功则返回 0 */ + public final static byte str2Byte_0(String s) + { + return str2Byte(s, (byte)0); + } + + /** String -> Character,如果转换不成功则返回 null */ + public final static Character str2Char(String s) + { + Character returnVal; + try { + returnVal = safeTrimString(s).charAt(0); + } catch(Exception e) { + returnVal = null; + } + return returnVal; + } + + /** String -> char,如果转换不成功则返回默认值 d */ + public final static char str2Char(String s, char d) + { + char returnVal; + try { + returnVal = safeTrimString(s).charAt(0); + } catch(Exception e) { + returnVal = d; + } + return returnVal; + } + + /** String -> char,如果转换不成功则返回 0 */ + public final static char str2Char_0(String s) + { + return str2Char(s, Character.MIN_VALUE); + } + + /** String -> Boolean,如果转换不成功则返回 null */ + public final static Boolean str2Boolean(String s) + { + return Boolean.valueOf(safeTrimString(s)); + } + + /** String -> boolean,如果转换不成功则返回默认值 d */ + public final static boolean str2Boolean(String s, boolean d) + { + s = safeTrimString(s); + + if(s.equalsIgnoreCase("true")) + return true; + else if(s.equalsIgnoreCase("false")) + return false; + + return d; + } + + /** String -> boolean,如果转换不成功则返回 0 */ + public final static boolean str2Boolean_False(String s) + { + return str2Boolean(s, false); + } + + /** String -> java.util.Date, str 的格式由 format 定义 */ + public final static Date str2Date(String str, String format) + { + Date date = null; + + try + { + DateFormat df = new SimpleDateFormat(format); + date = df.parse(safeTrimString(str)); + } + catch(Exception e) + { + + } + + return date; + } + + /** String -> java.util.Date,由函数自身判断 str 的格式 */ + public final static Date str2Date(String str) + { + Date date = null; + + try + { + final char SEPARATOR = '-'; + final String[] PATTERN = {"yyyy", "MM", "dd", "HH", "mm", "ss", "SSS"}; + String[] values = safeTrimString(str).split("\\D"); + String[] element = new String[values.length]; + + int length = 0; + for(String e : values) + { + e = e.trim(); + if(e.length() != 0) + { + element[length++] = e; + if(length == PATTERN.length) + break; + } + } + + if(length > 0) + { + StringBuilder value = new StringBuilder(); + + if(length > 1) + { + for(int i = 0; i < length; ++i) + { + value.append(element[i]); + value.append(SEPARATOR); + } + } + else + { + String src = element[0]; + int remain = src.length(); + int pos = 0; + int i = 0; + + for(i = 0; remain > 0 && i < PATTERN.length; ++i) + { + int p_length = PATTERN[i].length(); + int v_length = Math.min(p_length, remain); + String v = src.substring(pos, pos + v_length); + pos += v_length; + remain -= v_length; + + value.append(v); + value.append(SEPARATOR); + } + + length = i; + } + + StringBuilder format = new StringBuilder(); + + for(int i = 0; i < length; ++i) + { + format.append(PATTERN[i]); + format.append(SEPARATOR); + } + + date = str2Date(value.toString(), format.toString()); + } + } + catch(Exception e) + { + + } + + return date; + } + + /** String -> java.util.Date,由 Patterns 指定可能的日期格式 */ + public final static Date str2Date(String str, String[] Patterns) + { + Date date = null; + + for(int i = 0; i < Patterns.length; ++i) + { + date = str2Date(str, Patterns[i]); + + if( date != null) + break; + } + + return date; + } + + /** String -> java.util.Date,由 GeneralHelper.SHORT_DATE_PATTERN 指定可能的日期格式 */ + public final static Date str2ShortDate(String str) + { + return str2Date(str, SHORT_DATE_PATTERN); + } + + /** String -> java.util.Date,由 GeneralHelper.LONG_DATE_PATTERN 指定可能的日期格式 */ + public final static Date str2LongDate(String str) + { + return str2Date(str, LONG_DATE_PATTERN); + } + + /** String -> java.util.Date,由 GeneralHelper.LONG_DATE_PATTERN_WITH_MILSEC 指定可能的日期格式 */ + public final static Date str2LongDateWithMilliSecond(String str) + { + return str2Date(str, LONG_DATE_PATTERN_WITH_MILSEC); + } + + /** 类型转换处理器接口 */ + public static interface TypeHandler + { + T handle(String v); + } + + /** String -> Any,字符串转换为 8 种基础数据类型、及其包装类 {@link Date}、 或 {@link String} + * + * @param type : 目标类型的 {@link Class} 对象 + * @param v : 要转换的字符串 + * @return : 转换结果,如果转换不成功返回 null + * @throws : 如果目标类型不支持抛出 {@link IllegalArgumentException} + * + */ + public static final T str2Object(Class type, String v) + { + return str2Object(type, v, null); + } + + /** String -> Any,如果 handler 为 null 则把字符串转换为 8 种基础数据类型、及其包装类、 {@link Date} 或 {@link String}, + * 如果 handler 不为 null 则由 handler 执行转换 + * + * @param type : 目标类型的 {@link Class} 对象 + * @param v : 要转换的字符串 + * @param handler : 类型转换处理器 + * @return : 转换结果,如果转换不成功返回 null + * @throws : 如果目标类型不支持抛出 {@link IllegalArgumentException} + * + */ + @SuppressWarnings("unchecked") + public static final T str2Object(Class type, String v, TypeHandler handler) + { + Object param = null; + + if(handler != null) + return handler.handle(v); + + if(type == String.class) + param = safeTrimString(v); + else if(type == int.class) + param = str2Int_0(v); + else if(type == long.class) + param = str2Long_0(v); + else if(type == byte.class) + param = str2Byte_0(v); + else if(type == char.class) + param = str2Char_0(v); + else if(type == float.class) + param = str2Float_0(v); + else if(type == double.class) + param = str2Double_0(v); + else if(type == short.class) + param = str2Short_0(v); + else if(type == boolean.class) + param = str2Boolean_False(v); + else if(type == Integer.class) + param = str2Int(v); + else if(type == Long.class) + param = str2Long(v); + else if(type == Byte.class) + param = str2Byte(v); + else if(type == Character.class) + param = str2Char(v); + else if(type == Float.class) + param = str2Float(v); + else if(type == Double.class) + param = str2Double(v); + else if(type == Short.class) + param = str2Short(v); + else if(type == Boolean.class) + param = str2Boolean(v); + else if(Date.class.isAssignableFrom(type)) + param = str2Date(v); + else + throw new IllegalArgumentException(String.format("object type '%s' not valid", type)); + + return (T)param; + } + + /** Any -> Object[]
+ * + * obj == null : 返回 Object[] {null}
+ * obj 为对象数组 : 强制转换为 Object[], 并返回自身
+ * obj 为基础类型数组 : 返回 Object[], 其元素类型为基础类型的包装类
+ * obj 为 {@link Collection} : 通过 toArray() 方法返回 Object[]
+ * obj 为 {@link Iterable} : 遍历 {@link Iterable}, 并返回包含其所有元素的 Object[]
+ * obj 为 {@link Iterator} : 遍历 {@link Iterator}, 并返回包含其所有元素的 Object[]
+ * obj 为 {@link Enumeration} : 遍历 {@link Enumeration}, 并返回包含其所有元素的 Object[]
+ * obj 为普通对象 : 返回 Object[] {obj}
+ * + * @param obj : 任何对象 + * + */ + public static final Object[] object2Array(Object obj) + { + Object[] array; + + if(obj == null) + array = new Object[] {obj}; + else if(obj.getClass().isArray()) + { + Class clazz = obj.getClass().getComponentType(); + + if(Object.class.isAssignableFrom(clazz)) + array = (Object[])obj; + else + { + int length = Array.getLength(obj); + + if(length > 0) + { + array = new Object[length]; + + for(int i = 0; i < length; i++) + array[i] = Array.get(obj, i); + } + else + array = new Object[0]; + } + } + else if(obj instanceof Collection) + array = ((Collection)obj).toArray(); + else if(obj instanceof Iterable) + { + List list = new ArrayList(); + Iterator it = ((Iterable)obj).iterator(); + + while(it.hasNext()) + list.add(it.next()); + + array = list.toArray(); + } + else if(obj instanceof Iterator) + { + List list = new ArrayList(); + Iterator it = (Iterator)obj; + + while(it.hasNext()) + list.add(it.next()); + + array = list.toArray(); + } + else if(obj instanceof Enumeration) + { + List list = new ArrayList(); + Enumeration it = (Enumeration)obj; + + while(it.hasMoreElements()) + list.add(it.nextElement()); + + array = list.toArray(); + } + else + array = new Object[] {obj}; + + return array; + } + + /** 返回 date 加上 value 天后的日期(清除时间信息) */ + public final static Date addDate(Date date, int value) + { + return addDate(date, value, true); + } + + /** 返回 date 加上 value 天后的日期,trimTime 指定是否清除时间信息 */ + public final static Date addDate(Date date, int value, boolean trimTime) + { + return addTime(date, Calendar.DATE, value, trimTime); + + } + + /** 返回 date 加上 value 个 field 时间单元后的日期(不清除时间信息) */ + public final static Date addTime(Date date, int field, int value) + { + return addTime(date, field, value, false); + } + + /** 返回 date 加上 value 个 field 时间单元后的日期,trimTime 指定是否去除时间信息 */ + public final static Date addTime(Date date, int field, int value, boolean trimTime) + { + Calendar c = Calendar.getInstance(); + c.setTime(date); + c.add(field, value); + + if(trimTime) + { + c.set(Calendar.HOUR, 0); + c.set(Calendar.MINUTE, 0); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + } + + return c.getTime(); + + } + + /** java.util.Date -> String,str 的格式由 format 定义 */ + public final static String date2Str(Date date, String format) + { + DateFormat df = new SimpleDateFormat(format); + return df.format(date); + } + + /** 修整 SQL 语句字符串:' -> '',(includeWidlcard 指定是否对星号和问号作转换:* -> %, ? -> _) */ + public static final String regularSQLStr(String str, boolean includeWidlcard) + { + str = str.replace("'", "''"); + + if(includeWidlcard) + { + str = str.replace('*', '%'); + str = str.replace('?', '_'); + } + + return str; + } + + /** 获取 clazz 的 {@link ClassLoader} 对象,如果为 null 则返回当前线程的 Context {@link ClassLoader} */ + public static final ClassLoader getClassLoader(Class clazz) + { + ClassLoader loader = clazz.getClassLoader(); + + if(loader == null) + loader = Thread.currentThread().getContextClassLoader(); + + return loader; + } + + /** 加载类名为 className 的 {@link Class} 对象,如果加载失败则返回 null */ + public static final Class loadClass(String className) + { + Class clazz = null; + ClassLoader loader = getClassLoader(GeneralHelper.class); + + try + { + clazz = loader.loadClass(className); + } + catch(ClassNotFoundException e) + { + + } + + return clazz; + } + + /** 用 {@linkplain Class#forName(String)} 加载 {@link Class} 对象,如果加载失败则返回 null */ + public static final Class classForName(String name) + { + Class clazz = null; + + try + { + clazz = Class.forName(name); + } + catch(ClassNotFoundException e) + { + + } + + return clazz; + } + + /** 用 {@linkplain Class#forName(String, boolean, ClassLoader)} 加载 {@link Class} 对象,如果加载失败则返回 null */ + public static final Class classForName(String name, boolean initialize, ClassLoader loader) + { + Class clazz = null; + + try + { + clazz = Class.forName(name, initialize, loader); + } + catch(ClassNotFoundException e) + { + + } + + return clazz; + } + + /** 获取 clazz 资源环境中 resPath 相对路径的 URL 对象 */ + public static final URL getClassResource(Class clazz, String resPath) + { + URL url = clazz.getResource(resPath); + + if(url == null) + { + ClassLoader loader = clazz.getClassLoader(); + if(loader != null) url = loader.getResource(resPath); + + if(url == null) + { + loader = Thread.currentThread().getContextClassLoader(); + if(loader != null) url = loader.getResource(resPath); + } + } + + return url; + } + + /** 获取 clazz 资源环境中 resPath 相对路径的 URL 对象列表 */ + public static final List getClassResources(Class clazz, String resPath) + { + List urlList = new ArrayList(); + Enumeration urls = null; + + try + { + ClassLoader loader = clazz.getClassLoader(); + if(loader != null) urls = loader.getResources(resPath); + + if(urls == null || !urls.hasMoreElements()) + { + loader = Thread.currentThread().getContextClassLoader(); + if(loader != null) urls = loader.getResources(resPath); + } + } + catch(IOException e) + { + throw new RuntimeException(e); + } + + if(urls != null) + { + while(urls.hasMoreElements()) + urlList.add(urls.nextElement()); + } + + return urlList; + } + + /** 获取 clazz 资源环境中 resPath 的 {@link InputStream} */ + public static final InputStream getClassResourceAsStream(Class clazz, String resPath) + { + InputStream is = clazz.getResourceAsStream(resPath); + + if(is == null) + { + ClassLoader loader = clazz.getClassLoader(); + if(loader != null) is = loader.getResourceAsStream(resPath); + + if(is == null) + { + loader = Thread.currentThread().getContextClassLoader(); + if(loader != null) is = loader.getResourceAsStream(resPath); + } + } + + return is; + } + + /** 获取 clazz 资源环境中 resPath 相对路径的 URL 绝对路径(返还的绝对路径用 UTF-8 编码) */ + public static final String getClassResourcePath(Class clazz, String resPath) + { + return getClassResourcePath(clazz, resPath, DEFAULT_ENCODING); + } + + /** 获取 clazz 资源环境中 resPath 相对路径的 URL 绝对路径(返还的绝对路径用 pathEnc 编码) */ + public static final String getClassResourcePath(Class clazz, String resPath, String pathEnc) + { + String path = null; + + try + { + URL url = getClassResource(clazz, resPath); + + if(url != null) + { + path = url.getPath(); + path = URLDecoder.decode(path, pathEnc); + } + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + + return path; + } + + /** 获取 clazz 资源环境中 resPath 相对路径的 URL 绝对路径列表(返还的绝对路径用 UTF-8 编码) */ + public static final List getClassResourcePaths(Class clazz, String resPath) + { + return getClassResourcePaths(clazz, resPath, DEFAULT_ENCODING); + } + + /** 获取 clazz 资源环境中 resPath 相对路径的 URL 绝对路径列表(返还的绝对路径用 pathEnc 编码) */ + public static final List getClassResourcePaths(Class clazz, String resPath, String pathEnc) + { + List pathList = new ArrayList(); + + try + { + List urlList = getClassResources(clazz, resPath); + + for(URL url : urlList) + { + String path = URLDecoder.decode(url.getPath(), pathEnc); + pathList.add(path); + } + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + + return pathList; + } + + /** 获取 clazz 资源环境的当前 URL 绝对路径(返回的绝对路径用 pathEnc 编码) */ + public static final String getClassPath(Class clazz) + { + return getClassResourcePath(clazz, "."); + } + + /** 获取 resource 资源的 locale 本地化文件中名字为 key 的字符串资源,并代入 params 参数 */ + public static final String getResourceMessage(Locale locale, String resource, String key, Object ... params) + { + ResourceBundle bundle = ResourceBundle.getBundle(resource, locale); + String msg = bundle.getString(key); + + if(params != null && params.length > 0) + msg = MessageFormat.format(msg, params); + + return msg; + } + + /** 获取 resource 资源的默认本地化文件中名字为 key 的字符串资源,并代入 params 参数 */ + public static final String getResourceMessage(String resource, String key, Object ... params) + { + return getResourceMessage(Locale.getDefault(), resource, key, params); + } + + /** 获取 e 异常的堆栈信息,最大的堆栈层数由 levels 指定 */ + public static final String getExceptionMessageStackString(Throwable e, int levels) + { + StringBuilder sb = new StringBuilder(); + + if(levels == 0) + levels = Integer.MAX_VALUE; + + for(int i = 0; i < levels; ++i) + { + if(i > 0) sb.append("Caused by -> "); + sb.append(e.getClass().getName()); + + String msg = e.getLocalizedMessage(); + if(msg != null) sb.append(": ").append(msg); + + e = e.getCause(); + if(e == null) + break; + } + + return sb.toString(); + } + + /** 获取 e 异常的整个堆栈列表 */ + public static final String getExceptionMessageStackString(Throwable e) + { + return getExceptionMessageStackString(e, 0); + } + + /** 获取 e 异常的堆栈列表,最大的堆栈层数由 levels 指定 */ + public static final List getExceptionMessageStack(Throwable e, int levels) + { + List list = new ArrayList(); + + if(levels == 0) + levels = Integer.MAX_VALUE; + + for(int i = 0; i < levels; ++i) + { + StringBuilder sb = new StringBuilder(); + + if(i > 0) sb.append("Caused by -> "); + sb.append(e.getClass().getName()); + + String msg = e.getLocalizedMessage(); + if(msg != null) sb.append(": ").append(msg); + + list.add(sb.toString()); + + e = e.getCause(); + if(e == null) + break; + } + + return list; + } + + /** 获取 e 异常的整个堆栈列表 */ + public static final List getExceptionMessageStack(Throwable e) + { + return getExceptionMessageStack(e, 0); + } + + /** 输出 e 异常的 levels 层堆栈列表到 ps 中 */ + public static final void printExceptionMessageStack(Throwable e, int levels, PrintStream ps) + { + List list = getExceptionMessageStack(e, levels); + + for(String msg : list) + ps.println(msg); + } + + /** 输出 e 异常的 levels 层堆栈列表到标准错误流中 */ + public static final void printExceptionMessageStack(Throwable e, int levels) + { + printExceptionMessageStack(e, levels, System.err); + } + + /** 输出 e 异常的整个堆栈列表到 ps 中 */ + public static final void printExceptionMessageStack(Throwable e, PrintStream ps) + { + printExceptionMessageStack(e, 0, ps); + } + + /** 输出 e 异常的整个堆栈列表到标准错误流中 */ + public static final void printExceptionMessageStack(Throwable e) + { + printExceptionMessageStack(e, 0); + } + + /** 把元素添加到 {@link Map} 中,不保证线程安全(不替换原值) */ + public static final boolean tryPut(Map map, K key, V value) + { + return tryPut(map, key, value, false); + } + + /** 把元素添加到 {@link Map} 中,不保证线程安全 */ + public static final boolean tryPut(Map map, K key, V value, boolean replace) + { + if(replace || !map.containsKey(key)) + { + map.put(key, value); + return true; + } + + return false; + } + + /** 把元素添加到 {@link Map} 中,并保证线程安全(不替换原值) */ + public static final boolean syncTryPut(Map map, K key, V value) + { + return syncTryPut(map, key, value, false); + } + + /** 把元素添加到 {@link Map} 中,并保证线程安全 */ + public static final boolean syncTryPut(Map map, K key, V value, boolean replace) + { + synchronized(map) + { + return tryPut(map, key, value, replace); + } + } + + /** 把元素添加到 {@link Map} 中,不保证线程安全(不替换原值) */ + public static final int tryPutAll(Map map, Map src) + { + return tryPutAll(map, src, false); + } + + /** 把元素添加到 {@link Map} 中,不保证线程安全 */ + public static final int tryPutAll(Map map, Map src, boolean replace) + { + if(replace) + { + map.putAll(src); + return src.size(); + } + + int count = 0; + Set> entries = src.entrySet(); + + for(Map.Entry e : entries) + { + if(!map.containsKey(e.getKey())) + { + map.put(e.getKey(), e.getValue()); + ++count; + } + } + + return count; + } + + /** 把元素添加到 {@link Map} 中,并保证线程安全(不替换原值) */ + public static final int syncTryPutAll(Map map, Map src) + { + return syncTryPutAll(map, src, false); + } + + /** 把元素添加到 {@link Map} 中,并保证线程安全 */ + public static final int syncTryPutAll(Map map, Map src, boolean replace) + { + synchronized(map) + { + return tryPutAll(map, src, replace); + } + } + + /** 从 {@link Map} 中删除元素,不保证线程安全 */ + public static final boolean tryRemove(Map map, K key) + { + if(map.containsKey(key)) + { + map.remove(key); + return true; + } + + return false; + } + + /** 从 {@link Map} 中删除元素,并保证线程安全 */ + public static final boolean syncTryRemove(Map map, K key) + { + synchronized(map) + { + return tryRemove(map, key); + } + } + + /** 清空 {@link Map},不保证线程安全 */ + public static final void tryClear(Map map) + { + map.clear(); + } + + /** 清空 {@link Map},并保证线程安全 */ + public static final void syncTryClear(Map map) + { + synchronized(map) + { + tryClear(map); + } + } + + /** 获取当前 JVM 进程的 ID */ + public static final int getProcessId() + { + return Integer.parseInt(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]); + } + + /** 获取当前 JVM 进程的 Java 版本 */ + public static final String getJavaVersion() + { + return System.getProperty("java.version"); + } + + /** 获取当前操作系统的名称 */ + public static final String getOSName() + { + return System.getProperty("os.name"); + } + + /** 检查当前操作系统是否为 Windows 系列 */ + public static final boolean isWindowsPlatform() + { + // return CURRENT_OS.toUpperCase().indexOf("WINDOWS") != -1; + + return File.pathSeparatorChar == ';'; + } + + /** 按拼音排序的字符串比较器 */ + public static class PinYinComparator implements Comparator + { + @Override + public int compare(String o1, String o2) + { + java.text.Collator cmp = java.text.Collator.getInstance(java.util.Locale.CHINA); + return cmp.compare(o1, o2); + } + } + + /** 按文件名称进行文件筛选的文件过滤器,构造函数参数 name 指定文件名的正则表达式 */ + public static class FileNameFileFilter implements FileFilter + { + protected static final int FLAGS = IS_WINDOWS_PLATFORM ? Pattern.CASE_INSENSITIVE : 0; + + Pattern pattern; + + public FileNameFileFilter(String name) + { + String exp = name; + exp = exp.replace('.', '#'); + exp = exp.replaceAll("#", "\\\\."); + exp = exp.replace('*', '#'); + exp = exp.replaceAll("#", ".*"); + exp = exp.replace('?', '#'); + exp = exp.replaceAll("#", ".?"); + exp = "^" + exp + "$"; + + pattern = Pattern.compile(exp, FLAGS); + } + + @Override + public boolean accept(File file) + { + Matcher matcher = pattern.matcher(file.getName()); + return matcher.matches(); + } + } + + public static final String longList2Str(List list) + { + return list2Str(list); + } + + public static final String intList2Str(List list) + { + return list2Str(list); + } + + public static final String strList2Str(List list) + { + return list2Str(list); + } + + public static final String list2Str(List list) + { + String str = null; + + if(list != null) + { + int size = list.size(); + + if(size > 0) + { + StringBuilder sb = new StringBuilder(); + + for(int i = 0; i < size; i++) + { + sb.append(list.get(i)); + if(i < size - 1) + sb.append(','); + } + + str = sb.toString(); + } + } + + return str; + } + + public static final List str2LongList(String str) + { + return str2LongList(str, DELIMITERR_CHARS); + } + + public static final List str2LongList(String str, String delim) + { + List list = null; + + if(str != null) + { + StringTokenizer st = new StringTokenizer(str, delim); + list = new ArrayList(st.countTokens()); + + while(st.hasMoreTokens()) + list.add(Long.parseLong(st.nextToken())); + } + + return list != null ? list : new ArrayList(); + } + + public static final List str2IntList(String str) + { + return str2IntList(str, DELIMITERR_CHARS); + } + + public static final List str2IntList(String str, String delim) + { + List list = null; + + if(str != null) + { + StringTokenizer st = new StringTokenizer(str, delim); + list = new ArrayList(st.countTokens()); + + while(st.hasMoreTokens()) + list.add(Integer.parseInt(st.nextToken())); + } + + return list != null ? list : new ArrayList(); + } + + public static final List str2StrList(String str) + { + return str2StrList(str, DELIMITERR_CHARS); + } + + public static final List str2StrList(String str, String delim) + { + List list = null; + + if(str != null) + { + StringTokenizer st = new StringTokenizer(str, delim); + list = new ArrayList(st.countTokens()); + + while(st.hasMoreTokens()) + list.add(st.nextToken()); + } + + return list != null ? list : new ArrayList(); + } + + public static final Integer str2IntRounding(String str) + { + Double v = str2Double(str); + + if(v == null) + return null; + + return (int)(v + 0.5D); + } + + public static final int str2IntRounding(String str, int def) + { + Integer v = str2IntRounding(str); + + if(v == null) + return def; + + return v.intValue(); + } + + public static final int str2IntRounding_0(String str) + { + return str2IntRounding(str, 0); + } + + public static final boolean isNullOrEmpty(Collection c) + { + return c == null || c.isEmpty(); + } + + public static final boolean isNotNullOrEmpty(Collection c) + { + return !isNullOrEmpty(c); + } + + public static final boolean isNullOrEmpty(Map m) + { + return m == null || m.isEmpty(); + } + + public static final boolean isNotNullOrEmpty(Map m) + { + return !isNullOrEmpty(m); + } + + public static final int compare(Number n, byte v) + { + if(n == null) + return -1; + + return n.byteValue() - v; + } + + public static final boolean equals(Number n, byte v) + { + return compare(n, v) == 0; + } + + public static final int compare(Number n, short v) + { + if(n == null) + return -1; + + return n.shortValue() - v; + } + + public static final boolean equals(Number n, short v) + { + return compare(n, v) == 0; + } + + public static final int compare(Number n, int v) + { + if(n == null) + return -1; + + return n.intValue() - v; + } + + public static final boolean equals(Number n, int v) + { + return compare(n, v) == 0; + } + + public static final long compare(Number n, long v) + { + if(n == null) + return -1; + + return n.longValue() - v; + } + + public static final boolean equals(Number n, long v) + { + return compare(n, v) == 0; + } + + public static final boolean isNullOrZero(Byte n) + { + return n == null || n.byteValue() == (byte)0; + } + + public static final boolean isNullOrZero(Short n) + { + return n == null || n.shortValue() == (short)0; + } + + public static final boolean isNullOrZero(Integer n) + { + return n == null || n.intValue() == 0; + } + + public static final boolean isNullOrZero(Long n) + { + return n == null || n.longValue() == 0L; + } + + public static final boolean isNotNullAndZero(Byte n) + { + return !isNullOrZero(n); + } + + public static final boolean isNotNullAndZero(Short n) + { + return !isNullOrZero(n); + } + + public static final boolean isNotNullAndZero(Integer n) + { + return !isNullOrZero(n); + } + + public static final boolean isNotNullAndZero(Long n) + { + return !isNullOrZero(n); + } + + public static final boolean isNullOrNan(Float n) + { + return n == null || n.equals(Float.NaN); + } + + public static final boolean isNullOrNan(Double n) + { + return n == null || n.equals(Double.NaN); + } + + public static final boolean isNotNullAndNan(Float n) + { + return !isNullOrNan(n); + } + + public static final boolean isNotNullAndNan(Double n) + { + return !isNullOrNan(n); + } + + public static final boolean isNull(Object o) + { + return (o == null); + } + + public static final boolean isNotNull(Object o) + { + return !isNull(o); + } + + public static final String formatNumber(Number n) + { + if(n == null) + return ""; + + long lv = n.longValue(); + double dv = (double)lv; + + if(n.doubleValue() == dv) + return Long.toString(lv); + + return n.toString(); + } + + public static final int roundInt(Number n) + { + return (int)(double)(n.doubleValue() + 0.5D); + } + + public static final String mask(String str, int size, int offset) + { + if(GeneralHelper.isStrEmpty(str)) + return ""; + + int len = str.length(); + int end = len - offset; + int begin = len - size - offset; + + if(end <= 0) + return str; + if(begin < 0) + begin = 0; + + StringBuilder sb = new StringBuilder(len); + + for(int i = 0; i < len; i++) + { + if(i >= begin && i < end) + sb.append('*'); + else + sb.append(str.charAt(i)); + } + + return sb.toString(); + } + + public static final > T enumLookup(Class enumClass, String name, boolean ignoreCase) + { + Stream stream = Arrays.stream(enumClass.getEnumConstants()); + + return (ignoreCase) + ? stream.filter((e) -> e.name().equalsIgnoreCase(name)).findFirst().orElse(null) + : stream.filter((e) -> e.name().equals(name)).findFirst().orElse(null); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Pair.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Pair.java new file mode 100644 index 0000000..94d31d0 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Pair.java @@ -0,0 +1,103 @@ +package io.github.hpsocket.soa.framework.core.util; + +import java.io.Serializable; + +/** 通用值对
+ * + * {@link Pair#first} - 第一个值
+ * {@link Pair#second} - 第二个值 + * + * */ +@SuppressWarnings("serial") +public class Pair implements Serializable +{ + private F first; + private S second; + + public Pair() + { + } + + public Pair(F first) + { + set(first, null); + } + + public Pair(F first, S second) + { + set(first, second); + } + + public Pair(Pair other) + { + set(other.first, other.second); + } + + public F getFirst() + { + return first; + } + + public void setFirst(F first) + { + this.first = first; + } + + public S getSecond() + { + return second; + } + + public void setSecond(S second) + { + this.second = second; + } + + public void set(F first, S second) + { + this.first = first; + this.second = second; + } + + @Override + public boolean equals(Object obj) + { + if(this == obj) + return true; + + if(obj instanceof Pair) + { + Pair other = (Pair)obj; + + if(first == other.first && second == other.second) + return true; + + if(first != null && !first.equals(other.first)) + return false; + else if(first == null && other.first != null) + return false; + + if(second != null && !second.equals(other.second)) + return false; + else if(second == null && other.second != null) + return false; + + return true; + } + + return false; + } + + @Override + public int hashCode() + { + return (first != null ? first.hashCode() : 0) ^ (second != null ? second.hashCode() : 0); + } + + @Override + public String toString() + { + return String.format("{%s, %s}", first, second); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Range.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Range.java new file mode 100644 index 0000000..9c79ed6 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Range.java @@ -0,0 +1,110 @@ +package io.github.hpsocket.soa.framework.core.util; + +/** 通用数值范围类 */ +public class Range +{ + private T begin; + private T end; + + public Range() + { + + } + + public Range(T begin, T end) + { + this.begin = begin; + this.end = end; + } + + public T getBegin() + { + return begin; + } + + public void setBegin(T begin) + { + this.begin = begin; + } + + public T getEnd() + { + return end; + } + + public void setEnd(T end) + { + this.end = end; + } + + public byte byteSize() + { + return (byte)(end.byteValue() - begin.byteValue()); + } + + public short shortSize() + { + return (short)(end.shortValue() - begin.shortValue()); + } + + public int intSize() + { + return end.intValue() - begin.intValue(); + } + + public long longSize() + { + return end.longValue() - begin.longValue(); + } + + public float floatSize() + { + return end.floatValue() - begin.floatValue(); + } + + public double doubleSize() + { + return end.doubleValue() - begin.doubleValue(); + } + + @Override + public boolean equals(Object obj) + { + if(this == obj) + return true; + + if(obj instanceof Range) + { + Range other = (Range)obj; + + if(begin == other.begin && end == other.end) + return true; + + if(begin != null && !begin.equals(other.begin)) + return false; + else if(begin == null && other.begin != null) + return false; + + if(end != null && !end.equals(other.end)) + return false; + else if(end == null && other.end != null) + return false; + + return true; + } + + return false; + } + + @Override + public int hashCode() + { + return (begin != null ? begin.hashCode() : 0) ^ (end != null ? end.hashCode() : 0); + } + + @Override + public String toString() + { + return String.format("{%s - %s}", begin, end); + } +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Result.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Result.java new file mode 100644 index 0000000..e000ef8 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/Result.java @@ -0,0 +1,172 @@ +package io.github.hpsocket.soa.framework.core.util; + +import java.util.Date; + +/** 通用操作结果
+ * + * {@link Result#flag} - 结果状态标志
+ * {@link Result#value} - 结果值 + * + * */ +public class Result +{ + /** 获取一个 {@link Result} 对象初始值:{{@link Boolean#FALSE}, null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialBoolean() + { + return new Result(Boolean.FALSE); + } + + /** 获取一个 {@link Result} 对象初始值:{byte(0), null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialByte() + { + return new Result(0); + } + + /** 获取一个 {@link Result} 对象初始值:{char(0), null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialChar() + { + return new Result(0); + } + + /** 获取一个 {@link Result} 对象初始值:{short(0), null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialShort() + { + return new Result(0); + } + + /** 获取一个 {@link Result} 对象初始值:{int(0), null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialInt() + { + return new Result(0); + } + + /** 获取一个 {@link Result} 对象初始值:{long(0), null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialLong() + { + return new Result(0L); + } + + /** 获取一个 {@link Result} 对象初始值:{{@link Double#NaN}, null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialFloat() + { + return new Result(Float.NaN); + } + + /** 获取一个 {@link Result} 对象初始值:{{@link Double#NaN}, null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialDouble() + { + return new Result(Double.NaN); + } + + /** 获取一个 {@link Result} 对象初始值:{"", null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialString() + { + return new Result(""); + } + + /** 获取一个 {@link Result} 对象初始值:{Date("1970-1-1 00:00:00"), null} */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final Result initialDate() + { + return new Result(new Date(0)); + } + + private F flag; + private V value; + + public Result() + { + } + + public Result(F flag) + { + set(flag, null); + } + + public Result(F flag, V value) + { + set(flag, value); + } + + public Result(Result other) + { + set(other.flag, other.value); + } + + public F getFlag() + { + return flag; + } + + public void setFlag(F flag) + { + this.flag = flag; + } + + public V getValue() + { + return value; + } + + public void setValue(V value) + { + this.value = value; + } + + public void set(F flag, V value) + { + this.flag = flag; + this.value = value; + } + + @Override + public boolean equals(Object obj) + { + if(this == obj) + return true; + + if(obj instanceof Result) + { + Result other = (Result)obj; + + if(flag == other.flag && value == other.value) + return true; + + if(flag != null && !flag.equals(other.flag)) + return false; + else if(flag == null && other.flag != null) + return false; + + if(value != null && !value.equals(other.value)) + return false; + else if(value == null && other.value != null) + return false; + + return true; + } + + return false; + } + + @Override + public int hashCode() + { + return (flag != null ? flag.hashCode() : 0) ^ (value != null ? value.hashCode() : 0); + } + + @Override + public String toString() + { + return String.format("{%s, %s}", flag, value); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/SystemUtil.java b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/SystemUtil.java new file mode 100644 index 0000000..38c95b3 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/java/io/github/hpsocket/soa/framework/core/util/SystemUtil.java @@ -0,0 +1,147 @@ + +package io.github.hpsocket.soa.framework.core.util; + +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Set; + +/** 系统信息帮助类 */ +public class SystemUtil +{ + + private static String pid; + private static Addr addr; + + public static class Addr + { + public String host; + public String ip; + public Set ips = new LinkedHashSet(); + } + + public static String getPid() + { + checkPid(); + + return pid; + } + + public static String getHosName() + { + checkAddr(); + + return addr.host; + } + + public static String getAddress() + { + checkAddr(); + + return addr.ip; + } + + public static Set getAddresses() + { + checkAddr(); + + return addr.ips; + } + + private static void checkPid() + { + if(pid == null) + { + synchronized(SystemUtil.class) + { + if(pid == null) + { + String arr[] = ManagementFactory.getRuntimeMXBean().getName().split("@"); + + if(arr.length > 0) + pid = arr[0]; + } + } + } + } + + private static void checkAddr() + { + if(addr == null) + { + synchronized(SystemUtil.class) + { + if(addr == null) + { + addr = getNetworkAddress(); + } + } + } + } + + public static Addr getNetworkAddress() + { + Addr addr = new Addr(); + + try + { + Enumeration ifs = NetworkInterface.getNetworkInterfaces(); + + while(ifs.hasMoreElements()) + { + NetworkInterface ni = ifs.nextElement(); + Enumeration addresses = ni.getInetAddresses(); + + while(addresses.hasMoreElements()) + { + InetAddress ia = addresses.nextElement(); + + if(ia.isSiteLocalAddress()) + { + addr.ips.add(ia.getHostAddress()); + } + } + } + + InetAddress localAddr = InetAddress.getLocalHost(); + + if(!addr.ips.isEmpty()) + addr.ip = addr.ips.iterator().next(); + else + { + addr.ip = localAddr.getHostAddress(); + + if(GeneralHelper.isStrNotEmpty(addr.ip)) + addr.ips.add(addr.ip); + } + + addr.host = localAddr.getHostName(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + + return addr; + } + + public static boolean isLocalNetwork(String ip) + { + if(GeneralHelper.isStrEmpty(ip)) + return false; + + try + { + InetAddress addr = InetAddress.getByName(ip); + return addr.isSiteLocalAddress() || addr.isLoopbackAddress(); + } + catch(UnknownHostException e) + { + throw new RuntimeException(e); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-core/src/main/resources/log4j2-default-template.json b/hp-soa-framework/hp-soa-framework-core/src/main/resources/log4j2-default-template.json new file mode 100644 index 0000000..de490b8 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-core/src/main/resources/log4j2-default-template.json @@ -0,0 +1,89 @@ +{ + "mdc": { + "$resolver": "mdc" + }, + "exception": { + "exception_class": { + "$resolver": "exception", + "field": "className" + }, + "exception_message": { + "$resolver": "exception", + "field": "message", + "stringified": true + }, + "stacktrace": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": { + "truncation": { + "suffix": "... [truncated]", + "pointMatcherStrings": [ + "at org.apache.catalina", + "at org.apache.coyote", + "at org.apache.tomcat", + "at org.hibernate", + "at org.apache.el", + "at org.apache.cxf", + "at org.junit", + "at junit.framework", + "at org.jboss", + "at org.h2", + "at org.eclipse", + "at org.richfaces", + "at java.lang.reflect", + "at java.base/java.lang.reflect", + "at jdk.internal.reflect", + "at java.base/jdk.internal.reflect", + "at com.sun", + "at javax.servlet", + "at jakarta.servlet" + ] + } + } + } + } + }, + "line_number": { + "$resolver": "source", + "field": "lineNumber" + }, + "class": { + "$resolver": "source", + "field": "className" + }, + "source_host": "${hostName}", + "source_ip": "${sys:local.ip.address}", + "message": { + "$resolver": "message", + "stringified": true + }, + "thread_name": { + "$resolver": "thread", + "field": "name" + }, + "log_time": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC" + } + }, + "level": { + "$resolver": "level", + "field": "name" + }, + "file": { + "$resolver": "source", + "field": "fileName" + }, + "method": { + "$resolver": "source", + "field": "methodName" + }, + "logger_name": { + "$resolver": "logger", + "field": "name" + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/.gitignore b/hp-soa-framework/hp-soa-framework-web/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-framework/hp-soa-framework-web/pom.xml b/hp-soa-framework/hp-soa-framework-web/pom.xml new file mode 100644 index 0000000..6c9456c --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/pom.xml @@ -0,0 +1,218 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-framework + ${revision} + + hp-soa-framework-web + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-core + + + + org.apache.dubbo + dubbo-spring-boot-starter + + + org.apache.dubbo + dubbo-spring-boot-actuator + + + org.apache.dubbo + dubbo-qos + + + org.springframework.boot + spring-boot-configuration-processor + + + + org.springframework.cloud + spring-cloud-context + + + + org.apache.kafka + kafka-clients + + + + com.alibaba.fastjson2 + fastjson2 + + + com.alibaba.fastjson2 + fastjson2-extension-spring6 + + + + com.google.protobuf + protobuf-java + + + + com.blueconic + browscap-java + + + + + + org.hibernate.validator + hibernate-validator + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework + spring-core + + + + org.springframework + spring-test + test + + + + org.springframework + spring-oxm + + + + org.springframework + spring-orm + + + + org.springframework + spring-aspects + + + + org.springframework + spring-messaging + + + + org.springframework + spring-instrument + + + + org.springframework + spring-jdbc + + + + org.springframework + spring-context-support + + + + org.springframework + spring-context + + + + org.springframework + spring-aop + + + + org.springframework + spring-beans + + + + org.springframework + spring-expression + + + + org.springframework + spring-web + + + org.springframework + spring-webmvc + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerGlobalExceptionAdvice.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerGlobalExceptionAdvice.java new file mode 100644 index 0000000..e50ba07 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerGlobalExceptionAdvice.java @@ -0,0 +1,137 @@ +package io.github.hpsocket.soa.framework.web.advice; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.apache.dubbo.rpc.RpcException; +import io.github.hpsocket.soa.framework.core.exception.ServiceException; +import io.github.hpsocket.soa.framework.web.model.Response; + +import org.springframework.core.Ordered; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; +import org.springframework.web.HttpMediaTypeException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.NoHandlerFoundException; + +import static io.github.hpsocket.soa.framework.core.exception.ServiceException.*; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** HTTP 请求全局异常拦截器 */ +@Slf4j +@RestControllerAdvice +public class ControllerGlobalExceptionAdvice implements Ordered +{ + @Override + public int getOrder() + { + return 0; + } + + /** {@linkplain MethodArgumentNotValidException} 异常处理器 */ + @ExceptionHandler({MethodArgumentNotValidException.class}) + public Response handleMethodArgumentNotValidException(HttpServletRequest request, HttpServletResponse response, MethodArgumentNotValidException e) + { + BindingResult rs = e.getBindingResult(); + + Map> validationErrors = new LinkedHashMap<>(); + + for(ObjectError obj : rs.getAllErrors()) + { + if(obj instanceof FieldError) + { + FieldError field = (FieldError)obj; + String name = field.getField(); + + List errs = validationErrors.get(name); + + if(errs == null) + { + errs = new LinkedList<>(); + validationErrors.put(name, errs); + } + + errs.add(field.getDefaultMessage()); + } + } + + if(validationErrors.isEmpty()) + validationErrors.put(rs.getObjectName(), Arrays.asList(rs.toString())); + + ServiceException se = wrapServiceException(PARAM_VERIFY_EXCEPTION, e); + + logServiceException(log, se.getMessage(), se); + + return new Response<>(se, validationErrors); + } + + /** {@linkplain RpcException} 异常处理器 */ + @ExceptionHandler({RpcException.class}) + public Response handleException(HttpServletRequest request, HttpServletResponse response, RpcException e) + { + Throwable real = e; + Throwable cause = null; + + do + { + cause = real.getCause(); + + if(cause == null) + break; + + real = cause; + } while(cause instanceof RpcException); + + + ServiceException se = null; + + if(real instanceof TimeoutException) + se = wrapServiceException(TIMEOUT_EXCEPTION, real); + else if(real instanceof ExecutionException) + se = wrapServiceException(INNER_API_CALL_EXCEPTION, real); + + if(se == null) + se = wrapServiceException(GENERAL_EXCEPTION, real); + + logServiceException(log, se.getMessage(), se); + + return new Response<>(se); + } + + /** {@linkplain Exception} 异常处理器 */ + @ExceptionHandler({Exception.class}) + public Response handleException(HttpServletRequest request, HttpServletResponse response, Exception e) + { + ServiceException se = null; + + if(e instanceof NoHandlerFoundException) + se = wrapServiceException(NOT_EXIST_EXCEPTION, e); + else if(e instanceof HttpRequestMethodNotSupportedException) + se = wrapServiceException(NOT_IMPLEMENTED_EXCEPTION, e); + else if(e instanceof HttpMediaTypeException) + se = wrapServiceException(NOT_SUPPORTED_EXCEPTION, e); + else if(e instanceof HttpMessageConversionException) + se = wrapServiceException(BAD_REQUEST_EXCEPTION, e); + + if(se == null) + se = wrapServiceException(GENERAL_EXCEPTION, e); + + logServiceException(log, se.getMessage(), se); + + return new Response<>(se); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerRequestAdvice.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerRequestAdvice.java new file mode 100644 index 0000000..430c7eb --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerRequestAdvice.java @@ -0,0 +1,79 @@ +package io.github.hpsocket.soa.framework.web.advice; + +import java.lang.reflect.Type; + +import org.aspectj.lang.annotation.Aspect; +import io.github.hpsocket.soa.framework.core.mdc.MdcRunnable; +import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; + +import com.alibaba.fastjson2.JSONObject; + +import lombok.extern.slf4j.Slf4j; + +import io.github.hpsocket.soa.framework.web.model.RequestAttribute; + +import static io.github.hpsocket.soa.framework.web.support.WebServerHelper.*; + +/** HTTP 请求拦截器 */ +@Slf4j +@Aspect +@RestControllerAdvice +public class ControllerRequestAdvice extends RequestBodyAdviceAdapter implements Ordered +{ + @Override + public int getOrder() + { + return -10; + } + + @InitBinder + public void initBinder(WebDataBinder binder) + { + + } + + @Override + public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) + { + return true; + } + + @Override + public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) + { + RequestContext.setBody(body); + + logRequest(); + + return body; + } + + private void logRequest() + { + try + { + final RequestAttribute requestAttribute = RequestContext.getRequestAttribute(); + + ASYNC_LOG_EXECUTOR.execute(new MdcRunnable() + { + @Override + protected void doRun() + { + log.info("[ REQUEST: {} ] -> {}", requestAttribute.getRequestPath(), JSONObject.toJSONString(requestAttribute, JSON_SERIAL_FEATURES_NO_NULL_VAL)); + } + }); + } + catch(Exception e) + { + log.error("async write request log fail", e); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerResponseAdvice.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerResponseAdvice.java new file mode 100644 index 0000000..cdd1f79 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/ControllerResponseAdvice.java @@ -0,0 +1,266 @@ +package io.github.hpsocket.soa.framework.web.advice; + +import java.io.IOException; +import java.util.Map; + +import io.github.hpsocket.soa.framework.core.exception.ServiceException; +import io.github.hpsocket.soa.framework.core.mdc.MdcRunnable; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.holder.AppConfigHolder; +import io.github.hpsocket.soa.framework.web.model.RequestAttribute; +import io.github.hpsocket.soa.framework.web.model.Response; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cglib.beans.BeanMap; +import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import com.alibaba.fastjson2.JSONObject; +import com.blueconic.browscap.Capabilities; +import com.blueconic.browscap.ParseException; +import com.blueconic.browscap.UserAgentParser; +import com.blueconic.browscap.UserAgentService; + +import static io.github.hpsocket.soa.framework.core.exception.ServiceException.*; +import static io.github.hpsocket.soa.framework.web.support.WebServerHelper.*; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** HTTP 响应拦截器 */ +@Slf4j +@RestControllerAdvice +public class ControllerResponseAdvice implements ResponseBodyAdvice, Ordered +{ + private static final Logger MONITOR_LOGGER = LoggerFactory.getLogger(MONITOR_LOGGER_NAME); + private static final UserAgentParser uaParser; + + static + { + try + { + uaParser = new UserAgentService().loadParser(); + } + catch(IOException | ParseException e) + { + throw new RuntimeException(e); + } + } + + @Override + public int getOrder() + { + return -10; + } + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) + { + RequestAttribute attr = RequestContext.getRequestAttribute(); + + if(attr == null) + return false; + + String requestUri = attr.getRequestUri(); + + for(String ignoreLogPath : AppConfigHolder.getExcludedLogPaths()) + { + if(requestUri.startsWith(ignoreLogPath)) + return false; + } + + return true; + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) + { + HttpServletRequest req = ((ServletServerHttpRequest)request).getServletRequest(); + HttpServletResponse resp = ((ServletServerHttpResponse)response).getServletResponse(); + + if(body instanceof Response) + { + Response respBody = (Response)body; + + if(respBody.getResultCode() == null) + respBody.setResultCode(respBody.getStatusCode()); + + checkToken(respBody, req, resp); + + respBody.calcCostTime(RequestContext.getTimestamp()); + } + + logResponse(body, returnType, req); + + RequestContext.removeRequestAttribute(); + + return body; + } + + @SuppressWarnings("unchecked") + private void checkToken(Response respBody, HttpServletRequest request, HttpServletResponse response) + { + Pair tokenCookieAttr = null; + + Integer statusCode = respBody.getStatusCode(); + + if(GeneralHelper.equals(statusCode, ServiceException.OK)) + { + Integer rt = respBody.getRespType(); + + if(rt != null) + { + if(rt == Response.RT_LOGIN) + { + Object result = respBody.getResult(); + + if(result != null) + { + String token = null; + + if(result instanceof Map) + token = ((Map)result).get(HEADER_TOKEN); + else + { + BeanMap map = BeanMap.create(result); + token = (String)map.get(HEADER_TOKEN); + } + + if(GeneralHelper.isStrNotEmpty(token)) + tokenCookieAttr = new Pair(AppConfigHolder.getCookieMaxAge(), token); + } + } + else if(rt == Response.RT_LOGOUT) + tokenCookieAttr = new Pair(0, ""); + } + } + else if(GeneralHelper.equals(statusCode, ServiceException.LOGIN_INVALID)) + tokenCookieAttr = new Pair(0, ""); + + if(tokenCookieAttr != null) + { + Cookie cookie = createCookie(request, HEADER_TOKEN, tokenCookieAttr.getSecond(), tokenCookieAttr.getFirst()); + response.addCookie(cookie); + } + } + + private void logResponse(Object body, MethodParameter rt, HttpServletRequest req) + { + final RequestAttribute requestAttribute = RequestContext.getRequestAttribute(); + + try + { + ASYNC_LOG_EXECUTOR.execute(new MdcRunnable() + { + @Override + public void doRun() + { + log.info("[ RESPONSE: {} ] -> {}", requestAttribute.getRequestPath(), JSONObject.toJSONString(body, JSON_SERIAL_FEATURES_DEFAULT)); + } + }); + } + catch(Exception e) + { + log.error("async write response log fail", e); + } + + asyncWriteMonitorLog(body, rt, req, requestAttribute); + } + + private void asyncWriteMonitorLog(final Object body, final MethodParameter rt, final HttpServletRequest req, final RequestAttribute requestAttribute) + { + final String ua = getUserAgent(req); + + Runnable task = new MdcRunnable() + { + @Override + protected void doRun() + { + JSONObject json = JSONObject.from(requestAttribute.getBody()); + JSONObject jsonLog = new JSONObject(); + + if(json == null) + json = new JSONObject(); + + jsonLog.put("monitor_type", MONITOR_INGRESS); + + @SuppressWarnings("unchecked") + final Map reqAttr = BeanMap.create(requestAttribute);; + + reqAttr.forEach((k, v) -> { + if(v != null && !k.equals("body")) + jsonLog.put(k, v.toString()); + }); + + jsonLog.put("apiName", rt.getDeclaringClass().getSimpleName().concat("#").concat(rt.getMethod().getName())); + jsonLog.put("request", json.toString()); + jsonLog.put("response", JSONObject.toJSONString(body, JSON_SERIAL_FEATURES_DEFAULT)); + + if(body instanceof Response) + { + Response respBody = (Response)body; + + jsonLog.put("resultCode", respBody.getResultCode()); + jsonLog.put("statusCode", respBody.getStatusCode()); + jsonLog.put("costTime", respBody.getCostTime()); + jsonLog.put("msg", GeneralHelper.equals(respBody.getStatusCode(), PARAM_VERIFY_ERROR) ? respBody.getMsg() + ": " + JSONObject.toJSONString(respBody.getValidationErrors()) : respBody.getMsg()); + } + + if(GeneralHelper.isStrNotEmpty(ua)) + { + JSONObject jsonUa = new JSONObject(); + final Capabilities caps = uaParser.parse(ua); + + jsonUa.put("name", ua); + jsonUa.put("browser", caps.getBrowser().concat(GeneralHelper.isStrNotEmpty(caps.getBrowserMajorVersion()) ? " " + caps.getBrowserMajorVersion() : "")); + jsonUa.put("browserType", caps.getBrowserType()); + jsonUa.put("deviceType", caps.getDeviceType()); + jsonUa.put("platform", caps.getPlatform().concat(GeneralHelper.isStrNotEmpty(caps.getPlatformVersion()) ? " " + caps.getPlatformVersion() : "")); + + jsonLog.put("ua", jsonUa); + } + + String msg = jsonLog.toJSONString(); + + if(body instanceof Response) + { + Response respBody = (Response)body; + Integer statusCode = respBody.getStatusCode(); + + if(GeneralHelper.equals(statusCode, OK)) + MONITOR_LOGGER.info(msg); + else if(GeneralHelper.equals(statusCode, GENERAL_ERROR)) + MONITOR_LOGGER.error(msg); + else + MONITOR_LOGGER.warn(msg); + } + else + { + MONITOR_LOGGER.info(msg); + } + } + }; + + try + { + ASYNC_LOG_EXECUTOR.execute(task); + } + catch(Exception e) + { + log.error("async write monitor log fail", e); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/RequestContext.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/RequestContext.java new file mode 100644 index 0000000..48f0083 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/advice/RequestContext.java @@ -0,0 +1,236 @@ +package io.github.hpsocket.soa.framework.web.advice; + +import java.io.IOException; +import java.util.Map; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.core.util.Result; +import io.github.hpsocket.soa.framework.web.model.RequestAttribute; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import static io.github.hpsocket.soa.framework.web.support.WebServerHelper.*; +import static org.springframework.web.context.request.ServletRequestAttributes.*; + +/** HTTP 请求上下文 */ +public class RequestContext +{ + public static final ServletRequestAttributes getServletRequestAttributes() + { + return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()); + } + + public static final HttpServletRequest getServletRequest() + { + return getServletRequestAttributes().getRequest(); + } + + public static final HttpServletResponse getServletResponse() + { + return getServletRequestAttributes().getResponse(); + } + + public static final RequestAttribute getRequestAttribute() + { + return getAttribute(REQUEST_ATTRIBUTE_CONTEXT); + } + + static final void setRequestAttribute(RequestAttribute attribute) + { + setAttribute(REQUEST_ATTRIBUTE_CONTEXT, attribute); + } + + static final void removeRequestAttribute() + { + removeAttribute(REQUEST_ATTRIBUTE_CONTEXT); + } + + @SuppressWarnings("unchecked") + public static final T getAttribute(String name) + { + return (T)getServletRequestAttributes().getAttribute(name, SCOPE_REQUEST); + } + + static final void setAttribute(String name, T value) + { + getServletRequestAttributes().setAttribute(name, value, SCOPE_REQUEST); + } + + public static final void removeAttribute(String name) + { + getServletRequestAttributes().removeAttribute(name, SCOPE_REQUEST); + } + + public static final String getAppCode() + { + return getRequestAttribute().getAppCode(); + } + + public static final String getSrcAppCode() + { + return getRequestAttribute().getSrcAppCode(); + } + + public static final String getToken() + { + return getRequestAttribute().getToken(); + } + + public static final String getVersion() + { + return getRequestAttribute().getVersion(); + } + + public static final String getExtra() + { + return getRequestAttribute().getExtra(); + } + + public static final String getRequestId() + { + return getRequestAttribute().getRequestId(); + } + + public static final String getSessionId() + { + return getRequestAttribute().getSessionId(); + } + + public static final String getClientId() + { + return getRequestAttribute().getClientId(); + } + + public static final String getClientAddr() + { + return getRequestAttribute().getClientAddr(); + } + + public static final String getRequestUri() + { + return getRequestAttribute().getRequestUri(); + } + + public static final String getRequestPath() + { + return getRequestAttribute().getRequestPath(); + } + + public static final String getRequestMethod() + { + return getRequestAttribute().getRequestMethod(); + } + + public static final Long getGroupId() + { + return getRequestAttribute().getGroupId(); + } + + public static final Long getUserId() + { + return getRequestAttribute().getUserId(); + } + + public static final long getTimestamp() + { + return getRequestAttribute().getTimestamp(); + } + + public static final Object getBody() + { + return getRequestAttribute().getBody(); + } + + static final void setBody(Object body) + { + getRequestAttribute().setBody(body); + } + + public static final void setServletResponseStatus(int sc) + { + getServletResponse().setStatus(sc); + } + + public static final void sendServletResponseError(int sc, String sm) throws IOException + { + getServletResponse().sendError(sc, sm); + } + + public static final RequestAttribute parseRequestAttribute(HttpServletRequest request, HttpServletResponse response) + { + long timestamp = System.currentTimeMillis(); + Map reqInfos = parseRequestInfo(request); + + String appCode = parseRequestField(request, reqInfos, HEADER_APP_CODE, false); + String srcAppCode = parseRequestField(request, reqInfos, HEADER_SRC_APP_CODE, false); + String version = parseRequestField(request, reqInfos, HEADER_VERSION, false); + String extra = parseRequestField(request, reqInfos, HEADER_EXTRA, false); + String token = parseRequestField(request, reqInfos, HEADER_TOKEN, true); + String groupId = parseRequestField(request, reqInfos, HEADER_GROUP_ID, false); + String sessionId = parseRequestField(request, reqInfos, HEADER_SESSION_ID, true); + String requestId = parseRequestField(request, reqInfos, HEADER_REQUEST_ID, false); + String clientId = parseRequestField(request, reqInfos, HEADER_CLIENT_ID, false); + + if(GeneralHelper.isStrEmpty(clientId)) + { + if(checkUserAgent(getUserAgent(request))) + { + Result clientCookieRS = checkClientCookie(request); + Cookie cookie = clientCookieRS.getValue(); + String cookieValue = cookie.getValue(); + + clientId = cookieValue; + + if(!clientCookieRS.getFlag()) + response.addCookie(cookie); + } + } + + if(GeneralHelper.isStrEmpty(requestId)) + requestId = WebServerHelper.randomUUID(); + + String requestUri = WebServerHelper.getRequestUri(request); + String requestPath = WebServerHelper.getRequestPath(request); + String requestMethod = WebServerHelper.getRequestMethod(request); + + RequestAttribute reqAttr = new RequestAttribute(appCode, srcAppCode, token, + clientId, requestId, sessionId, + GeneralHelper.str2Long(groupId)); + reqAttr.setVersion(version); + reqAttr.setExtra(extra); + reqAttr.setClientAddr(WebServerHelper.getIpAddr(request)); + reqAttr.setRequestUri(requestUri); + reqAttr.setRequestPath(requestPath); + reqAttr.setRequestMethod(requestMethod); + reqAttr.setTimestamp(timestamp); + + setRequestAttribute(reqAttr); + + return reqAttr; + } + + private static String parseRequestField(HttpServletRequest request, Map attrs, String name, boolean checkCookie) + { + String value = attrs.get(name); + + if(GeneralHelper.isStrEmpty(value)) + { + value = request.getHeader(name); + + if(GeneralHelper.isStrEmpty(value) && checkCookie) + { + Cookie cookie = getCookie(request, name); + + if(cookie != null) + value = cookie.getValue(); + } + } + + return value; + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/annotation/AccessVerification.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/annotation/AccessVerification.java new file mode 100644 index 0000000..da77f9c --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/annotation/AccessVerification.java @@ -0,0 +1,33 @@ +package io.github.hpsocket.soa.framework.web.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** HTTP 请求校验注解 */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface AccessVerification +{ + /** HTTP 请求校验类型 */ + public static enum Type + { + /** 不作任何校验 */ + NO_CHECK, + /** 不校验登录(只校验 appCode) */ + NO_LOGIN, + /** 不校验登录(只校验 appCode),如果已登录则加载用户信息 */ + MAYBE_LOGIN, + /** 校验登录 */ + REQUIRE_LOGIN, + /** 校验授权 */ + REQUIRE_AUTHORIZED + } + + Type value() default Type.NO_LOGIN; +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/annotation/SiteLocal.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/annotation/SiteLocal.java new file mode 100644 index 0000000..16fae03 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/annotation/SiteLocal.java @@ -0,0 +1,19 @@ + +package io.github.hpsocket.soa.framework.web.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** 内网 HTTP 请求注解 */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface SiteLocal +{ + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/aspect/AccessVerificationInspector.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/aspect/AccessVerificationInspector.java new file mode 100644 index 0000000..4fac532 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/aspect/AccessVerificationInspector.java @@ -0,0 +1,186 @@ +package io.github.hpsocket.soa.framework.web.aspect; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.logging.log4j.core.config.Order; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.jboss.logging.MDC; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.model.RequestAttribute; +import io.github.hpsocket.soa.framework.web.model.Response; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; +import org.springframework.util.Assert; + +import static io.github.hpsocket.soa.framework.core.exception.ServiceException.*; +import static io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type.*; + +/** HTTP 请求校验拦截器
+ * 处理 {@linkplain AccessVerification} 注解 + */ +@Aspect +@Order(0) +public class AccessVerificationInspector +{ + static final String POINTCUT_PATTERN = "execution (public io.github.hpsocket.soa.framework.web.model.Response *.*(..)) && @within(org.springframework.web.bind.annotation.RestController)"; + + @Pointcut(POINTCUT_PATTERN) + protected void inspectMethod() {} + + private AccessVerification.Type defaultAccessPolicy; + private AccessVerificationService accessVerificationService; + + private Map annotations = new ConcurrentHashMap<>(); + + public AccessVerificationInspector(AccessVerification.Type defaultAccessPolicy, AccessVerificationService accessVerificationService) + { + Assert.notNull(accessVerificationService, String.format("'%s' bean not found", AccessVerificationService.class.getSimpleName())); + + this.defaultAccessPolicy = defaultAccessPolicy; + this.accessVerificationService = accessVerificationService; + } + + @Around("inspectMethod()") + public Object inspect(ProceedingJoinPoint joinPoint) throws Throwable + { + MethodSignature signature = (MethodSignature)joinPoint.getSignature(); + AccessVerification.Type type = getInspectVerificationType(signature.getMethod()); + + if(type != NO_CHECK) + { + RequestAttribute reqAttr = RequestContext.getRequestAttribute(); + + if(reqAttr == null) + return new Response(BAD_REQUEST_EXCEPTION); + + Response resp = inspectApp(reqAttr); + + if(resp.getResult() == null) + return resp; + + if(type != NO_LOGIN) + { + resp = inspectUser(reqAttr, type); + + if(resp.getResult() == null) + return resp; + + if(type == REQUIRE_AUTHORIZED) + { + resp = inspectRole(reqAttr); + + if(resp.getResult() == null) + return resp; + } + } + } + + return joinPoint.proceed(); + } + + private AccessVerification.Type getInspectVerificationType(Method method) + { + AccessVerification.Type type = annotations.get(method); + + if(type == null) + { + type = getVerificationType(method); + annotations.putIfAbsent(method, type); + } + + return type; + } + + private AccessVerification.Type getVerificationType(Method method) + { + AccessVerification annotation = method.getAnnotation(AccessVerification.class); + + if(annotation == null) + annotation = method.getDeclaringClass().getAnnotation(AccessVerification.class); + + if(annotation == null) + return defaultAccessPolicy; + + return annotation.value(); + } + + private Response inspectApp(RequestAttribute reqAttr) + { + String appCode = reqAttr.getAppCode(); + + if(GeneralHelper.isStrEmpty(appCode)) + return new Response<>("缺少参数:appCode", PARAM_VALIDATION_ERROR); + + if(!accessVerificationService.verifyAppCode(appCode)) + return new Response<>(APPCODE_NOT_EXIST_EXCEPTION); + + return new Response<>(Boolean.TRUE); + } + + private Response inspectUser(RequestAttribute reqAttr, AccessVerification.Type type) + { + String token = reqAttr.getToken(); + + if(GeneralHelper.isStrEmpty(token)) + { + if(type == MAYBE_LOGIN) + return new Response<>(0L); + else + return new Response<>(NOT_LOGGED_IN_EXCEPTION); + } + + Pair rs = accessVerificationService.verifyUserByTokenAndGroupId(token, reqAttr.getGroupId()); + Long userId = rs.getFirst(); + + if(userId == null) + { + if(type == MAYBE_LOGIN) + return new Response<>(0L); + else + { + String msg = rs.getSecond(); + + if(GeneralHelper.isStrEmpty(msg)) + msg = AUTHEN_EXCEPTION.getMessage(); + + return new Response<>(msg, AUTHEN_ERROR); + } + } + + reqAttr.setUserId(userId); + MDC.put(MdcAttr.MDC_USER_ID_KEY, userId.toString()); + + return new Response<>(userId); + } + + private Response inspectRole(RequestAttribute reqAttr) + { + if(reqAttr.getGroupId() == null) + return new Response<>("缺少参数:groupId", AUTHOR_ERROR); + + Pair rs = accessVerificationService.verifyRouteAuthorized(reqAttr.getRequestUri(), reqAttr.getAppCode(), reqAttr.getGroupId(), reqAttr.getUserId()); + + if(!Boolean.TRUE.equals(rs.getFirst())) + { + String msg = rs.getSecond(); + + if(GeneralHelper.isStrEmpty(msg)) + msg = AUTHOR_EXCEPTION.getMessage(); + + return new Response<>(msg, AUTHOR_ERROR); + } + + return new Response<>(Boolean.TRUE); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/aspect/SiteLocalInspector.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/aspect/SiteLocalInspector.java new file mode 100644 index 0000000..8dd30d5 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/aspect/SiteLocalInspector.java @@ -0,0 +1,36 @@ +package io.github.hpsocket.soa.framework.web.aspect; + +import org.apache.logging.log4j.core.config.Order; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import io.github.hpsocket.soa.framework.core.util.SystemUtil; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.annotation.SiteLocal; + +import static io.github.hpsocket.soa.framework.core.exception.ServiceException.*; + +/** 内网 HTTP 请求拦截器
+ * 处理 {@linkplain SiteLocal} 注解 + */ +@Aspect +@Order(Integer.MIN_VALUE) +public class SiteLocalInspector +{ + private static final String POINTCUT_PATTERN = AccessVerificationInspector.POINTCUT_PATTERN + + "&& (@annotation(io.github.hpsocket.soa.framework.web.annotation.SiteLocal) || @within(io.github.hpsocket.soa.framework.web.annotation.SiteLocal))"; + + @Pointcut(POINTCUT_PATTERN) + protected void beforeMethod() {} + + @Before(value = "beforeMethod()") + public void verifyRequestIP(JoinPoint point) + { + String ip = RequestContext.getClientAddr(); + + if(!SystemUtil.isLocalNetwork(ip)) + throw FORBID_EXCEPTION; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/dubbo/filter/DubboMdcFilter.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/dubbo/filter/DubboMdcFilter.java new file mode 100644 index 0000000..a391b0e --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/dubbo/filter/DubboMdcFilter.java @@ -0,0 +1,157 @@ +package io.github.hpsocket.soa.framework.web.dubbo.filter; + +import org.slf4j.MDC; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.holder.AppConfigHolder; + +import java.util.Arrays; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcContextAttachment; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.RpcServiceContext; + +import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; +import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; + +import static io.github.hpsocket.soa.framework.core.mdc.MdcAttr.*; + +/** Dubbo MDC 过滤器
+ * 为 Dubbo 服务调用注入调用链跟踪信息 + */ +@Activate(group = {CONSUMER, PROVIDER}, order = (Integer.MIN_VALUE + 1000)) +public class DubboMdcFilter implements Filter, Filter.Listener +{ + private static final ThreadLocal GEN_APP_ID = new ThreadLocal<>(); + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException + { + RpcServiceContext ctx = RpcContext.getServiceContext(); + + if(ctx.getUrl() == null) + return invoker.invoke(invocation); + + boolean isConsumer = ctx.isConsumerSide(); + final RpcContextAttachment ctxAttach = isConsumer + ? RpcContext.getClientAttachment() + : RpcContext.getServerAttachment(); + GEN_APP_ID.set(Boolean.FALSE); + + try + { + if(isConsumer) + { + for(int i = 0; i < TRANSFER_MDC_KEYS.length; i++) + { + String key = TRANSFER_MDC_KEYS[i]; + String val = MDC.get(key); + + if(GeneralHelper.isStrNotEmpty(val)) + ctxAttach.setAttachment(key, val); + else if(key.equals(MDC_APP_ID_KEY)) + GEN_APP_ID.set(Boolean.TRUE); + } + + ctxAttach.setAttachment(MDC_FROM_SERVICE_ID_KEY, AppConfigHolder.getAppId()); + ctxAttach.setAttachment(MDC_FROM_SERVICE_NAME_KEY, AppConfigHolder.getAppName()); + ctxAttach.setAttachment(MDC_FROM_SERVICE_ADDR_KEY, AppConfigHolder.getAppAddress()); + } + else + { + for(int i = 0; i < TRANSFER_MDC_KEYS.length; i++) + { + String key = TRANSFER_MDC_KEYS[i]; + String val = ctxAttach.getAttachment(key); + + if(GeneralHelper.isStrNotEmpty(val)) + MDC.put(key, val); + else if(key.equals(MDC_APP_ID_KEY)) + GEN_APP_ID.set(Boolean.TRUE); + } + + MDC.put(MDC_SERVICE_ID_KEY, AppConfigHolder.getAppId()); + MDC.put(MDC_SERVICE_NAME_KEY, AppConfigHolder.getAppName()); + MDC.put(MDC_SERVICE_ADDR_KEY, AppConfigHolder.getAppAddress()); + + if(GeneralHelper.isStrNotEmpty(AppConfigHolder.getAppOrganization())) + MDC.put(MDC_ORG_KEY, AppConfigHolder.getAppOrganization()); + if(GeneralHelper.isStrNotEmpty(AppConfigHolder.getAppOwner())) + MDC.put(MDC_OWNER_KEY, AppConfigHolder.getAppOwner()); + + MDC.put(MDC_FROM_SERVICE_ID_KEY, ctxAttach.getAttachment(MDC_FROM_SERVICE_ID_KEY)); + MDC.put(MDC_FROM_SERVICE_NAME_KEY, ctxAttach.getAttachment(MDC_FROM_SERVICE_NAME_KEY)); + MDC.put(MDC_FROM_SERVICE_ADDR_KEY, ctxAttach.getAttachment(MDC_FROM_SERVICE_ADDR_KEY)); + } + + if(GEN_APP_ID.get()) + { + String appId = AppConfigHolder.getAppId(); + String appName = AppConfigHolder.getAppName(); + + MDC.put(MDC_APP_ID_KEY, appId); + MDC.put(MDC_APP_NAME_KEY, appName); + ctxAttach.setAttachment(MDC_APP_ID_KEY, appId); + ctxAttach.setAttachment(MDC_APP_NAME_KEY, appName); + } + + return invoker.invoke(invocation); + } + finally + { + + } + } + + @Override + public void onResponse(Result appResponse, Invoker invoker, Invocation invocation) + { + afterInvoke(); + } + + @Override + public void onError(Throwable t, Invoker invoker, Invocation invocation) + { + afterInvoke(); + } + + private void afterInvoke() + { + RpcServiceContext ctx = RpcContext.getServiceContext(); + + if(ctx.getUrl() == null) + return; + + boolean isConsumer = ctx.isConsumerSide(); + final RpcContextAttachment ctxAttach = isConsumer + ? RpcContext.getClientAttachment() + : RpcContext.getServerAttachment(); + if(isConsumer) + { + Arrays.stream(TRANSFER_MDC_ALL_KEYS).forEach((key) -> ctxAttach.removeAttachment(key)); + + if(GEN_APP_ID.get()) + { + MDC.remove(MDC_APP_ID_KEY); + MDC.remove(MDC_APP_NAME_KEY); + } + } + else + { + Arrays.stream(TRANSFER_MDC_ALL_KEYS).forEach((key) -> MDC.remove(key)); + + if(GEN_APP_ID.get()) + { + ctxAttach.removeAttachment(MDC_APP_ID_KEY); + ctxAttach.removeAttachment(MDC_APP_NAME_KEY); + } + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/event/ReadOnlyEvent.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/event/ReadOnlyEvent.java new file mode 100644 index 0000000..b3534c6 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/event/ReadOnlyEvent.java @@ -0,0 +1,37 @@ +package io.github.hpsocket.soa.framework.web.event; + +import org.springframework.context.ApplicationEvent; + +import lombok.Getter; +import lombok.Setter; + +/** 应用程序只读事件
+ * 触发时机: + *
    + *
  1. 应用程序启动时,跟随 {@linkplain org.springframework.context.event.ContextRefreshedEvent ContextRefreshedEvent} 事件之后 (此时 {@linkplain ReadOnlyEvent#isInitial()} 为 true)
  2. + *
  3. 动态修改应用程序的 ${hp.soa.web.app.read-only} 配置时,(此时 {@linkplain ReadOnlyEvent#isInitial()} 为 faise)
  4. + *
+ */ +@Getter +@Setter +@SuppressWarnings("serial") +public class ReadOnlyEvent extends ApplicationEvent +{ + /** 是否只读 */ + private boolean readOnly; + /** 是否初始触发 */ + private boolean initial; + + public ReadOnlyEvent(Object source, boolean readOnly) + { + this(source, readOnly, false); + } + + public ReadOnlyEvent(Object source, boolean readOnly, boolean initial) + { + super(source); + this.readOnly = readOnly; + this.initial = initial; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/filter/HttpMdcFilter.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/filter/HttpMdcFilter.java new file mode 100644 index 0000000..34d555b --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/filter/HttpMdcFilter.java @@ -0,0 +1,90 @@ + package io.github.hpsocket.soa.framework.web.filter; + +import java.io.IOException; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.advice.RequestContext; +import io.github.hpsocket.soa.framework.web.model.RequestAttribute; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; + +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** HTTP 请求 MDC 过滤器
+ * 主要功能: + *
    + *
  1. 为 HTTP 请求注入调用链跟踪信息
  2. + *
  3. 创建HTTP 请求上下文 {@linkplain RequestContext} + *
+ */ +@Slf4j +@Setter +public class HttpMdcFilter implements Filter +{ + public static final int ORDER = -100; + public static final String DISPLAY_NAME = HttpMdcFilter.class.getSimpleName(); + public static final String URL_PATTERNS = "/*"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + log.info("starting up: ({}) ...", DISPLAY_NAME); + } + + @Override + public void destroy() + { + log.info("shutting down: ({}) OK!", DISPLAY_NAME); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + MdcAttr mdcAttr = WebServerHelper.createMdcAttr(false); + RequestAttribute reqAttr = RequestContext.parseRequestAttribute((HttpServletRequest)request, (HttpServletResponse)response); + + try + { + fillMdcAttr(mdcAttr, reqAttr); + mdcAttr.putMdc(); + + chain.doFilter(request, response); + } + finally + { + mdcAttr.removeMdc(); + } + } + + private void fillMdcAttr(MdcAttr mdcAttr, RequestAttribute reqAttr) + { + if(GeneralHelper.isStrNotEmpty(reqAttr.getClientId())) + mdcAttr.setClientId(reqAttr.getClientId()); + if(GeneralHelper.isStrNotEmpty(reqAttr.getRequestId())) + mdcAttr.setRequestId(reqAttr.getRequestId()); + if(GeneralHelper.isStrNotEmpty(reqAttr.getSessionId())) + mdcAttr.setSessionId(reqAttr.getSessionId()); + if(GeneralHelper.isStrNotEmpty(reqAttr.getAppCode())) + mdcAttr.setAppCode(reqAttr.getAppCode()); + if(GeneralHelper.isStrNotEmpty(reqAttr.getSrcAppCode())) + mdcAttr.setSrcAppCode(reqAttr.getSrcAppCode()); + if(GeneralHelper.isStrNotEmpty(reqAttr.getToken())) + mdcAttr.setToken(reqAttr.getToken()); + if(reqAttr.getUserId() != null) + mdcAttr.setUserId(reqAttr.getUserId().toString()); + if(reqAttr.getGroupId() != null) + mdcAttr.setGroupId(reqAttr.getGroupId().toString()); + if(GeneralHelper.isStrNotEmpty(reqAttr.getExtra())) + mdcAttr.setExtra(reqAttr.getExtra()); + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/holder/AppConfigHolder.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/holder/AppConfigHolder.java new file mode 100644 index 0000000..6db2cf4 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/holder/AppConfigHolder.java @@ -0,0 +1,191 @@ + +package io.github.hpsocket.soa.framework.web.holder; + +import java.util.HashSet; +import java.util.Set; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.core.util.SystemUtil; +import io.github.hpsocket.soa.framework.web.propertries.IAppProperties; +import io.github.hpsocket.soa.framework.web.propertries.IServletPathsPropertries; + +/** 应用程序 Web 基本配置持有者 */ +public class AppConfigHolder +{ + public static final String REQUEST_PATH_SEPARATOR = "/"; + public static final String FAVICON_PATH = "/favicon.ico"; + + private static boolean readOnly; + + private static String appId; + private static String appName; + private static String appVersion; + private static String appOrganization; + private static String appOwner; + private static String appAddress; + + private static int cookieMaxAge; + private static String servletContextPath; + private static String springMvcServletPath; + private static String servletUriPrefix; + private static String managementEndpointsBasePath; + private static String springdocApiDocsPath; + private static String springdocSwaggerUiPath; + private static String managementEndpointsBaseFullPath; + private static String springdocApiDocsFullPath; + private static String springdocSwaggerUiFullPath; + + private static Set excludedLogPaths = new HashSet<>(); + + private static boolean initialized; + + public static final void init(IAppProperties appProperties, IServletPathsPropertries servletProperties) + { + if(!initialized) + { + synchronized(AppConfigHolder.class) + { + if(!initialized) + { + readOnly = appProperties.isReadOnly(); + appId = appProperties.getId(); + appName = appProperties.getName(); + appVersion = appProperties.getVersion(); + appOrganization = appProperties.getOrganization(); + appOwner = appProperties.getOwner(); + cookieMaxAge = appProperties.getCookieMaxAge(); + appAddress = SystemUtil.getAddress(); + + servletContextPath = servletProperties.getServletContextPath(); + springMvcServletPath = servletProperties.getSpringMvcServletPath(); + managementEndpointsBasePath = servletProperties.getManagementEndpointsBasePath(); + springdocApiDocsPath = servletProperties.getSpringdocApiDocsPath(); + springdocSwaggerUiPath = servletProperties.getSpringdocSwaggerUiPath(); + + StringBuilder sb = new StringBuilder();; + + if(GeneralHelper.isStrNotEmpty(servletContextPath) && !servletContextPath.equals(REQUEST_PATH_SEPARATOR)) + sb.append(servletContextPath); + if(GeneralHelper.isStrNotEmpty(springMvcServletPath) && !springMvcServletPath.equals(REQUEST_PATH_SEPARATOR)) + sb.append(springMvcServletPath); + + servletUriPrefix = sb.toString(); + managementEndpointsBaseFullPath = servletUriPrefix + managementEndpointsBasePath; + springdocApiDocsFullPath = servletUriPrefix + springdocApiDocsPath; + springdocSwaggerUiFullPath = servletUriPrefix + springdocSwaggerUiPath; + + + excludedLogPaths.add(managementEndpointsBaseFullPath); + /* + excludedLogPaths.add(springdocApiDocsFullPath); + excludedLogPaths.add(springdocSwaggerUiFullPath); + excludedLogPaths.add(FAVICON_PATH); + */ + + initialized = true; + } + } + } + } + + public static boolean isReadOnly() + { + return readOnly; + } + + public static void setReadOnly(boolean readOnly) + { + AppConfigHolder.readOnly = readOnly; + } + + public static String getAppId() + { + return appId; + } + + public static String getAppName() + { + return appName; + } + + public static String getAppVersion() + { + return appVersion; + } + + public static String getAppOrganization() + { + return appOrganization; + } + + public static String getAppOwner() + { + return appOwner; + } + + public static String getAppAddress() + { + return appAddress; + } + + public static int getCookieMaxAge() + { + return cookieMaxAge; + } + + public static String getServletContextPath() + { + return servletContextPath; + } + + public static String getSpringMvcServletPath() + { + return springMvcServletPath; + } + + public static String getServletUriPrefix() + { + return servletUriPrefix; + } + + public static String getManagementEndpointsBasePath() + { + return managementEndpointsBasePath; + } + + public static String getSpringdocApiDocsPath() + { + return springdocApiDocsPath; + } + + public static String getSpringdocSwaggerUiPath() + { + return springdocSwaggerUiPath; + } + + public static String getManagementEndpointsBaseFullPath() + { + return managementEndpointsBaseFullPath; + } + + public static String getSpringdocApiDocsFullPath() + { + return springdocApiDocsFullPath; + } + + public static String getSpringdocSwaggerUiFullPath() + { + return springdocSwaggerUiFullPath; + } + + public static Set getExcludedLogPaths() + { + return excludedLogPaths; + } + + public static boolean isInitialized() + { + return initialized; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/holder/SpringContextHolder.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/holder/SpringContextHolder.java new file mode 100644 index 0000000..ef763e8 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/holder/SpringContextHolder.java @@ -0,0 +1,213 @@ + +package io.github.hpsocket.soa.framework.web.holder; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; + +import io.github.hpsocket.soa.framework.web.service.TracingContext; + +/** Spring 上下文持有者 */ +public class SpringContextHolder implements ApplicationContextAware +{ + private static ApplicationContext applicationContext; + private static TracingContext tracingContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + { + SpringContextHolder.applicationContext = applicationContext; + SpringContextHolder.tracingContext = getBean(TracingContext.class, false); + } + + public static ApplicationContext getApplicationContext() + { + return getApplicationContext(true); + } + + public static ApplicationContext getApplicationContext(boolean throwExceptionIfNull) + { + if(applicationContext == null && throwExceptionIfNull) + throw new RuntimeException("SpringContextHolder applicationContext has NOT been injected !"); + + return applicationContext; + } + + public static final TracingContext getTracingContext() + { + return tracingContext; + } + + public static final void publishEvent(ApplicationEvent event) + { + getApplicationContext().publishEvent(event); + } + + public static final void publishEvent(Object event) + { + getApplicationContext().publishEvent(event); + } + + public static T getBean(String beanId) + { + return getBean(getApplicationContext(), beanId, true); + } + + public static T getBean(String beanId, boolean valid) + { + return getBean(getApplicationContext(), beanId, valid); + } + + public static T getBean(String beanId, Object ... args) + { + return getBean(getApplicationContext(), beanId, true, args); + } + + public static T getBean(String beanId, boolean valid, Object ... args) + { + return getBean(getApplicationContext(), beanId, valid, args); + } + + public static T getBean(String beanId, Class clazz) + { + return getBean(getApplicationContext(), beanId, clazz, true); + } + + public static T getBean(String beanId, Class clazz, boolean valid) + { + return getBean(getApplicationContext(), beanId, clazz, valid); + } + + public static T getBean(Class clazz) + { + return getBean(getApplicationContext(), clazz, true); + } + + public static T getBean(Class clazz, boolean valid) + { + return getBean(getApplicationContext(), clazz, valid); + } + + public static T getBean(Class clazz, Object ... args) + { + return getBean(getApplicationContext(), clazz, true, args); + } + + public static T getBean(Class clazz, boolean valid, Object ... args) + { + return getBean(getApplicationContext(), clazz, valid, args); + } + + public static T getBean(ApplicationContext ctx, String beanId) + { + return getBean(ctx, beanId, true); + } + + @SuppressWarnings("unchecked") + public static T getBean(ApplicationContext ctx, String beanId, boolean valid) + { + T obj = null; + + try + { + obj = (T)ctx.getBean(beanId); + } + catch(RuntimeException e) + { + if(valid) + throw e; + } + + return obj; + } + + public static T getBean(ApplicationContext ctx, String beanId, Object ... args) + { + return getBean(ctx, beanId, true, args); + } + + @SuppressWarnings("unchecked") + public static T getBean(ApplicationContext ctx, String beanId, boolean valid, Object ... args) + { + T obj = null; + + try + { + obj = (T)ctx.getBean(beanId, args); + } + catch(RuntimeException e) + { + if(valid) + throw e; + } + + return obj; + } + + public static T getBean(ApplicationContext ctx, String beanId, Class clazz) + { + return getBean(ctx, beanId, clazz, true); + } + + public static T getBean(ApplicationContext ctx, String beanId, Class clazz, boolean valid) + { + T obj = null; + + try + { + obj = ctx.getBean(beanId, clazz); + } + catch(RuntimeException e) + { + if(valid) + throw e; + } + + return obj; + } + + public static T getBean(ApplicationContext ctx, Class clazz) + { + return getBean(ctx, clazz, true); + } + + public static T getBean(ApplicationContext ctx, Class clazz, boolean valid) + { + T obj = null; + + try + { + obj = ctx.getBean(clazz); + } + catch(RuntimeException e) + { + if(valid) + throw e; + } + + return obj; + } + + public static T getBean(ApplicationContext ctx, Class clazz, Object ... args) + { + return getBean(ctx, clazz, true, args); + } + + public static T getBean(ApplicationContext ctx, Class clazz, boolean valid, Object ... args) + { + T obj = null; + + try + { + obj = ctx.getBean(clazz, args); + } + catch(RuntimeException e) + { + if(valid) + throw e; + } + + return obj; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/json/FastJsonExcludePropertyFilter.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/json/FastJsonExcludePropertyFilter.java new file mode 100644 index 0000000..669894d --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/json/FastJsonExcludePropertyFilter.java @@ -0,0 +1,81 @@ +package io.github.hpsocket.soa.framework.web.json; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import com.alibaba.fastjson2.filter.PropertyFilter; +import io.github.hpsocket.soa.framework.web.json.JSONFieldExclude.Exclude; + +/** FastJson 属性排除过滤器
+ * 处理 {@linkplain JSONFieldExclude} 注解,序列化时排除特定属性 + */ +public class FastJsonExcludePropertyFilter implements PropertyFilter +{ + @Override + public boolean apply(Object object, String name, Object value) + { + if(object == null) + return true; + + Class clazz = object.getClass(); + + if(Map.class.isAssignableFrom(clazz) || Collection.class.isAssignableFrom(clazz) || clazz.isArray()) + return true; + + try + { + Field field = clazz.getDeclaredField(name); + + JSONFieldExclude annotation = field.getAnnotation(JSONFieldExclude.class); + + if(annotation == null) + return true; + + JSONFieldExclude.Exclude exclude = annotation.value(); + + if(exclude == Exclude.ALWAYS) + return false; + else if(exclude == Exclude.NULL) + return Objects.nonNull(value); + else if(exclude == Exclude.ABSENT) + { + if(Objects.isNull(value)) + return false; + + if(value instanceof Optional opt) + return opt.isPresent(); + } + else if(exclude == Exclude.EMPTY) + { + if(Objects.isNull(value)) + return false; + + if(value instanceof Optional opt) + return opt.isPresent(); + else if(value instanceof String str) + return !str.isEmpty(); + else if(value instanceof Collection col) + return !col.isEmpty(); + else if(value instanceof Map map) + return !map.isEmpty(); + else if(clazz.isArray()) + return Array.getLength(value) > 0; + } + else + { + throw new IllegalArgumentException("Unexpected value: " + exclude); + } + } + catch(NoSuchFieldException | SecurityException e) + { + + } + + return true; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/json/JSONFieldExclude.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/json/JSONFieldExclude.java new file mode 100644 index 0000000..a753afb --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/json/JSONFieldExclude.java @@ -0,0 +1,33 @@ +package io.github.hpsocket.soa.framework.web.json; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Optional; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** Json 属性排除注解
+ * 序列化时排除 {@linkplain Exclude} 注解指定的属性 + */ +@Documented +@Retention(RUNTIME) +@Target(ElementType.FIELD) +public @interface JSONFieldExclude +{ + Exclude value() default Exclude.ALWAYS; + + /** Json 属性排除类型 */ + public static enum Exclude + { + /** 如果属性值为 null 时,不序列化该属性 */ + NULL, + /** 如果属性值为 null 空 {@linkplain Optional} 时,不序列化该属性 */ + ABSENT, + /** 如果属性值为 null、空 {@linkplain Optional}、空字符串、空数组、空 {@linkplain java.util.Collection Collection}、空 {@linkplain java.util.Map Map} 时,不序列化该属性 */ + EMPTY, + /** 任何情况下都不序列化该属性 */ + ALWAYS + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/listener/ReadOnlyContextRefreshedEventListener.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/listener/ReadOnlyContextRefreshedEventListener.java new file mode 100644 index 0000000..53d9200 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/listener/ReadOnlyContextRefreshedEventListener.java @@ -0,0 +1,34 @@ +package io.github.hpsocket.soa.framework.web.listener; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; + +import io.github.hpsocket.soa.framework.web.event.ReadOnlyEvent; +import io.github.hpsocket.soa.framework.web.holder.AppConfigHolder; +import io.github.hpsocket.soa.framework.web.holder.SpringContextHolder; +import lombok.extern.slf4j.Slf4j; + +/** {@linkplain ReadOnlyEvent} 事件初始触发器
+ * 触发时机:应用程序启动时,跟随 {@linkplain org.springframework.context.event.ContextRefreshedEvent ContextRefreshedEvent} 事件之后 (此时 {@linkplain ReadOnlyEvent#isInitial()} 为 true) + */ +@Slf4j +public class ReadOnlyContextRefreshedEventListener implements ApplicationListener, Ordered +{ + @Override + public void onApplicationEvent(ContextRefreshedEvent event) + { + boolean readOnly = AppConfigHolder.isReadOnly(); + + if(readOnly) + log.info("application initial state -> (read-only: {})", readOnly); + + SpringContextHolder.publishEvent(new ReadOnlyEvent(event, readOnly, true)); + } + + @Override + public int getOrder() + { + return Ordered.LOWEST_PRECEDENCE; + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/listener/ReadOnlyRefreshEventListener.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/listener/ReadOnlyRefreshEventListener.java new file mode 100644 index 0000000..579f575 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/listener/ReadOnlyRefreshEventListener.java @@ -0,0 +1,43 @@ +package io.github.hpsocket.soa.framework.web.listener; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.endpoint.event.RefreshEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; + +import io.github.hpsocket.soa.framework.web.event.ReadOnlyEvent; +import io.github.hpsocket.soa.framework.web.holder.AppConfigHolder; +import io.github.hpsocket.soa.framework.web.holder.SpringContextHolder; +import io.github.hpsocket.soa.framework.web.propertries.IAppProperties; +import lombok.extern.slf4j.Slf4j; + +/** {@linkplain ReadOnlyEvent} 事件动态更新触发器
+ * 触发时机:动态修改应用程序的 ${hp.soa.web.app.read-only} 配置时,(此时 {@linkplain ReadOnlyEvent#isInitial()} 为 faise) + */ +@Slf4j +public class ReadOnlyRefreshEventListener implements ApplicationListener, Ordered +{ + @Autowired + IAppProperties appProperties; + + @Override + public void onApplicationEvent(RefreshEvent event) + { + boolean preVal = AppConfigHolder.isReadOnly(); + boolean curVal = appProperties.isReadOnly(); + + if(preVal != curVal) + { + log.info("application state switch -> (read-only: {})", curVal); + + AppConfigHolder.setReadOnly(curVal); + SpringContextHolder.publishEvent(new ReadOnlyEvent(event, curVal, false)); + } + } + + @Override + public int getOrder() + { + return Ordered.LOWEST_PRECEDENCE; + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/model/RequestAttribute.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/model/RequestAttribute.java new file mode 100644 index 0000000..8c90761 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/model/RequestAttribute.java @@ -0,0 +1,49 @@ +package io.github.hpsocket.soa.framework.web.model; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** HTTP 请求属性 */ +@Getter +@Setter +@NoArgsConstructor +public class RequestAttribute +{ + private String appCode; + private String srcAppCode; + private String token; + private String clientId; + private String requestId; + private String sessionId; + private Long groupId; + + private String clientAddr; + private String requestUri; + private String requestPath; + private String requestMethod; + + private String version; + private String extra; + + private transient Long userId; + private transient long timestamp; + + private Object body; + + public RequestAttribute(String appCode, String srcAppCode, String token, String clientId, String requestId, String sessionId) + { + this(appCode, srcAppCode, token, clientId, requestId, sessionId, null); + } + + public RequestAttribute(String appCode, String srcAppCode, String token, String clientId, String requestId, String sessionId, Long groupId) + { + this.appCode = appCode; + this.srcAppCode = srcAppCode; + this.token = token; + this.clientId = clientId; + this.requestId = requestId; + this.sessionId = sessionId; + this.groupId = groupId; + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/model/Response.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/model/Response.java new file mode 100644 index 0000000..37268e1 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/model/Response.java @@ -0,0 +1,111 @@ + +package io.github.hpsocket.soa.framework.web.model; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import io.github.hpsocket.soa.framework.core.exception.ServiceException; +import io.github.hpsocket.soa.framework.web.json.JSONFieldExclude; +import io.github.hpsocket.soa.framework.web.json.JSONFieldExclude.Exclude; + +import lombok.Getter; +import lombok.Setter; + +import static io.github.hpsocket.soa.framework.core.exception.ServiceException.*; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +/** HTTP 请求统一响应对象 */ +@Getter +@Setter +@SuppressWarnings("serial") +@Schema(description="接口方法返回值对象") +public class Response implements Serializable +{ + public static final String MSG_OK = "ok"; + public static final Integer RT_LOGIN = Integer.valueOf(1); + public static final Integer RT_LOGOUT = Integer.valueOf(2); + + /** 状态码:应用程序可自定义状态码,系统状态码参考 {@linkplain ServiceException} */ + @Schema(title="状态码", example="0", requiredMode=RequiredMode.REQUIRED, nullable=false) + private Integer statusCode = OK; + + /** 结果码:用于服务内部监控、统计,不暴露到调用方,应用程序可自定义结果码,系统结果码参考 {@linkplain ServiceException} */ + //@JsonIgnore + //@JSONField(serialize = false) + @Schema(title="业务处理代码", example="0", requiredMode=RequiredMode.NOT_REQUIRED, nullable=true) + private transient Integer resultCode = OK; + + /** 状态描述 */ + @Schema(title="状态描述", example=MSG_OK, requiredMode=RequiredMode.NOT_REQUIRED, nullable=true) + private String msg = MSG_OK; + + /** 服务端处理耗时(毫秒) */ + @Schema(title="耗时(毫秒)", example="456", requiredMode=RequiredMode.NOT_REQUIRED, nullable=true) + private long costTime; + + /** 参数校验错误列表 */ + //@JsonInclude(Include.NON_NULL) + @JSONFieldExclude(Exclude.NULL) + @Schema(title="参数校验错误列表", example="name is empty", requiredMode=RequiredMode.NOT_REQUIRED, nullable=true) + private Map> validationErrors; + + /** 业务模型对象 */ + @Schema(title="业务模型对象", example="Any Object", requiredMode=RequiredMode.NOT_REQUIRED, nullable=true) + private T result; + + /** 响应类型(目前仅用于登录登出操作,1 - 登录 - 2:登出) */ + //@JsonIgnore + //@JSONField(serialize = false) + @Schema(title="响应类型(目前仅用于登录登出操作,1 - 登录 - 2:登出)", example="null", requiredMode=RequiredMode.NOT_REQUIRED, nullable=true) + private transient Integer respType; + + public Response() + { + + } + + public Response(T result) + { + this.result = result; + } + + public Response(String msg, Integer statusCode) + { + this.msg = msg; + this.statusCode = statusCode; + } + + public Response(ServiceException e) + { + this(e.getMessage(), e.getStatusCode(), e.getResultCode()); + } + + public Response(Map> validationErrors) + { + this(PARAM_VERIFY_EXCEPTION, validationErrors); + } + + public Response(ServiceException e, Map> validationErrors) + { + this(e); + this.validationErrors = validationErrors; + } + + public Response(String msg, Integer statusCode, Integer resultCode) + { + this.msg = msg; + this.statusCode = statusCode; + this.resultCode = resultCode; + } + + public long calcCostTime(long beginTime) + { + costTime = System.currentTimeMillis() - beginTime; + + return costTime; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAccessVerificationProperties.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAccessVerificationProperties.java new file mode 100644 index 0000000..728b3f5 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAccessVerificationProperties.java @@ -0,0 +1,10 @@ +package io.github.hpsocket.soa.framework.web.propertries; + +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; + +/** HTTP 请求校验属性接口 */ +public interface IAccessVerificationProperties +{ + /** 默认 HTTP 请求校验类型 */ + AccessVerification.Type getDefaultAccessPolicyEnum(); +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAppProperties.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAppProperties.java new file mode 100644 index 0000000..5575675 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAppProperties.java @@ -0,0 +1,24 @@ +package io.github.hpsocket.soa.framework.web.propertries; + +/** 应用程序基本属性接口
+ * 由 HP-SOA 框架实现,应用程序可注入该接口服务 + */ +public interface IAppProperties +{ + /** 获取应用程序 ID */ + String getId(); + /** 获取应用程序名称 */ + String getName(); + /** 获取应用程序版本 */ + String getVersion(); + /** 获取应用程序所在组织 */ + String getOrganization(); + /** 获取应用程序拥有者 */ + String getOwner(); + + /** 获取应用程序默认 Cookie 最大生命周期 */ + int getCookieMaxAge(); + + /** 检测应用程序是否只读 */ + boolean isReadOnly(); +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAsyncProperties.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAsyncProperties.java new file mode 100644 index 0000000..2d505d4 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IAsyncProperties.java @@ -0,0 +1,15 @@ +package io.github.hpsocket.soa.framework.web.propertries; + +/** 异步配置接口
+ * 由 HP-SOA 框架实现,应用程序可注入该接口服务
+ * 由 ${hp.soa.web.async} 配置项提供配置值 + */ +public interface IAsyncProperties +{ + int getCorePoolSize(); + int getMaxPoolSize(); + int getKeepAliveSeconds(); + int getQueueCapacity(); + String getRejectionPolicy(); + boolean isAllowCoreThreadTimeOut(); +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IServletPathsPropertries.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IServletPathsPropertries.java new file mode 100644 index 0000000..061551a --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/propertries/IServletPathsPropertries.java @@ -0,0 +1,13 @@ +package io.github.hpsocket.soa.framework.web.propertries; + +/** Servlet 相关路径配置接口
+ * 由 HP-SOA 框架实现,应用程序可注入该接口服务 + */ +public interface IServletPathsPropertries +{ + String getServletContextPath(); + String getSpringMvcServletPath(); + String getManagementEndpointsBasePath(); + String getSpringdocApiDocsPath(); + String getSpringdocSwaggerUiPath(); +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/server/init/ServerInitializer.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/server/init/ServerInitializer.java new file mode 100644 index 0000000..0bb901f --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/server/init/ServerInitializer.java @@ -0,0 +1,109 @@ +package io.github.hpsocket.soa.framework.web.server.init; + +import java.io.IOException; +import java.util.Properties; + +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.DefaultPropertySourceFactory; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.core.util.SystemUtil; + +/** 应用程序初始化器
+ *
    + *
  1. 加载由 {@linkplain #SYSTEM_PROPERTIES_FILE_KEY} JVM 启动参数指定的系统属性配置文件
  2. + *
  3. 设置其它系统属性
  4. + *
+ */ +public class ServerInitializer +{ + private static final String SYSTEM_PROPERTIES_FILE_KEY = "hp.soa.system.properties.file"; + private static final String LOCAL_IP_ADDRESS = "local.ip.address"; + private static final String LOG4J2_CONTEXT_SELECTOR = "log4j2.contextSelector"; + private static final String LOG4J2_CONTEXT_SELECTOR_VALUE = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"; + private static final String LOG4J2_GARBAGE_FREE_THREAD_CONTEXT_MAP = "log4j2.garbagefreeThreadContextMap"; + private static final String LOG4J2_IS_THREAD_CONTEXT_MAP_INHERITABLE = "log4j2.isThreadContextMapInheritable"; + private static final String LOG4J2_LAYOUT_JSON_TEMPLATE_LOCATION_INFO_ENABLED = "log4j.layout.jsonTemplate.locationInfoEnabled"; + + public static final void initSystemProperties() + { + loadExternalSystemProperties(); + + setSystemProperties(LOCAL_IP_ADDRESS, SystemUtil.getAddress()); + setSystemProperties(LOG4J2_CONTEXT_SELECTOR, LOG4J2_CONTEXT_SELECTOR_VALUE); + setSystemProperties(LOG4J2_GARBAGE_FREE_THREAD_CONTEXT_MAP, Boolean.TRUE); + setSystemProperties(LOG4J2_IS_THREAD_CONTEXT_MAP_INHERITABLE, Boolean.TRUE); + setSystemProperties(LOG4J2_LAYOUT_JSON_TEMPLATE_LOCATION_INFO_ENABLED, Boolean.TRUE); + } + + private static void loadExternalSystemProperties() + { + StandardEnvironment environment = new StandardEnvironment(); + String filePath = environment.getProperty(SYSTEM_PROPERTIES_FILE_KEY); + + if(GeneralHelper.isStrEmpty(filePath)) + { + System.out.println("hp-soa not uses system properties file"); + return; + } + + System.out.println("load hp-soa system properties file -> " + filePath); + + String resolvedLocation = "file:" + environment.resolveRequiredPlaceholders(filePath); + PropertySourceFactory factory = new DefaultPropertySourceFactory(); + Resource resource = new DefaultResourceLoader().getResource(resolvedLocation); + + try + { + PropertySource propertySource = factory.createPropertySource(filePath, new EncodedResource(resource)); + Properties props = (Properties)propertySource.getSource(); + + props.forEach((k, v) -> setSystemProperties((String)k, (String)v)); + } + catch(IOException e) + { + String msg = String.format("load hp-soa system properties file fail -> [%s] %s", filePath, e.getMessage()); + + System.err.println(msg); + e.printStackTrace(); + + System.exit(1); + } + } + + public static final boolean setSystemProperties(String key, Object value) + { + return setSystemProperties(key, value, false); + } + + public static final boolean setSystemProperties(String key, Object value, boolean override) + { + if(value == null) + { + System.getProperties().remove(key); + return true; + } + + boolean rs = true; + String realVal = value.toString(); + + if(override) + System.setProperty(key, realVal); + else + { + String val = System.getProperty(key); + + if(GeneralHelper.isStrEmpty(val)) + System.setProperty(key, realVal); + else + rs = false; + } + + return rs; + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/server/main/AppStarter.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/server/main/AppStarter.java new file mode 100644 index 0000000..eaef0bd --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/server/main/AppStarter.java @@ -0,0 +1,40 @@ + +package io.github.hpsocket.soa.framework.web.server.main; + +import io.github.hpsocket.soa.framework.web.server.init.ServerInitializer; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; +import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** 默认应用程序启动器
+ * 启动应用程序 + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, + RedisAutoConfiguration.class, + RedisRepositoriesAutoConfiguration.class, + MongoAutoConfiguration.class, + KafkaAutoConfiguration.class, + RabbitAutoConfiguration.class + }) +@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true) +@ComponentScan(basePackages = {"${hp.soa.web.component-scan.base-package:}"}) +public class AppStarter +{ + static + { + ServerInitializer.initSystemProperties(); + } + + public static void main(String[] args) + { + SpringApplication.run(AppStarter.class, args); + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/AccessVerificationService.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/AccessVerificationService.java new file mode 100644 index 0000000..202ce28 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/AccessVerificationService.java @@ -0,0 +1,17 @@ +package io.github.hpsocket.soa.framework.web.service; + +import io.github.hpsocket.soa.framework.core.util.Pair; + +/** HTTP 请求校验服务接口
+ * 所有需要执行 HTTP 请求校验的应用程序都必须实现该接口 + */ +public interface AccessVerificationService +{ + /** appCode 校验 */ + boolean verifyAppCode(String appCode); + /** 用户认证校验 */ + Pair verifyUserByTokenAndGroupId(String token, Long groupId); + /** 用户授权校验 */ + Pair verifyRouteAuthorized(String route, String appCode, Long groupId, Long userId); + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/AsyncService.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/AsyncService.java new file mode 100644 index 0000000..826c883 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/AsyncService.java @@ -0,0 +1,24 @@ +package io.github.hpsocket.soa.framework.web.service; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +/** 异步服务接口
+ * 由 HP-SOA 框架实现,该服务支持调用链跟踪,应用程序可注入该接口服务 + */ +public interface AsyncService +{ + /** 异步执行 {@linkplain Runnable} */ + void runAsync(Runnable task); + /** 异步调用某个对象的方法 */ + void invokeAsync(final Object obj, final Method method, final Object ... args); + /** 异步执行 {@linkplain Runnable} */ + void execute(Runnable task); + /** 异步提交 {@linkplain Runnable} */ + Future submit(Runnable task); + /** 异步提交 {@linkplain Callable} */ + Future submit(Callable task); + /** 异步提交 {@linkplain Runnable} */ + Future submit(Runnable task, T result); +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/TracingContext.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/TracingContext.java new file mode 100644 index 0000000..32a8d35 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/TracingContext.java @@ -0,0 +1,12 @@ +package io.github.hpsocket.soa.framework.web.service; + +/** 调用链上下文接口
+ * 由 HP-SOA 框架实现,应用程序可注入该接口服务 + */ +public interface TracingContext +{ + /** 获取调用链 traceId */ + String getTraceId(); + /** 获取调用链 spanId */ + String getSpanId(); +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/impl/AsyncServiceImpl.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/impl/AsyncServiceImpl.java new file mode 100644 index 0000000..b431c69 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/service/impl/AsyncServiceImpl.java @@ -0,0 +1,95 @@ +package io.github.hpsocket.soa.framework.web.service.impl; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +import javax.annotation.PreDestroy; + +import io.github.hpsocket.soa.framework.core.thread.AsyncThreadPoolExecutor; +import io.github.hpsocket.soa.framework.web.service.AsyncService; + +import lombok.Getter; +import lombok.Setter; + +/** 异步服务实现
+ * 实现 {@linkplain AsyncService} 接口,支持调用链跟踪 + */ +@Getter +@Setter +public class AsyncServiceImpl implements AsyncService +{ + private AsyncThreadPoolExecutor executor; + + public AsyncServiceImpl(AsyncThreadPoolExecutor executor) + { + this.executor = executor; + } + + @PreDestroy + public void preDestroy() + { + if(executor != null) + { + executor.shutdown(); + executor = null; + } + } + + @Override + public void runAsync(Runnable task) + { + execute(task); + } + + @Override + public void invokeAsync(final Object obj, final Method method, final Object ... args) + { + execute(new Runnable() { + + @Override + public void run() + { + try + { + method.invoke(obj, args); + } + catch(Throwable e) + { + if(e instanceof InvocationTargetException) + e = e.getCause(); + + String msg = String.format("invoke async method '%s' exception", method); + throw new RuntimeException(msg, e); + } + } + }); + + } + + @Override + public void execute(Runnable task) + { + executor.execute(task); + } + + @Override + public Future submit(Runnable task) + { + return executor.submit(task); + } + + @Override + public Future submit(Callable task) + { + return executor.submit(task); + } + + @Override + public Future submit(Runnable task, T result) + { + return executor.submit(task, result); + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/AspectHelper.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/AspectHelper.java new file mode 100644 index 0000000..17a8954 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/AspectHelper.java @@ -0,0 +1,216 @@ + +package io.github.hpsocket.soa.framework.web.support; + +import java.lang.annotation.Annotation; +import java.lang.ref.SoftReference; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import com.google.common.collect.Maps; + +/** Aspect 拦截信息检索辅助类 */ +public class AspectHelper +{ + private static ExpressionParser expressionParser = new SpelExpressionParser(); + private static ConcurrentMap expressionMap = Maps.newConcurrentMap(); + + public static final EvaluationContext buildContext(JoinPoint point, Object result) + { + EvaluationContext context = buildContext(point); + context.setVariable("result", result); + + return context; + } + + public static final EvaluationContext buildContext(JoinPoint point) + { + EvaluationContext context = new StandardEvaluationContext(); + MethodSignature ms = (MethodSignature)point.getSignature(); + + String[] parameterNames = ms.getParameterNames(); + Object[] args = point.getArgs(); + + for(int i = 0; i < parameterNames.length; i++) + context.setVariable(parameterNames[i], args[i]); + + return context; + } + + public static final Method getMethod(JoinPoint point) + { + Class targetCls = point.getTarget().getClass(); + MethodSignature ms = (MethodSignature)point.getSignature(); + + try + { + return targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes()); + } + catch(NoSuchMethodException e) + { + return null; + } + } + + public static final T getMethodAnnotation(JoinPoint point, Class annotationType) + { + Method targetMethod = getMethod(point); + + if(targetMethod == null) + return null; + + return targetMethod.getAnnotation(annotationType); + } + + public static final Annotation[] getMethodAnnotations(JoinPoint point) + { + Method targetMethod = getMethod(point); + + if(targetMethod == null) + return new Annotation[0]; + + return targetMethod.getAnnotations(); + } + + public static final Class getMethodReturnType(JoinPoint point) + { + Method targetMethod = getMethod(point); + + if(targetMethod == null) + return null; + + return targetMethod.getReturnType(); + } + + public static final T getClassAnnotation(JoinPoint point, Class annotationType) + { + Class targetCls = point.getTarget().getClass(); + return targetCls.getAnnotation(annotationType); + } + + public static final T getMethodOrClassAnnotation(JoinPoint point, Class annotationType) + { + T annotation = getMethodAnnotation(point, annotationType); + + if(annotation == null) + annotation = getClassAnnotation(point, annotationType); + + return annotation; + } + + public static final Expression getExpressionByKey(String key) + { + if(expressionMap.containsKey(key)) + return expressionMap.get(key); + + Expression expression = expressionParser.parseExpression(key); + expressionMap.putIfAbsent(key, expression); + + return expression; + } + + public static final Map getParameters(JoinPoint point) + { + MethodSignature ms = (MethodSignature)point.getSignature(); + String[] parameterNames = ms.getParameterNames(); + Object[] args = point.getArgs(); + + Map paramMap = Maps.newHashMapWithExpectedSize(parameterNames.length); + + for(int i = 0; i < parameterNames.length; i++) + paramMap.put(parameterNames[i], args[i]); + + return paramMap; + } + + public static final Object[] getArgs(JoinPoint point) + { + return point.getArgs(); + } + + public static final Object findFirstArgByType(JoinPoint point, Class clazz) + { + Object[] args = getArgs(point); + + for(Object arg : args) + { + if(clazz.isInstance(arg)) + return arg; + } + + return null; + } + + public static final Object findFirstArgByTypes(JoinPoint point, Class ... clazzes) + { + Object[] args = getArgs(point); + + for(Object arg : args) + { + for(Class clazz : clazzes) + { + if(clazz.isInstance(arg)) + return arg; + } + } + + return null; + } + + /** 注解信息持有者 */ + abstract public static class AnnotationHolder + { + private Map> map = new ConcurrentHashMap<>(); + + public AnnotationHolder() {} + + @SuppressWarnings("unchecked") + public T findAnnotation(JoinPoint jp, Function funcKey, BiFunction, T> funcFind) + { + Object key = funcKey.apply(jp); + T val = Optional.ofNullable(map.get(key)).map(SoftReference::get).orElse(null); + + if(val == null) + { + ParameterizedType type = (ParameterizedType)this.getClass().getGenericSuperclass(); + Class tClazz = (Class)type.getActualTypeArguments()[0]; + + val = funcFind.apply(jp, tClazz); + + if(val != null) + map.put(key, new SoftReference<>(val)); + } + + return val; + } + + public T findAnnotationByMethod(JoinPoint jp) + { + return findAnnotation(jp, (j) -> ((MethodSignature)j.getSignature()).getMethod(), (j, cls) -> getMethodAnnotation(j, cls)); + } + + public T findAnnotationByMethodOrClass(JoinPoint jp) + { + return findAnnotation(jp, (j) -> ((MethodSignature)j.getSignature()).getMethod(), (j, cls) -> getMethodOrClassAnnotation(j, cls)); + } + + public T findAnnotationByClass(JoinPoint jp) + { + return findAnnotation(jp, (j) -> j.getTarget().getClass(), (j, cls) -> getClassAnnotation(j, cls)); + } + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/WebServerHelper.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/WebServerHelper.java new file mode 100644 index 0000000..fd3759b --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/WebServerHelper.java @@ -0,0 +1,420 @@ +package io.github.hpsocket.soa.framework.web.support; + +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.MDC; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import io.github.hpsocket.soa.framework.core.id.IdGenerator; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.core.thread.SynchronousRejectedExecutionHandler; +import io.github.hpsocket.soa.framework.core.util.CryptHelper; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.core.util.Result; + +import com.alibaba.fastjson2.JSONWriter; +import io.github.hpsocket.soa.framework.web.holder.AppConfigHolder; +import io.github.hpsocket.soa.framework.web.holder.SpringContextHolder; +import io.github.hpsocket.soa.framework.web.service.TracingContext; + +import static com.alibaba.fastjson2.JSONWriter.Feature.*; + +import static io.github.hpsocket.soa.framework.web.holder.AppConfigHolder.*; + +/** 通用 Web 功能辅助类 */ +public class WebServerHelper +{ + public static final String REQUEST_ATTRIBUTE_JSON_BODY = "__request.attribute.json.body__"; + public static final String REQUEST_ATTRIBUTE_RESP_BODY = "__request.attribute.resp.body__"; + public static final String REQUEST_ATTRIBUTE_CONTEXT = "__request.attribute.context__"; + public static final String REQUEST_ATTRIBUTE_INFO = "__request.attribute.info__"; + + public static final String HEADER_REQUEST_INFO = "X-Request-Info"; + public static final String HEADER_REQUEST_ID = "requestId"; + public static final String HEADER_CLIENT_ID = "clientId"; + public static final String HEADER_SESSION_ID = "sessionId"; + public static final String HEADER_TOKEN = "token"; + public static final String HEADER_APP_CODE = "appCode"; + public static final String HEADER_SRC_APP_CODE = "srcAppCode"; + public static final String HEADER_GROUP_ID = "groupId"; + public static final String HEADER_VERSION = "version"; + public static final String HEADER_EXTRA = "extra"; + + public static final int DEFAULT_COOKIE_MAX_AGE = 10 * 365 * 24 * 60 * 60; + public static final String DEFAULT_CHARSET = GeneralHelper.DEFAULT_ENCODING; + public static final Charset DEFAULT_CHARSET_OBJ = Charset.forName(DEFAULT_CHARSET); + + public static final boolean HTTP_ONLY_COOKIE = false; + + public static final String GET = "GET"; + public static final String PUT = "PUT"; + public static final String POST = "POST"; + public static final String DELETE = "DELETE"; + public static final String HEAD = "HEAD"; + public static final String PATCH = "PATCH"; + public static final String OPTIONS = "OPTIONS"; + public static final String TRACE = "TRACE"; + public static final String CONNECT = "CONNECT"; + + public static final String REQUEST_FORMAT_JSON = "json"; + public static final String REQUEST_FORMAT_FORM = "form"; + public static final String DEFAULT_REQUEST_CHARSET = DEFAULT_CHARSET; + public static final String DEFAULT_REQUEST_FORMAT = REQUEST_FORMAT_JSON; + public static final String RESPONSE_CONTENT_TYPE = "application/json;charset=" + DEFAULT_REQUEST_CHARSET.toLowerCase(); + public static final Pattern DOMAIN_PATTERN = Pattern.compile("[0-9a-zA-Z]+((\\.com\\.cn)|(\\.com)|(\\.cn)|(\\.net)|(\\.org)|(\\.edu))$"); + public static final Pattern SPIDER_PATTERN = Pattern.compile(".*(Googlebot|Baiduspider|Yahoo\\!\\ Slurp|iaskspider|YodaoBot|msnbot|\\ spider)([\\/\\+\\ \\;]).*", Pattern.CASE_INSENSITIVE); + + public static final String MONITOR_LOGGER_NAME = "SOA-MONITOR"; + public static final String MONITOR_INGRESS = "MONITOR-INGRESS"; + public static final String MONITOR_EGRESS = "MONITOR-EGRESS"; + + public static final JSONWriter.Feature[] JSON_SERIAL_FEATURES_DEFAULT = {WriteByteArrayAsBase64, WriteNonStringKeyAsString, WriteMapNullValue}; + public static final JSONWriter.Feature[] JSON_SERIAL_FEATURES_NO_NULL_VAL = {WriteByteArrayAsBase64, WriteNonStringKeyAsString}; + + public static final ThreadPoolExecutor ASYNC_LOG_EXECUTOR = new ThreadPoolExecutor( 4, + 16, + 60, + TimeUnit.SECONDS, + new LinkedBlockingDeque<>(2000), + new ThreadPoolExecutor.CallerRunsPolicy()); + + /** 检测 HTTP 请求的 User-Agent 是否合法 */ + public static final boolean checkUserAgent(String ua) + { + return (GeneralHelper.isStrNotEmpty(ua) && !SPIDER_PATTERN.matcher(ua).matches()); + } + + /** 检测 HTTP 请求是否包含 Body */ + public static final boolean isHasBodyRequest(String method) + { + return GeneralHelper.isStrNotEmpty(method) + && (POST.equalsIgnoreCase(method) || PUT.equalsIgnoreCase(method) || PATCH.equals(method)); + } + + /** 检测 HTTP Content-Type 是否为 JSON 类型 */ + public static final boolean isJsonContentType(String ct) + { + if(GeneralHelper.isStrNotEmpty(ct)) + return false; + + int index = ct.indexOf('/'); + + if(index < 0) + return false; + + return ct.regionMatches(true, 0, REQUEST_FORMAT_JSON, index + 1, REQUEST_FORMAT_JSON.length()); + } + + /** 解析 HTTP 响应 Cookie 的 Domain */ + public static final String retriveHostDomain(String host) + { + int index = host.indexOf(':'); + + if(index != -1) + host = host.substring(0, index); + + if(!GeneralHelper.isStrIPAddress(host)) + { + Matcher m = DOMAIN_PATTERN.matcher(host); + + if(m.find()) + host = m.group(); + } + + return host; + } + + /** 检测 HTTP 请求 Cookie */ + public static final Result checkClientCookie(HttpServletRequest req) + { + boolean exists = false; + Cookie clientCookie = getCookie(req, HEADER_CLIENT_ID); + + if(clientCookie != null) + exists = true; + + if(!exists) + clientCookie = createCookie(req, HEADER_CLIENT_ID, randomUUID(), getCookieMaxAge()); + + return new Result<>(exists, clientCookie); + } + + /** 创建 HTTP 响应 Cookie */ + public static final Cookie createCookie(HttpServletRequest req, String name, String value, int maxAge) + { + Cookie cookie = new Cookie(name, value); + + cookie.setPath("/"); + cookie.setMaxAge(maxAge); + cookie.setHttpOnly(HTTP_ONLY_COOKIE); + + String host = req.getHeader("Host"); + + if(GeneralHelper.isStrNotEmpty(host)) + { + host = retriveHostDomain(host); + cookie.setDomain(host); + } + + return cookie; + } + + /** 获取 HTTP 请求 Cookie */ + public static final Cookie getCookie(HttpServletRequest req, String name) + { + return getCookie(req, name, false); + } + + /** 获取 HTTP 请求 Cookie */ + public static final Cookie getCookie(HttpServletRequest req, String name, boolean includeEmptyValue) + { + Cookie cookie = null; + Cookie[] cookies = req.getCookies(); + + if(cookies != null && cookies.length > 0) + { + for(Cookie c : cookies) + { + if(name.equals(c.getName()) && (includeEmptyValue || GeneralHelper.isStrNotEmpty(c.getValue()))) + { + cookie = c; + break; + } + } + } + + return cookie; + } + + /** 检查是否为 JSON 类型 HTTP 请求 */ + public static final boolean isJsonRequest(HttpServletRequest req) + { + return isHasBodyRequest(req.getMethod()) && isJsonContentType(req.getContentType()); + } + + /** 获取 HTTP 请求 User-Agent */ + public static final String getUserAgent(HttpServletRequest req) + { + return req.getHeader("User-Agent"); + } + + /** 获取 HTTP 请求客户端 IP */ + public static final String getIpAddr(HttpServletRequest request) + { + String ip = request.getHeader("X-Real-IP"); + + if(GeneralHelper.isStrEmpty(ip)) + { + String forwards = request.getHeader("X-Forwarded-For"); + + if(GeneralHelper.isStrNotEmpty(forwards)) + { + int i = forwards.indexOf(','); + ip = GeneralHelper.safeTrimString(i >= 0 ? forwards.substring(0, i) : forwards); + } + + if(GeneralHelper.isStrEmpty(ip)) + { + ip = request.getRemoteAddr(); + } + } + + return ip; + } + + /** 获取 HTTP 请求 Url */ + public static final String getRequestUri(HttpServletRequest request) + { + String requestUri = request.getRequestURI(); + + if(GeneralHelper.isStrEmpty(requestUri)) + { + requestUri = REQUEST_PATH_SEPARATOR; + } + + return requestUri; + } + + /** 获取 HTTP 请求 Path */ + public static final String getRequestPath(HttpServletRequest request) + { + String requestUri = getRequestUri(request); + String requestPath = requestUri.substring(getServletUriPrefix().length()); + + return requestPath; + } + + /** 获取 HTTP 请求 Method */ + public static final String getRequestMethod(HttpServletRequest request) + { + return request.getMethod(); + } + + /** 解析 HTTP 请求信息 */ + @SuppressWarnings("unchecked") + public static final Map parseRequestInfo(HttpServletRequest request) + { + Map infos = (Map)request.getAttribute(REQUEST_ATTRIBUTE_INFO); + + if(infos != null) + return infos; + + infos = new HashMap<>(); + + String encoding = request.getCharacterEncoding(); + + if(GeneralHelper.isStrEmpty(encoding)) + encoding = DEFAULT_REQUEST_CHARSET; + + String header = request.getHeader(HEADER_REQUEST_INFO); + + if(GeneralHelper.isStrNotEmpty(header)) + { + StringTokenizer st = new StringTokenizer(header, ";"); + + while(st.hasMoreTokens()) + { + String field = st.nextToken(); + int i = field.indexOf('='); + + if(i > 0) + { + String key = field.substring(0, i).trim(); + + if(GeneralHelper.isStrNotEmpty(key)) + { + String value = field.substring(i + 1).trim(); + + infos.put(key, CryptHelper.urlDecode(value)); + } + } + } + } + + request.setAttribute(REQUEST_ATTRIBUTE_INFO, infos); + + return infos; + } + + /** 创建调用链 MDC 相关属性 */ + public static final MdcAttr createMdcAttr() + { + return createMdcAttr(true); + } + + /** 创建调用链 MDC 相关属性 */ + public static final MdcAttr createMdcAttr(boolean generateRequestId) + { + MdcAttr mdcAttr = new MdcAttr(); + + mdcAttr.setAppId(AppConfigHolder.getAppId()); + mdcAttr.setAppName(AppConfigHolder.getAppName()); + mdcAttr.setServiceId(AppConfigHolder.getAppId()); + mdcAttr.setServiceName(AppConfigHolder.getAppName()); + mdcAttr.setServiceAddr(AppConfigHolder.getAppAddress()); + + mdcAttr.setFromServiceId(AppConfigHolder.getAppId()); + mdcAttr.setFromServiceName(AppConfigHolder.getAppName()); + mdcAttr.setFromServiceAddr(AppConfigHolder.getAppAddress()); + + if(GeneralHelper.isStrNotEmpty(AppConfigHolder.getAppOrganization())) + mdcAttr.setOrganization(AppConfigHolder.getAppOrganization()); + if(GeneralHelper.isStrNotEmpty(AppConfigHolder.getAppOwner())) + mdcAttr.setOwner(AppConfigHolder.getAppOwner()); + + if(generateRequestId) + mdcAttr.setRequestId(randomUUID()); + + String traceId = getTraceId(); + + if(GeneralHelper.isStrNotEmpty(traceId)) + mdcAttr.setTraceId(traceId); + + return mdcAttr; + } + + /** 调用链的 traceId 注入到 MDC */ + public static final void putMdcTraceId() + { + String traceId = getTraceId(); + + if(GeneralHelper.isStrNotEmpty(traceId)) + MDC.put(MdcAttr.MDC_TRACE_ID_KEY, traceId); + } + + /** 调用链的 traceId 从 MDC 中删除 */ + public static final void removeMdcTraceId() + { + MDC.remove(MdcAttr.MDC_TRACE_ID_KEY); + } + + /** 获取调用链的 traceId */ + public static final String getTraceId() + { + String traceId = null; + TracingContext tracingContext = SpringContextHolder.getTracingContext(); + + if(tracingContext != null) + traceId = tracingContext.getTraceId(); + + return traceId; + } + + /** 获取调用链的当前 spanId */ + public static final String getSpanId() + { + String spanId = null; + TracingContext tracingContext = SpringContextHolder.getTracingContext(); + + if(tracingContext != null) + spanId = tracingContext.getSpanId(); + + return spanId; + } + + /** 字符串转换为 {@linkplain RejectedExecutionHandler} 对象 */ + public static final RejectedExecutionHandler parseRejectedExecutionHandler(String rejectionPolicy, String defaultRejectionPolicy) + { + RejectedExecutionHandler rjh = null; + + if(GeneralHelper.isStrEmpty(rejectionPolicy)) + rejectionPolicy = defaultRejectionPolicy; + + if("CALLER_RUNS".equalsIgnoreCase(rejectionPolicy)) + rjh = new ThreadPoolExecutor.CallerRunsPolicy(); + else if("ABORT".equalsIgnoreCase(rejectionPolicy)) + rjh = new ThreadPoolExecutor.AbortPolicy(); + else if("DISCARD".equalsIgnoreCase(rejectionPolicy)) + rjh = new ThreadPoolExecutor.DiscardPolicy(); + else if("DISCARD_OLDEST".equalsIgnoreCase(rejectionPolicy)) + rjh = new ThreadPoolExecutor.DiscardOldestPolicy(); + else if("SYNC".equalsIgnoreCase(rejectionPolicy)) + rjh = new SynchronousRejectedExecutionHandler(); + else + throw new RuntimeException(String.format("invalid rejection execution handler '%s'", rejectionPolicy)); + + return rjh; + } + + /** 检查应用程序是否只读 */ + public static final boolean isAppReadOnly() + { + return AppConfigHolder.isReadOnly(); + } + + /** 创建随机 UUID */ + public static final String randomUUID() + { + return IdGenerator.nextCompactUUID(); + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/YamlPropertiesLoader.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/YamlPropertiesLoader.java new file mode 100644 index 0000000..e1f6801 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/YamlPropertiesLoader.java @@ -0,0 +1,51 @@ +package io.github.hpsocket.soa.framework.web.support; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Enumeration; +import java.util.Properties; + +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.io.InputStreamResource; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; + +/** YAML 属性加载器 */ +public class YamlPropertiesLoader +{ + public static final Properties loadAllProperties(String resourceName) throws IOException + { + return loadAllProperties(resourceName, null); + } + + public static final Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException + { + Properties props = new Properties(); + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + + if(classLoader == null) + classLoader = ClassUtils.getDefaultClassLoader(); + + Enumeration urls = (classLoader != null ? classLoader.getResources(resourceName) : ClassLoader.getSystemResources(resourceName)); + + while(urls.hasMoreElements()) + { + URL url = urls.nextElement(); + + URLConnection con = url.openConnection(); + ResourceUtils.useCachesIfNecessary(con); + + try(InputStream is = con.getInputStream()) + { + factory.setResources(new InputStreamResource(is, resourceName)); + factory.afterPropertiesSet(); + props.putAll(factory.getObject()); + } + } + + return props; + } + +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/YamlPropertySourceFactory.java b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/YamlPropertySourceFactory.java new file mode 100644 index 0000000..d098941 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/java/io/github/hpsocket/soa/framework/web/support/YamlPropertySourceFactory.java @@ -0,0 +1,44 @@ + +package io.github.hpsocket.soa.framework.web.support; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; + +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; +import org.springframework.lang.Nullable; + +/** YAML {@linkplain PropertySourceFactory} */ +public class YamlPropertySourceFactory implements PropertySourceFactory +{ + @Override + public PropertySource createPropertySource(@Nullable String name, EncodedResource resource) throws IOException + { + Properties propertiesFromYaml = loadYamlIntoProperties(resource); + String sourceName = name != null ? name : resource.getResource().getFilename(); + return new PropertiesPropertySource(sourceName, propertiesFromYaml); + } + + private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException + { + try + { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(resource.getResource()); + factory.afterPropertiesSet(); + return factory.getObject(); + } + catch(IllegalStateException e) + { + Throwable cause = e.getCause(); + if(cause instanceof FileNotFoundException) + throw (FileNotFoundException)e.getCause(); + + throw e; + } + } +} diff --git a/hp-soa-framework/hp-soa-framework-web/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter b/hp-soa-framework/hp-soa-framework-web/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter new file mode 100644 index 0000000..01b0316 --- /dev/null +++ b/hp-soa-framework/hp-soa-framework-web/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter @@ -0,0 +1 @@ +mdc=io.github.hpsocket.soa.framework.web.dubbo.filter.DubboMdcFilter \ No newline at end of file diff --git a/hp-soa-framework/pom.xml b/hp-soa-framework/pom.xml new file mode 100644 index 0000000..35bd81f --- /dev/null +++ b/hp-soa-framework/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-parent + ${revision} + + hp-soa-framework + ${project.artifactId} + pom + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + hp-soa-framework-core + hp-soa-framework-web + + + + + + io.github.hpsocket + hp-soa-dependencies + ${project.parent.version} + pom + import + + + + + + + junit + junit + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-install-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + + + diff --git a/hp-soa-starter/.gitignore b/hp-soa-starter/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/.gitignore b/hp-soa-starter/hp-soa-starter-data-mysql/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/pom.xml b/hp-soa-starter/hp-soa-starter-data-mysql/pom.xml new file mode 100644 index 0000000..5c3a32d --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-data-mysql + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + com.mysql + mysql-connector-j + + + com.alibaba + druid-spring-boot-3-starter + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + com.baomidou + mybatis-plus-boot-starter + + + com.baomidou + dynamic-datasource-spring-boot3-starter + + + diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaDruidConfig.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaDruidConfig.java new file mode 100644 index 0000000..38c60a7 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaDruidConfig.java @@ -0,0 +1,90 @@ + +package io.github.hpsocket.soa.starter.data.mysql.config; + +import java.io.IOException; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; + +import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure; +import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** HP-SOA Druid 数据库连接池配置 */ +@Slf4j +@AutoConfiguration +@ConditionalOnWebApplication +@AutoConfigureAfter(DruidDataSourceAutoConfigure.class) +@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true") +public class SoaDruidConfig +{ + + /** Druid 监控页面广告屏蔽 {@linkplain Filter} */ + @Bean + public FilterRegistrationBean druidAdFilterRegistrationBean(DruidStatProperties properties) + { + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + + Filter filter = new Filter() + { + private String commonJs; + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + log.info("starting up: (DruidAdRemoverFilter) ..."); + } + + @Override + public void destroy() + { + log.info("shutting down: (DruidAdRemoverFilter) OK!"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + chain.doFilter(request, response); + response.resetBuffer(); + + if(GeneralHelper.isStrEmpty(commonJs)) + { + synchronized(this) + { + if(GeneralHelper.isStrEmpty(commonJs)) + { + String text = Utils.readFromResource(filePath); + commonJs = text.replaceAll("", ""); + } + } + } + + response.getWriter().write(commonJs); + } + }; + + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + + return registrationBean; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaMapperScanConfig.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaMapperScanConfig.java new file mode 100644 index 0000000..cbead7b --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaMapperScanConfig.java @@ -0,0 +1,17 @@ + +package io.github.hpsocket.soa.starter.data.mysql.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +/** HP-SOA Mybatis 默认 {@linkplain MapperScan#basePackages()} 配置 */ +@AutoConfiguration +@MapperScan("${hp.soa.web.mapper-scan.base-package}") +@ConditionalOnExpression("'${hp.soa.web.mapper-scan.base-package:}' != ''") +@ConditionalOnProperty(name = "spring.datasource.dynamic.enabled", matchIfMissing = true) +public class SoaMapperScanConfig +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaMybatisPlusConfig.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaMybatisPlusConfig.java new file mode 100644 index 0000000..f7c9749 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaMybatisPlusConfig.java @@ -0,0 +1,66 @@ + +package io.github.hpsocket.soa.starter.data.mysql.config; + +import java.time.LocalDateTime; + +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; + +import io.github.hpsocket.soa.starter.data.mysql.interceptor.ReadOnlyInterceptor; + +/** HP-SOA mybatis-plus 配置 */ +@AutoConfiguration +@ConditionalOnProperty(name = "spring.datasource.dynamic.enabled", matchIfMissing = true) +public class SoaMybatisPlusConfig +{ + public static final String CREATE_TIME_FIELD_NAME = "createTime"; + public static final String UPDATE_TIME_FIELD_NAME = "updateTime"; + + /** mybatis-plus 默认加载的 {@linkplain MybatisPlusInterceptor} */ + @Bean + @ConditionalOnMissingBean(MybatisPlusInterceptor.class) + public MybatisPlusInterceptor mybatisPlusInterceptor() + { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + + interceptor.addInnerInterceptor(new ReadOnlyInterceptor()); + interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); + + return interceptor; + } + + /** mybatis-plus 默认 {@linkplain MetaObjectHandler} */ + @Bean + @ConditionalOnMissingBean(MetaObjectHandler.class) + MetaObjectHandler metaObjectHandler() + { + return new MetaObjectHandler() + { + @Override + public void insertFill(MetaObject metaObject) + { + LocalDateTime now = LocalDateTime.now(); + fillStrategy(metaObject, CREATE_TIME_FIELD_NAME, now); + fillStrategy(metaObject, UPDATE_TIME_FIELD_NAME, now); + } + + @Override + public void updateFill(MetaObject metaObject) + { + fillStrategy(metaObject, UPDATE_TIME_FIELD_NAME, LocalDateTime.now()); + } + }; + } +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaTransactionConfig.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaTransactionConfig.java new file mode 100644 index 0000000..b53f70d --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/config/SoaTransactionConfig.java @@ -0,0 +1,176 @@ + +package io.github.hpsocket.soa.starter.data.mysql.config; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.aop.Advisor; +import org.springframework.aop.aspectj.AspectJExpressionPointcut; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.transaction.TransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; +import org.springframework.transaction.interceptor.RollbackRuleAttribute; +import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; +import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.transaction.interceptor.TransactionInterceptor; + +/** HP-SOA 全局事务配置
+ * 当 ${global-transaction-management.enabled} 为 true 时,启用全局事务配置 + */ +@AutoConfiguration +@EnableTransactionManagement +@ConditionalOnProperty(name = "global-transaction-management.enabled", matchIfMissing = false) +public class SoaTransactionConfig +{ + @Value("${global-transaction-management.timeout:3000}") + private int timeout; + @Value("${global-transaction-management.isolation:ISOLATION_READ_COMMITTED}") + private String isolation; + @Value("${global-transaction-management.propagation:PROPAGATION_REQUIRED}") + private String propagation; + @Value("${global-transaction-management.rollback-for:java.lang.Exception}") + Class[] rollbackFor; + + @Value("${global-transaction-management.read-only-timeout:3000}") + private int readOnlyTimeout; + @Value("${global-transaction-management.read-only-isolation:ISOLATION_READ_COMMITTED}") + private String readOnlyIsolation; + @Value("${global-transaction-management.read-only-propagation:PROPAGATION_REQUIRED}") + private String readOnlyPropagation; + + + @Value("${global-transaction-management.pointcut-expression:}") + private String pointcutExpression; + + /** 全局事务的默认 Advice */ + @Bean("txAdvice") + @ConditionalOnMissingBean(name = "txAdvice") + public TransactionInterceptor txAdvice( TransactionManager transactionManager, + SoaTransactionAttributeSourceNameMapProvider soaTransactionAttributeSourceNameMapProvider, + @Qualifier("requiredRuleBasedTransactionAttribute") RuleBasedTransactionAttribute required, + @Qualifier("readOnlyRuleBasedTransactionAttribute") RuleBasedTransactionAttribute readOnly) + { + NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource(); + source.setNameMap(soaTransactionAttributeSourceNameMapProvider.getNameMap(required, readOnly)); + + return new TransactionInterceptor(transactionManager, source); + } + + /** 全局事务的默认普通事务属性 */ + @Bean("requiredRuleBasedTransactionAttribute") + @ConditionalOnMissingBean(name = "requiredRuleBasedTransactionAttribute") + RuleBasedTransactionAttribute requiredRuleBasedTransactionAttribute() + { + List ruleAttrs = new ArrayList<>(rollbackFor.length); + + for(Class e : rollbackFor) + ruleAttrs.add(new RollbackRuleAttribute(e)); + + RuleBasedTransactionAttribute required = new RuleBasedTransactionAttribute(); + required.setPropagationBehaviorName(propagation); + required.setIsolationLevelName(isolation); + required.setTimeout(timeout); + required.setRollbackRules(ruleAttrs); + + return required; + } + + /** 全局事务的默认只读事务属性 */ + @Bean("readOnlyRuleBasedTransactionAttribute") + @ConditionalOnMissingBean(name = "readOnlyRuleBasedTransactionAttribute") + RuleBasedTransactionAttribute readOnlyRuleBasedTransactionAttribute() + { + RuleBasedTransactionAttribute readOnly = new RuleBasedTransactionAttribute(); + readOnly.setPropagationBehaviorName(readOnlyPropagation); + readOnly.setIsolationLevelName(readOnlyIsolation); + readOnly.setTimeout(readOnlyTimeout); + readOnly.setReadOnly(true); + + return readOnly; + } + + /** 全局事务的默认事务拦截匹配参数提供者 */ + @Bean("defaultSoaTransactionAttributeSourceNameMapProvider") + @ConditionalOnMissingBean(name = "defaultSoaTransactionAttributeSourceNameMapProvider") + SoaTransactionAttributeSourceNameMapProvider defaultSoaTransactionAttributeSourceNameMapProvider() + { + return new SoaTransactionAttributeSourceNameMapProvider() {}; + } + + /** 全局事务的默认 Advisor */ + @Bean("txAdviceAdvisor") + @ConditionalOnMissingBean(name = "txAdviceAdvisor") + @ConditionalOnExpression("'${global-transaction-management.pointcut-expression:}' != ''") + public Advisor txAdviceAdvisor(@Qualifier("txAdvice") TransactionInterceptor txAdvice) + { + AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); + pointcut.setExpression(pointcutExpression); + return new DefaultPointcutAdvisor(pointcut, txAdvice); + } + + static interface SoaTransactionAttributeSourceNameMapProvider + { + default Map getNameMap(RuleBasedTransactionAttribute required, RuleBasedTransactionAttribute readOnly) + { + Map attributesMap = new HashMap<>(); + + attributesMap.put("save*", required); + attributesMap.put("remove*", required); + attributesMap.put("update*", required); + attributesMap.put("batch*", required); + attributesMap.put("clear*", required); + attributesMap.put("add*", required); + attributesMap.put("append*", required); + attributesMap.put("modify*", required); + attributesMap.put("edit*", required); + attributesMap.put("insert*", required); + attributesMap.put("del*", required); + attributesMap.put("delete*", required); + attributesMap.put("do*", required); + attributesMap.put("erase*", required); + attributesMap.put("erase*", required); + attributesMap.put("change*", required); + attributesMap.put("create*", required); + attributesMap.put("import*", required); + attributesMap.put("increase*", required); + attributesMap.put("reduce*", required); + attributesMap.put("refund*", required); + attributesMap.put("confirm*", required); + attributesMap.put("consume*", required); + attributesMap.put("cancel*", required); + attributesMap.put("use*", required); + attributesMap.put("bind*", required); + attributesMap.put("apply*", required); + attributesMap.put("submit*", required); + attributesMap.put("lock*", required); + attributesMap.put("unLock*", required); + attributesMap.put("open*", required); + attributesMap.put("close*", required); + attributesMap.put("mark*", required); + attributesMap.put("set*", required); + attributesMap.put("reset*", required); + + attributesMap.put("select*", readOnly); + attributesMap.put("get*", readOnly); + attributesMap.put("valid*", readOnly); + attributesMap.put("list*", readOnly); + attributesMap.put("count*", readOnly); + attributesMap.put("find*", readOnly); + attributesMap.put("load*", readOnly); + attributesMap.put("search*", readOnly); + + return attributesMap; + } + } + +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseEntity.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseEntity.java new file mode 100644 index 0000000..d20b445 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseEntity.java @@ -0,0 +1,31 @@ +package io.github.hpsocket.soa.starter.data.mysql.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; + +import lombok.Getter; +import lombok.Setter; + +/** 基础实体
+ * 数据库实体的基类,定义了 {@linkplain #id}、{@linkplain #createTime}、{@linkplain #updateTime} 字段 + */ +@Getter +@Setter +@SuppressWarnings("serial") +public class BaseEntity implements Serializable +{ + /** 主键 */ + @TableId + private Long id; + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + /** 最后更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseLogicDeleteEntity.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseLogicDeleteEntity.java new file mode 100644 index 0000000..bc2c4ac --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseLogicDeleteEntity.java @@ -0,0 +1,19 @@ +package io.github.hpsocket.soa.starter.data.mysql.entity; + +import com.baomidou.mybatisplus.annotation.TableLogic; + +import lombok.Getter; +import lombok.Setter; + +/** 逻辑删除基础实体
+ * 支持逻辑删除的数据库实体基类,定义了逻辑删除字段 {@linkplain #deleted} + */ +@Getter +@Setter +@SuppressWarnings("serial") +public class BaseLogicDeleteEntity extends BaseEntity +{ + /** 逻辑删除标记(0 - 未删除,1 - 已删除) */ + @TableLogic + private Byte deleted; +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseLogicDeleteVersioningEntity.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseLogicDeleteVersioningEntity.java new file mode 100644 index 0000000..9fd087b --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseLogicDeleteVersioningEntity.java @@ -0,0 +1,22 @@ +package io.github.hpsocket.soa.starter.data.mysql.entity; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.Version; + +import lombok.Getter; +import lombok.Setter; + +/** 逻辑删除与乐观锁基础实体
+ * 支持逻辑删除与乐观锁的数据库实体基类,定义了逻辑删除字段 {@linkplain #deleted},乐观锁字段 {@linkplain #version} + */@Getter +@Setter +@SuppressWarnings("serial") +public class BaseLogicDeleteVersioningEntity extends BaseEntity +{ + /** 逻辑删除标记(0 - 未删除,1 - 已删除) */ + @TableLogic + private Byte deleted; + /** 乐观锁版本号 */ + @Version + private Integer version; +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseVersioningEntity.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseVersioningEntity.java new file mode 100644 index 0000000..d9033b0 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/entity/BaseVersioningEntity.java @@ -0,0 +1,19 @@ +package io.github.hpsocket.soa.starter.data.mysql.entity; + +import com.baomidou.mybatisplus.annotation.Version; + +import lombok.Getter; +import lombok.Setter; + +/** 乐观锁基础实体
+ * 支持乐观锁的数据库实体基类,定义了乐观锁字段 {@linkplain #version} + */ +@Getter +@Setter +@SuppressWarnings("serial") +public class BaseVersioningEntity extends BaseEntity +{ + /** 乐观锁版本号 */ + @Version + private Integer version; +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/handler/FastJson2TypeHandler.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/handler/FastJson2TypeHandler.java new file mode 100644 index 0000000..1c9c6ea --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/handler/FastJson2TypeHandler.java @@ -0,0 +1,44 @@ + +package io.github.hpsocket.soa.starter.data.mysql.handler; + +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; + +/** mybatis-plus FastJson2 类型转换器 */ +@MappedTypes({Object.class}) +@MappedJdbcTypes(JdbcType.VARCHAR) +public class FastJson2TypeHandler extends AbstractJsonTypeHandler +{ + boolean jsonObjecct; + private final Class type; + + public FastJson2TypeHandler(Class type) + { + this.type = type; + this.jsonObjecct = (JSONObject.class.isAssignableFrom(type)); + } + + @Override + protected Object parse(String json) + { + if(jsonObjecct) + return JSONObject.parse(json); + + return JSON.parseObject(json, type); + } + + @Override + protected String toJson(Object obj) + { + if(jsonObjecct) + return ((JSONObject)(obj)).toString(JSONWriter.Feature.WriteNonStringKeyAsString); + + return JSON.toJSONString(obj, JSONWriter.Feature.WriteNonStringKeyAsString); + } +} \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/interceptor/ReadOnlyInterceptor.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/interceptor/ReadOnlyInterceptor.java new file mode 100644 index 0000000..50e674a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/java/io/github/hpsocket/soa/starter/data/mysql/interceptor/ReadOnlyInterceptor.java @@ -0,0 +1,29 @@ +package io.github.hpsocket.soa.starter.data.mysql.interceptor; + +import java.sql.SQLException; + +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; + +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; + +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; + +import static org.apache.ibatis.mapping.SqlCommandType.*; + +/** mybatis-plus 应用程序只读拦截器
+ * 当应用程序为只读时,不能执行 {@linkplain SqlCommandType#INSERT INSERT}、{@linkplain SqlCommandType#UPDATE UPDATE}、{@linkplain SqlCommandType#DELETE DELETE} 等数据库更新操作 + */ +public class ReadOnlyInterceptor implements InnerInterceptor +{ + @Override + public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException + { + SqlCommandType type = ms.getSqlCommandType(); + + if(type == INSERT || type == UPDATE || type == DELETE) + Assert.isFalse(WebServerHelper.isAppReadOnly(), "application is read-only, can NOT execute update");; + } +} diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..f36fd07 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,4 @@ +io.github.hpsocket.soa.starter.data.mysql.config.SoaDruidConfig +io.github.hpsocket.soa.starter.data.mysql.config.SoaMybatisPlusConfig +io.github.hpsocket.soa.starter.data.mysql.config.SoaMapperScanConfig +io.github.hpsocket.soa.starter.data.mysql.config.SoaTransactionConfig diff --git a/hp-soa-starter/hp-soa-starter-data-mysql/src/test/java/io/github/hpsocket/test/data/mysql/DruidPasswordEncryption.java b/hp-soa-starter/hp-soa-starter-data-mysql/src/test/java/io/github/hpsocket/test/data/mysql/DruidPasswordEncryption.java new file mode 100644 index 0000000..6be0f23 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-mysql/src/test/java/io/github/hpsocket/test/data/mysql/DruidPasswordEncryption.java @@ -0,0 +1,24 @@ + +package io.github.hpsocket.test.data.mysql; + +import com.alibaba.druid.filter.config.ConfigTools; + +public class DruidPasswordEncryption +{ + private static final String DEFAULT_PRIVATE_KEY_STRING = "MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAlGEoY2vcgAlyzj7TKz/jObBQmgFioB3HoRzKJYgG3twgVLlB2u5qROYaTxc5U8xXP2M4s6+E9+MvOA+DBoqjpQIDAQABAkBFWDzcbingisnlWtYs9dA3g0/AEdqqcxB7mu1cafywBR/aIA/oSxYAqVP4m64kj1oFKuNp17z+lVWZ9rvPHa2HAiEAq/CZ6dIPCG04JV5D3aGnshKLeah76UzJtwz+eQTHmjMCIQDc69PNTDGpjMMPanuIMW0tYGODCtL/JkSM49Ssdn0ixwIgCmkG6KFPR7NVMu4CLekbvixhRXxuBDIiBHNE9Q7VBwECIEIlAadIFt5y3Lwy34WpdszNPT4w8XefV4rvc++nElRlAiAs6THhTAW1kenBSXhGn99gcaO9T8j7sJ3XmOU9qpqv2Q=="; + public static final String DEFAULT_PUBLIC_KEY_STRING = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJRhKGNr3IAJcs4+0ys/4zmwUJoBYqAdx6EcyiWIBt7cIFS5QdruakTmGk8XOVPMVz9jOLOvhPfjLzgPgwaKo6UCAwEAAQ=="; + + public static void main(String[] args) throws Exception + { + String password = args.length > 0 ? args[0] : "123456"; + + System.out.println("privateKey: " + DEFAULT_PRIVATE_KEY_STRING); + System.out.println("publicKey: " + DEFAULT_PUBLIC_KEY_STRING); + System.out.println(); + + String encPassword = ConfigTools.encrypt(DEFAULT_PRIVATE_KEY_STRING, password); + + System.out.println("enc-password: " + encPassword); + System.out.println("dec-password: " + ConfigTools.decrypt(DEFAULT_PUBLIC_KEY_STRING, encPassword)); + } +} diff --git a/hp-soa-starter/hp-soa-starter-data-redis/.gitignore b/hp-soa-starter/hp-soa-starter-data-redis/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-data-redis/pom.xml b/hp-soa-starter/hp-soa-starter-data-redis/pom.xml new file mode 100644 index 0000000..55b6bd8 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-data-redis + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + + + org.redisson + redisson-spring-boot-starter + + + com.esotericsoftware + kryo + + + diff --git a/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/config/SoaRedisConfig.java b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/config/SoaRedisConfig.java new file mode 100644 index 0000000..cad2034 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/config/SoaRedisConfig.java @@ -0,0 +1,232 @@ + +package io.github.hpsocket.soa.starter.data.redis.config; + +import java.lang.reflect.Method; +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.redisson.Redisson; +import org.redisson.spring.starter.RedissonAutoConfiguration; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.MapFactoryBean; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.core.env.MapPropertySource; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import com.alibaba.fastjson2.support.config.FastJsonConfig; +import com.alibaba.fastjson2.support.spring6.data.redis.FastJsonRedisSerializer; +import com.alibaba.fastjson2.support.spring6.data.redis.GenericFastJsonRedisSerializer; +import io.github.hpsocket.soa.framework.core.util.CryptHelper; +import io.github.hpsocket.soa.starter.data.redis.serializer.KryoNotNullRedisSerializer; +import io.github.hpsocket.soa.starter.data.redis.serializer.KryoRedisSerializer; + +/** HP-SOA Redis 配置 */ +@AutoConfiguration +@EnableCaching(order = 0) +@AutoConfigureBefore(RedissonAutoConfiguration.class) +@ConditionalOnClass({Redisson.class, RedissonAutoConfiguration.class}) +public class SoaRedisConfig +{ + private static final StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + + private static final FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + private static final GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer(true); + + private static final KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); + private static final KryoNotNullRedisSerializer kryoNotNullRedisSerializer = new KryoNotNullRedisSerializer(); + + static + { + FastJsonConfig config = new FastJsonConfig(); + config.setJSONB(true); + fastJsonRedisSerializer.setFastJsonConfig(config); + } + + /** 默认 {@linkplain RedisTemplate} */ + @Primary + @Bean("redisTemplate") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) + { + return genericJsonRedisTemplate(redisConnectionFactory); + } + + /** 基于 FastJson 序列化的 {@linkplain RedisTemplate} */ + @Bean("redisJsonTemplate") + public RedisTemplate jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) + { + RedisTemplate template = new RedisTemplate(); + + template.setKeySerializer(RedisSerializer.string()); + template.setValueSerializer(fastJsonRedisSerializer); + template.setHashKeySerializer(RedisSerializer.string()); + template.setHashValueSerializer(fastJsonRedisSerializer); + template.setConnectionFactory(redisConnectionFactory); + + return template; + } + + /** 基于通用 FastJson 序列化的 {@linkplain RedisTemplate} */ + @Bean("redisGenericJsonTemplate") + public RedisTemplate genericJsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) + { + RedisTemplate template = new RedisTemplate(); + + template.setKeySerializer(RedisSerializer.string()); + template.setValueSerializer(genericFastJsonRedisSerializer); + template.setHashKeySerializer(RedisSerializer.string()); + template.setHashValueSerializer(genericFastJsonRedisSerializer); + template.setConnectionFactory(redisConnectionFactory); + + return template; + } + + /** 基于 Kryo 序列化的 {@linkplain RedisTemplate} */ + @Bean("redisKryoTemplate") + public RedisTemplate kryoRedisTemplate(RedisConnectionFactory redisConnectionFactory) + { + RedisTemplate template = new RedisTemplate(); + + template.setKeySerializer(RedisSerializer.string()); + template.setValueSerializer(kryoRedisSerializer); + template.setHashKeySerializer(RedisSerializer.string()); + template.setHashValueSerializer(kryoRedisSerializer); + template.setConnectionFactory(redisConnectionFactory); + + return template; + } + + /** 基于 Kryo 序列化的 {@linkplain RedisTemplate} (不支持存储 null 值)*/ + @Bean("redisKryoNotNullTemplate") + public RedisTemplate kryoNotNullRedisTemplate(RedisConnectionFactory redisConnectionFactory) + { + RedisTemplate template = new RedisTemplate(); + + template.setKeySerializer(RedisSerializer.string()); + template.setValueSerializer(kryoNotNullRedisSerializer); + template.setHashKeySerializer(RedisSerializer.string()); + template.setHashValueSerializer(kryoNotNullRedisSerializer); + template.setConnectionFactory(redisConnectionFactory); + + return template; + } + + /** 默认 {@linkplain RedisCacheManager} */ + @Bean("redisCacheManager") + @SuppressWarnings("unchecked") + @ConditionalOnMissingBean(name = "redisCacheManager") + public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, + @Qualifier("rediseDfaultCacheConfiguration") RedisCacheConfiguration rediseDfaultCacheConfiguration, + @Qualifier("redisInitialCacheConfigurations") MapPropertySource redisInitialCacheConfigurations) + { + RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory) + .cacheDefaults(rediseDfaultCacheConfiguration) + .withInitialCacheConfigurations((Map)(Map)redisInitialCacheConfigurations.getSource()) + .allowCreateOnMissingCache(false); + + return builder.build(); + + } + + /** 默认 {@linkplain RedisCacheConfiguration} */ + @Bean("rediseDfaultCacheConfiguration") + @ConditionalOnMissingBean(name = "rediseDfaultCacheConfiguration") + public RedisCacheConfiguration rediseDfaultCacheConfiguration() + { + return createRedisCacheConfiguration(3600); + } + + /** 初始 {@linkplain RedisCacheConfiguration} {@linkplain Map} */ + @SuppressWarnings("unchecked") + @Bean("redisInitialCacheConfigurations") + @ConditionalOnMissingBean(name = "redisInitialCacheConfigurations") + public MapPropertySource redisInitialCacheConfigurations() + { + Map cfgs = new LinkedHashMap<>(); + + cfgs.put("10s", createRedisCacheConfiguration(10)); + cfgs.put("30s", createRedisCacheConfiguration(30)); + cfgs.put("1m", createRedisCacheConfiguration(60)); + cfgs.put("5m", createRedisCacheConfiguration(300)); + cfgs.put("15m", createRedisCacheConfiguration(900)); + cfgs.put("30m", createRedisCacheConfiguration(1800)); + cfgs.put("1h", createRedisCacheConfiguration(3600)); + cfgs.put("3h", createRedisCacheConfiguration(10800)); + cfgs.put("6h", createRedisCacheConfiguration(21600)); + cfgs.put("12h", createRedisCacheConfiguration(43200)); + cfgs.put("1d", createRedisCacheConfiguration(86400)); + cfgs.put("7d", createRedisCacheConfiguration(604800)); + cfgs.put("15d", createRedisCacheConfiguration(1296000)); + cfgs.put("30d", createRedisCacheConfiguration(2592000)); + + MapFactoryBean bean = new MapFactoryBean(); + bean.setSourceMap(cfgs); + + return new MapPropertySource("redisInitialCacheConfigurations", (Map)(Map)cfgs); + } + + private RedisCacheConfiguration createRedisCacheConfiguration(long ttlSeconds) + { + return RedisCacheConfiguration + .defaultCacheConfig() + .entryTtl(Duration.ofSeconds(ttlSeconds)) + .serializeKeysWith(SerializationPair.fromSerializer(stringRedisSerializer)) + .serializeValuesWith(SerializationPair.fromSerializer(genericFastJsonRedisSerializer)); + } + + /** 默认 Redis {@linkplain KeyGenerator} */ + @Bean("redisStringKeyGenerator") + @ConditionalOnMissingBean(name = "redisStringKeyGenerator") + public KeyGenerator keyGenerator() + { + return new KeyGenerator() + { + @Override + public Object generate(Object target, Method method, Object... params) + { + StringBuilder sb1 = new StringBuilder() + .append(target.getClass().getName()) + .append(':') + .append(method.getName()); + + String prefix = sb1.toString(); + + int length = params.length; + + if(length == 0) + return prefix; + + StringBuilder sb2 = new StringBuilder(); + + for(int i = 0; i < length; i++) + { + Object obj = params[i]; + sb2.append(obj == null ? "null" : obj.toString()); + + if(i < length - 1) + sb2.append(':'); + } + + String key = sb2.toString(); + + if(key.length() > 40) + key = CryptHelper.md5(key); + + return prefix + ':' + key; + } + }; + } +} diff --git a/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/BaseKryoRedisSerializer.java b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/BaseKryoRedisSerializer.java new file mode 100644 index 0000000..0466142 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/BaseKryoRedisSerializer.java @@ -0,0 +1,84 @@ + +package io.github.hpsocket.soa.starter.data.redis.serializer; + +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** Kryo 序列化器基类 */ +public abstract class BaseKryoRedisSerializer implements RedisSerializer +{ + private static final ThreadLocal kryos = ThreadLocal.withInitial(BaseKryoRedisSerializer::createKryo); + + /* + private static Pool kryoPool = new Pool(true, true, 1000) + { + @Override + protected Kryo create() + { + return createKryo(); + } + }; + */ + + private static Kryo createKryo() + { + Kryo kryo = new Kryo(); + + kryo.setRegistrationRequired(false); + kryo.setReferences(false); + + return kryo; + } + + protected abstract boolean checkSerializeParam(T t); + protected abstract boolean checkDeserializeParam(byte[] bytes); + + @Override + public byte[] serialize(T t) throws SerializationException + { + if(!checkSerializeParam(t)) + { + return null; + } + + Kryo kryo = null; + + try(Output output = new Output(4096, -1)) + { + kryo = kryos.get(); + kryo.writeClassAndObject(output, t); + + return output.getBuffer(); + } + catch(Exception e) + { + throw new SerializationException(e.getMessage(), e); + } + } + + @Override + @SuppressWarnings("unchecked") + public T deserialize(byte[] bytes) throws SerializationException + { + if(!checkDeserializeParam(bytes)) + { + return null; + } + + Kryo kryo = null; + + try(Input input = new Input(bytes);) + { + kryo = kryos.get(); + return (T)kryo.readClassAndObject(input); + } + catch(Exception e) + { + throw new SerializationException(e.getMessage(), e); + } + } +} diff --git a/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/KryoNotNullRedisSerializer.java b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/KryoNotNullRedisSerializer.java new file mode 100644 index 0000000..857825b --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/KryoNotNullRedisSerializer.java @@ -0,0 +1,33 @@ + +package io.github.hpsocket.soa.starter.data.redis.serializer; + +import org.springframework.data.redis.serializer.SerializationException; + +/** Kryo 序列化器
+ * 该序列化器不支持存储 null 值 + */ +public class KryoNotNullRedisSerializer extends BaseKryoRedisSerializer +{ + @Override + protected boolean checkSerializeParam(T t) + { + if(t == null) + { + throw new SerializationException("input object is null"); + } + + return true; + } + + @Override + protected boolean checkDeserializeParam(byte[] bytes) + { + if(bytes == null || bytes.length == 0) + { + throw new SerializationException("input bytes is null or length is 0"); + } + + return true; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/KryoRedisSerializer.java b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/KryoRedisSerializer.java new file mode 100644 index 0000000..25e740c --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/src/main/java/io/github/hpsocket/soa/starter/data/redis/serializer/KryoRedisSerializer.java @@ -0,0 +1,30 @@ + +package io.github.hpsocket.soa.starter.data.redis.serializer; + +/** Kryo 序列化器
+ * 该序列化器支持存储 null 值 + */ +public class KryoRedisSerializer extends BaseKryoRedisSerializer +{ + @Override + protected boolean checkSerializeParam(T t) + { + if(t == null) + { + return false; + } + + return true; + } + + @Override + protected boolean checkDeserializeParam(byte[] bytes) + { + if(bytes == null || bytes.length == 0) + { + return false; + } + + return true; + } +} diff --git a/hp-soa-starter/hp-soa-starter-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..f7f70d4 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.github.hpsocket.soa.starter.data.redis.config.SoaRedisConfig \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-data-redis/src/test/java/io/github/hpsocket/test/data/redis/Main.java b/hp-soa-starter/hp-soa-starter-data-redis/src/test/java/io/github/hpsocket/test/data/redis/Main.java new file mode 100644 index 0000000..d6dd898 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-data-redis/src/test/java/io/github/hpsocket/test/data/redis/Main.java @@ -0,0 +1,205 @@ +package io.github.hpsocket.test.data.redis; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import org.redisson.client.DefaultNettyHook; +import org.redisson.client.NettyHook; +import org.redisson.client.codec.Codec; +import org.redisson.config.ClusterServersConfig; +import org.redisson.config.MasterSlaveServersConfig; +import org.redisson.config.ReplicatedServersConfig; +import org.redisson.config.SentinelServersConfig; +import org.redisson.config.SingleServerConfig; +import org.redisson.config.TransportMode; +import org.redisson.connection.ConnectionListener; +import org.redisson.connection.ConnectionManager; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.util.StopWatch; + +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.support.spring6.data.redis.FastJsonRedisSerializer; +import com.alibaba.fastjson2.support.spring6.data.redis.GenericFastJsonRedisSerializer; +import io.github.hpsocket.soa.starter.data.redis.serializer.KryoRedisSerializer; + +import io.netty.channel.EventLoopGroup; +import lombok.Getter; +import lombok.Setter; + +public class Main +{ + private static final FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class); + private static final GenericFastJsonRedisSerializer genericFastJsonRedisSerializer1 = new GenericFastJsonRedisSerializer(true); + private static final GenericFastJsonRedisSerializer genericFastJsonRedisSerializer2 = new GenericFastJsonRedisSerializer(false); + + private static final KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); + + public static void main(String[] args) + { + final int COUNT = 100000; + + MyClass my = new MyClass(); + my.setName("Kingfisher"); + my.setAge(23); + + testFastJson(my, false); + testFastJson(my, true); + testGenericFastJson1(my); + testGenericFastJson2(my); + testKryo(my); + + StopWatch sw = new StopWatch("serial"); + + sw.start(); + for(int i = 0; i < COUNT; i++) + { + testFastJson(my, false); + } + sw.stop(); + System.out.println("testFastJson - 1: " + sw.getLastTaskTimeMillis()); + + sw.start(); + for(int i = 0; i < COUNT; i++) + { + testFastJson(my, true); + } + sw.stop(); + System.out.println("testFastJson - 2: " + sw.getLastTaskTimeMillis()); + + sw.start(); + for(int i = 0; i < COUNT; i++) + { + testGenericFastJson1(my); + } + sw.stop(); + System.out.println("testGenericFastJson - 1: " + sw.getLastTaskTimeMillis()); + + sw.start(); + for(int i = 0; i < COUNT; i++) + { + testGenericFastJson2(my); + } + sw.stop(); + System.out.println("testGenericFastJson - 2: " + sw.getLastTaskTimeMillis()); + + sw.start(); + for(int i = 0; i < COUNT; i++) + { + testKryo(my); + } + sw.stop(); + System.out.println("testKryo: " + sw.getLastTaskTimeMillis()); + + } + + @SuppressWarnings("unused") + private static void testFastJson(MyClass my, boolean toJavaObj) + { + byte[] bytes = fastJsonRedisSerializer.serialize(my); + Object obj = fastJsonRedisSerializer.deserialize(bytes); + + if(toJavaObj) + { + Object obj2 = ((JSONObject)obj).toJavaObject(MyClass.class); + } + + //System.out.println(obj2); + + } + + @SuppressWarnings("unused") + private static void testGenericFastJson1(MyClass my) + { + byte[] bytes = genericFastJsonRedisSerializer1.serialize(my); + Object obj = genericFastJsonRedisSerializer1.deserialize(bytes); + + //System.out.println(obj); + } + + @SuppressWarnings("unused") + private static void testGenericFastJson2(MyClass my) + { + byte[] bytes = genericFastJsonRedisSerializer2.serialize(my); + Object obj = genericFastJsonRedisSerializer2.deserialize(bytes); + + //System.out.println(obj); + } + + @SuppressWarnings("unused") + private static void testKryo(MyClass my) + { + byte[] bytes = kryoRedisSerializer.serialize(my); + Object obj = kryoRedisSerializer.deserialize(bytes); + + //System.out.println(obj); + } +} + +@Getter +@Setter +class MyClass +{ + String name; + Integer age; + + ///* + private RedisProperties redisProperties = new RedisProperties(); + + private SentinelServersConfig sentinelServersConfig = new SentinelServersConfig(); + + private MasterSlaveServersConfig masterSlaveServersConfig = new MasterSlaveServersConfig(); + + private SingleServerConfig singleServerConfig; + + private ClusterServersConfig clusterServersConfig = new ClusterServersConfig(); + //*/ + + private ReplicatedServersConfig replicatedServersConfig = new ReplicatedServersConfig(); + + private ConnectionManager connectionManager; + + private int threads = 16; + + private int nettyThreads = 32; + + private Codec codec; + + private ExecutorService executor; + + private boolean referenceEnabled = true; + + private TransportMode transportMode = TransportMode.NIO; + + private EventLoopGroup eventLoopGroup; + + private long lockWatchdogTimeout = 30 * 1000; + + private boolean checkLockSyncedSlaves = true; + + private long slavesSyncTimeout = 1000; + + private long reliableTopicWatchdogTimeout = TimeUnit.MINUTES.toMillis(10); + + private boolean keepPubSubOrder = true; + + private boolean useScriptCache = false; + + private int minCleanUpDelay = 5; + + private int maxCleanUpDelay = 30*60; + + private int cleanUpKeysAmount = 100; + + private NettyHook nettyHook = new DefaultNettyHook(); + + private ConnectionListener connectionListener; + + private boolean useThreadClassLoader = true; + + + @Override + public String toString() + { + return String.format("{name: %s, age: %d}", name, age); + } +} \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-job-exclusive/.gitignore b/hp-soa-starter/hp-soa-starter-job-exclusive/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-exclusive/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-job-exclusive/pom.xml b/hp-soa-starter/hp-soa-starter-job-exclusive/pom.xml new file mode 100644 index 0000000..7710532 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-exclusive/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-job-exclusive + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + io.github.hpsocket + hp-soa-starter-task + + + io.github.hpsocket + hp-soa-starter-data-redis + + + diff --git a/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/annotation/ExclusiveJob.java b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/annotation/ExclusiveJob.java new file mode 100644 index 0000000..14fb679 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/annotation/ExclusiveJob.java @@ -0,0 +1,58 @@ + +package io.github.hpsocket.soa.starter.job.exclusive.annotation; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.scheduling.annotation.Scheduled; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** 分布式独占 Job 注解 */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Scheduled +public @interface ExclusiveJob +{ + /** JOB 唯一名称(必填参数) */ + String jobName(); + + /** 默认值:${spring.application.name},JOB 完整名称为:${prefix}:${jobName} */ + String prefix() default ""; + + /** 锁定时间值 + * {@linkplain ExclusiveJob#maxLockTime maxLockTime} <= 0 会开启自动续期功能,当连接异常断开 30 秒后自动解锁
+ * {@linkplain ExclusiveJob#maxLockTime maxLockTime} > 0 不会开启自动续期功能,当超过 {@linkplain ExclusiveJob#maxLockTime maxLockTime} 后自动解锁 + */ + long maxLockTime() default -1L; + /** {@linkplain ExclusiveJob#maxLockTime maxLockTime} 时间单位(默认:毫秒) */ + TimeUnit lockTimeUnit() default TimeUnit.MILLISECONDS; + + /** 参考 {@linkplain Scheduled#cron} */ + @AliasFor(annotation = Scheduled.class) + String cron() default ""; + /** 参考 {@linkplain Scheduled#zone} */ + @AliasFor(annotation = Scheduled.class) + String zone() default ""; + /** 参考 {@linkplain Scheduled#fixedDelay} */ + @AliasFor(annotation = Scheduled.class) + long fixedDelay() default -1; + /** 参考 {@linkplain Scheduled#fixedDelayString} */ + @AliasFor(annotation = Scheduled.class) + String fixedDelayString() default ""; + /** 参考 {@linkplain Scheduled#fixedRate} */ + @AliasFor(annotation = Scheduled.class) + long fixedRate() default -1; + /** 参考 {@linkplain Scheduled#fixedRateString} */ + @AliasFor(annotation = Scheduled.class) + String fixedRateString() default ""; + /** 参考 {@linkplain Scheduled#initialDelay} */ + @AliasFor(annotation = Scheduled.class) + long initialDelay() default -1; + /** 参考 {@linkplain Scheduled#initialDelayString} */ + @AliasFor(annotation = Scheduled.class) + String initialDelayString() default ""; + /** 参考 {@linkplain Scheduled#timeUnit} */ + @AliasFor(annotation = Scheduled.class) + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; +} diff --git a/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/aspect/ExclusiveJobInspector.java b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/aspect/ExclusiveJobInspector.java new file mode 100644 index 0000000..79b7458 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/aspect/ExclusiveJobInspector.java @@ -0,0 +1,151 @@ + +package io.github.hpsocket.soa.starter.job.exclusive.aspect; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.logging.log4j.core.config.Order; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; +import org.springframework.util.StopWatch; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.holder.AppConfigHolder; +import io.github.hpsocket.soa.framework.web.service.AsyncService; +import io.github.hpsocket.soa.framework.web.support.AspectHelper; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.job.exclusive.annotation.ExclusiveJob; +import io.github.hpsocket.soa.starter.job.exclusive.exception.ExclusiveJobExceptionHandler; + +import lombok.extern.slf4j.Slf4j; + +/** 分布式独占 Job 拦截器
+ * (注:当应用程序为只读时,不执行任何 Job 业务逻辑) + */ +@Slf4j +@Aspect +@Order(0) +public class ExclusiveJobInspector +{ + private static final String JOB_LOCK_KEY_PREFIX = "hp.soa.job:lock:"; + private static final String POINTCUT_PATTERN = "execution (public void *.*()) && " + + "@annotation(io.github.hpsocket.soa.starter.job.exclusive.annotation.ExclusiveJob)"; + + private static final AtomicBoolean RUNNING = new AtomicBoolean(false); + private static final AspectHelper.AnnotationHolder ANNOTATION_HOLDER = new AspectHelper.AnnotationHolder<>() {}; + + @Autowired(required = false) + ExclusiveJobExceptionHandler exceptionHandler; + + @Autowired(required = false) + AsyncService asyncService; + @Autowired + private RedissonClient redissonClient; + + @Pointcut(POINTCUT_PATTERN) + protected void aroundMethod() {} + + @Around(value = "aroundMethod()") + public Object inspect(ProceedingJoinPoint joinPoint) throws Throwable + { + MdcAttr mdcAttr = WebServerHelper.createMdcAttr(); + + ExclusiveJob job = ANNOTATION_HOLDER.findAnnotationByMethod(joinPoint); + Assert.notNull(job, "ExclusiveJob annotation not found"); + + String prefix = job.prefix(); + String jobName = job.jobName(); + + if(GeneralHelper.isStrEmpty(prefix)) + prefix = AppConfigHolder.getAppName(); + + String fullJobName = prefix + ':' + jobName; + boolean needThrow = true; + + try + { + if(!RUNNING.compareAndSet(false, true)) + return null; + + mdcAttr.putMdc(); + + if(WebServerHelper.isAppReadOnly()) + { + if(log.isTraceEnabled()) + { + String msg = String.format("current application is read only, skip exclusive job '%s'", fullJobName); + log.trace(msg); + } + + return null; + } + + String lockKey = JOB_LOCK_KEY_PREFIX + fullJobName; + RLock lock = redissonClient.getLock(lockKey); + + if(lock.tryLock(0, job.maxLockTime(), job.lockTimeUnit())) + { + StopWatch sw = new StopWatch(fullJobName); + + try + { + if(log.isTraceEnabled()) + log.trace("start exclusive job -> {}", fullJobName); + + sw.start(); + + return joinPoint.proceed(); + } + catch(Exception e) + { + log.error("execute exclusive job -> {} exception : {}", fullJobName, e.getMessage(), e); + + needThrow = false; + throw e; + } + finally + { + sw.stop(); + + try {lock.unlock();} catch(Exception e) {} + + if(log.isTraceEnabled()) + log.trace("end exclusive job -> {} (costTime: {})", fullJobName, sw.getLastTaskTimeMillis()); + } + } + } + catch(Exception e) + { + invokeExceptionHandler(prefix, jobName, e); + + if(needThrow) + throw e; + } + finally + { + RUNNING.compareAndSet(true, false); + mdcAttr.removeMdc(); + } + + return null; + } + + private void invokeExceptionHandler(String prefix, String jobName, Exception e) + { + if(exceptionHandler == null) + return; + + if(asyncService == null) + exceptionHandler.handleException(prefix, jobName, e); + else + { + asyncService.execute(() -> exceptionHandler.handleException(prefix, jobName, e)); + } + } +} diff --git a/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/config/SoaExclusiveJobConfig.java b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/config/SoaExclusiveJobConfig.java new file mode 100644 index 0000000..30cdf1a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/config/SoaExclusiveJobConfig.java @@ -0,0 +1,22 @@ + +package io.github.hpsocket.soa.starter.job.exclusive.config; + +import org.redisson.api.RedissonClient; +import org.redisson.spring.starter.RedissonAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.SchedulingTaskExecutor; + +import io.github.hpsocket.soa.starter.job.exclusive.aspect.ExclusiveJobInspector; + +/** HP-SOA 分布式独占 Job 配置 */ +@AutoConfiguration +@Import(ExclusiveJobInspector.class) +@AutoConfigureAfter(RedissonAutoConfiguration.class) +@ConditionalOnBean({SchedulingTaskExecutor.class, RedissonClient.class}) +public class SoaExclusiveJobConfig +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/exception/ExclusiveJobExceptionHandler.java b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/exception/ExclusiveJobExceptionHandler.java new file mode 100644 index 0000000..6a1eb65 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/java/io/github/hpsocket/soa/starter/job/exclusive/exception/ExclusiveJobExceptionHandler.java @@ -0,0 +1,7 @@ +package io.github.hpsocket.soa.starter.job.exclusive.exception; + +/** 分布式独占 Job 异常处理器接口 */ +public interface ExclusiveJobExceptionHandler +{ + void handleException(String jobPrefix, String jobName, Exception e); +} diff --git a/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..6ce4241 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-exclusive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.github.hpsocket.soa.starter.job.exclusive.config.SoaExclusiveJobConfig \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-job-xxljob/.gitignore b/hp-soa-starter/hp-soa-starter-job-xxljob/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-xxljob/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-job-xxljob/pom.xml b/hp-soa-starter/hp-soa-starter-job-xxljob/pom.xml new file mode 100644 index 0000000..b36de42 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-xxljob/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-job-xxljob + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + com.xuxueli + xxl-job-core + + + diff --git a/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/aspect/XxlJobInspector.java b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/aspect/XxlJobInspector.java new file mode 100644 index 0000000..dec1ea8 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/aspect/XxlJobInspector.java @@ -0,0 +1,131 @@ + +package io.github.hpsocket.soa.starter.job.xxljob.aspect; + +import java.lang.reflect.InvocationTargetException; + +import org.apache.logging.log4j.core.config.Order; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Assert; +import org.springframework.util.StopWatch; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.web.service.AsyncService; +import io.github.hpsocket.soa.framework.web.support.AspectHelper; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.job.xxljob.exception.XxlJobExceptionHandler; +import com.xxl.job.core.context.XxlJobHelper; +import com.xxl.job.core.handler.annotation.XxlJob; + +import lombok.extern.slf4j.Slf4j; + +/** XxlJob 拦截器
+ * (注:当应用程序为只读时,不执行任何 Job 业务逻辑) + */ +@Slf4j +@Aspect +@Order(0) +public class XxlJobInspector +{ + private static final int SKIP_RESULT_CODE = 202; + private static final String POINTCUT_PATTERN = "execution (public void *.*()) && " + + "@annotation(com.xxl.job.core.handler.annotation.XxlJob)"; + + private static final AspectHelper.AnnotationHolder ANNOTATION_HOLDER = new AspectHelper.AnnotationHolder<>() {}; + + @Autowired(required = false) + AsyncService asyncService; + @Autowired(required = false) + private XxlJobExceptionHandler exceptionHandler; + + @Pointcut(POINTCUT_PATTERN) + protected void aroundMethod() {} + + @Around(value = "aroundMethod()") + public Object inspect(ProceedingJoinPoint joinPoint) throws Throwable + { + MdcAttr mdcAttr = WebServerHelper.createMdcAttr(); + + XxlJob job = ANNOTATION_HOLDER.findAnnotationByMethod(joinPoint); + Assert.notNull(job, "XxlJob annotation not found"); + + String jobName = job.value(); + long jobId = XxlJobHelper.getJobId(); + String param = XxlJobHelper.getJobParam(); + + try + { + mdcAttr.putMdc(); + + if(WebServerHelper.isAppReadOnly()) + { + String msg = String.format("current application is read only, skip xxl-job '%s'", jobName); + + if(log.isTraceEnabled()) + log.trace(msg); + + XxlJobHelper.handleResult(SKIP_RESULT_CODE, msg); + + return null; + } + + StopWatch sw = new StopWatch(jobName); + + try + { + if(log.isTraceEnabled()) + log.trace("start xxl-job -> {} (id: {}, param: '{}')", jobName, jobId, param); + + sw.start(); + + return joinPoint.proceed(); + } + catch(Exception e) + { + Exception cause = null; + + if(e instanceof InvocationTargetException) + cause = (Exception)e.getCause(); + if(cause == null) + cause = e; + + log.error("execute xxl-job -> {} exception : {}", jobName, cause.getMessage(), cause); + + throw cause; + } + finally + { + sw.stop(); + + if(log.isTraceEnabled()) + log.trace("end xxl-job -> {} (id: {}, costTime: {})", jobName, jobId, sw.getLastTaskTimeMillis()); + } + } + catch(Exception e) + { + invokeExceptionHandler(jobName, jobId, param, e); + + throw e; + } + finally + { + mdcAttr.removeMdc(); + } + } + + private void invokeExceptionHandler(String jobName, long jobId, String param, Exception e) + { + if(exceptionHandler == null) + return; + + if(asyncService == null) + exceptionHandler.handleException(jobName, jobId, param, e); + else + { + asyncService.execute(() -> exceptionHandler.handleException(jobName, jobId, param, e)); + } + } +} diff --git a/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/config/SoaXxlJobConfig.java b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/config/SoaXxlJobConfig.java new file mode 100644 index 0000000..05e99c6 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/config/SoaXxlJobConfig.java @@ -0,0 +1,47 @@ + +package io.github.hpsocket.soa.starter.job.xxljob.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import io.github.hpsocket.soa.starter.job.xxljob.aspect.XxlJobInspector; +import io.github.hpsocket.soa.starter.job.xxljob.properties.SoaXxlJobProperties; +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; + +/** HP-SOA XxlJob 配置 */ +@AutoConfiguration +@Import(XxlJobInspector.class) +@ConditionalOnClass(XxlJobSpringExecutor.class) +@EnableConfigurationProperties({SoaXxlJobProperties.class}) +@ConditionalOnProperty(name = "xxl.job.enabled", matchIfMissing = true) +public class SoaXxlJobConfig +{ + private SoaXxlJobProperties soaXxlJobProperties; + + public SoaXxlJobConfig(SoaXxlJobProperties soaXxlJobProperties) + { + this.soaXxlJobProperties = soaXxlJobProperties; + } + + @Bean + public XxlJobSpringExecutor xxlJobSpringExecutor() + { + XxlJobSpringExecutor executor = new XxlJobSpringExecutor(); + + executor.setAdminAddresses(soaXxlJobProperties.getAdmin().getAddresses()); + executor.setAppname(soaXxlJobProperties.getExecutor().getAppname()); + executor.setAddress(soaXxlJobProperties.getExecutor().getAddress()); + executor.setIp(soaXxlJobProperties.getExecutor().getIp()); + executor.setPort(soaXxlJobProperties.getExecutor().getPort()); + executor.setLogPath(soaXxlJobProperties.getExecutor().getLogPath()); + executor.setLogRetentionDays(soaXxlJobProperties.getExecutor().getLogRetentionDays()); + executor.setAccessToken(soaXxlJobProperties.getAccessToken()); + + return executor; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/exception/XxlJobExceptionHandler.java b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/exception/XxlJobExceptionHandler.java new file mode 100644 index 0000000..9ed1c1f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/exception/XxlJobExceptionHandler.java @@ -0,0 +1,7 @@ +package io.github.hpsocket.soa.starter.job.xxljob.exception; + +/** XxlJob 异常处理器接口 */ +public interface XxlJobExceptionHandler +{ + void handleException(String jobName, long jobId, String param, Exception e); +} diff --git a/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/properties/SoaXxlJobProperties.java b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/properties/SoaXxlJobProperties.java new file mode 100644 index 0000000..71d769c --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/java/io/github/hpsocket/soa/starter/job/xxljob/properties/SoaXxlJobProperties.java @@ -0,0 +1,42 @@ +package io.github.hpsocket.soa.starter.job.xxljob.properties; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Getter; +import lombok.Setter; + +/** HP-SOA XxlJob 属性 */ +@Getter +@Setter +@ConfigurationProperties(prefix = "xxl.job") +@ConditionalOnProperty(name = "xxl.job.enabled", matchIfMissing = true) +public class SoaXxlJobProperties +{ + private boolean enabled = true; + + private String accessToken; + + Admin admin = new Admin(); + Executor executor = new Executor(); + + @Getter + @Setter + public static class Admin + { + private String addresses; + } + + @Getter + @Setter + public static class Executor + { + private String appname; + private String address; + private String ip; + private int port; + private String logPath; + private int logRetentionDays; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..eab6bb1 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-job-xxljob/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.github.hpsocket.soa.starter.job.xxljob.config.SoaXxlJobConfig \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-nacos/.gitignore b/hp-soa-starter/hp-soa-starter-nacos/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-nacos/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-nacos/pom.xml b/hp-soa-starter/hp-soa-starter-nacos/pom.xml new file mode 100644 index 0000000..67c65b4 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-nacos/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-nacos + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + diff --git a/hp-soa-starter/hp-soa-starter-nacos/src/main/java/io/github/hpsocket/soa/starter/nacos/config/SoaNacosConfig.java b/hp-soa-starter/hp-soa-starter-nacos/src/main/java/io/github/hpsocket/soa/starter/nacos/config/SoaNacosConfig.java new file mode 100644 index 0000000..4ac4511 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-nacos/src/main/java/io/github/hpsocket/soa/starter/nacos/config/SoaNacosConfig.java @@ -0,0 +1,13 @@ + +package io.github.hpsocket.soa.starter.nacos.config; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +/** HP-SOA Nacos 配置 */ +@AutoConfiguration +@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true) +public class SoaNacosConfig +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-nacos/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-nacos/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..5698214 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-nacos/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.github.hpsocket.soa.starter.nacos.config.SoaNacosConfig \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/.gitignore b/hp-soa-starter/hp-soa-starter-rabbitmq/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/pom.xml b/hp-soa-starter/hp-soa-starter-rabbitmq/pom.xml new file mode 100644 index 0000000..a3dd285 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-rabbitmq + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.amqp + spring-rabbit-stream + + + io.github.hpsocket + hp-soa-starter-data-mysql + true + + + io.github.hpsocket + hp-soa-starter-job-exclusive + true + + + org.apache.skywalking + apm-toolkit-trace + true + + + diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqAll.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqAll.java new file mode 100644 index 0000000..a48d22f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqAll.java @@ -0,0 +1,23 @@ +package io.github.hpsocket.soa.starter.rabbitmq.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** HP-SOA Rabbitmq 启用注解
+ * 同时启用 Producer 和 Consumer + */ +@Inherited +@Documented +@Target(TYPE) +@Retention(RUNTIME) +@EnableSoaRabbitmqProducer +@EnableSoaRabbitmqConsumer +public @interface EnableSoaRabbitmqAll +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqConsumer.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqConsumer.java new file mode 100644 index 0000000..3e922d9 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqConsumer.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.soa.starter.rabbitmq.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import io.github.hpsocket.soa.starter.rabbitmq.consumer.config.SoaRabbitmqConsumerConfig; + +/** HP-SOA Rabbitmq Consumer 启用注解
+ * 只启用 Consumer + */ +@Inherited +@Documented +@Target(TYPE) +@Retention(RUNTIME) +@Import({SoaRabbitmqConsumerConfig.class}) +public @interface EnableSoaRabbitmqConsumer +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqProducer.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqProducer.java new file mode 100644 index 0000000..9dae1c8 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/annotation/EnableSoaRabbitmqProducer.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.soa.starter.rabbitmq.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import io.github.hpsocket.soa.starter.rabbitmq.producer.config.SoaRabbitmqProducerConfig; + +/** HP-SOA Rabbitmq Producer 启用注解
+ * 只启用 Producer + */ +@Inherited +@Documented +@Target(TYPE) +@Retention(RUNTIME) +@Import({SoaRabbitmqProducerConfig.class}) +public @interface EnableSoaRabbitmqProducer +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaAbstractRabbitmqConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaAbstractRabbitmqConfig.java new file mode 100644 index 0000000..852f04a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaAbstractRabbitmqConfig.java @@ -0,0 +1,171 @@ + +package io.github.hpsocket.soa.starter.rabbitmq.common.config; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy; +import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.amqp.CachingConnectionFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.ConnectionFactoryCustomizer; +import org.springframework.boot.autoconfigure.amqp.EnvironmentBuilderCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionFactoryBeanConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.core.io.ResourceLoader; +import org.springframework.rabbit.stream.support.StreamAdmin; + +import com.rabbitmq.client.impl.CredentialsProvider; +import com.rabbitmq.client.impl.CredentialsRefreshService; +import com.rabbitmq.stream.ByteCapacity; +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.EnvironmentBuilder; + +public class SoaAbstractRabbitmqConfig +{ + public static final ByteCapacity DEFAULT_STREAM_MAX_LENGTH_BYTES = ByteCapacity.GB(100); + public static final ByteCapacity DEFAULT_STREAM_MAX_SEGMENT_SIZE_BYTES = ByteCapacity.MB(100); + public static final Duration DEFAULT_STREAM_MAX_AGE = Duration.ofHours(72); + + protected final RabbitProperties properties; + + public SoaAbstractRabbitmqConfig(RabbitProperties properties) + { + this.properties = properties; + } + + RabbitConnectionDetails rabbitConnectionDetails() + { + return new RabbitConnectionDetails() { + + @Override + public String getUsername() + { + return properties.determineUsername(); + } + + @Override + public String getPassword() + { + return properties.determinePassword(); + } + + @Override + public String getVirtualHost() + { + return properties.determineVirtualHost(); + } + + @Override + public List
getAddresses() + { + List
addresses = new ArrayList<>(); + for(String address : properties.determineAddresses().split(",")) + { + String[] components = address.split(":"); + addresses.add(new Address(components[0], Integer.parseInt(components[1]))); + } + return addresses; + } + }; + } + + public RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer( + ResourceLoader resourceLoader, + RabbitConnectionDetails connectionDetails, + ObjectProvider credentialsProvider, + ObjectProvider credentialsRefreshService) + { + RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader, this.properties, connectionDetails); + configurer.setCredentialsProvider(credentialsProvider.getIfUnique()); + configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique()); + + return configurer; + } + + public CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer( + RabbitConnectionDetails connectionDetails, + ObjectProvider connectionNameStrategy) + { + CachingConnectionFactoryConfigurer configurer = new CachingConnectionFactoryConfigurer(this.properties, connectionDetails); + configurer.setConnectionNameStrategy(connectionNameStrategy.getIfUnique()); + + return configurer; + } + + public CachingConnectionFactory rabbitConnectionFactory( + RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer, + CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer, + ObjectProvider connectionFactoryCustomizers) throws Exception + { + + RabbitConnectionFactoryBean connectionFactoryBean = new RabbitConnectionFactoryBean(); + rabbitConnectionFactoryBeanConfigurer.configure(connectionFactoryBean); + connectionFactoryBean.afterPropertiesSet(); + com.rabbitmq.client.ConnectionFactory connectionFactory = connectionFactoryBean.getObject(); + connectionFactoryCustomizers.orderedStream().forEach((customizer) -> customizer.customize(connectionFactory)); + + CachingConnectionFactory factory = new CachingConnectionFactory(connectionFactory); + rabbitCachingConnectionFactoryConfigurer.configure(factory); + + return factory; + } + + public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) + { + return new RabbitAdmin(connectionFactory); + } + + public Environment rabbitStreamEnvironment( + ObjectProvider customizers) + { + EnvironmentBuilder builder = configure(Environment.builder(), this.properties); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + + return builder.build(); + } + + StreamAdmin streamAdmin(Environment env) + { + return new StreamAdmin(env, sc -> + { + sc + .stream(this.properties.getStream().getName()) + .maxAge(DEFAULT_STREAM_MAX_AGE) + .maxLengthBytes(DEFAULT_STREAM_MAX_LENGTH_BYTES) + .maxSegmentSizeBytes(DEFAULT_STREAM_MAX_SEGMENT_SIZE_BYTES) + .create(); + }); + } + + protected static EnvironmentBuilder configure(EnvironmentBuilder builder, RabbitProperties properties) + { + builder.lazyInitialization(true); + RabbitProperties.Stream stream = properties.getStream(); + PropertyMapper map = PropertyMapper.get(); + + map.from(stream.getHost()).to(builder::host); + map.from(stream.getPort()).to(builder::port); + map.from(properties::getVirtualHost).to(builder::virtualHost); + map.from(stream.getUsername()).as(withFallback(properties::getUsername)).whenNonNull().to(builder::username); + map.from(stream.getPassword()).as(withFallback(properties::getPassword)).whenNonNull().to(builder::password); + + return builder; + } + + private static Function withFallback(Supplier fallback) + { + return (value) -> (value != null) ? value : fallback.get(); + } + + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaDefaultRabbitmqConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaDefaultRabbitmqConfig.java new file mode 100644 index 0000000..72677f5 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaDefaultRabbitmqConfig.java @@ -0,0 +1,109 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.config; + +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.CachingConnectionFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.ConnectionFactoryCustomizer; +import org.springframework.boot.autoconfigure.amqp.EnvironmentBuilderCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionFactoryBeanConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.ResourceLoader; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.support.StreamAdmin; + +import com.rabbitmq.client.impl.CredentialsProvider; +import com.rabbitmq.client.impl.CredentialsRefreshService; +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaDefaultRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaDefaultRabbitmqProperties.class}) +public class SoaDefaultRabbitmqConfig extends SoaAbstractRabbitmqConfig +{ + public SoaDefaultRabbitmqConfig(SoaDefaultRabbitmqProperties properties) + { + super(properties); + } + + @Primary + @Override + @Bean("defaultRabbitConnectionDetails") + RabbitConnectionDetails rabbitConnectionDetails() { + return super.rabbitConnectionDetails(); + } + + @Primary + @Override + @Bean("defaultRabbitConnectionFactoryBeanConfigurer") + public RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer( + ResourceLoader resourceLoader, + @Qualifier("defaultRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("defaultRabbitCredentialsProvider") ObjectProvider credentialsProvider, + @Qualifier("defaultRabbitCredentialsRefreshService") ObjectProvider credentialsRefreshService) + { + return super.rabbitConnectionFactoryBeanConfigurer(resourceLoader, connectionDetails, credentialsProvider, credentialsRefreshService); + } + + @Primary + @Override + @Bean("defaultRabbitCachingConnectionFactoryConfigurer") + public CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer( + @Qualifier("defaultRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("defaultRabbitConnectionNameStrategy") ObjectProvider connectionNameStrategy) + { + return super.rabbitConnectionFactoryConfigurer(connectionDetails, connectionNameStrategy); + } + + @Primary + @Override + @Bean("defaultRabbitCachingConnectionFactory") + public CachingConnectionFactory rabbitConnectionFactory( + @Qualifier("defaultRabbitConnectionFactoryBeanConfigurer") RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer, + @Qualifier("defaultRabbitCachingConnectionFactoryConfigurer") CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer, + @Qualifier("defaultRabbitConnectionFactoryCustomizer") ObjectProvider connectionFactoryCustomizers) throws Exception + { + return super.rabbitConnectionFactory(rabbitConnectionFactoryBeanConfigurer, rabbitCachingConnectionFactoryConfigurer, connectionFactoryCustomizers); + } + + @Primary + @Override + @Bean("defaultAmqpAdmin") + public AmqpAdmin amqpAdmin(@Qualifier("defaultRabbitCachingConnectionFactory") ConnectionFactory connectionFactory) + { + return super.amqpAdmin(connectionFactory); + } + + @Primary + @Override + @Bean(name = "defaultRabbitStreamEnvironment") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + public Environment rabbitStreamEnvironment( + @Qualifier("defaultRabbitStreamEnvironmentBuilderCustomizer") ObjectProvider customizers) + { + return super.rabbitStreamEnvironment(customizers); + } + + @Primary + @Override + @Bean(name = "defaultStreamAdmin") + @ConditionalOnMissingBean(name = "defaultStreamAdmin") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq.stream", name = "name") + public StreamAdmin streamAdmin(@Qualifier("defaultRabbitStreamEnvironment") Environment env) + { + return super.streamAdmin(env); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaFirstRabbitmqConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaFirstRabbitmqConfig.java new file mode 100644 index 0000000..068b496 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaFirstRabbitmqConfig.java @@ -0,0 +1,102 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.config; + +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.CachingConnectionFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.ConnectionFactoryCustomizer; +import org.springframework.boot.autoconfigure.amqp.EnvironmentBuilderCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionFactoryBeanConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ResourceLoader; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.support.StreamAdmin; + +import com.rabbitmq.client.impl.CredentialsProvider; +import com.rabbitmq.client.impl.CredentialsRefreshService; +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaFirstRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaFirstRabbitmqProperties.class}) +public class SoaFirstRabbitmqConfig extends SoaAbstractRabbitmqConfig +{ + public SoaFirstRabbitmqConfig(SoaFirstRabbitmqProperties properties) + { + super(properties); + } + + @Override + @Bean("firstRabbitConnectionDetails") + RabbitConnectionDetails rabbitConnectionDetails() + { + return super.rabbitConnectionDetails(); + } + + @Override + @Bean("firstRabbitConnectionFactoryBeanConfigurer") + public RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer( + ResourceLoader resourceLoader, + @Qualifier("firstRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("firstRabbitCredentialsProvider") ObjectProvider credentialsProvider, + @Qualifier("firstRabbitCredentialsRefreshService") ObjectProvider credentialsRefreshService) + { + return super.rabbitConnectionFactoryBeanConfigurer(resourceLoader, connectionDetails, credentialsProvider, credentialsRefreshService); + } + + @Override + @Bean("firstRabbitCachingConnectionFactoryConfigurer") + public CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer( + @Qualifier("firstRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("firstRabbitConnectionNameStrategy") ObjectProvider connectionNameStrategy) + { + return super.rabbitConnectionFactoryConfigurer(connectionDetails, connectionNameStrategy); + } + + @Override + @Bean("firstRabbitCachingConnectionFactory") + public CachingConnectionFactory rabbitConnectionFactory( + @Qualifier("firstRabbitConnectionFactoryBeanConfigurer") RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer, + @Qualifier("firstRabbitCachingConnectionFactoryConfigurer") CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer, + @Qualifier("firstRabbitConnectionFactoryCustomizer") ObjectProvider connectionFactoryCustomizers) throws Exception + { + return super.rabbitConnectionFactory(rabbitConnectionFactoryBeanConfigurer, rabbitCachingConnectionFactoryConfigurer, connectionFactoryCustomizers); + } + + @Override + @Bean("firstAmqpAdmin") + public AmqpAdmin amqpAdmin(@Qualifier("firstRabbitCachingConnectionFactory") ConnectionFactory connectionFactory) + { + return super.amqpAdmin(connectionFactory); + } + + @Override + @Bean(name = "firstRabbitStreamEnvironment") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + public Environment rabbitStreamEnvironment( + @Qualifier("firstRabbitStreamEnvironmentBuilderCustomizer") ObjectProvider customizers) + { + return super.rabbitStreamEnvironment(customizers); + } + + @Override + @Bean(name = "firstStreamAdmin") + @ConditionalOnMissingBean(name = "firstStreamAdmin") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-first.stream", name = "name") + public StreamAdmin streamAdmin(@Qualifier("firstRabbitStreamEnvironment") Environment env) + { + return super.streamAdmin(env); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaRabbitmqCommonConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaRabbitmqCommonConfig.java new file mode 100644 index 0000000..feafea5 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaRabbitmqCommonConfig.java @@ -0,0 +1,41 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.config; + +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Primary; + +import io.github.hpsocket.soa.starter.rabbitmq.common.converter.FastJsonMessageConverter; +import io.github.hpsocket.soa.starter.rabbitmq.common.converter.TextMessageConverter; + +/** HP-SOA Rabbitmq 通用配置 */ +@AutoConfiguration +@ComponentScan(basePackages = "io.github.hpsocket.soa.starter.rabbitmq.common.properties") +public class SoaRabbitmqCommonConfig +{ + /** 默认消息转换器 */ + @Primary + @Bean("messageConverter") + @ConditionalOnMissingBean(name = "messageConverter") + MessageConverter messageConverter() + { + MessageConverter textConverter = new TextMessageConverter(); + MessageConverter fastJsonConverter = new FastJsonMessageConverter(); + ContentTypeDelegatingMessageConverter delegatingConverter = new ContentTypeDelegatingMessageConverter(fastJsonConverter); + + delegatingConverter.addDelegate(MessageProperties.CONTENT_TYPE_JSON_ALT, fastJsonConverter); + delegatingConverter.addDelegate(MessageProperties.CONTENT_TYPE_JSON, fastJsonConverter); + delegatingConverter.addDelegate("text/json", fastJsonConverter); + delegatingConverter.addDelegate("json", fastJsonConverter); + + delegatingConverter.addDelegate(MessageProperties.CONTENT_TYPE_TEXT_PLAIN, textConverter); + delegatingConverter.addDelegate(MessageProperties.CONTENT_TYPE_XML, textConverter); + delegatingConverter.addDelegate("text", textConverter); + + return delegatingConverter; + } +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaSecondRabbitmqConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaSecondRabbitmqConfig.java new file mode 100644 index 0000000..b1ee291 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaSecondRabbitmqConfig.java @@ -0,0 +1,102 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.config; + +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.CachingConnectionFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.ConnectionFactoryCustomizer; +import org.springframework.boot.autoconfigure.amqp.EnvironmentBuilderCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionFactoryBeanConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ResourceLoader; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.support.StreamAdmin; + +import com.rabbitmq.client.impl.CredentialsProvider; +import com.rabbitmq.client.impl.CredentialsRefreshService; +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaSecondRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaSecondRabbitmqProperties.class}) +public class SoaSecondRabbitmqConfig extends SoaAbstractRabbitmqConfig +{ + public SoaSecondRabbitmqConfig(SoaSecondRabbitmqProperties properties) + { + super(properties); + } + + @Override + @Bean("secondRabbitConnectionDetails") + RabbitConnectionDetails rabbitConnectionDetails() + { + return super.rabbitConnectionDetails(); + } + + @Override + @Bean("secondRabbitConnectionFactoryBeanConfigurer") + public RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer( + ResourceLoader resourceLoader, + @Qualifier("secondRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("secondRabbitCredentialsProvider") ObjectProvider credentialsProvider, + @Qualifier("secondRabbitCredentialsRefreshService") ObjectProvider credentialsRefreshService) + { + return super.rabbitConnectionFactoryBeanConfigurer(resourceLoader, connectionDetails, credentialsProvider, credentialsRefreshService); + } + + @Override + @Bean("secondRabbitCachingConnectionFactoryConfigurer") + public CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer( + @Qualifier("secondRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("secondRabbitConnectionNameStrategy") ObjectProvider connectionNameStrategy) + { + return super.rabbitConnectionFactoryConfigurer(connectionDetails, connectionNameStrategy); + } + + @Override + @Bean("secondRabbitCachingConnectionFactory") + public CachingConnectionFactory rabbitConnectionFactory( + @Qualifier("secondRabbitConnectionFactoryBeanConfigurer") RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer, + @Qualifier("secondRabbitCachingConnectionFactoryConfigurer") CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer, + @Qualifier("secondRabbitConnectionFactoryCustomizer") ObjectProvider connectionFactoryCustomizers) throws Exception + { + return super.rabbitConnectionFactory(rabbitConnectionFactoryBeanConfigurer, rabbitCachingConnectionFactoryConfigurer, connectionFactoryCustomizers); + } + + @Override + @Bean("secondAmqpAdmin") + public AmqpAdmin amqpAdmin(@Qualifier("secondRabbitCachingConnectionFactory") ConnectionFactory connectionFactory) + { + return super.amqpAdmin(connectionFactory); + } + + @Override + @Bean(name = "secondRabbitStreamEnvironment") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + public Environment rabbitStreamEnvironment( + @Qualifier("secondRabbitStreamEnvironmentBuilderCustomizer") ObjectProvider customizers) + { + return super.rabbitStreamEnvironment(customizers); + } + + @Override + @Bean(name = "secondStreamAdmin") + @ConditionalOnMissingBean(name = "secondStreamAdmin") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-second.stream", name = "name") + public StreamAdmin streamAdmin(@Qualifier("secondRabbitStreamEnvironment") Environment env) + { + return super.streamAdmin(env); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaThirdRabbitmqConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaThirdRabbitmqConfig.java new file mode 100644 index 0000000..b263c75 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/config/SoaThirdRabbitmqConfig.java @@ -0,0 +1,102 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.config; + +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.CachingConnectionFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.ConnectionFactoryCustomizer; +import org.springframework.boot.autoconfigure.amqp.EnvironmentBuilderCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails; +import org.springframework.boot.autoconfigure.amqp.RabbitConnectionFactoryBeanConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ResourceLoader; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.support.StreamAdmin; + +import com.rabbitmq.client.impl.CredentialsProvider; +import com.rabbitmq.client.impl.CredentialsRefreshService; +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaThirdRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaThirdRabbitmqProperties.class}) +public class SoaThirdRabbitmqConfig extends SoaAbstractRabbitmqConfig +{ + public SoaThirdRabbitmqConfig(SoaThirdRabbitmqProperties properties) + { + super(properties); + } + + @Override + @Bean("thirdRabbitConnectionDetails") + RabbitConnectionDetails rabbitConnectionDetails() + { + return super.rabbitConnectionDetails(); + } + + @Override + @Bean("thirdRabbitConnectionFactoryBeanConfigurer") + public RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer( + ResourceLoader resourceLoader, + @Qualifier("thirdRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("thirdRabbitCredentialsProvider") ObjectProvider credentialsProvider, + @Qualifier("thirdRabbitCredentialsRefreshService") ObjectProvider credentialsRefreshService) + { + return super.rabbitConnectionFactoryBeanConfigurer(resourceLoader, connectionDetails, credentialsProvider, credentialsRefreshService); + } + + @Override + @Bean("thirdRabbitCachingConnectionFactoryConfigurer") + public CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer( + @Qualifier("thirdRabbitConnectionDetails") RabbitConnectionDetails connectionDetails, + @Qualifier("thirdRabbitConnectionNameStrategy") ObjectProvider connectionNameStrategy) + { + return super.rabbitConnectionFactoryConfigurer(connectionDetails, connectionNameStrategy); + } + + @Override + @Bean("thirdRabbitCachingConnectionFactory") + public CachingConnectionFactory rabbitConnectionFactory( + @Qualifier("thirdRabbitConnectionFactoryBeanConfigurer") RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer, + @Qualifier("thirdRabbitCachingConnectionFactoryConfigurer") CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer, + @Qualifier("thirdRabbitConnectionFactoryCustomizer") ObjectProvider connectionFactoryCustomizers) throws Exception + { + return super.rabbitConnectionFactory(rabbitConnectionFactoryBeanConfigurer, rabbitCachingConnectionFactoryConfigurer, connectionFactoryCustomizers); + } + + @Override + @Bean("thirdAmqpAdmin") + public AmqpAdmin amqpAdmin(@Qualifier("thirdRabbitCachingConnectionFactory") ConnectionFactory connectionFactory) + { + return super.amqpAdmin(connectionFactory); + } + + @Override + @Bean(name = "thirdRabbitStreamEnvironment") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + public Environment rabbitStreamEnvironment( + @Qualifier("thirdRabbitStreamEnvironmentBuilderCustomizer") ObjectProvider customizers) + { + return super.rabbitStreamEnvironment(customizers); + } + + @Override + @Bean(name = "thirdStreamAdmin") + @ConditionalOnMissingBean(name = "thirdStreamAdmin") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-third.stream", name = "name") + public StreamAdmin streamAdmin(@Qualifier("thirdRabbitStreamEnvironment") Environment env) + { + return super.streamAdmin(env); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/converter/FastJsonMessageConverter.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/converter/FastJsonMessageConverter.java new file mode 100644 index 0000000..caf9a30 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/converter/FastJsonMessageConverter.java @@ -0,0 +1,49 @@ + +package io.github.hpsocket.soa.starter.rabbitmq.common.converter; + +import org.slf4j.MDC; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.support.converter.AbstractMessageConverter; +import org.springframework.amqp.support.converter.MessageConversionException; + +import com.alibaba.fastjson2.JSON; + +import io.github.hpsocket.soa.framework.core.id.IdGenerator; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.rabbitmq.producer.entity.DomainEvent; + +import static io.github.hpsocket.soa.starter.rabbitmq.common.util.RabbitmqConstant.*; + +/** Fastjson 消息转换器 */ +public class FastJsonMessageConverter extends AbstractMessageConverter +{ + @Override + public Object fromMessage(Message message) throws MessageConversionException + { + return JSON.parse(message.getBody()); + } + + @Override + protected Message createMessage(Object object, MessageProperties messageProperties) + { + if(object instanceof DomainEvent event) + return event.toMessage(messageProperties); + + String json = JSON.toJSONString(object); + byte[] bytes = json.getBytes(WebServerHelper.DEFAULT_CHARSET_OBJ); + String msgId = IdGenerator.nextIdStr(); + String sourceRequestId = MDC.get(MdcAttr.MDC_REQUEST_ID_KEY); + + messageProperties.setMessageId(msgId); + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); + messageProperties.setContentEncoding(WebServerHelper.DEFAULT_CHARSET); + messageProperties.setContentLength(bytes.length); + messageProperties.setHeader(HEADER_MSG_ID, msgId); + messageProperties.setHeader(HEADER_SOURCE_REQUEST_ID, sourceRequestId); + + return new Message(bytes, messageProperties); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/converter/TextMessageConverter.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/converter/TextMessageConverter.java new file mode 100644 index 0000000..f4e4639 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/converter/TextMessageConverter.java @@ -0,0 +1,42 @@ + +package io.github.hpsocket.soa.starter.rabbitmq.common.converter; + +import org.slf4j.MDC; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.support.converter.AbstractMessageConverter; +import org.springframework.amqp.support.converter.MessageConversionException; + +import io.github.hpsocket.soa.framework.core.id.IdGenerator; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; + +import static io.github.hpsocket.soa.starter.rabbitmq.common.util.RabbitmqConstant.*; + +/** 普通文本消息转换器 */ +public class TextMessageConverter extends AbstractMessageConverter +{ + @Override + public Object fromMessage(Message message) throws MessageConversionException + { + return new String(message.getBody(), WebServerHelper.DEFAULT_CHARSET_OBJ); + } + + @Override + protected Message createMessage(Object object, MessageProperties messageProperties) + { + byte[] bytes = object.toString().getBytes(WebServerHelper.DEFAULT_CHARSET_OBJ); + String msgId = IdGenerator.nextIdStr(); + String sourceRequestId = MDC.get(MdcAttr.MDC_REQUEST_ID_KEY); + + messageProperties.setMessageId(msgId); + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN); + messageProperties.setContentEncoding(WebServerHelper.DEFAULT_CHARSET); + messageProperties.setContentLength(bytes.length); + messageProperties.setHeader(HEADER_MSG_ID, msgId); + messageProperties.setHeader(HEADER_SOURCE_REQUEST_ID, sourceRequestId); + + return new Message(bytes, messageProperties); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaDefaultRabbitmqProperties.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaDefaultRabbitmqProperties.java new file mode 100644 index 0000000..67ca7e4 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaDefaultRabbitmqProperties.java @@ -0,0 +1,16 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.properties; + +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@Primary +@Component +@ConfigurationProperties(prefix = "spring.rabbitmq") +@ConditionalOnExpression("'${spring.rabbitmq.host:}' != '' || '${spring.rabbitmq.addresses:}' != ''") +public class SoaDefaultRabbitmqProperties extends RabbitProperties +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaFirstRabbitmqProperties.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaFirstRabbitmqProperties.java new file mode 100644 index 0000000..728825f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaFirstRabbitmqProperties.java @@ -0,0 +1,14 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.properties; + +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "spring.rabbitmq-first") +@ConditionalOnExpression("'${spring.rabbitmq-first.host:}' != '' || '${spring.rabbitmq-first.addresses:}' != ''") +public class SoaFirstRabbitmqProperties extends RabbitProperties +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaSecondRabbitmqProperties.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaSecondRabbitmqProperties.java new file mode 100644 index 0000000..f933ba3 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaSecondRabbitmqProperties.java @@ -0,0 +1,14 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.properties; + +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "spring.rabbitmq-second") +@ConditionalOnExpression("'${spring.rabbitmq-second.host:}' != '' || '${spring.rabbitmq-second.addresses:}' != ''") +public class SoaSecondRabbitmqProperties extends RabbitProperties +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaThirdRabbitmqProperties.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaThirdRabbitmqProperties.java new file mode 100644 index 0000000..162a826 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/properties/SoaThirdRabbitmqProperties.java @@ -0,0 +1,14 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.properties; + +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "spring.rabbitmq-third") +@ConditionalOnExpression("'${spring.rabbitmq-third.host:}' != '' || '${spring.rabbitmq-third.addresses:}' != ''") +public class SoaThirdRabbitmqProperties extends RabbitProperties +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/util/RabbitmqConstant.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/util/RabbitmqConstant.java new file mode 100644 index 0000000..56b85f2 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/common/util/RabbitmqConstant.java @@ -0,0 +1,32 @@ +package io.github.hpsocket.soa.starter.rabbitmq.common.util; + +/** HP-SOA Rabbitmq 常量 */ +public class RabbitmqConstant +{ + /** 消息头:领域名称 */ + public static final String HEADER_DOMAIN_NAME = "x-domain-name"; + /** 消息头:事件名称 */ + public static final String HEADER_EVENT_NAME = "x-event-name"; + /** 消息头:消息 ID */ + public static final String HEADER_MSG_ID = "x-msg-id"; + /** 消息头:源请求 ID */ + public static final String HEADER_SOURCE_REQUEST_ID = "x-source-request-id"; + /** 消息头:AMQP 关联数据 ID */ + public static final String HEADER_CORRELA_DATA_ID = "amqp_correlationId"; + /** 消息头:AMQP 消息 ID */ + public static final String HEADER_AMQP_MESSAGE_ID = "amqp_messageId"; + /** 消息头:AMQP 消息投递标签 */ + public static final String HEADER_AMQP_DELIVERY_TAG = "amqp_deliveryTag"; + + + /** 消息发送状态:未发送 */ + public static final int SF_NOT_SEND = 0; + /** 消息发送状态:正在发送 */ + public static final int SF_SENDING = 1; + /** 消息发送状态:发送成功 */ + public static final int SF_SEND_SUCC = 2; + /** 消息发送状态:发送失败 */ + public static final int SF_SEND_FAIL = 3; + + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/aspect/RabbitmqListenerMdcInspector.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/aspect/RabbitmqListenerMdcInspector.java new file mode 100644 index 0000000..4a59e92 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/aspect/RabbitmqListenerMdcInspector.java @@ -0,0 +1,131 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.aspect; + +import org.apache.logging.log4j.core.config.Order; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.messaging.MessageHeaders; +import org.springframework.util.Assert; +import org.springframework.util.StopWatch; + +import com.rabbitmq.stream.Properties; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.support.AspectHelper; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import lombok.extern.slf4j.Slf4j; + +import static io.github.hpsocket.soa.starter.rabbitmq.common.util.RabbitmqConstant.*; + +import java.util.Map; + +/** Rabbitmq Listener MDC 拦截器
+ * 用于注入 MDC 调用链跟踪信息 + */ +@Slf4j +@Aspect +@Order(0) +public class RabbitmqListenerMdcInspector +{ + static final String INSPECTOR_POINTCUT_PATTERN = "execution (public void *.*(..)) && " + + "(" + + " @annotation(org.springframework.amqp.rabbit.annotation.RabbitListener) || " + + " (" + + " @target(org.springframework.amqp.rabbit.annotation.RabbitListener) &&" + + " @annotation(org.springframework.amqp.rabbit.annotation.RabbitHandler)" + + " )" + + ")"; + + private static final AspectHelper.AnnotationHolder ANNOTATION_HOLDER = new AspectHelper.AnnotationHolder<>() {}; + + @Pointcut(INSPECTOR_POINTCUT_PATTERN) + protected void aroundMethod() {} + + @Around(value = "aroundMethod()") + public Object inspect(ProceedingJoinPoint joinPoint) throws Throwable + { + MdcAttr mdcAttr = WebServerHelper.createMdcAttr(); + + RabbitListener listener = ANNOTATION_HOLDER.findAnnotationByMethodOrClass(joinPoint); + Assert.notNull(listener, "RabbitListener annotation not found"); + + String listenerId = listener.id(); + String correlationId = null; + String messageId = null; + String sourceRequestId = null; + String domainName = null; + String eventName = null; + + Object obj = AspectHelper.findFirstArgByTypes(joinPoint, Message.class + , org.springframework.messaging.Message.class + , com.rabbitmq.stream.Message.class); + + if(obj != null) + { + if(obj instanceof Message msg) + { + MessageProperties properties = msg.getMessageProperties(); + messageId = properties.getMessageId(); + correlationId = properties.getCorrelationId(); + sourceRequestId = properties.getHeader(HEADER_SOURCE_REQUEST_ID); + domainName = properties.getHeader(HEADER_DOMAIN_NAME); + eventName = properties.getHeader(HEADER_EVENT_NAME); + } + else if(obj instanceof org.springframework.messaging.Message msg) + { + MessageHeaders headers = msg.getHeaders(); + messageId = (String)headers.get(HEADER_MSG_ID); + correlationId = (String)headers.get(HEADER_CORRELA_DATA_ID); + sourceRequestId = (String)headers.get(HEADER_SOURCE_REQUEST_ID); + domainName = (String)headers.get(HEADER_DOMAIN_NAME); + eventName = (String)headers.get(HEADER_EVENT_NAME); + } + else if(obj instanceof com.rabbitmq.stream.Message msg) + { + Properties props = msg.getProperties(); + Map appProps = msg.getApplicationProperties(); + + messageId = props.getMessageIdAsString(); + correlationId = props.getCorrelationIdAsString(); + sourceRequestId = (String)appProps.get(HEADER_SOURCE_REQUEST_ID); + domainName = (String)appProps.get(HEADER_DOMAIN_NAME); + eventName = (String)appProps.get(HEADER_EVENT_NAME); + } + + if(GeneralHelper.isStrNotEmpty(messageId)) + mdcAttr.setMessageId(messageId); + if(GeneralHelper.isStrNotEmpty(sourceRequestId)) + mdcAttr.setSourceRequestId(sourceRequestId); + } + + mdcAttr.putMdc(); + + StopWatch sw = new StopWatch(listenerId); + + try + { + if(log.isTraceEnabled()) + log.trace("rabbit listener start consume message -> (listenerId: {}, correlationId: {}, messageId: {}, sourceRequestId: {}, domainName: {}, eventName: {})" + , listenerId, correlationId, messageId, sourceRequestId, domainName, eventName); + + sw.start(); + + return joinPoint.proceed(); + } + finally + { + sw.stop(); + + if(log.isTraceEnabled()) + log.trace("rabbit listener end consume message -> (listenerId: {}, correlationId: {}, messageId: {}, sourceRequestId: {}, domainName: {}, eventName: {}, costTime: {})" + , listenerId, correlationId, messageId, sourceRequestId, domainName, eventName, sw.getLastTaskTimeMillis()); + + mdcAttr.removeMdc(); + } + } +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/aspect/RabbitmqListenerTracingInspector.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/aspect/RabbitmqListenerTracingInspector.java new file mode 100644 index 0000000..6373223 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/aspect/RabbitmqListenerTracingInspector.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.aspect; + +import org.apache.logging.log4j.core.config.Order; +import org.apache.skywalking.apm.toolkit.trace.Trace; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +/** Rabbitmq Listener Tracing 拦截器
+ * 用于注入 traceId + */ +@Aspect +@Order(Integer.MIN_VALUE) +public class RabbitmqListenerTracingInspector +{ + @Pointcut(RabbitmqListenerMdcInspector.INSPECTOR_POINTCUT_PATTERN) + protected void aroundMethod() {} + + @Trace + @Around(value = "aroundMethod()") + public Object inspect(ProceedingJoinPoint joinPoint) throws Throwable + { + return joinPoint.proceed(); + } +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaAbstractRabbitmqConsumerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaAbstractRabbitmqConsumerConfig.java new file mode 100644 index 0000000..8d6ff81 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaAbstractRabbitmqConsumerConfig.java @@ -0,0 +1,183 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.config; + +import java.time.Duration; +import java.util.List; + +import org.springframework.amqp.rabbit.config.AbstractRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.retry.MessageRecoverer; +import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitProperties.ListenerRetry; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.listener.ConsumerCustomizer; +import org.springframework.rabbit.stream.listener.StreamListenerContainer; +import org.springframework.retry.backoff.ExponentialBackOffPolicy; +import org.springframework.retry.policy.SimpleRetryPolicy; +import org.springframework.retry.support.RetryTemplate; + +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; + +public class SoaAbstractRabbitmqConsumerConfig +{ + protected final ObjectProvider messageConverter; + protected final ObjectProvider messageRecoverer; + protected final ObjectProvider retryTemplateCustomizers; + + protected final RabbitProperties properties; + + public SoaAbstractRabbitmqConsumerConfig( + ObjectProvider messageConverter, + ObjectProvider messageRecoverer, + ObjectProvider retryTemplateCustomizers, + RabbitProperties properties) + { + this.messageConverter = messageConverter; + this.messageRecoverer = messageRecoverer; + this.retryTemplateCustomizers = retryTemplateCustomizers; + this.properties = properties; + } + + public SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer() + { + SimpleRabbitListenerContainerFactoryConfigurer configurer = new SimpleRabbitListenerContainerFactoryConfigurer(this.properties); + + /* + configurer.setMessageConverter(this.messageConverter.getIfUnique()); + configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique()); + configurer.setRetryTemplateCustomizers(this.retryTemplateCustomizers.orderedStream().toList()); + */ + + return configurer; + } + + public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory( + SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory, + ObjectProvider> simpleContainerCustomizer) + { + SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory(); + factory.setMessageConverter(this.messageConverter.getIfUnique()); + configurer.configure(factory, connectionFactory); + parseRetryConfig(factory); + + simpleContainerCustomizer.ifUnique(factory::setContainerCustomizer); + + return factory; + } + + public DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurer() + { + DirectRabbitListenerContainerFactoryConfigurer configurer = new DirectRabbitListenerContainerFactoryConfigurer(this.properties); + + /* + configurer.setMessageConverter(this.messageConverter.getIfUnique()); + configurer.setMessageRecoverer(this.messageRecoverer.getIfUnique()); + configurer.setRetryTemplateCustomizers(this.retryTemplateCustomizers.orderedStream().toList()); + */ + + return configurer; + } + + public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory( + DirectRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory, + ObjectProvider> directContainerCustomizer) + { + DirectRabbitListenerContainerFactory factory = new DirectRabbitListenerContainerFactory(); + factory.setMessageConverter(this.messageConverter.getIfUnique()); + configurer.configure(factory, connectionFactory); + parseRetryConfig(factory); + + directContainerCustomizer.ifUnique(factory::setContainerCustomizer); + + return factory; + } + + protected void parseRetryConfig(AbstractRabbitListenerContainerFactory factory) + { + ListenerRetry retryConfig = this.properties.getListener().getSimple().getRetry(); + + if (retryConfig.isEnabled()) + { + RetryInterceptorBuilder builder = (retryConfig.isStateless()) + ? RetryInterceptorBuilder.stateless() + : RetryInterceptorBuilder.stateful(); + RetryTemplate retryTemplate = createRetryTemplate(retryConfig, RabbitRetryTemplateCustomizer.Target.LISTENER); + builder.retryOperations(retryTemplate); + + MessageRecoverer recoverer = this.messageRecoverer.getIfUnique(); + + if(recoverer == null) + recoverer = new RejectAndDontRequeueRecoverer(); + + builder.recoverer(recoverer); + factory.setAdviceChain(builder.build()); + } + } + + private RetryTemplate createRetryTemplate(RabbitProperties.Retry properties, RabbitRetryTemplateCustomizer.Target target) + { + PropertyMapper map = PropertyMapper.get(); + RetryTemplate template = new RetryTemplate(); + SimpleRetryPolicy policy = new SimpleRetryPolicy(); + + map.from(properties::getMaxAttempts).to(policy::setMaxAttempts); + template.setRetryPolicy(policy); + ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); + map.from(properties::getInitialInterval) + .whenNonNull() + .as(Duration::toMillis) + .to(backOffPolicy::setInitialInterval); + map.from(properties::getMultiplier).to(backOffPolicy::setMultiplier); + map.from(properties::getMaxInterval).whenNonNull().as(Duration::toMillis).to(backOffPolicy::setMaxInterval); + template.setBackOffPolicy(backOffPolicy); + + List customizers = this.retryTemplateCustomizers.orderedStream().toList(); + + if (GeneralHelper.isNotNullOrEmpty(customizers)) + { + for (RabbitRetryTemplateCustomizer customizer : customizers) + { + customizer.customize(target, template); + } + } + + return template; + } + + public ConsumerCustomizer defaultConsumerCustomizer(String name, OffsetSpecification offset) + { + return (id, builder) -> { + builder.name(name) + .offset(offset) + .autoTrackingStrategy(); + }; + } + + public StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory( + Environment rabbitStreamEnvironment, + ObjectProvider consumerCustomizer, + ObjectProvider> containerCustomizer) + { + StreamRabbitListenerContainerFactory factory = new StreamRabbitListenerContainerFactory(rabbitStreamEnvironment); + factory.setNativeListener(this.properties.getListener().getStream().isNativeListener()); + consumerCustomizer.ifUnique(factory::setConsumerCustomizer); + containerCustomizer.ifUnique(factory::setContainerCustomizer); + + return factory; + } +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaDefaultRabbitmqConsumerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaDefaultRabbitmqConsumerConfig.java new file mode 100644 index 0000000..4ef848a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaDefaultRabbitmqConsumerConfig.java @@ -0,0 +1,108 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.config; + +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.retry.MessageRecoverer; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.listener.ConsumerCustomizer; +import org.springframework.rabbit.stream.listener.StreamListenerContainer; + +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaDefaultRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqConsumerConfig.class, SoaDefaultRabbitmqProperties.class}) +public class SoaDefaultRabbitmqConsumerConfig extends SoaAbstractRabbitmqConsumerConfig +{ + public SoaDefaultRabbitmqConsumerConfig( + ObjectProvider messageConverter, + ObjectProvider messageRecoverer, + ObjectProvider retryTemplateCustomizers, + SoaDefaultRabbitmqProperties properties) + { + super(messageConverter, messageRecoverer, retryTemplateCustomizers, properties); + } + + @Primary + @Override + @Bean("defaultSimpleRabbitListenerContainerFactoryConfigurer") + public SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer() + { + return super.simpleRabbitListenerContainerFactoryConfigurer(); + } + + @Primary + @Override + @Bean("defaultSimpleRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple", matchIfMissing = true) + public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory( + @Qualifier("defaultSimpleRabbitListenerContainerFactoryConfigurer") SimpleRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("defaultRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("defaultRabbitSimpleContainerCustomizer") ObjectProvider> simpleContainerCustomizer) + { + return super.simpleRabbitListenerContainerFactory(configurer, connectionFactory, simpleContainerCustomizer); + } + + @Primary + @Override + @Bean("defaultDirectRabbitListenerContainerFactoryConfigurer") + public DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurer() + { + return super.directRabbitListenerContainerFactoryConfigurer(); + } + + @Primary + @Override + @Bean("defaultDirectRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "direct") + public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory( + @Qualifier("defaultDirectRabbitListenerContainerFactoryConfigurer") DirectRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("defaultRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("defaultRabbitDirectContainerCustomizer") ObjectProvider> directContainerCustomizer) + { + return super.directRabbitListenerContainerFactory(configurer, connectionFactory, directContainerCustomizer); + } + + @Primary + @Bean(name = "defaultRabbitStreamConsumerCustomizer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "defaultRabbitStreamConsumerCustomizer") + @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "stream") + ConsumerCustomizer consumerCustomizer() + { + return defaultConsumerCustomizer("defaultRabbitStreamConsumer", OffsetSpecification.next()); + } + + @Primary + @Override + @Bean(name = "defaultStreamRabbitListenerContainerFactory") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "stream") + public StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory( + @Qualifier("defaultRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("defaultRabbitStreamConsumerCustomizer") ObjectProvider consumerCustomizer, + @Qualifier("defaultRabbitStreamContainerCustomizer") ObjectProvider> containerCustomizer) + { + return super.streamRabbitListenerContainerFactory(rabbitStreamEnvironment, consumerCustomizer, containerCustomizer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaFirstRabbitmqConsumerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaFirstRabbitmqConsumerConfig.java new file mode 100644 index 0000000..3f0af7c --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaFirstRabbitmqConsumerConfig.java @@ -0,0 +1,101 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.config; + +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.retry.MessageRecoverer; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.listener.ConsumerCustomizer; +import org.springframework.rabbit.stream.listener.StreamListenerContainer; + +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaFirstRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqConsumerConfig.class, SoaFirstRabbitmqProperties.class}) +public class SoaFirstRabbitmqConsumerConfig extends SoaAbstractRabbitmqConsumerConfig +{ + public SoaFirstRabbitmqConsumerConfig( + ObjectProvider messageConverter, + ObjectProvider messageRecoverer, + ObjectProvider retryTemplateCustomizers, + SoaFirstRabbitmqProperties properties) + { + super(messageConverter, messageRecoverer, retryTemplateCustomizers, properties); + } + + @Override + @Bean("firstSimpleRabbitListenerContainerFactoryConfigurer") + public SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer() + { + return super.simpleRabbitListenerContainerFactoryConfigurer(); + } + + @Override + @Bean("firstSimpleRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq-first.listener", name = "type", havingValue = "simple", matchIfMissing = true) + public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory( + @Qualifier("firstSimpleRabbitListenerContainerFactoryConfigurer") SimpleRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("firstRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("firstRabbitSimpleContainerCustomizer") ObjectProvider> simpleContainerCustomizer) + { + return super.simpleRabbitListenerContainerFactory(configurer, connectionFactory, simpleContainerCustomizer); + } + + @Override + @Bean("firstDirectRabbitListenerContainerFactoryConfigurer") + public DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurer() + { + return super.directRabbitListenerContainerFactoryConfigurer(); + } + + @Override + @Bean("firstDirectRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq-first.listener", name = "type", havingValue = "direct") + public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory( + @Qualifier("firstDirectRabbitListenerContainerFactoryConfigurer") DirectRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("firstRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("firstRabbitDirectContainerCustomizer") ObjectProvider> directContainerCustomizer) + { + return super.directRabbitListenerContainerFactory(configurer, connectionFactory, directContainerCustomizer); + } + + @Bean(name = "firstRabbitStreamConsumerCustomizer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "firstRabbitStreamConsumerCustomizer") + @ConditionalOnProperty(prefix = "spring.rabbitmq-first.listener", name = "type", havingValue = "stream") + ConsumerCustomizer consumerCustomizer() + { + return defaultConsumerCustomizer("firstRabbitStreamConsumer", OffsetSpecification.next()); + } + + @Override + @Bean(name = "firstStreamRabbitListenerContainerFactory") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-first.listener", name = "type", havingValue = "stream") + public StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory( + @Qualifier("firstRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("firstRabbitStreamConsumerCustomizer") ObjectProvider consumerCustomizer, + @Qualifier("firstRabbitStreamContainerCustomizer") ObjectProvider> containerCustomizer) + { + return super.streamRabbitListenerContainerFactory(rabbitStreamEnvironment, consumerCustomizer, containerCustomizer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaRabbitmqConsumerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaRabbitmqConsumerConfig.java new file mode 100644 index 0000000..5d2fe6f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaRabbitmqConsumerConfig.java @@ -0,0 +1,38 @@ + +package io.github.hpsocket.soa.starter.rabbitmq.consumer.config; + +import org.apache.skywalking.apm.toolkit.trace.Trace; +import org.springframework.amqp.rabbit.annotation.EnableRabbit; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; + +import io.github.hpsocket.soa.starter.rabbitmq.consumer.aspect.RabbitmqListenerMdcInspector; +import io.github.hpsocket.soa.starter.rabbitmq.consumer.aspect.RabbitmqListenerTracingInspector; +import io.github.hpsocket.soa.starter.rabbitmq.consumer.listener.RabbitmqReadOnlyEventListener; + +/** HP-SOA Rabbitmq Consumer 配置 */ +@EnableRabbit +@AutoConfiguration +public class SoaRabbitmqConsumerConfig +{ + @Bean + RabbitmqReadOnlyEventListener rabbitmqReadOnlyEventListener() + { + return new RabbitmqReadOnlyEventListener(); + } + + @Bean + @ConditionalOnClass(Trace.class) + RabbitmqListenerTracingInspector rabbitmqListenerTracingInspector() + { + return new RabbitmqListenerTracingInspector(); + } + + @Bean + RabbitmqListenerMdcInspector rabbitmqListenerMdcInspector() + { + return new RabbitmqListenerMdcInspector(); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaSecondRabbitmqConsumerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaSecondRabbitmqConsumerConfig.java new file mode 100644 index 0000000..1c5575a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaSecondRabbitmqConsumerConfig.java @@ -0,0 +1,101 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.config; + +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.retry.MessageRecoverer; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.listener.ConsumerCustomizer; +import org.springframework.rabbit.stream.listener.StreamListenerContainer; + +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaSecondRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqConsumerConfig.class, SoaSecondRabbitmqProperties.class}) +public class SoaSecondRabbitmqConsumerConfig extends SoaAbstractRabbitmqConsumerConfig +{ + public SoaSecondRabbitmqConsumerConfig( + ObjectProvider messageConverter, + ObjectProvider messageRecoverer, + ObjectProvider retryTemplateCustomizers, + SoaSecondRabbitmqProperties properties) + { + super(messageConverter, messageRecoverer, retryTemplateCustomizers, properties); + } + + @Override + @Bean("secondSimpleRabbitListenerContainerFactoryConfigurer") + public SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer() + { + return super.simpleRabbitListenerContainerFactoryConfigurer(); + } + + @Override + @Bean("secondSimpleRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq-second.listener", name = "type", havingValue = "simple", matchIfMissing = true) + public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory( + @Qualifier("secondSimpleRabbitListenerContainerFactoryConfigurer") SimpleRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("secondRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("secondRabbitSimpleContainerCustomizer") ObjectProvider> simpleContainerCustomizer) + { + return super.simpleRabbitListenerContainerFactory(configurer, connectionFactory, simpleContainerCustomizer); + } + + @Override + @Bean("secondDirectRabbitListenerContainerFactoryConfigurer") + public DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurer() + { + return super.directRabbitListenerContainerFactoryConfigurer(); + } + + @Override + @Bean("secondDirectRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq-second.listener", name = "type", havingValue = "direct") + public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory( + @Qualifier("secondDirectRabbitListenerContainerFactoryConfigurer") DirectRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("secondRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("secondRabbitDirectContainerCustomizer") ObjectProvider> directContainerCustomizer) + { + return super.directRabbitListenerContainerFactory(configurer, connectionFactory, directContainerCustomizer); + } + + @Bean(name = "secondRabbitStreamConsumerCustomizer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "secondRabbitStreamConsumerCustomizer") + @ConditionalOnProperty(prefix = "spring.rabbitmq-second.listener", name = "type", havingValue = "stream") + ConsumerCustomizer consumerCustomizer() + { + return defaultConsumerCustomizer("secondRabbitStreamConsumer", OffsetSpecification.next()); + } + + @Override + @Bean(name = "secondStreamRabbitListenerContainerFactory") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-second.listener", name = "type", havingValue = "stream") + public StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory( + @Qualifier("secondRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("secondRabbitStreamConsumerCustomizer") ObjectProvider consumerCustomizer, + @Qualifier("secondRabbitStreamContainerCustomizer") ObjectProvider> containerCustomizer) + { + return super.streamRabbitListenerContainerFactory(rabbitStreamEnvironment, consumerCustomizer, containerCustomizer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaThirdRabbitmqConsumerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaThirdRabbitmqConsumerConfig.java new file mode 100644 index 0000000..fcf523a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/config/SoaThirdRabbitmqConsumerConfig.java @@ -0,0 +1,101 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.config; + +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.DirectMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.retry.MessageRecoverer; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.listener.ConsumerCustomizer; +import org.springframework.rabbit.stream.listener.StreamListenerContainer; + +import com.rabbitmq.stream.Environment; +import com.rabbitmq.stream.OffsetSpecification; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaThirdRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqConsumerConfig.class, SoaThirdRabbitmqProperties.class}) +public class SoaThirdRabbitmqConsumerConfig extends SoaAbstractRabbitmqConsumerConfig +{ + public SoaThirdRabbitmqConsumerConfig( + ObjectProvider messageConverter, + ObjectProvider messageRecoverer, + ObjectProvider retryTemplateCustomizers, + SoaThirdRabbitmqProperties properties) + { + super(messageConverter, messageRecoverer, retryTemplateCustomizers, properties); + } + + @Override + @Bean("thirdSimpleRabbitListenerContainerFactoryConfigurer") + public SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer() + { + return super.simpleRabbitListenerContainerFactoryConfigurer(); + } + + @Override + @Bean("thirdSimpleRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq-third.listener", name = "type", havingValue = "simple", matchIfMissing = true) + public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory( + @Qualifier("thirdSimpleRabbitListenerContainerFactoryConfigurer") SimpleRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("thirdRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("thirdRabbitSimpleContainerCustomizer") ObjectProvider> simpleContainerCustomizer) + { + return super.simpleRabbitListenerContainerFactory(configurer, connectionFactory, simpleContainerCustomizer); + } + + @Override + @Bean("thirdDirectRabbitListenerContainerFactoryConfigurer") + public DirectRabbitListenerContainerFactoryConfigurer directRabbitListenerContainerFactoryConfigurer() + { + return super.directRabbitListenerContainerFactoryConfigurer(); + } + + @Override + @Bean("thirdDirectRabbitListenerContainerFactory") + @ConditionalOnProperty(prefix = "spring.rabbitmq-third.listener", name = "type", havingValue = "direct") + public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory( + @Qualifier("thirdDirectRabbitListenerContainerFactoryConfigurer") DirectRabbitListenerContainerFactoryConfigurer configurer, + @Qualifier("thirdRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("thirdRabbitDirectContainerCustomizer") ObjectProvider> directContainerCustomizer) + { + return super.directRabbitListenerContainerFactory(configurer, connectionFactory, directContainerCustomizer); + } + + @Bean(name = "thirdRabbitStreamConsumerCustomizer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "thirdRabbitStreamConsumerCustomizer") + @ConditionalOnProperty(prefix = "spring.rabbitmq-third.listener", name = "type", havingValue = "stream") + ConsumerCustomizer consumerCustomizer() + { + return defaultConsumerCustomizer("thirdRabbitStreamConsumer", OffsetSpecification.next()); + } + + @Override + @Bean(name = "thirdStreamRabbitListenerContainerFactory") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-third.listener", name = "type", havingValue = "stream") + public StreamRabbitListenerContainerFactory streamRabbitListenerContainerFactory( + @Qualifier("thirdRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("thirdRabbitStreamConsumerCustomizer") ObjectProvider consumerCustomizer, + @Qualifier("thirdRabbitStreamContainerCustomizer") ObjectProvider> containerCustomizer) + { + return super.streamRabbitListenerContainerFactory(rabbitStreamEnvironment, consumerCustomizer, containerCustomizer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/listener/RabbitmqReadOnlyEventListener.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/listener/RabbitmqReadOnlyEventListener.java new file mode 100644 index 0000000..b268025 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/consumer/listener/RabbitmqReadOnlyEventListener.java @@ -0,0 +1,49 @@ +package io.github.hpsocket.soa.starter.rabbitmq.consumer.listener; + +import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; + +import io.github.hpsocket.soa.framework.web.event.ReadOnlyEvent; +import lombok.extern.slf4j.Slf4j; + +/** {@linkplain ReadOnlyEvent} 应用程序事件处理器
+ * 当应用程序为只读时,关闭所有消息监听器,不接收任何消息 + */ +@Slf4j +public class RabbitmqReadOnlyEventListener implements ApplicationListener, Ordered +{ + @Autowired + private RabbitListenerEndpointRegistry registry; + + @Override + public void onApplicationEvent(ReadOnlyEvent event) + { + boolean readOnly = event.isReadOnly(); + boolean initial =event.isInitial(); + + if(!initial) + { + log.info("receive read-only switch event (read-only: {}), prepare to {} all consumers", readOnly, readOnly ? "STOP" : "START"); + + if(readOnly) + registry.stop(); + else + registry.start(); + } + else if(readOnly && registry.isRunning()) + { + log.info("application is read-only, then STOP RabbitListenerEndpointRegistry"); + + registry.stop(); + } + } + + @Override + public int getOrder() + { + return Ordered.HIGHEST_PRECEDENCE; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaAbstractRabbitmqProducerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaAbstractRabbitmqProducerConfig.java new file mode 100644 index 0000000..596c591 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaAbstractRabbitmqProducerConfig.java @@ -0,0 +1,129 @@ + +package io.github.hpsocket.soa.starter.rabbitmq.producer.config; + +import org.springframework.amqp.core.ReturnedMessage; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.connection.CorrelationData; +import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnsCallback; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitStreamTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateCustomizer; +import org.springframework.rabbit.stream.producer.ProducerCustomizer; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; +import org.springframework.rabbit.stream.support.converter.StreamMessageConverter; +import org.springframework.retry.RecoveryCallback; +import org.springframework.retry.RetryContext; + +import com.rabbitmq.stream.Environment; + +public class SoaAbstractRabbitmqProducerConfig +{ + protected final RabbitProperties properties; + + public SoaAbstractRabbitmqProducerConfig(RabbitProperties properties) + { + this.properties = properties; + } + + public RabbitTemplateConfigurer rabbitTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider retryTemplateCustomizers) + { + RabbitTemplateConfigurer configurer = new RabbitTemplateConfigurer(properties); + configurer.setMessageConverter(messageConverter.getIfUnique()); + configurer.setRetryTemplateCustomizers(retryTemplateCustomizers.orderedStream().toList()); + + return configurer; + } + + public RabbitTemplate rabbitTemplate( + RabbitTemplateConfigurer configurer, + ConnectionFactory connectionFactory, + ObjectProvider customizers, + ObjectProvider returnsCallback, + ObjectProvider confirmCallback, + ObjectProvider> recoveryCallback) + { + RabbitTemplate template = new RabbitTemplate(); + configurer.configure(template, connectionFactory); + customizers.orderedStream().forEach((customizer) -> customizer.customize(template)); + + template.setReturnsCallback(returnsCallback.getIfUnique()); + template.setConfirmCallback(confirmCallback.getIfUnique()); + template.setRecoveryCallback(recoveryCallback.getIfUnique()); + + return template; + } + + public RabbitMessagingTemplate rabbitMessagingTemplate(RabbitTemplate rabbitTemplate) + { + return new RabbitMessagingTemplate(rabbitTemplate); + } + + public ReturnsCallback returnsCallback() + { + return new ReturnsCallback() + { + @Override + public void returnedMessage(ReturnedMessage returned) + { + + } + }; + } + + public ConfirmCallback confirmCallback() + { + return new ConfirmCallback() + { + @Override + public void confirm(CorrelationData correlationData, boolean ack, String cause) + { + + } + }; + } + + public RecoveryCallback recoveryCallback() + { + return new RecoveryCallback() + { + @Override + public T recover(RetryContext context) throws Exception + { + return null; + } + }; + } + + public RabbitStreamTemplateConfigurer rabbitStreamTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider streamMessageConverter, + ObjectProvider producerCustomizer) + { + RabbitStreamTemplateConfigurer configurer = new RabbitStreamTemplateConfigurer(); + configurer.setMessageConverter(messageConverter.getIfUnique()); + configurer.setStreamMessageConverter(streamMessageConverter.getIfUnique()); + configurer.setProducerCustomizer(producerCustomizer.getIfUnique()); + + return configurer; + } + + public RabbitStreamTemplate rabbitStreamTemplate( + Environment rabbitStreamEnvironment, + RabbitStreamTemplateConfigurer configurer) + { + RabbitStreamTemplate template = new RabbitStreamTemplate(rabbitStreamEnvironment, this.properties.getStream().getName()); + configurer.configure(template); + + return template; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaDefaultRabbitmqProducerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaDefaultRabbitmqProducerConfig.java new file mode 100644 index 0000000..594455c --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaDefaultRabbitmqProducerConfig.java @@ -0,0 +1,125 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.config; + +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnsCallback; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitStreamTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateCustomizer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.producer.ProducerCustomizer; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; +import org.springframework.rabbit.stream.support.converter.StreamMessageConverter; +import org.springframework.retry.RecoveryCallback; + +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaDefaultRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqProducerConfig.class, SoaDefaultRabbitmqProperties.class}) +public class SoaDefaultRabbitmqProducerConfig extends SoaAbstractRabbitmqProducerConfig +{ + public SoaDefaultRabbitmqProducerConfig(SoaDefaultRabbitmqProperties properties) + { + super(properties); + } + + @Primary + @Override + @Bean("defaultRabbitTemplateConfigurer") + public RabbitTemplateConfigurer rabbitTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider retryTemplateCustomizers) + { + return super.rabbitTemplateConfigurer(messageConverter, retryTemplateCustomizers); + } + + @Primary + @Override + @Bean("defaultRabbitTemplate") + public RabbitTemplate rabbitTemplate( + @Qualifier("defaultRabbitTemplateConfigurer") RabbitTemplateConfigurer configurer, + @Qualifier("defaultRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("defaultRabbitTemplateCustomizer") ObjectProvider customizers, + @Qualifier("defaultRabbitReturnsCallback") ObjectProvider returnsCallback, + @Qualifier("defaultRabbitConfirmCallback") ObjectProvider confirmCallback, + @Qualifier("defaultRabbitRecoveryCallback") ObjectProvider> recoveryCallback) + { + return super.rabbitTemplate(configurer, connectionFactory, customizers, returnsCallback, confirmCallback, recoveryCallback); + } + + @Primary + @Override + @Bean("defaultRabbitReturnsCallback") + @ConditionalOnMissingBean(name = "defaultRabbitReturnsCallback") + public ReturnsCallback returnsCallback() + { + return super.returnsCallback(); + } + + @Primary + @Override + @Bean("defaultRabbitConfirmCallback") + @ConditionalOnMissingBean(name = "defaultRabbitConfirmCallback") + public ConfirmCallback confirmCallback() + { + return super.confirmCallback(); + } + + @Primary + @Override + @Bean("defaultRabbitRecoveryCallback") + @ConditionalOnMissingBean(name = "defaultRabbitRecoveryCallback") + public RecoveryCallback recoveryCallback() + { + return super.recoveryCallback(); + } + + @Primary + @Override + @Bean("defaultRabbitMessagingTemplate") + public RabbitMessagingTemplate rabbitMessagingTemplate(@Qualifier("defaultRabbitTemplate") RabbitTemplate rabbitTemplate) + { + return super.rabbitMessagingTemplate(rabbitTemplate); + } + + @Primary + @Override + @Bean("defaultRabbitStreamTemplateConfigurer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "defaultRabbitStreamTemplateConfigurer") + public RabbitStreamTemplateConfigurer rabbitStreamTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider streamMessageConverter, + @Qualifier("defaultRabbitStreamProducerCustomizer")ObjectProvider producerCustomizer) + { + return super.rabbitStreamTemplateConfigurer(messageConverter, streamMessageConverter, producerCustomizer); + } + + @Primary + @Override + @Bean("defaultRabbitStreamTemplate") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq.stream", name = "name") + public RabbitStreamTemplate rabbitStreamTemplate( + @Qualifier("defaultRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("defaultRabbitStreamTemplateConfigurer") RabbitStreamTemplateConfigurer configurer) + { + return super.rabbitStreamTemplate(rabbitStreamEnvironment, configurer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaFirstRabbitmqProducerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaFirstRabbitmqProducerConfig.java new file mode 100644 index 0000000..490cde6 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaFirstRabbitmqProducerConfig.java @@ -0,0 +1,116 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.config; + +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnsCallback; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitStreamTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateCustomizer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.producer.ProducerCustomizer; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; +import org.springframework.rabbit.stream.support.converter.StreamMessageConverter; +import org.springframework.retry.RecoveryCallback; + +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaFirstRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqProducerConfig.class, SoaFirstRabbitmqProperties.class}) +public class SoaFirstRabbitmqProducerConfig extends SoaAbstractRabbitmqProducerConfig +{ + public SoaFirstRabbitmqProducerConfig(SoaFirstRabbitmqProperties properties) + { + super(properties); + } + + @Override + @Bean("firstRabbitTemplateConfigurer") + public RabbitTemplateConfigurer rabbitTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider retryTemplateCustomizers) + { + return super.rabbitTemplateConfigurer(messageConverter, retryTemplateCustomizers); + } + + @Override + @Bean("firstRabbitTemplate") + public RabbitTemplate rabbitTemplate( + @Qualifier("firstRabbitTemplateConfigurer") RabbitTemplateConfigurer configurer, + @Qualifier("firstRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("firstRabbitTemplateCustomizer") ObjectProvider customizers, + @Qualifier("firstRabbitReturnsCallback") ObjectProvider returnsCallback, + @Qualifier("firstRabbitConfirmCallback") ObjectProvider confirmCallback, + @Qualifier("firstRabbitRecoveryCallback") ObjectProvider> recoveryCallback) + { + return super.rabbitTemplate(configurer, connectionFactory, customizers, returnsCallback, confirmCallback, recoveryCallback); + } + + @Override + @Bean("firstRabbitReturnsCallback") + @ConditionalOnMissingBean(name = "firstRabbitReturnsCallback") + public ReturnsCallback returnsCallback() + { + return super.returnsCallback(); + } + + @Override + @Bean("firstRabbitConfirmCallback") + @ConditionalOnMissingBean(name = "firstRabbitConfirmCallback") + public ConfirmCallback confirmCallback() + { + return super.confirmCallback(); + } + + @Override + @Bean("firstRabbitRecoveryCallback") + @ConditionalOnMissingBean(name = "firstRabbitRecoveryCallback") + public RecoveryCallback recoveryCallback() + { + return super.recoveryCallback(); + } + + @Override + @Bean("firstRabbitMessagingTemplate") + public RabbitMessagingTemplate rabbitMessagingTemplate(@Qualifier("firstRabbitTemplate") RabbitTemplate rabbitTemplate) + { + return super.rabbitMessagingTemplate(rabbitTemplate); + } + + @Override + @Bean("firstRabbitStreamTemplateConfigurer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "firstRabbitStreamTemplateConfigurer") + public RabbitStreamTemplateConfigurer rabbitStreamTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider streamMessageConverter, + @Qualifier("firstRabbitStreamProducerCustomizer")ObjectProvider producerCustomizer) + { + return super.rabbitStreamTemplateConfigurer(messageConverter, streamMessageConverter, producerCustomizer); + } + + @Override + @Bean("firstRabbitStreamTemplate") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-first.stream", name = "name") + public RabbitStreamTemplate rabbitStreamTemplate( + @Qualifier("firstRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("firstRabbitStreamTemplateConfigurer") RabbitStreamTemplateConfigurer configurer) + { + return super.rabbitStreamTemplate(rabbitStreamEnvironment, configurer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaRabbitmqProducerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaRabbitmqProducerConfig.java new file mode 100644 index 0000000..36ed1fd --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaRabbitmqProducerConfig.java @@ -0,0 +1,13 @@ + +package io.github.hpsocket.soa.starter.rabbitmq.producer.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.AutoConfiguration; + +/** HP-SOA Rabbitmq Producer 配置 */ +@AutoConfiguration +@MapperScan(basePackages = "io.github.hpsocket.soa.starter.rabbitmq.producer.mapper") +public class SoaRabbitmqProducerConfig +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaSecondRabbitmqProducerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaSecondRabbitmqProducerConfig.java new file mode 100644 index 0000000..81fe9af --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaSecondRabbitmqProducerConfig.java @@ -0,0 +1,116 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.config; + +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnsCallback; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitStreamTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateCustomizer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.producer.ProducerCustomizer; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; +import org.springframework.rabbit.stream.support.converter.StreamMessageConverter; +import org.springframework.retry.RecoveryCallback; + +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaSecondRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqProducerConfig.class, SoaSecondRabbitmqProperties.class}) +public class SoaSecondRabbitmqProducerConfig extends SoaAbstractRabbitmqProducerConfig +{ + public SoaSecondRabbitmqProducerConfig(SoaSecondRabbitmqProperties properties) + { + super(properties); + } + + @Override + @Bean("secondRabbitTemplateConfigurer") + public RabbitTemplateConfigurer rabbitTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider retryTemplateCustomizers) + { + return super.rabbitTemplateConfigurer(messageConverter, retryTemplateCustomizers); + } + + @Override + @Bean("secondRabbitTemplate") + public RabbitTemplate rabbitTemplate( + @Qualifier("secondRabbitTemplateConfigurer") RabbitTemplateConfigurer configurer, + @Qualifier("secondRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("secondRabbitTemplateCustomizer") ObjectProvider customizers, + @Qualifier("secondRabbitReturnsCallback") ObjectProvider returnsCallback, + @Qualifier("secondRabbitConfirmCallback") ObjectProvider confirmCallback, + @Qualifier("secondRabbitRecoveryCallback") ObjectProvider> recoveryCallback) + { + return super.rabbitTemplate(configurer, connectionFactory, customizers, returnsCallback, confirmCallback, recoveryCallback); + } + + @Override + @Bean("secondRabbitReturnsCallback") + @ConditionalOnMissingBean(name = "secondRabbitReturnsCallback") + public ReturnsCallback returnsCallback() + { + return super.returnsCallback(); + } + + @Override + @Bean("secondRabbitConfirmCallback") + @ConditionalOnMissingBean(name = "secondRabbitConfirmCallback") + public ConfirmCallback confirmCallback() + { + return super.confirmCallback(); + } + + @Override + @Bean("secondRabbitRecoveryCallback") + @ConditionalOnMissingBean(name = "secondRabbitRecoveryCallback") + public RecoveryCallback recoveryCallback() + { + return super.recoveryCallback(); + } + + @Override + @Bean("secondRabbitMessagingTemplate") + public RabbitMessagingTemplate rabbitMessagingTemplate(@Qualifier("secondRabbitTemplate") RabbitTemplate rabbitTemplate) + { + return super.rabbitMessagingTemplate(rabbitTemplate); + } + + @Override + @Bean("secondRabbitStreamTemplateConfigurer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "secondRabbitStreamTemplateConfigurer") + public RabbitStreamTemplateConfigurer rabbitStreamTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider streamMessageConverter, + @Qualifier("secondRabbitStreamProducerCustomizer")ObjectProvider producerCustomizer) + { + return super.rabbitStreamTemplateConfigurer(messageConverter, streamMessageConverter, producerCustomizer); + } + + @Override + @Bean("secondRabbitStreamTemplate") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-second.stream", name = "name") + public RabbitStreamTemplate rabbitStreamTemplate( + @Qualifier("secondRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("secondRabbitStreamTemplateConfigurer") RabbitStreamTemplateConfigurer configurer) + { + return super.rabbitStreamTemplate(rabbitStreamEnvironment, configurer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaThirdRabbitmqProducerConfig.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaThirdRabbitmqProducerConfig.java new file mode 100644 index 0000000..c14b89f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/config/SoaThirdRabbitmqProducerConfig.java @@ -0,0 +1,116 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.config; + +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback; +import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnsCallback; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer; +import org.springframework.boot.autoconfigure.amqp.RabbitStreamTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer; +import org.springframework.boot.autoconfigure.amqp.RabbitTemplateCustomizer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory; +import org.springframework.rabbit.stream.producer.ProducerCustomizer; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; +import org.springframework.rabbit.stream.support.converter.StreamMessageConverter; +import org.springframework.retry.RecoveryCallback; + +import com.rabbitmq.stream.Environment; + +import io.github.hpsocket.soa.starter.rabbitmq.common.properties.SoaThirdRabbitmqProperties; + +@AutoConfiguration +@ConditionalOnBean({SoaRabbitmqProducerConfig.class, SoaThirdRabbitmqProperties.class}) +public class SoaThirdRabbitmqProducerConfig extends SoaAbstractRabbitmqProducerConfig +{ + public SoaThirdRabbitmqProducerConfig(SoaThirdRabbitmqProperties properties) + { + super(properties); + } + + @Override + @Bean("thirdRabbitTemplateConfigurer") + public RabbitTemplateConfigurer rabbitTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider retryTemplateCustomizers) + { + return super.rabbitTemplateConfigurer(messageConverter, retryTemplateCustomizers); + } + + @Override + @Bean("thirdRabbitTemplate") + public RabbitTemplate rabbitTemplate( + @Qualifier("thirdRabbitTemplateConfigurer") RabbitTemplateConfigurer configurer, + @Qualifier("thirdRabbitCachingConnectionFactory") ConnectionFactory connectionFactory, + @Qualifier("thirdRabbitTemplateCustomizer") ObjectProvider customizers, + @Qualifier("thirdRabbitReturnsCallback") ObjectProvider returnsCallback, + @Qualifier("thirdRabbitConfirmCallback") ObjectProvider confirmCallback, + @Qualifier("thirdRabbitRecoveryCallback") ObjectProvider> recoveryCallback) + { + return super.rabbitTemplate(configurer, connectionFactory, customizers, returnsCallback, confirmCallback, recoveryCallback); + } + + @Override + @Bean("thirdRabbitReturnsCallback") + @ConditionalOnMissingBean(name = "thirdRabbitReturnsCallback") + public ReturnsCallback returnsCallback() + { + return super.returnsCallback(); + } + + @Override + @Bean("thirdRabbitConfirmCallback") + @ConditionalOnMissingBean(name = "thirdRabbitConfirmCallback") + public ConfirmCallback confirmCallback() + { + return super.confirmCallback(); + } + + @Override + @Bean("thirdRabbitRecoveryCallback") + @ConditionalOnMissingBean(name = "thirdRabbitRecoveryCallback") + public RecoveryCallback recoveryCallback() + { + return super.recoveryCallback(); + } + + @Override + @Bean("thirdRabbitMessagingTemplate") + public RabbitMessagingTemplate rabbitMessagingTemplate(@Qualifier("thirdRabbitTemplate") RabbitTemplate rabbitTemplate) + { + return super.rabbitMessagingTemplate(rabbitTemplate); + } + + @Override + @Bean("thirdRabbitStreamTemplateConfigurer") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnMissingBean(name = "thirdRabbitStreamTemplateConfigurer") + public RabbitStreamTemplateConfigurer rabbitStreamTemplateConfigurer( + ObjectProvider messageConverter, + ObjectProvider streamMessageConverter, + @Qualifier("thirdRabbitStreamProducerCustomizer")ObjectProvider producerCustomizer) + { + return super.rabbitStreamTemplateConfigurer(messageConverter, streamMessageConverter, producerCustomizer); + } + + @Override + @Bean("thirdRabbitStreamTemplate") + @ConditionalOnClass(StreamRabbitListenerContainerFactory.class) + @ConditionalOnProperty(prefix = "spring.rabbitmq-third.stream", name = "name") + public RabbitStreamTemplate rabbitStreamTemplate( + @Qualifier("thirdRabbitStreamEnvironment") Environment rabbitStreamEnvironment, + @Qualifier("thirdRabbitStreamTemplateConfigurer") RabbitStreamTemplateConfigurer configurer) + { + return super.rabbitStreamTemplate(rabbitStreamEnvironment, configurer); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/entity/DomainEvent.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/entity/DomainEvent.java new file mode 100644 index 0000000..cd8892d --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/entity/DomainEvent.java @@ -0,0 +1,174 @@ + +package io.github.hpsocket.soa.starter.rabbitmq.producer.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import org.slf4j.MDC; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageBuilder; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.rabbit.stream.support.StreamMessageProperties; + +import io.github.hpsocket.soa.framework.core.id.IdGenerator; +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.data.mysql.entity.BaseLogicDeleteEntity; +import io.github.hpsocket.soa.starter.rabbitmq.common.util.RabbitmqConstant; +import lombok.Getter; +import lombok.Setter; + +import static io.github.hpsocket.soa.starter.rabbitmq.common.util.RabbitmqConstant.*; + +/** 领域事件实体
+ * 对应领域事件数据库表,应用程序可以继承该实体,扩展自定义字段 + */ +@Getter +@Setter +@SuppressWarnings("serial") +public class DomainEvent extends BaseLogicDeleteEntity implements Serializable +{ + /** + * 领域名称 + */ + private String domainName; + + /** + * 事件名称 + */ + private String eventName; + + /** + * 交换机(领域事件的交换机是fanout类型) + */ + private String exchange; + + /** + * 路由键(领域事件的路由键为空) + */ + private String routingKey; + + /** + * 事件唯一Id + */ + private String msgId; + + /** + * 消息体 + */ + private String msg; + + /** + * 关联请求Id + */ + private String sourceRequestId; + + /** + * 发送标记({@linkplain RabbitmqConstant#SF_NOT_SEND SF_NOT_SEND} - 未发送,{@linkplain RabbitmqConstant#SF_SENDING SF_SENDING} - 正在发送,{@linkplain RabbitmqConstant#SF_SEND_SUCC SF_SEND_SUCC} - 发送成功,{@linkplain RabbitmqConstant#SF_SEND_FAIL SF_SEND_FAIL} - 发送失败) + */ + private Integer sendFlag; + + /** + * 重试次数 + */ + private Integer retries; + + /** + * 最后发送时间 + */ + private LocalDateTime lastSendTime; + + public DomainEvent() + { + this(IdGenerator.nextIdStr()); + } + + public DomainEvent(String msgId) + { + this(msgId, MDC.get(MdcAttr.MDC_REQUEST_ID_KEY)); + } + + public DomainEvent(String msgId, String sourceRequestId) + { + this.msgId = msgId; + this.sourceRequestId = sourceRequestId; + } + + public Message toMessage() + { + return toMessage(null, null); + } + + public Message toMessage(MessageProperties messageProperties) + { + return toMessage(messageProperties, null); + } + + public Message toMessage(String correlationId) + { + return toMessage(null, correlationId); + } + + public Message toMessage(MessageProperties messageProperties, String correlationId) + { + MessageBuilder builder = MessageBuilder.withBody(msg.getBytes(WebServerHelper.DEFAULT_CHARSET_OBJ)); + + if(messageProperties != null) + builder.andProperties(messageProperties); + + if(GeneralHelper.isStrNotEmpty(correlationId)) + builder.setCorrelationId(correlationId); + + return builder + .setContentEncoding(WebServerHelper.DEFAULT_CHARSET) + .setContentType(MessageProperties.CONTENT_TYPE_JSON) + .setMessageId(msgId) + + .setHeader(HEADER_DOMAIN_NAME, domainName) + .setHeader(HEADER_EVENT_NAME, eventName) + .setHeader(HEADER_MSG_ID, msgId) + .setHeader(HEADER_SOURCE_REQUEST_ID, sourceRequestId) + .build(); + } + + public Message toStreamMessage() + { + return toStreamMessage(null, null); + } + + public Message toStreamMessage(StreamMessageProperties streamMessageProperties) + { + return toStreamMessage(streamMessageProperties, null); + } + + public Message toStreamMessage(String correlationId) + { + return toStreamMessage(null, correlationId); + } + + public Message toStreamMessage(StreamMessageProperties streamMessageProperties, String correlationId) + { + if(streamMessageProperties == null) + streamMessageProperties = new StreamMessageProperties(); + + streamMessageProperties.setGroupId(domainName); + + MessageBuilder builder = MessageBuilder.withBody(msg.getBytes(WebServerHelper.DEFAULT_CHARSET_OBJ)); + builder.andProperties(streamMessageProperties); + + if(GeneralHelper.isStrNotEmpty(correlationId)) + builder.setCorrelationId(correlationId); + + return builder + .setContentEncoding(WebServerHelper.DEFAULT_CHARSET) + .setContentType(MessageProperties.CONTENT_TYPE_JSON) + .setMessageId(msgId) + .setHeader(HEADER_DOMAIN_NAME, domainName) + .setHeader(HEADER_EVENT_NAME, eventName) + .setHeader(HEADER_MSG_ID, msgId) + .setHeader(HEADER_SOURCE_REQUEST_ID, sourceRequestId) + .build(); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/mapper/DomainEventMapper.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/mapper/DomainEventMapper.java new file mode 100644 index 0000000..075653b --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/mapper/DomainEventMapper.java @@ -0,0 +1,11 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import io.github.hpsocket.soa.starter.rabbitmq.producer.entity.DomainEvent; + +/** 领域事件 {@linkplain BaseMapper Mapper} */ +public interface DomainEventMapper extends BaseMapper +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/sender/AbstractRabbitmqDomainEventSender.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/sender/AbstractRabbitmqDomainEventSender.java new file mode 100644 index 0000000..6ebfb4f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/sender/AbstractRabbitmqDomainEventSender.java @@ -0,0 +1,119 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.sender; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.connection.CorrelationData; +import org.springframework.amqp.rabbit.connection.CorrelationData.Confirm; +import org.springframework.amqp.rabbit.core.RabbitTemplate; + +import io.github.hpsocket.soa.starter.rabbitmq.producer.entity.DomainEvent; +import io.github.hpsocket.soa.starter.rabbitmq.producer.service.DomainEventService; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** 领域事件消息发送器基类(经典消息)
+ * 使用步骤:
+ *
    + *
  1. 应用程序 Bean 继承该类,并实现 {@linkplain #getRabbitTemplate(T)} 和 {@linkplain #getDomainEventService()} 抽象方法
  2. + *
  3. 应用程序 Bean 通过 ExclusiveJob 或 XxlJob 定时调用 {@linkplain #sendMqEvent()} 发送领域事件
  4. + *
  5. 应用程序 Bean 通过 ExclusiveJob 或 XxlJob 定时调用 {@linkplain #compensateMqEvent()} 补偿领域事件
  6. + *
+ */ +@Slf4j +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +abstract public class AbstractRabbitmqDomainEventSender +{ + private int sendBatchSize = DomainEventService.DEFAULT_SEND_BATCH_SIZE; + private int compensateBatchSize = DomainEventService.DEFAULT_COMPENSATE_BATCH_SIZE; + private int compensateTimeout = DomainEventService.DEFAULT_COMPENSATE_TIMEOUT; + + /** 获取消息发送模版 {@linkplain RabbitTemplate} + * @param event 待发送的领域事件 + */ + abstract protected RabbitTemplate getRabbitTemplate(T event); + + /** 获取领域事件服务 {@linkplain DomainEventService} */ + abstract protected DomainEventService getDomainEventService(); + + public AbstractRabbitmqDomainEventSender(int sendBatchSize) + { + this.sendBatchSize = sendBatchSize; + } + + /** (分批)发送领域事件 */ + public void sendMqEvent() + { + Long preId = 0L; + DomainEventService domainEventService = getDomainEventService(); + + while(true) + { + List events = domainEventService.markMultiEventToSend(sendBatchSize, preId); + int size = events.size(); + + if(size == 0) + break; + + List succ = new ArrayList<>(size); + List fail = new ArrayList<>(size); + + for(T event : events) + { + Long id = event.getId(); + String msgId = event.getMsgId(); + String corId = new StringBuilder(msgId).append('#').append(event.getRetries()).toString(); + + try + { + Message message = event.toMessage(corId); + CorrelationData corData = new CorrelationData(corId); + RabbitTemplate template = getRabbitTemplate(event); + + template.send(event.getExchange(), event.getRoutingKey(), message, corData); + Confirm confirm = corData.getFuture().get(); + + if(!confirm.isAck()) + log.warn("send MQ message un-ack -> (id: {}, msgId: {}, corId: {}) : {}", id, msgId, corId, confirm.getReason()); + + succ.add(id); + } + catch(Exception e) + { + fail.add(id); + log.warn("send MQ message fail -> (id: {}, msgId: {}, corId: {}) : {}", id, msgId, corId, e.getMessage(), e); + } + } + + if(!succ.isEmpty()) + domainEventService.markMultiEventSendSuccess(succ); + if(!fail.isEmpty()) + domainEventService.markMultiEventSendFail(fail); + + if(size < sendBatchSize) + break; + + preId = events.get(size - 1).getId(); + } + } + + /** (分批)补偿领域事件 */ + public void compensateMqEvent() + { + DomainEventService domainEventService = getDomainEventService(); + + while(true) + { + if(!domainEventService.resetSendingEventToSendFail(compensateTimeout, compensateBatchSize)) + break; + } + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/sender/AbstractRabbitmqStreamDomainEventSender.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/sender/AbstractRabbitmqStreamDomainEventSender.java new file mode 100644 index 0000000..32e3f66 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/sender/AbstractRabbitmqStreamDomainEventSender.java @@ -0,0 +1,115 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.sender; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.springframework.amqp.core.Message; +import org.springframework.rabbit.stream.producer.RabbitStreamTemplate; + +import io.github.hpsocket.soa.starter.rabbitmq.producer.entity.DomainEvent; +import io.github.hpsocket.soa.starter.rabbitmq.producer.service.DomainEventService; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** 领域事件消息发送器基类(Stream 消息)
+ * 使用步骤:
+ *
    + *
  1. 应用程序 Bean 继承该类,并实现 {@linkplain #getRabbitStreamTemplate(T)} 和 {@linkplain #getDomainEventService()} 抽象方法
  2. + *
  3. 应用程序 Bean 通过 ExclusiveJob 或 XxlJob 定时调用 {@linkplain #sendMqEvent()} 发送领域事件
  4. + *
  5. 应用程序 Bean 通过 ExclusiveJob 或 XxlJob 定时调用 {@linkplain #compensateMqEvent()} 补偿领域事件
  6. + *
+ */ +@Slf4j +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +abstract public class AbstractRabbitmqStreamDomainEventSender +{ + private int sendBatchSize = DomainEventService.DEFAULT_SEND_BATCH_SIZE; + private int compensateBatchSize = DomainEventService.DEFAULT_COMPENSATE_BATCH_SIZE; + private int compensateTimeout = DomainEventService.DEFAULT_COMPENSATE_TIMEOUT; + + /** 获取消息发送模版 {@linkplain RabbitStreamTemplate} + * @param event 待发送的领域事件 + */ + abstract protected RabbitStreamTemplate getRabbitStreamTemplate(T event); + + /** 获取领域事件服务 {@linkplain DomainEventService} */ + abstract protected DomainEventService getDomainEventService(); + + public AbstractRabbitmqStreamDomainEventSender(int sendBatchSize) + { + this.sendBatchSize = sendBatchSize; + } + + /** (分批)发送领域事件 */ + public void sendMqEvent() + { + Long preId = 0L; + DomainEventService domainEventService = getDomainEventService(); + + while(true) + { + List events = domainEventService.markMultiEventToSend(sendBatchSize, preId); + int size = events.size(); + + if(size == 0) + break; + + List succ = new ArrayList<>(size); + List fail = new ArrayList<>(size); + + for(T event : events) + { + Long id = event.getId(); + String msgId = event.getMsgId(); + String corId = new StringBuilder(msgId).append('#').append(event.getRetries()).toString(); + + try + { + Message message = event.toStreamMessage(corId); + RabbitStreamTemplate template = getRabbitStreamTemplate(event); + CompletableFuture future = template.send(message); + + if(!future.get()) + log.warn("send MQ message un-ack -> (id: {}, msgId: {}, corId: {})", id, msgId, corId); + + succ.add(id); + } + catch(Exception e) + { + fail.add(id); + log.warn("send MQ message fail -> (id: {}, msgId: {}, corId: {}) : {}", id, msgId, corId, e.getMessage(), e); + } + } + + if(!succ.isEmpty()) + domainEventService.markMultiEventSendSuccess(succ); + if(!fail.isEmpty()) + domainEventService.markMultiEventSendFail(fail); + + if(size < sendBatchSize) + break; + + preId = events.get(size - 1).getId(); + } + } + + /** (分批)补偿领域事件 */ + public void compensateMqEvent() + { + DomainEventService domainEventService = getDomainEventService(); + + while(true) + { + if(!domainEventService.resetSendingEventToSendFail(compensateTimeout, compensateBatchSize)) + break; + } + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/service/DomainEventService.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/service/DomainEventService.java new file mode 100644 index 0000000..e5da630 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/service/DomainEventService.java @@ -0,0 +1,50 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.service; + +import java.util.Collection; +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; + +import io.github.hpsocket.soa.starter.rabbitmq.producer.entity.DomainEvent; + +/** 领域事件服务接口 */ +public interface DomainEventService extends IService +{ + /** 默认发送批次大小 */ + int DEFAULT_SEND_BATCH_SIZE = 100; + /** 默认补偿批次大小 */ + int DEFAULT_COMPENSATE_BATCH_SIZE = 200; + /** 默认超时补偿时间(秒) */ + int DEFAULT_COMPENSATE_TIMEOUT = 30; + + /** 标记一个领域事件为正在发送 */ + T markOneEventToSend(); + /** 标记一个领域事件为正在发送 */ + T markOneEventToSend(Long preId); + + /** 标记多个领域事件为正在发送 */ + List markMultiEventToSend(); + /** 标记多个领域事件为正在发送 */ + List markMultiEventToSend(Long preId); + /** 标记多个领域事件为正在发送 */ + List markMultiEventToSend(int batchSize); + /** 标记多个领域事件为正在发送 */ + List markMultiEventToSend(int batchSize, Long preId); + + /** 标记一个领域事件为发送成功 */ + boolean markOneEventSendSuccess(Long id); + /** 标记多个领域事件为发送成功 */ + boolean markMultiEventSendSuccess(Collection ids); + + /** 标记一个领域事件为发送失败 */ + boolean markOneEventSendFail(Long id); + /** 标记多个领域事件为发送失败 */ + boolean markMultiEventSendFail(Collection ids); + + /** 重设正在发送的领域事件状态为发送失败 */ + boolean resetSendingEventToSendFail(); + /** 重设正在发送的领域事件状态为发送失败 */ + boolean resetSendingEventToSendFail(int sendTimeout); + /** 重设正在发送的领域事件状态为发送失败 */ + boolean resetSendingEventToSendFail(int sendTimeout, int batchSize); +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/service/impl/DomainEventServiceImpl.java b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/service/impl/DomainEventServiceImpl.java new file mode 100644 index 0000000..a836910 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/java/io/github/hpsocket/soa/starter/rabbitmq/producer/service/impl/DomainEventServiceImpl.java @@ -0,0 +1,207 @@ +package io.github.hpsocket.soa.starter.rabbitmq.producer.service.impl; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import io.github.hpsocket.soa.framework.core.util.Pair; +import io.github.hpsocket.soa.starter.rabbitmq.producer.entity.DomainEvent; +import io.github.hpsocket.soa.starter.rabbitmq.producer.service.DomainEventService; + +import static io.github.hpsocket.soa.starter.rabbitmq.common.util.RabbitmqConstant.*; + +/** 领域事件服务实现
+ * 继承 {@linkplain ServiceImpl} 并实现 {@linkplain DomainEventService} 接口 + */ +public class DomainEventServiceImpl, T extends DomainEvent> extends ServiceImpl implements DomainEventService +{ + private static final String SQL_SET_RETRIES = String.format("retries = IF(send_flag = %d, 0, retries + 1)", SF_NOT_SEND); + private static final String SQL_PATTERN_LAST_SEND_TIME_LE = "DATE_ADD(NOW(), INTERVAL -%d SECOND)"; + + @Override + public T markOneEventToSend() + { + return markOneEventToSend(0L); + } + + @Override + public T markOneEventToSend(Long preId) + { + T event = lambdaQuery() + .gt(T::getId, preId) + .in(T::getSendFlag, SF_NOT_SEND, SF_SEND_FAIL) + .orderByAsc(T::getId) + .last("LIMIT 1") + .one(); + + if(event != null) + { + lambdaUpdate() + .eq(T::getId, event.getId()) + .setSql(SQL_SET_RETRIES) + .set(T::getSendFlag, SF_SENDING) + .set(T::getLastSendTime, LocalDateTime.now()) + .update(); + } + + return event; + } + + @Override + public List markMultiEventToSend() + { + return markMultiEventToSend(DEFAULT_SEND_BATCH_SIZE, 0L); + } + + @Override + public List markMultiEventToSend(Long preId) + { + return markMultiEventToSend(DEFAULT_SEND_BATCH_SIZE, preId); + } + + @Override + public List markMultiEventToSend(int batchSize) + { + return markMultiEventToSend(batchSize, 0L); + } + + @Override + public List markMultiEventToSend(int batchSize, Long preId) + { + List events = lambdaQuery() + .gt(T::getId, preId) + .in(T::getSendFlag, SF_NOT_SEND, SF_SEND_FAIL) + .orderByAsc(T::getId) + .last("LIMIT " + batchSize) + .list(); + + if(!events.isEmpty()) + { + lambdaUpdate() + .in(T::getId, events.stream().map(T::getId).toList()) + .setSql(SQL_SET_RETRIES) + .set(T::getSendFlag, SF_SENDING) + .set(T::getLastSendTime, LocalDateTime.now()) + .update(); + } + + return events; + } + + @Override + public boolean markOneEventSendSuccess(Long id) + { + return updateEventSendFlag(id, SF_SEND_SUCC, SF_SENDING); + } + + @Override + public boolean markMultiEventSendSuccess(Collection ids) + { + return updateEventSendFlag(ids, SF_SEND_SUCC, SF_SENDING); + } + + @Override + public boolean markOneEventSendFail(Long id) + { + return updateEventSendFlag(id, SF_SEND_FAIL, SF_SENDING); + } + + @Override + public boolean markMultiEventSendFail(Collection ids) + { + return updateEventSendFlag(ids, SF_SEND_FAIL, SF_SENDING); + } + + @Override + public boolean resetSendingEventToSendFail() + { + return resetSendingEventToSendFail(DEFAULT_COMPENSATE_TIMEOUT, DEFAULT_COMPENSATE_BATCH_SIZE); + } + + @Override + public boolean resetSendingEventToSendFail(int sendTimeout) + { + return resetSendingEventToSendFail(sendTimeout, DEFAULT_COMPENSATE_BATCH_SIZE); + } + + @Override + public boolean resetSendingEventToSendFail(int sendTimeout, int batchSize) + { + List events = lambdaQuery() + .eq(T::getSendFlag, SF_SENDING) + .leSql(T::getLastSendTime, String.format(SQL_PATTERN_LAST_SEND_TIME_LE, sendTimeout)) + .orderByAsc(T::getId) + .last("LIMIT " + batchSize) + .list(); + + if(events.isEmpty()) + return false; + + return lambdaUpdate() + .in(T::getId, events.stream().map(T::getId).toList()) + .set(T::getSendFlag, SF_SEND_FAIL) + .update(); + } + + protected boolean updateEventSendFlag(Long id, int sendFlag, int curSendFlag) + { + LambdaUpdateChainWrapper query = lambdaUpdate().eq(T::getId, id); + + if(curSendFlag >= 0) + query.eq(T::getSendFlag, curSendFlag); + + return query + .set(T::getSendFlag, sendFlag) + .update(); + } + + protected boolean updateEventSendFlag(Collection ids, int sendFlag, int curSendFlag) + { + if(ids.isEmpty()) + return false; + + LambdaUpdateChainWrapper query = lambdaUpdate().in(T::getId, ids); + + if(curSendFlag >= 0) + query.eq(T::getSendFlag, curSendFlag); + + return query + .set(T::getSendFlag, sendFlag) + .update(); + } + + protected boolean updateEventSendFlag(Collection> idSendFlags) + { + Map> sendFlagIds = idSendFlags.stream() + .collect( + Collectors.groupingBy( + Pair::getSecond, + Collectors.mapping( + Pair::getFirst, + Collectors.toCollection(HashSet::new)))); + + return updateEventSendFlag(sendFlagIds); + } + + protected boolean updateEventSendFlag(Map> sendFlagIds) + { + boolean isOK = false; + + for(Entry> entry : sendFlagIds.entrySet()) + { + if(updateEventSendFlag(entry.getValue(), entry.getKey(), -1) && !isOK) + isOK = true; + } + + return isOK; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..683d221 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-rabbitmq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,15 @@ +io.github.hpsocket.soa.starter.rabbitmq.common.config.SoaRabbitmqCommonConfig +io.github.hpsocket.soa.starter.rabbitmq.common.config.SoaDefaultRabbitmqConfig +io.github.hpsocket.soa.starter.rabbitmq.common.config.SoaFirstRabbitmqConfig +io.github.hpsocket.soa.starter.rabbitmq.common.config.SoaSecondRabbitmqConfig +io.github.hpsocket.soa.starter.rabbitmq.common.config.SoaThirdRabbitmqConfig + +io.github.hpsocket.soa.starter.rabbitmq.producer.config.SoaDefaultRabbitmqProducerConfig +io.github.hpsocket.soa.starter.rabbitmq.producer.config.SoaFirstRabbitmqProducerConfig +io.github.hpsocket.soa.starter.rabbitmq.producer.config.SoaSecondRabbitmqProducerConfig +io.github.hpsocket.soa.starter.rabbitmq.producer.config.SoaThirdRabbitmqProducerConfig + +io.github.hpsocket.soa.starter.rabbitmq.consumer.config.SoaDefaultRabbitmqConsumerConfig +io.github.hpsocket.soa.starter.rabbitmq.consumer.config.SoaFirstRabbitmqConsumerConfig +io.github.hpsocket.soa.starter.rabbitmq.consumer.config.SoaSecondRabbitmqConsumerConfig +io.github.hpsocket.soa.starter.rabbitmq.consumer.config.SoaThirdRabbitmqConsumerConfig diff --git a/hp-soa-starter/hp-soa-starter-sentinel/.gitignore b/hp-soa-starter/hp-soa-starter-sentinel/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-sentinel/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-sentinel/pom.xml b/hp-soa-starter/hp-soa-starter-sentinel/pom.xml new file mode 100644 index 0000000..b74786a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-sentinel/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-sentinel + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + com.alibaba.csp + sentinel-apache-dubbo3-adapter + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + com.alibaba.csp + sentinel-datasource-nacos + + + diff --git a/hp-soa-starter/hp-soa-starter-sentinel/src/main/java/io/github/hpsocket/soa/starter/sentinel/advice/SentinelExceptionAdvice.java b/hp-soa-starter/hp-soa-starter-sentinel/src/main/java/io/github/hpsocket/soa/starter/sentinel/advice/SentinelExceptionAdvice.java new file mode 100644 index 0000000..f02fe73 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-sentinel/src/main/java/io/github/hpsocket/soa/starter/sentinel/advice/SentinelExceptionAdvice.java @@ -0,0 +1,82 @@ +package io.github.hpsocket.soa.starter.sentinel.advice; + +import java.lang.reflect.UndeclaredThrowableException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.Ordered; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import io.github.hpsocket.soa.framework.core.exception.ServiceException; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.advice.ControllerGlobalExceptionAdvice; +import io.github.hpsocket.soa.framework.web.model.Response; + +import static io.github.hpsocket.soa.framework.core.exception.ServiceException.*; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** SHTTP 请求 Sentinel 异常拦截器 */ +@Slf4j +@RestControllerAdvice +public class SentinelExceptionAdvice implements Ordered +{ + @Autowired + ControllerGlobalExceptionAdvice globalExceptionAdvice; + + private static final String BLOCK_EXCEPTION_MESSAGE_PREFIX = "SentinelBlockException:"; + + @Override + public int getOrder() + { + return -100; + } + + /** {@linkplain BlockException} 异常处理器 */ + @ExceptionHandler({BlockException.class}) + public Response handleException(HttpServletRequest request, HttpServletResponse response, BlockException e) + { + ServiceException se = wrapServiceException(FORBID_EXCEPTION, e);; + logServiceException(log, se.getMessage(), se); + + return new Response<>(se); + } + + /** {@linkplain UndeclaredThrowableException} 异常处理器 */ + @ExceptionHandler({UndeclaredThrowableException.class}) + public Response handleException(HttpServletRequest request, HttpServletResponse response, UndeclaredThrowableException e) + { + Throwable t = e.getCause(); + + if(t instanceof BlockException) + return handleException(request, response, (BlockException)t); + + return globalExceptionAdvice.handleException(request, response, e); + } + + /** {@linkplain RuntimeException} 异常处理器 */ + @ExceptionHandler({RuntimeException.class}) + public Response handleException(HttpServletRequest request, HttpServletResponse response, RuntimeException e) + { + ServiceException se = null; + String message = e.getMessage(); + + if(GeneralHelper.isStrNotEmpty(message)) + { + if(message.startsWith(BLOCK_EXCEPTION_MESSAGE_PREFIX)) + se = wrapServiceException(FORBID_EXCEPTION, e); + } + + if(se == null) + return globalExceptionAdvice.handleException(request, response, e); + + logServiceException(log, se.getMessage(), se); + + return new Response<>(se); + } + + +} diff --git a/hp-soa-starter/hp-soa-starter-sentinel/src/main/java/io/github/hpsocket/soa/starter/sentinel/config/SoaSentinelConfig.java b/hp-soa-starter/hp-soa-starter-sentinel/src/main/java/io/github/hpsocket/soa/starter/sentinel/config/SoaSentinelConfig.java new file mode 100644 index 0000000..f42e88b --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-sentinel/src/main/java/io/github/hpsocket/soa/starter/sentinel/config/SoaSentinelConfig.java @@ -0,0 +1,95 @@ + +package io.github.hpsocket.soa.starter.sentinel.config; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; +import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.fastjson2.JSON; +import io.github.hpsocket.soa.framework.core.exception.ServiceException; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.model.Response; +import io.github.hpsocket.soa.starter.sentinel.advice.SentinelExceptionAdvice; + +import static io.github.hpsocket.soa.framework.core.exception.ServiceException.*; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** HP-SOA Sentinel 配置 */ +@Slf4j +@AutoConfiguration +@Import(SentinelExceptionAdvice.class) +@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true) +public class SoaSentinelConfig +{ + /** 资源变换器 */ + @Bean + @ConditionalOnMissingBean(UrlCleaner.class) + @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) + public UrlCleaner urlCleaner() + { + final Set suffixSet = new HashSet<>(Arrays.asList(".js", ".css", ".html", ".ico", ".txt", ".md", ".jpg", ".png")); + + return new UrlCleaner() + { + @Override + public String clean(String originUrl) + { + if(GeneralHelper.isStrEmpty(originUrl)) + return originUrl; + + int i = originUrl.lastIndexOf('.'); + + if(i < 0) + return originUrl; + + String suffix = originUrl.substring(i).toLowerCase(); + + if(suffixSet.contains(suffix)) + return null; + + return originUrl; + } + }; + } + + /** 限流处理器 */ + @Bean + @ConditionalOnMissingBean(BlockExceptionHandler.class) + @ConditionalOnProperty(name = "spring.cloud.sentinel.filter.enabled", matchIfMissing = true) + public BlockExceptionHandler blockExceptionHandler() + { + return new BlockExceptionHandler() + { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception + { + ServiceException se = wrapServiceException(FREQUENCY_LIMIT_EXCEPTION, e); + + logServiceException(log, se.getMessage(), se); + + response.setStatus(FREQUENCY_LIMIT_ERROR); + response.setContentType("application/json; charset=utf-8"); + + try(PrintWriter out = response.getWriter()) + { + out.print(JSON.toJSONString(new Response<>(se))); + out.flush(); + } + } + }; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..83f2c8f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.github.hpsocket.soa.starter.sentinel.config.SoaSentinelConfig \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-skywalking/.gitignore b/hp-soa-starter/hp-soa-starter-skywalking/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-skywalking/pom.xml b/hp-soa-starter/hp-soa-starter-skywalking/pom.xml new file mode 100644 index 0000000..468ecbe --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-skywalking + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + io.github.hpsocket + hp-soa-starter-web + provided + + + io.github.hpsocket + hp-soa-starter-task + provided + + + + org.apache.skywalking + apm-toolkit-trace + + + org.apache.skywalking + apm-toolkit-micrometer-registry + + + org.apache.skywalking + apm-toolkit-micrometer-1.10 + + + + diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingAsyncThreadPoolExecutor.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingAsyncThreadPoolExecutor.java new file mode 100644 index 0000000..077a417 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingAsyncThreadPoolExecutor.java @@ -0,0 +1,38 @@ +package io.github.hpsocket.soa.starter.skywalking.async; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import io.github.hpsocket.soa.framework.core.thread.AsyncThreadPoolExecutor; + +/** 异步线程池(注入 {@linkplain org.slf4j.MDC MDC} 和 traceId 调用链跟踪信息) */ +public class TracingAsyncThreadPoolExecutor extends AsyncThreadPoolExecutor +{ + public TracingAsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); + } + + public TracingAsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); + } + + public TracingAsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); + } + + public TracingAsyncThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) + { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); + } + + @Override + protected Runnable decorate(Runnable task) + { + return TracingRunnableWrapper.of(task); + } +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingCallableWrapper.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingCallableWrapper.java new file mode 100644 index 0000000..7015f2e --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingCallableWrapper.java @@ -0,0 +1,47 @@ +package io.github.hpsocket.soa.starter.skywalking.async; + +import java.util.concurrent.Callable; + +import org.apache.skywalking.apm.toolkit.trace.CallableWrapper; +import org.apache.skywalking.apm.toolkit.trace.TraceCrossThread; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +/** {@linkplain Callable} 包装类(注入 {@linkplain org.slf4j.MDC MDC} 和 traceId 调用链跟踪信息) */ +@TraceCrossThread +public class TracingCallableWrapper extends CallableWrapper +{ + private final MdcAttr mdcAttr; + + public TracingCallableWrapper(Callable c) + { + this(c, MdcAttr.fromMdc()); + } + + public TracingCallableWrapper(Callable c, MdcAttr mdcAttr) + { + super(c); + + this.mdcAttr = mdcAttr; + } + + @Override + public V call() throws Exception + { + try + { + mdcAttr.putMdc(); + + return super.call(); + } + finally + { + mdcAttr.removeMdc(); + } + } + + public static TracingCallableWrapper of(Callable r) + { + return new TracingCallableWrapper<>(r); + } +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingConsumerWrapper.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingConsumerWrapper.java new file mode 100644 index 0000000..2d12ab0 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingConsumerWrapper.java @@ -0,0 +1,48 @@ +package io.github.hpsocket.soa.starter.skywalking.async; + +import java.util.function.Consumer; + +import org.apache.skywalking.apm.toolkit.trace.ConsumerWrapper; +import org.apache.skywalking.apm.toolkit.trace.TraceCrossThread; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +/** {@linkplain Consumer} 包装类(注入 {@linkplain org.slf4j.MDC MDC} 和 traceId 调用链跟踪信息) */ +@TraceCrossThread +public class TracingConsumerWrapper extends ConsumerWrapper +{ + private final MdcAttr mdcAttr; + + public TracingConsumerWrapper(Consumer c) + { + this(c, MdcAttr.fromMdc()); + } + + public TracingConsumerWrapper(Consumer c, MdcAttr mdcAttr) + { + super(c); + + this.mdcAttr = mdcAttr; + } + + @Override + public void accept(V v) + { + try + { + mdcAttr.putMdc(); + + super.accept(v); + } + finally + { + mdcAttr.removeMdc(); + } + } + + public static TracingConsumerWrapper of(Consumer c) + { + return new TracingConsumerWrapper<>(c); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingFunctionWrapper.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingFunctionWrapper.java new file mode 100644 index 0000000..82fd675 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingFunctionWrapper.java @@ -0,0 +1,48 @@ +package io.github.hpsocket.soa.starter.skywalking.async; + +import java.util.function.Function; + +import org.apache.skywalking.apm.toolkit.trace.FunctionWrapper; +import org.apache.skywalking.apm.toolkit.trace.TraceCrossThread; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +/** {@linkplain Function} 包装类(注入 {@linkplain org.slf4j.MDC MDC} 和 traceId 调用链跟踪信息) */ +@TraceCrossThread +public class TracingFunctionWrapper extends FunctionWrapper +{ + private final MdcAttr mdcAttr; + + public TracingFunctionWrapper(Function f) + { + this(f, MdcAttr.fromMdc()); + } + + public TracingFunctionWrapper(Function f, MdcAttr mdcAttr) + { + super(f); + + this.mdcAttr = mdcAttr; + } + + @Override + public R apply(T t) + { + try + { + mdcAttr.putMdc(); + + return super.apply(t); + } + finally + { + mdcAttr.removeMdc(); + } + } + + public static TracingFunctionWrapper of(Function f) + { + return new TracingFunctionWrapper<>(f); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingRunnableWrapper.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingRunnableWrapper.java new file mode 100644 index 0000000..4828b99 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingRunnableWrapper.java @@ -0,0 +1,45 @@ +package io.github.hpsocket.soa.starter.skywalking.async; + +import org.apache.skywalking.apm.toolkit.trace.RunnableWrapper; +import org.apache.skywalking.apm.toolkit.trace.TraceCrossThread; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +/** {@linkplain Runnable} 包装类(注入 {@linkplain org.slf4j.MDC MDC} 和 traceId 调用链跟踪信息) */ +@TraceCrossThread +public class TracingRunnableWrapper extends RunnableWrapper +{ + private final MdcAttr mdcAttr; + + public TracingRunnableWrapper(Runnable r) + { + this(r, MdcAttr.fromMdc()); + } + + public TracingRunnableWrapper(Runnable r, MdcAttr mdcAttr) + { + super(r); + + this.mdcAttr = mdcAttr; + } + + @Override + public void run() + { + try + { + mdcAttr.putMdc(); + + super.run(); + } + finally + { + mdcAttr.removeMdc(); + } + } + + public static TracingRunnableWrapper of(Runnable r) + { + return new TracingRunnableWrapper(r); + } +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingSupplierWrapper.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingSupplierWrapper.java new file mode 100644 index 0000000..6f9c4f5 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/async/TracingSupplierWrapper.java @@ -0,0 +1,47 @@ +package io.github.hpsocket.soa.starter.skywalking.async; + +import java.util.function.Supplier; + +import org.apache.skywalking.apm.toolkit.trace.SupplierWrapper; +import org.apache.skywalking.apm.toolkit.trace.TraceCrossThread; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; + +/** {@linkplain Supplier} 包装类(注入 {@linkplain org.slf4j.MDC MDC} 和 traceId 调用链跟踪信息) */ +@TraceCrossThread +public class TracingSupplierWrapper extends SupplierWrapper +{ + private final MdcAttr mdcAttr; + + public TracingSupplierWrapper(Supplier s) + { + this(s, MdcAttr.fromMdc()); + } + + public TracingSupplierWrapper(Supplier s, MdcAttr mdcAttr) + { + super(s); + + this.mdcAttr = mdcAttr; + } + + @Override + public V get() + { + try + { + mdcAttr.putMdc(); + + return super.get(); + } + finally + { + mdcAttr.removeMdc(); + } + } + + public static TracingSupplierWrapper of(Supplier s) + { + return new TracingSupplierWrapper<>(s); + } +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingAsyncConfig.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingAsyncConfig.java new file mode 100644 index 0000000..3d505c6 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingAsyncConfig.java @@ -0,0 +1,51 @@ + +package io.github.hpsocket.soa.starter.skywalking.config; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; + +import io.github.hpsocket.soa.framework.web.propertries.IAsyncProperties; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.skywalking.async.TracingAsyncThreadPoolExecutor; +import io.github.hpsocket.soa.starter.web.config.WebConfig; + +/** HP-SOA Skywalking Async 配置 */ +@AutoConfiguration +@ConditionalOnClass({TraceContext.class, WebConfig.class}) +@AutoConfigureBefore(WebConfig.class) +public class SoaSkyWalkingAsyncConfig +{ + /** 异步线程池(注入 traceId) */ + @Bean("asyncThreadPoolExecutor") + @ConditionalOnProperty(name="hp.soa.web.async.enabled", havingValue="true", matchIfMissing = true) + public TracingAsyncThreadPoolExecutor asyncThreadPoolExecutor(IAsyncProperties asyncProperties) + { + RejectedExecutionHandler rjh = WebServerHelper.parseRejectedExecutionHandler(asyncProperties.getRejectionPolicy(), "CALLER_RUNS"); + BlockingQueue queue = asyncProperties.getQueueCapacity() == 0 + ? new SynchronousQueue<>() + : new LinkedBlockingDeque<>(asyncProperties.getQueueCapacity()); + + TracingAsyncThreadPoolExecutor executor = new TracingAsyncThreadPoolExecutor( + asyncProperties.getCorePoolSize(), + asyncProperties.getMaxPoolSize(), + asyncProperties.getKeepAliveSeconds(), + TimeUnit.SECONDS, + queue, + rjh); + + executor.allowCoreThreadTimeOut(asyncProperties.isAllowCoreThreadTimeOut()); + + return executor; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingConfig.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingConfig.java new file mode 100644 index 0000000..715d284 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingConfig.java @@ -0,0 +1,80 @@ + +package io.github.hpsocket.soa.starter.skywalking.config; + +import java.util.List; + +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.skywalking.apm.meter.micrometer.SkywalkingMeterRegistry; +import org.apache.skywalking.apm.toolkit.micrometer.observation.SkywalkingDefaultTracingHandler; +import org.apache.skywalking.apm.toolkit.micrometer.observation.SkywalkingMeterHandler; +import org.apache.skywalking.apm.toolkit.micrometer.observation.SkywalkingReceiverTracingHandler; +import org.apache.skywalking.apm.toolkit.micrometer.observation.SkywalkingSenderTracingHandler; +import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.service.TracingContext; + +import io.micrometer.core.instrument.observation.MeterObservationHandler; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.observation.ObservationRegistry; + +/** HP-SOA Skywalking 基本配置 */ +@AutoConfiguration +@ConditionalOnClass(TraceContext.class) +public class SoaSkyWalkingConfig +{ + /** 调用链上下文服务 */ + @Bean + public TracingContext tracingContext() + { + return new TracingContext() { + @Override + public String getTraceId() + { + return TraceContext.traceId(); + } + + @Override + public String getSpanId() + { + String segmentId = TraceContext.segmentId(); + + if(GeneralHelper.isStrEmpty(segmentId)) + return null; + + return (segmentId + '#' + TraceContext.spanId()); + } + }; + } + + @Bean + ObservationRegistry observationRegistry(List> handlers) + { + ObservationRegistry registry = ObservationRegistry.create(); + + registry.observationConfig().observationHandler( + new ObservationHandler.FirstMatchingCompositeObservationHandler( + new SkywalkingMeterHandler(new SkywalkingMeterRegistry()))); + registry.observationConfig().observationHandler( + new ObservationHandler.FirstMatchingCompositeObservationHandler(handlers)); + registry.observationConfig().observationHandler( + new ObservationHandler.FirstMatchingCompositeObservationHandler( + new SkywalkingSenderTracingHandler(), + new SkywalkingReceiverTracingHandler(), + new SkywalkingDefaultTracingHandler())); + + return registry; + } + + @Bean + ApplicationModel applicationModel(ObservationRegistry observationRegistry) + { + ApplicationModel applicationModel = ApplicationModel.defaultModel(); + applicationModel.getBeanFactory().registerBean(observationRegistry); + + return applicationModel; + } +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingTaskConfig.java b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingTaskConfig.java new file mode 100644 index 0000000..0d19fb5 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/java/io/github/hpsocket/soa/starter/skywalking/config/SoaSkyWalkingTaskConfig.java @@ -0,0 +1,34 @@ + +package io.github.hpsocket.soa.starter.skywalking.config; + +import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.core.task.TaskDecorator; + +import io.github.hpsocket.soa.starter.skywalking.async.TracingRunnableWrapper; +import io.github.hpsocket.soa.starter.task.config.SoaTaskConfig; + +/** HP-SOA Skywalking Task 配置 */ +@AutoConfiguration +@ConditionalOnClass({TraceContext.class, SoaTaskConfig.class}) +@AutoConfigureBefore(SoaTaskConfig.class) +public class SoaSkyWalkingTaskConfig +{ + /** Task 任务装饰器(注入 {@linkplain org.slf4j.MDC MDC} 和 traceId 调用链跟踪信息)*/ + @Bean("mdcTaskDecorator") + TaskDecorator taskDecorator() + { + return new TaskDecorator() + { + @Override + public Runnable decorate(Runnable runnable) + { + return TracingRunnableWrapper.of(runnable); + } + }; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-skywalking/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-skywalking/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..163fca1 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-skywalking/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +io.github.hpsocket.soa.starter.skywalking.config.SoaSkyWalkingConfig +io.github.hpsocket.soa.starter.skywalking.config.SoaSkyWalkingAsyncConfig +io.github.hpsocket.soa.starter.skywalking.config.SoaSkyWalkingTaskConfig \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-task/.gitignore b/hp-soa-starter/hp-soa-starter-task/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-task/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-task/pom.xml b/hp-soa-starter/hp-soa-starter-task/pom.xml new file mode 100644 index 0000000..5203e10 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-task/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-task + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + diff --git a/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/aspect/ScheduledTraceIdInspector.java b/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/aspect/ScheduledTraceIdInspector.java new file mode 100644 index 0000000..b8b7d97 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/aspect/ScheduledTraceIdInspector.java @@ -0,0 +1,55 @@ +package io.github.hpsocket.soa.starter.task.aspect; + +import org.apache.logging.log4j.core.config.Order; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.scheduling.annotation.Scheduled; + +import io.github.hpsocket.soa.framework.web.support.AspectHelper; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; + +import lombok.extern.slf4j.Slf4j; + +/** {@linkplain Scheduled} traceId 拦截器
+ * 为 {@linkplain Scheduled} 注解的方法注入 traceId 调用链跟踪信息 + */ +@Slf4j +@Aspect +@Order(Integer.MIN_VALUE) +public class ScheduledTraceIdInspector +{ + private static final String POINTCUT_PATTERN = "execution (public void *.*()) && " + + "@annotation(org.springframework.scheduling.annotation.Scheduled)"; + + @Pointcut(POINTCUT_PATTERN) + protected void aroundMethod() {} + + @Around(value = "aroundMethod()") + public Object inspect(ProceedingJoinPoint joinPoint) throws Throwable + { + WebServerHelper.putMdcTraceId(); + + if(WebServerHelper.isAppReadOnly()) + { + String methodName = AspectHelper.getMethod(joinPoint).getName(); + String msg = String.format("current application is read only, skip scheduled task '%s'", methodName); + + log.debug(msg); + + return null; + } + + try + { + return joinPoint.proceed(); + } + finally + { + WebServerHelper.removeMdcTraceId(); + } + } + +} + diff --git a/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/config/SoaTaskConfig.java b/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/config/SoaTaskConfig.java new file mode 100644 index 0000000..bf6e58a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/config/SoaTaskConfig.java @@ -0,0 +1,190 @@ + +package io.github.hpsocket.soa.starter.task.config; + +import java.util.concurrent.Callable; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.task.TaskExecutorCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.core.task.TaskDecorator; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import io.github.hpsocket.soa.framework.core.mdc.MdcAttr; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.task.aspect.ScheduledTraceIdInspector; +import io.github.hpsocket.soa.starter.task.properties.SoaTaskProperties; +import io.github.hpsocket.soa.starter.task.properties.SoaTaskProperties.Scheduling; + +/** HP-SOA Task 配置 */ +@EnableAsync +@EnableScheduling +@AutoConfiguration +@Import(ScheduledTraceIdInspector.class) +@EnableConfigurationProperties({SoaTaskProperties.class}) +@ConditionalOnProperty(name = "spring.task.enabled", matchIfMissing = true) +public class SoaTaskConfig +{ + private SoaTaskProperties soaTaskProperties; + + public SoaTaskConfig(SoaTaskProperties soaTaskProperties) + { + this.soaTaskProperties = soaTaskProperties; + } + + /** Task 任务装饰器(注入 {@linkplain org.slf4j.MDC MDC} 调用链跟踪信息)*/ + @Bean("mdcTaskDecorator") + @ConditionalOnMissingBean(name = "mdcTaskDecorator") + TaskDecorator taskDecorator() + { + return new TaskDecorator() + { + @Override + public Runnable decorate(Runnable runnable) + { + MdcAttr mdcAttr = MdcAttr.fromMdc(); + + return new Runnable() + { + @Override + public void run() + { + try + { + mdcAttr.putMdc(); + runnable.run(); + } + finally + { + mdcAttr.removeMdc(); + } + } + }; + } + }; + } + + @Bean + TaskExecutorCustomizer taskExecutorCustomizer() + { + return new TaskExecutorCustomizer() + { + @Override + public void customize(ThreadPoolTaskExecutor taskExecutor) + { + String executionRejectionPolicy = soaTaskProperties.getExecution().getPool().getRejectionPolicy(); + RejectedExecutionHandler rjh = WebServerHelper.parseRejectedExecutionHandler(executionRejectionPolicy, "CALLER_RUNS"); + taskExecutor.setRejectedExecutionHandler(rjh); + } + }; + } + + @Bean + @SuppressWarnings("serial") + ThreadPoolTaskScheduler taskScheduler() + { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler() + { + @Override + protected ScheduledExecutorService createExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) + { + return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler) + { + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) + { + return super.schedule(wrapCommand(command), delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) + { + return super.schedule(wrapCommand(callable), delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) + { + return super.scheduleAtFixedRate(wrapCommand(command), initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) + { + return super.scheduleWithFixedDelay(wrapCommand(command), initialDelay, delay, unit); + } + + private Runnable wrapCommand(Runnable r) + { + return new Runnable() + { + @Override + public void run() + { + MdcAttr mdcAttr = WebServerHelper.createMdcAttr(); + + try + { + mdcAttr.putMdc(); + r.run(); + } + finally + { + mdcAttr.removeMdc(); + } + } + }; + } + + private Callable wrapCommand(Callable t) + { + return new Callable<>() + { + @Override + public T call() throws Exception + { + MdcAttr mdcAttr = WebServerHelper.createMdcAttr(); + + try + { + mdcAttr.putMdc(); + return t.call(); + } + finally + { + mdcAttr.removeMdc(); + } + } + }; + } + }; + } + }; + + Scheduling scheduling = soaTaskProperties.getScheduling(); + + scheduler.setThreadNamePrefix(scheduling.getThreadNamePrefix()); + scheduler.setPoolSize(scheduling.getPool().getSize()); + scheduler.setWaitForTasksToCompleteOnShutdown(scheduling.getShutdown().isAwaitTermination()); + scheduler.setAwaitTerminationMillis(scheduling.getShutdown().getAwaitTerminationPeriod()); + + String schedulingRejectionPolicy = scheduling.getPool().getRejectionPolicy(); + RejectedExecutionHandler rjh = WebServerHelper.parseRejectedExecutionHandler(schedulingRejectionPolicy, "ABORT"); + scheduler.setRejectedExecutionHandler(rjh); + + return scheduler; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/properties/SoaTaskProperties.java b/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/properties/SoaTaskProperties.java new file mode 100644 index 0000000..7b51eba --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-task/src/main/java/io/github/hpsocket/soa/starter/task/properties/SoaTaskProperties.java @@ -0,0 +1,79 @@ +package io.github.hpsocket.soa.starter.task.properties; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@ConfigurationProperties(prefix = "spring.task") +@ConditionalOnProperty(name = "spring.task.enabled", matchIfMissing = true) +public class SoaTaskProperties +{ + private boolean enabled = true; + + @NestedConfigurationProperty + Execution execution = new Execution(); + @NestedConfigurationProperty + Scheduling scheduling = new Scheduling(); + + + @Getter + @Setter + public static class Execution + { + private String threadNamePrefix = "task-"; + + private Pool pool = new Pool(); + private Shutdown shutdown = new Shutdown(); + + @Getter + @Setter + public static class Pool + { + private int coreSize = 8; + private int maxSize = 24; + private int queueCapacity = 1000; + private int keepAlive = 60; + private boolean allowCoreThreadTimeout = true; + private String rejectionPolicy = "CALLER_RUNS"; + } + + @Getter + @Setter + public static class Shutdown + { + private boolean awaitTermination = false; + private int awaitTerminationPeriod = 5; + } + } + + @Getter + @Setter + public static class Scheduling + { + private String threadNamePrefix = "scheduling-"; + + private Pool pool = new Pool(); + private Shutdown shutdown = new Shutdown(); + + @Getter + @Setter + public static class Pool + { + private int size = 1; + private String rejectionPolicy = "ABORT"; + } + + @Getter + @Setter + public static class Shutdown + { + private boolean awaitTermination = false; + private int awaitTerminationPeriod = 5; + } + } +} diff --git a/hp-soa-starter/hp-soa-starter-task/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-task/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..0db5489 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-task/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +io.github.hpsocket.soa.starter.task.config.SoaTaskConfig \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-web/.gitignore b/hp-soa-starter/hp-soa-starter-web/.gitignore new file mode 100644 index 0000000..1ee8e9f --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/.gitignore @@ -0,0 +1,5 @@ +.classpath +.project +.flattened-pom.xml +.settings +/target diff --git a/hp-soa-starter/hp-soa-starter-web/pom.xml b/hp-soa-starter/hp-soa-starter-web/pom.xml new file mode 100644 index 0000000..26cb272 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-starter + ${revision} + + hp-soa-starter-web + ${project.artifactId} + jar + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + + io.github.hpsocket + hp-soa-framework-web + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/AspectAopConfig.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/AspectAopConfig.java new file mode 100644 index 0000000..29cc7be --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/AspectAopConfig.java @@ -0,0 +1,26 @@ +package io.github.hpsocket.soa.starter.web.config; + +import io.github.hpsocket.soa.framework.web.aspect.AccessVerificationInspector; +import io.github.hpsocket.soa.framework.web.aspect.SiteLocalInspector; +import io.github.hpsocket.soa.framework.web.propertries.IAccessVerificationProperties; +import io.github.hpsocket.soa.framework.web.service.AccessVerificationService; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** HP-SOA Web Aspect 配置 */ +@AutoConfiguration +@Import({ + SiteLocalInspector.class + }) +public class AspectAopConfig +{ + @Bean + @ConditionalOnProperty(name="hp.soa.web.access-verification.enabled", havingValue="true", matchIfMissing = true) + AccessVerificationInspector accessVerificationInspector(IAccessVerificationProperties accessVerificationProperties, AccessVerificationService accessVerificationService) + { + return new AccessVerificationInspector(accessVerificationProperties.getDefaultAccessPolicyEnum(), accessVerificationService); + } +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/ControllerAdviceConfig.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/ControllerAdviceConfig.java new file mode 100644 index 0000000..56a3f29 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/ControllerAdviceConfig.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.soa.starter.web.config; + +import io.github.hpsocket.soa.framework.web.advice.ControllerGlobalExceptionAdvice; +import io.github.hpsocket.soa.framework.web.advice.ControllerRequestAdvice; +import io.github.hpsocket.soa.framework.web.advice.ControllerResponseAdvice; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Import; + +/** HP-SOA Web Controller Advice 配置 */ +@AutoConfiguration +@Import ({ + ControllerRequestAdvice.class, + ControllerResponseAdvice.class, + ControllerGlobalExceptionAdvice.class + }) +public class ControllerAdviceConfig +{ + +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/SwaggerConfig.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/SwaggerConfig.java new file mode 100644 index 0000000..ad23092 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/SwaggerConfig.java @@ -0,0 +1,114 @@ + +package io.github.hpsocket.soa.starter.web.config; + +import io.github.hpsocket.soa.starter.web.properties.SwaggerProperties; + +import java.lang.reflect.Field; +import java.util.List; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.config.annotation.InterceptorRegistration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import static io.github.hpsocket.soa.framework.web.support.WebServerHelper.*; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.ExternalDocumentation; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.parameters.HeaderParameter; + +/** HP-SOA Web Swagger 配置 */ +@AutoConfiguration +@EnableConfigurationProperties(SwaggerProperties.class) +@ConditionalOnProperty(name="springdoc.api-docs.enabled", havingValue="true", matchIfMissing = false) +public class SwaggerConfig implements WebMvcConfigurer +{ + private static final String SWAGGER_UI_REDIRECT_PATH = "/swagger-ui"; + + @Value("${springdoc.api-docs.path:/v3/api-docs}") + String apiDocPath; + @Value("${springdoc.swagger-ui.path:/swagger-ui.html}") + String swaggerUiPath; + + private final SwaggerProperties swaggerProperties; + + public SwaggerConfig(SwaggerProperties swaggerProperties) + { + this.swaggerProperties = swaggerProperties; + } + + @Bean + @ConditionalOnMissingBean(OpenAPI.class) + public OpenAPI springDocOpenAPI() + { + return new OpenAPI() + .info(new Info() + .title(swaggerProperties.getTitle()) + .description(swaggerProperties.getDescription()) + .version(swaggerProperties.getVersion()) + ) + .components(new Components() + + ) + .externalDocs(new ExternalDocumentation() + .description("SpringDoc Documentation") + .url("https://springdoc.org") + ); + } + + @Bean + @ConditionalOnMissingBean(OpenApiCustomizer.class) + public OpenApiCustomizer customerGlobalHeaderOpenApiCustomiser() + { + return openApi -> openApi.getPaths().values().stream().flatMap(pathItem -> pathItem.readOperations().stream()) + .forEach(operation -> operation + .addParametersItem(new HeaderParameter().name(HEADER_REQUEST_INFO).required(false).example("appCode=; token=; groupId=; requestId=; clientId=; sessionId=; srcAppCode=; version=; extra=")) + .addParametersItem(new HeaderParameter().name(HEADER_APP_CODE).required(false).example("100")) + .addParametersItem(new HeaderParameter().name(HEADER_TOKEN).required(false).example("2d2fc1d30cffa1cf185cfeed9037b658")) + .addParametersItem(new HeaderParameter().name(HEADER_GROUP_ID).required(false).example("123456")) + .addParametersItem(new HeaderParameter().name(HEADER_REQUEST_ID).required(false).example("")) + .addParametersItem(new HeaderParameter().name(HEADER_CLIENT_ID).required(false).example("")) + .addParametersItem(new HeaderParameter().name(HEADER_SESSION_ID).required(false).example("")) + .addParametersItem(new HeaderParameter().name(HEADER_SRC_APP_CODE).required(false).example("")) + .addParametersItem(new HeaderParameter().name(HEADER_VERSION).required(false).example("")) + .addParametersItem(new HeaderParameter().name(HEADER_EXTRA).required(false).example("")) + ); + } + + @Override + @SuppressWarnings("unchecked") + public void addInterceptors(InterceptorRegistry registry) + { + try + { + Field registrationsField = FieldUtils.getField(InterceptorRegistry.class, "registrations", true); + List registrations = (List)ReflectionUtils.getField(registrationsField, registry); + + if(registrations != null) + { + for(InterceptorRegistration interceptorRegistration : registrations) + { + interceptorRegistration + .excludePathPatterns(SWAGGER_UI_REDIRECT_PATH + "/**") + .excludePathPatterns(swaggerUiPath) + .excludePathPatterns(apiDocPath) + .excludePathPatterns(apiDocPath + "/**"); + } + } + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/WebConfig.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/WebConfig.java new file mode 100644 index 0000000..29a3206 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/config/WebConfig.java @@ -0,0 +1,236 @@ + +package io.github.hpsocket.soa.starter.web.config; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.DependsOn; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.web.servlet.config.annotation.CorsRegistration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.alibaba.fastjson2.support.config.FastJsonConfig; +import com.alibaba.fastjson2.support.spring6.http.converter.FastJsonHttpMessageConverter; +import io.github.hpsocket.soa.framework.core.thread.AsyncThreadPoolExecutor; +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.filter.HttpMdcFilter; +import io.github.hpsocket.soa.framework.web.holder.AppConfigHolder; +import io.github.hpsocket.soa.framework.web.holder.SpringContextHolder; +import io.github.hpsocket.soa.framework.web.json.FastJsonExcludePropertyFilter; +import io.github.hpsocket.soa.framework.web.listener.ReadOnlyContextRefreshedEventListener; +import io.github.hpsocket.soa.framework.web.listener.ReadOnlyRefreshEventListener; +import io.github.hpsocket.soa.framework.web.propertries.IAsyncProperties; +import io.github.hpsocket.soa.framework.web.service.AsyncService; +import io.github.hpsocket.soa.framework.web.service.impl.AsyncServiceImpl; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; +import io.github.hpsocket.soa.starter.web.properties.SecurityProperties; +import io.github.hpsocket.soa.starter.web.properties.WebProperties; +import io.github.hpsocket.soa.starter.web.properties.WebProperties.AppProperties; + +/** HP-SOA Web 基础配置 */ +@AutoConfiguration +@EnableConfigurationProperties({WebProperties.class, SecurityProperties.class}) +public class WebConfig implements WebMvcConfigurer +{ + private final WebProperties webProperties; + private final SecurityProperties securityProperties; + + public WebConfig(WebProperties webProperties, SecurityProperties securityProperties) + { + AppProperties app = webProperties.getApp(); + + if(GeneralHelper.isStrEmpty(app.getId()) || GeneralHelper.isStrEmpty(app.getName())) + throw new RuntimeException(String.format("({}) init fail -> 'hp.soa.web.app.id' or 'hp.soa.web.app.name' property is empty", WebConfig.class.getSimpleName())); + + this.webProperties = webProperties; + this.securityProperties = securityProperties; + + AppConfigHolder.init(webProperties, securityProperties); + } + + /** {@linkplain SpringContextHolder} Spring 上下文持有者配置 */ + @Bean("springContextHolder") + public SpringContextHolder springContextHolder() + { + return new SpringContextHolder(); + } + + /** {@linkplain ReadOnlyContextRefreshedEventListener} 应用程序监听器配置 */ + @Bean("readOnlyContextRefreshedEventListener") + @DependsOn("springContextHolder") + public ReadOnlyContextRefreshedEventListener readOnlyContextRefreshedEventListener() + { + return new ReadOnlyContextRefreshedEventListener(); + } + + /** {@linkplain ReadOnlyRefreshEventListener} 应用程序监听器配置 */ + @Bean("readOnlyRefreshEventListener") + @DependsOn("springContextHolder") + public ReadOnlyRefreshEventListener readOnlyRefreshEventListener() + { + return new ReadOnlyRefreshEventListener(); + } + + /** {@linkplain HttpMdcFilter} 过滤器配置 */ + @Bean + public FilterRegistrationBean mdcTracingFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new HttpMdcFilter()); + registration.setName(HttpMdcFilter.DISPLAY_NAME); + registration.addUrlPatterns(HttpMdcFilter.URL_PATTERNS); + registration.setOrder(HttpMdcFilter.ORDER); + registration.setEnabled(true); + + return registration; + } + + /** {@linkplain SecurityFilterChain} 安全过滤器链配置 */ + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception + { + String endpointsWebBasePath = securityProperties.getManagementEndpointsBasePath(); + + if(GeneralHelper.isStrEmpty(endpointsWebBasePath) || endpointsWebBasePath.equals("/")) + http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests.anyRequest().permitAll()); + else + { + String servletContextPath = securityProperties.getServletContextPath(); + String mvcServletPath = securityProperties.getSpringMvcServletPath(); + + StringBuilder sb = new StringBuilder(); + + if(GeneralHelper.isStrNotEmpty(servletContextPath) && !servletContextPath.equals("/")) + sb.append(servletContextPath); + if(GeneralHelper.isStrNotEmpty(mvcServletPath) && !mvcServletPath.equals("/")) + sb.append(mvcServletPath); + + sb.append(endpointsWebBasePath); + + String managementBasePath = sb.append(SecurityProperties.ANY_PATH_PATTERN).toString(); + + http + .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests + .requestMatchers(AntPathRequestMatcher.antMatcher(managementBasePath)).authenticated() + .requestMatchers(AntPathRequestMatcher.antMatcher(SecurityProperties.ANY_PATH_PATTERN)).permitAll()) + .formLogin(Customizer.withDefaults()) + .httpBasic(Customizer.withDefaults()); + } + + http.csrf((csrf) -> csrf.disable()); + + return http.build(); + } + + /** {@linkplain WebSecurityCustomizer} Web 安全定制器配置 */ + @Bean + public WebSecurityCustomizer webSecurityCustomizer() + { + return ((web) -> {}); + } + + /** {@linkplain AsyncThreadPoolExecutor} 异步线程池配置 */ + @Bean("asyncThreadPoolExecutor") + @ConditionalOnMissingBean(name = "asyncThreadPoolExecutor") + @ConditionalOnProperty(name="hp.soa.web.async.enabled", havingValue="true", matchIfMissing = true) + public AsyncThreadPoolExecutor asyncThreadPoolExecutor(IAsyncProperties asyncProperties) + { + RejectedExecutionHandler rjh = WebServerHelper.parseRejectedExecutionHandler(asyncProperties.getRejectionPolicy(), "CALLER_RUNS"); + BlockingQueue queue = asyncProperties.getQueueCapacity() == 0 + ? new SynchronousQueue<>() + : new LinkedBlockingDeque<>(asyncProperties.getQueueCapacity()); + + AsyncThreadPoolExecutor executor = new AsyncThreadPoolExecutor( + asyncProperties.getCorePoolSize(), + asyncProperties.getMaxPoolSize(), + asyncProperties.getKeepAliveSeconds(), + TimeUnit.SECONDS, + queue, + rjh); + + executor.allowCoreThreadTimeOut(asyncProperties.isAllowCoreThreadTimeOut()); + + return executor; + } + + /** {@linkplain AsyncService} 异步服务配置 */ + @Bean + @ConditionalOnProperty(name="hp.soa.web.async.enabled", havingValue="true", matchIfMissing = true) + public AsyncService asyncService(AsyncThreadPoolExecutor asyncThreadPoolExecutor) + { + return new AsyncServiceImpl(asyncThreadPoolExecutor); + } + + /** HTTP 请求报文转换器配置 */ + @Override + public void extendMessageConverters(List> converters) + { + int i = 0; + List mediaTypes = null; + + for(; i < converters.size(); i++) + { + if(converters.get(i) instanceof MappingJackson2HttpMessageConverter jackson2Converter) + { + mediaTypes = jackson2Converter.getSupportedMediaTypes(); + break; + } + } + + if(mediaTypes == null) + mediaTypes = Arrays.asList(MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); + + FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); + converter.setSupportedMediaTypes(mediaTypes); + + FastJsonConfig config = converter.getFastJsonConfig(); + config.setWriterFeatures(WebServerHelper.JSON_SERIAL_FEATURES_DEFAULT); + config.setWriterFilters(new FastJsonExcludePropertyFilter()); + config.setJSONB(true); + + converters.add(i, converter); + + WebMvcConfigurer.super.extendMessageConverters(converters); + } + + /** 跨域配置 */ + @Override + public void addCorsMappings(CorsRegistry registry) + { + WebProperties.CorsProperties crosProperties = webProperties.getCors(); + + CorsRegistration mapping = registry.addMapping(crosProperties.getMapping()); + + mapping + .allowedOrigins(crosProperties.getAllowedOrigins()) + .allowedHeaders(crosProperties.getAllowedHeaders()) + .allowedMethods(crosProperties.getAllowedMethods()) + .allowCredentials(crosProperties.isAllowCredentials()) + .maxAge(crosProperties.getMaxAge()); + + String[] exposedHeaders = crosProperties.getExposedHeaders(); + + if(exposedHeaders != null && exposedHeaders.length > 0 && !GeneralHelper.isStrEmpty(exposedHeaders[0])) + mapping.exposedHeaders(exposedHeaders); + } + +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/processor/ExternalPropertiesFilePostProcessor.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/processor/ExternalPropertiesFilePostProcessor.java new file mode 100644 index 0000000..1426fa7 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/processor/ExternalPropertiesFilePostProcessor.java @@ -0,0 +1,85 @@ +package io.github.hpsocket.soa.starter.web.processor; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.dubbo.config.spring.context.event.DubboConfigInitEvent; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.logging.DeferredLog; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.DefaultPropertySourceFactory; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; +import org.springframework.stereotype.Component; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; + +/** HP-SOA 外部属性文件加载器
+ *
    + *
  • 加载由 {@linkplain #EXTERNAL_PROPERTIES_FILE_KEY} JVM 启动参数指定的外部属性配置文件
  • + *
+ */ +@Component +public class ExternalPropertiesFilePostProcessor implements EnvironmentPostProcessor, Ordered, ApplicationListener +{ + public static final String EXTERNAL_PROPERTIES_FILE_KEY = "hp.soa.external.properties.file"; + + private static final DeferredLog logger = new DeferredLog(); + private static AtomicBoolean processed = new AtomicBoolean(); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) + { + if(!processed.compareAndSet(false, true)) + return; + + String filePath = environment.getProperty(EXTERNAL_PROPERTIES_FILE_KEY); + + if(GeneralHelper.isStrEmpty(filePath)) + { + logger.info("hp-soa not uses external properties file"); + return; + } + + logger.info("load hp-soa external properties file -> " + filePath); + + String resolvedLocation = "file:" + environment.resolveRequiredPlaceholders(filePath); + PropertySourceFactory factory = new DefaultPropertySourceFactory(); + Resource resource = new DefaultResourceLoader().getResource(resolvedLocation); + + try + { + PropertySource propertySource = factory.createPropertySource(filePath, new EncodedResource(resource)); + MutablePropertySources propertySources = environment.getPropertySources(); + propertySources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, propertySource); + } + catch(IOException e) + { + String msg = String.format("load hp-soa external properties file fail -> [%s] %s", filePath, e.getMessage()); + + logger.error(msg, e); + throw new RuntimeException(msg, e); + } + } + + @Override + public void onApplicationEvent(DubboConfigInitEvent event) + { + logger.replayTo(getClass()); + } + + @Override + public int getOrder() + { + return Ordered.HIGHEST_PRECEDENCE + 3; + } + +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/SecurityProperties.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/SecurityProperties.java new file mode 100644 index 0000000..171c640 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/SecurityProperties.java @@ -0,0 +1,29 @@ +package io.github.hpsocket.soa.starter.web.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import io.github.hpsocket.soa.framework.web.propertries.IServletPathsPropertries; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@ConfigurationProperties +public class SecurityProperties implements IServletPathsPropertries +{ + public static final String ANY_PATH_PATTERN = "/**"; + + @Value("${server.servlet.context-path:/}") + private String servletContextPath; + @Value("${spring.mvc.servlet.path:/}") + private String springMvcServletPath; + @Value("${management.endpoints.web.base-path:/actuator}") + private String managementEndpointsBasePath; + @Value ("${springdoc.api-docs.path:/v3/api-docs}") + private String springdocApiDocsPath; + @Value ("${springdoc.swagger-ui.path:/swagger-ui}") + private String springdocSwaggerUiPath; + +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/SwaggerProperties.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/SwaggerProperties.java new file mode 100644 index 0000000..865164a --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/SwaggerProperties.java @@ -0,0 +1,20 @@ +package io.github.hpsocket.soa.starter.web.properties; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@ConfigurationProperties(prefix = "springdoc.api-infos") +@ConditionalOnProperty(name="springdoc.api-docs.enabled", havingValue="true", matchIfMissing = false) +public class SwaggerProperties +{ + private String groupName = "HP-Socket"; + private String title; + private String description; + private String version; + +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/WebProperties.java b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/WebProperties.java new file mode 100644 index 0000000..0e65050 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/java/io/github/hpsocket/soa/starter/web/properties/WebProperties.java @@ -0,0 +1,182 @@ +package io.github.hpsocket.soa.starter.web.properties; + +import io.github.hpsocket.soa.framework.core.util.GeneralHelper; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification; +import io.github.hpsocket.soa.framework.web.annotation.AccessVerification.Type; +import io.github.hpsocket.soa.framework.web.propertries.IAccessVerificationProperties; +import io.github.hpsocket.soa.framework.web.propertries.IAppProperties; +import io.github.hpsocket.soa.framework.web.propertries.IAsyncProperties; +import io.github.hpsocket.soa.framework.web.support.WebServerHelper; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@ConfigurationProperties(prefix = "hp.soa.web") +public class WebProperties implements IAppProperties, IAsyncProperties, IAccessVerificationProperties +{ + @NestedConfigurationProperty + private AppProperties app = new AppProperties(); + @NestedConfigurationProperty + private AsyncProperties async = new AsyncProperties(); + @NestedConfigurationProperty + private CookieProperties cookie = new CookieProperties(); + @NestedConfigurationProperty + private CorsProperties cors = new CorsProperties(); + @NestedConfigurationProperty + private AccessVerificationProperties accessVerification = new AccessVerificationProperties(); + + @Getter + @Setter + public static class AppProperties + { + private String id; + private String name; + private String version; + private String organization; + private String owner; + private boolean readOnly; + } + + @Getter + @Setter + public static class AsyncProperties + { + private boolean enabled = true; + private int corePoolSize = 4; + private int maxPoolSize = 16; + private int keepAliveSeconds = 30; + private int queueCapacity = 2000; + private String rejectionPolicy = "CALLER_RUNS"; + private boolean allowCoreThreadTimeOut = true; + } + + @Getter + @Setter + public static class CookieProperties + { + private int maxAge = WebServerHelper.DEFAULT_COOKIE_MAX_AGE; + } + + @Getter + @Setter + public static class CorsProperties + { + private String mapping = "/**"; + private String[] allowedOrigins = {"*"}; + private String[] allowedHeaders = {"*"}; + private String[] allowedMethods = {"*"}; + private String[] exposedHeaders = {}; + private boolean allowCredentials = true; + private int maxAge = 3600; + } + + public static class AccessVerificationProperties + { + @Getter + @Setter + private boolean enabled = true; + + @Getter + private AccessVerification.Type defaultAccessPolicyEnum = AccessVerification.Type.MAYBE_LOGIN; + + public void setDefaultAccessPolicy(String defaultAccessPolicy) + { + if(GeneralHelper.isStrNotEmpty(defaultAccessPolicy)) + { + defaultAccessPolicyEnum = GeneralHelper.enumLookup(AccessVerification.Type.class, defaultAccessPolicy, true); + + if(defaultAccessPolicyEnum == null) + throw new RuntimeException(String.format("invalid config value for property 'hp.soa.web.access.default-accessVerification-policy' -> '%s'", defaultAccessPolicy)); + } + } + } + + @Override + public Type getDefaultAccessPolicyEnum() + { + return accessVerification.getDefaultAccessPolicyEnum(); + } + + @Override + public String getId() + { + return app.getId(); + } + + @Override + public String getName() + { + return app.getName(); + } + + @Override + public String getVersion() + { + return app.getVersion(); + } + + @Override + public String getOrganization() + { + return app.getOrganization(); + } + + @Override + public String getOwner() + { + return app.getOwner(); + } + + @Override + public boolean isReadOnly() + { + return app.isReadOnly(); + } + + @Override + public int getCookieMaxAge() + { + return cookie.getMaxAge(); + } + + @Override + public int getCorePoolSize() + { + return async.getCorePoolSize(); + } + + @Override + public int getMaxPoolSize() + { + return async.getMaxPoolSize(); + } + + @Override + public int getKeepAliveSeconds() + { + return async.getKeepAliveSeconds(); + } + + @Override + public int getQueueCapacity() + { + return async.getQueueCapacity(); + } + + @Override + public String getRejectionPolicy() + { + return async.getRejectionPolicy(); + } + + @Override + public boolean isAllowCoreThreadTimeOut() + { + return async.isAllowCoreThreadTimeOut(); + } +} diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/resources/META-INF/spring.factories b/hp-soa-starter/hp-soa-starter-web/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..dca2545 --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.env.EnvironmentPostProcessor=io.github.hpsocket.soa.starter.web.processor.ExternalPropertiesFilePostProcessor \ No newline at end of file diff --git a/hp-soa-starter/hp-soa-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hp-soa-starter/hp-soa-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..d7be9fb --- /dev/null +++ b/hp-soa-starter/hp-soa-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,4 @@ +io.github.hpsocket.soa.starter.web.config.WebConfig +io.github.hpsocket.soa.starter.web.config.SwaggerConfig +io.github.hpsocket.soa.starter.web.config.ControllerAdviceConfig +io.github.hpsocket.soa.starter.web.config.AspectAopConfig diff --git a/hp-soa-starter/pom.xml b/hp-soa-starter/pom.xml new file mode 100644 index 0000000..d9b2092 --- /dev/null +++ b/hp-soa-starter/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + io.github.hpsocket + hp-soa-parent + ${revision} + + hp-soa-starter + ${project.artifactId} + pom + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + + + + hp-soa-starter-web + hp-soa-starter-task + hp-soa-starter-nacos + hp-soa-starter-sentinel + hp-soa-starter-skywalking + hp-soa-starter-data-redis + hp-soa-starter-data-mysql + hp-soa-starter-job-exclusive + hp-soa-starter-job-xxljob + hp-soa-starter-rabbitmq + + + + + + io.github.hpsocket + hp-soa-dependencies + ${project.parent.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-configuration-processor + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-install-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + + + diff --git a/misc/conf/application.yml b/misc/conf/application.yml new file mode 100644 index 0000000..14ef671 --- /dev/null +++ b/misc/conf/application.yml @@ -0,0 +1 @@ +## ---------------------------- Environment-independent application private configurations ---------------------------- ## diff --git a/misc/conf/bootstrap.yml b/misc/conf/bootstrap.yml new file mode 100644 index 0000000..29dd080 --- /dev/null +++ b/misc/conf/bootstrap.yml @@ -0,0 +1,117 @@ +# app +hp.soa.web: + app: + id: "0010100001" + name: ${project.artifactId} + version: ${project.version} + organization: HP-Socket + owner: Kingfisher + component-scan: + base-package: ${project.groupId} + cookie: + max-age: 315360000 + cors: + mapping: "/**" + allowed-origins: "*" + allowed-headers: "*" + allowed-methods: "*" + exposed-headers: + allow-credentials: false + maxAge: 3600 + access-verification: + enabled: true + DefaultAccessPolicy: maybe_login + +# logging +logging.config: classpath:log4j2-kafka.xml + +# dubbo +dubbo: + scan.base-packages: ${hp.soa.web.component-scan.base-package} + protocols: + #dubbo: + # name: dubbo + # port: 5001 + tri: + name: tri + port: 6001 + registry: + file: "/data/dubbo/.cache/dubbo-registry_${hp.soa.web.app.name}.properties" + application: + qos-port: 7001 + qos-enable: false + name: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + owner: ${hp.soa.web.app.owner:} + organization: ${hp.soa.web.app.organization:} + +# server +server: + port: 9001 + servlet: + context-path: / + tomcat: + max-connections: 10000 + connection-timeout: 30000 + threads: + max: 200 + min-spare: 5 + undertow: + buffer-size: 1024 + direct-buffers: true + eager-filter-init: true + max-http-post-size: 10MB + threads: + worker: 64 + io: 4 + +# spring +spring: + application.name: ${hp.soa.web.app.name} + mvc: + servlet: + load-on-startup: 1 + path: / + throw-exception-if-no-handler-found: true + web: + resources: + add-mappings: false + security: + user: + name: admin + password: "123456" + roles: ADMIN + servlet: + multipart: + enabled: false + max-file-size: 10MB + max-request-size: 10MB + +# management +management: + endpoint: + health.show-details: when-authorized + shutdown.enabled: false + endpoints: + enabled-by-default: true + jmx.exposure.include: "*" + web.base-path: /__admin + web.exposure.include: "*" + metrics: + export.influx.enabled: false + tags.application: ${hp.soa.web.app.name} + +# api doc +springdoc: + api-docs: + enabled: true + path: /v3/api-docs + swagger-ui: + enabled: true + path: /swagger-ui.html + packagesToScan: io.github.hpsocket.soa,${hp.soa.web.component-scan.base-package} + api-infos: + group-name: ${hp.soa.web.app.name} + title: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + description: "Spring Boot Project >> ${hp.soa.web.app.name} (v${hp.soa.web.app.version})" diff --git a/misc/conf/log4j2-kafka.xml b/misc/conf/log4j2-kafka.xml new file mode 100644 index 0000000..3116311 --- /dev/null +++ b/misc/conf/log4j2-kafka.xml @@ -0,0 +1,139 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.kafka.bootstrap.servers:-192.168.56.23:9092} + ${sys:log4j.kafka.sasl.jaas.config:-} + ${sys:log4j.kafka.topic:-hp-soa} + ${sys:log4j.kafka.logType:-hp-soa} + true + true + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + classpath:log4j2-default-template.json + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + ${kafka.bootstrap.servers} + ${kafka.sasl.jaas.config} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/conf/log4j2.xml b/misc/conf/log4j2.xml new file mode 100644 index 0000000..9b9eb32 --- /dev/null +++ b/misc/conf/log4j2.xml @@ -0,0 +1,100 @@ + + + + + ${project.groupId} + com\.github\.hpsocket\.demo\..*\.dao\..+ + ${sys:log4j.include.location:-true} + ${sys:log4j.log.level:-DEBUG} + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId} + 100 MB + 10 + ${sys:log4j.logfile.path:-/data/logs/access}/${project.artifactId}/${date:yyyy-MM} + %d{yyyy-MM-dd}-%i.log.gz + org.springframework.security.web.ObservationFilterChainDecorator,org.apache.catalina,org.apache.coyote,org.apache.tomcat,org.hibernate,org.apache.el,org.apache.cxf,org.junit,junit.framework,org.jboss,org.h2,org.eclipse,org.richfaces,java.lang.reflect,java.base/java.lang.reflect,jdk.internal.reflect,java.base/jdk.internal.reflect,com.sun,javax.servlet,jakarta.servlet + %d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %C{1.1.1.*}#%M\(L:%L\) -> %m%xEx{filters(${logfile.stacktrace.filters})}%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/data/hp-soa/config/dubbo-resolve.properties b/misc/data/hp-soa/config/dubbo-resolve.properties new file mode 100644 index 0000000..368fe0a --- /dev/null +++ b/misc/data/hp-soa/config/dubbo-resolve.properties @@ -0,0 +1 @@ +#com.github.hpsocket.demo.infra.service.DemoService=tri://localhost:6002?timeout=300000 diff --git a/misc/data/hp-soa/config/soa-config.properties b/misc/data/hp-soa/config/soa-config.properties new file mode 100644 index 0000000..92fb803 --- /dev/null +++ b/misc/data/hp-soa/config/soa-config.properties @@ -0,0 +1,43 @@ +#logging.config=classpath:log4j2-kafka.xml +logging.config=classpath:log4j2.xml + +spring.cloud.nacos.config.server-addr=192.168.56.23:8848 +spring.cloud.nacos.config.username=nacos +spring.cloud.nacos.config.password=123456 +spring.cloud.nacos.config.namespace=DEV + +#dubbo.registry.address=nacos://nacos:123456@192.168.56.23:8848?namespace=DEV®ister=false®ister-consumer-url=true +dubbo.registry.address=nacos://nacos:123456@192.168.56.23:8848?namespace=DEV&group=DUBBO_GROUP®ister-consumer-url=true +dubbo.config-center.address=nacos://nacos:123456@192.168.56.23:8848?namespace=DEV&group=DUBBO_GROUP®ister-consumer-url=true +dubbo.metadata-report.address=nacos://nacos:123456@192.168.56.23:8848?namespace=DEV&group=DUBBO_GROUP®ister-consumer-url=true + +# local / remote +#dubbo.application.metadata-type=remote + +# instance / interface / all +dubbo.registry.register-mode=instance +dubbo.registry.simplified=true +dubbo.registry.enable-empty-protection=false +#dubbo.monitor.protocol=registry +#dubbo.protocol.prefer-serialization=fastjson2,protobuf +#dubbo.protocol.serialization=fastjson2 +#dubbo.protocols.dubbo.serialization=fastjson2 +#dubbo.protocols.tri.serialization=fastjson2 +dubbo.consumer.filter= +dubbo.consumer.retries=0 +dubbo.consumer.check=false +#dubbo.consumer.lazy=true +#dubbo.consumer.timeout=5000 +#dubbo.consumer.connections=1 +#dubbo.consumer.shareconnections=3 +# jvalidationNew / false +dubbo.consumer.validation=jvalidationNew +# jvalidationNew / false +dubbo.provider.validation=false +dubbo.provider.filter= +#dubbo.provider.delay=0 +#dubbo.provider.connections=1 +#dubbo.provider.prefer-serialization=fastjson2,protobuf +#dubbo.provider.serialization=fastjson2 + +dubbo.application.qosEnable=false diff --git a/misc/data/hp-soa/config/system-config.properties b/misc/data/hp-soa/config/system-config.properties new file mode 100644 index 0000000..71e6da7 --- /dev/null +++ b/misc/data/hp-soa/config/system-config.properties @@ -0,0 +1,25 @@ +# log4j config +log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector +log4j2.garbagefreeThreadContextMap=true +log4j2.isThreadContextMapInheritable=true +log4j.layout.jsonTemplate.locationInfoEnabled=true +log4j.include.location=true +log4j.log.level=DEBUG +log4j.logfile.path=/data/logs/access + +# log4j kafka appender config +log4j.kafka.bootstrap.servers=192.168.56.23:9092 +log4j.kafka.sasl.jaas.config= +log4j.kafka.topic=hp-soa +log4j.kafka.logType=hp-soa + +# nacos log config +JM.LOG.PATH=/data/logs +com.alibaba.nacos.naming.log.level=warn +com.alibaba.nacos.config.log.level=warn +com.alibaba.nacos.remote.log.level=warn + +# sentinel log config +csp.sentinel.log.level=WARNING +csp.sentinel.log.dir=/data/logs/csp +EAGLEEYE.LOG.PATH=/data/logs/eagleeye \ No newline at end of file diff --git a/misc/db/mysql/soa-demo-1_db/t_user.sql b/misc/db/mysql/soa-demo-1_db/t_user.sql new file mode 100644 index 0000000..7f62175 --- /dev/null +++ b/misc/db/mysql/soa-demo-1_db/t_user.sql @@ -0,0 +1,40 @@ +/* + Navicat Premium Data Transfer + + Source Server : docker-mysql + Source Server Type : MySQL + Source Server Version : 80033 (8.0.33) + Source Host : 192.168.56.23:3306 + Source Schema : soa-demo-1_db + + Target Server Type : MySQL + Target Server Version : 80033 (8.0.33) + File Encoding : 65001 + + Date: 16/08/2023 19:25:35 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_user +-- ---------------------------- +DROP TABLE IF EXISTS `t_user`; +CREATE TABLE `t_user` ( + `id` bigint NOT NULL, + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `age` int NOT NULL, + `version` int NULL DEFAULT 1, + `deleted` tinyint NULL DEFAULT 0, + `create_time` timestamp(3) NULL DEFAULT NULL, + `update_time` timestamp(3) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of t_user +-- ---------------------------- +INSERT INTO `t_user` VALUES (1, 'slave-1', 33, 1, 0, '2023-08-16 19:24:20.049', '2023-08-16 19:24:20.049'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/misc/db/mysql/soa-demo-2_db/t_user.sql b/misc/db/mysql/soa-demo-2_db/t_user.sql new file mode 100644 index 0000000..e2178f8 --- /dev/null +++ b/misc/db/mysql/soa-demo-2_db/t_user.sql @@ -0,0 +1,40 @@ +/* + Navicat Premium Data Transfer + + Source Server : docker-mysql + Source Server Type : MySQL + Source Server Version : 80033 (8.0.33) + Source Host : 192.168.56.23:3306 + Source Schema : soa-demo-2_db + + Target Server Type : MySQL + Target Server Version : 80033 (8.0.33) + File Encoding : 65001 + + Date: 16/08/2023 19:25:47 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_user +-- ---------------------------- +DROP TABLE IF EXISTS `t_user`; +CREATE TABLE `t_user` ( + `id` bigint NOT NULL, + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `age` int NOT NULL, + `version` int NULL DEFAULT 1, + `deleted` tinyint NULL DEFAULT 0, + `create_time` timestamp(3) NULL DEFAULT NULL, + `update_time` timestamp(3) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of t_user +-- ---------------------------- +INSERT INTO `t_user` VALUES (1, 'slave-2', 99, 1, 0, '2023-08-16 19:24:20.049', '2023-08-16 19:24:20.049'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/misc/db/mysql/soa-demo_db/t_demo_event.sql b/misc/db/mysql/soa-demo_db/t_demo_event.sql new file mode 100644 index 0000000..fa1d810 --- /dev/null +++ b/misc/db/mysql/soa-demo_db/t_demo_event.sql @@ -0,0 +1,47 @@ +/* + Navicat Premium Data Transfer + + Source Server : docker-mysql + Source Server Type : MySQL + Source Server Version : 80033 (8.0.33) + Source Host : 192.168.56.23:3306 + Source Schema : soa-demo_db + + Target Server Type : MySQL + Target Server Version : 80033 (8.0.33) + File Encoding : 65001 + + Date: 01/09/2023 18:48:00 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_demo_event +-- ---------------------------- +DROP TABLE IF EXISTS `t_demo_event`; +CREATE TABLE `t_demo_event` ( + `id` bigint NOT NULL, + `biz_id` bigint NOT NULL, + `region_id` int NOT NULL, + `domain_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `event_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `exchange` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `routing_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `msg_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `msg` json NOT NULL, + `source_request_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `send_flag` int NOT NULL DEFAULT 0, + `retries` int NOT NULL DEFAULT 0, + `last_send_time` timestamp(3) NULL DEFAULT NULL, + `deleted` tinyint NOT NULL DEFAULT 0, + `create_time` timestamp(3) NOT NULL, + `update_time` timestamp(3) NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `ux_msg_id`(`msg_id` ASC) USING BTREE, + INDEX `ix_id_send_flag`(`id` ASC, `send_flag` ASC) USING BTREE, + INDEX `ix_last_send_time`(`last_send_time` ASC, `send_flag` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/misc/db/mysql/soa-demo_db/t_order.sql b/misc/db/mysql/soa-demo_db/t_order.sql new file mode 100644 index 0000000..cdb10fb --- /dev/null +++ b/misc/db/mysql/soa-demo_db/t_order.sql @@ -0,0 +1,36 @@ +/* + Navicat Premium Data Transfer + + Source Server : docker-mysql + Source Server Type : MySQL + Source Server Version : 80033 (8.0.33) + Source Host : 192.168.56.23:3306 + Source Schema : soa-demo_db + + Target Server Type : MySQL + Target Server Version : 80033 (8.0.33) + File Encoding : 65001 + + Date: 01/09/2023 18:48:13 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_order +-- ---------------------------- +DROP TABLE IF EXISTS `t_order`; +CREATE TABLE `t_order` ( + `id` bigint NOT NULL, + `region_id` int NOT NULL, + `order_number` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `price` bigint NOT NULL, + `deleted` tinyint NOT NULL DEFAULT 0, + `create_time` timestamp(3) NOT NULL, + `update_time` timestamp(3) NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `ux_order_number`(`order_number` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/misc/db/mysql/soa-demo_db/t_user.sql b/misc/db/mysql/soa-demo_db/t_user.sql new file mode 100644 index 0000000..e4edb33 --- /dev/null +++ b/misc/db/mysql/soa-demo_db/t_user.sql @@ -0,0 +1,40 @@ +/* + Navicat Premium Data Transfer + + Source Server : docker-mysql + Source Server Type : MySQL + Source Server Version : 80033 (8.0.33) + Source Host : 192.168.56.23:3306 + Source Schema : soa-demo_db + + Target Server Type : MySQL + Target Server Version : 80033 (8.0.33) + File Encoding : 65001 + + Date: 16/08/2023 19:25:27 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for t_user +-- ---------------------------- +DROP TABLE IF EXISTS `t_user`; +CREATE TABLE `t_user` ( + `id` bigint NOT NULL, + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `age` int NOT NULL, + `version` int NULL DEFAULT 1, + `deleted` tinyint NULL DEFAULT 0, + `create_time` timestamp(3) NULL DEFAULT NULL, + `update_time` timestamp(3) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of t_user +-- ---------------------------- +INSERT INTO `t_user` VALUES (1, 'master', 23, 1, 0, '2023-08-16 19:24:20.049', '2023-08-16 19:24:20.049'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/misc/elk/1-hp-soa-geoip-pipeline.json.put b/misc/elk/1-hp-soa-geoip-pipeline.json.put new file mode 100644 index 0000000..4dae539 --- /dev/null +++ b/misc/elk/1-hp-soa-geoip-pipeline.json.put @@ -0,0 +1,12 @@ + +PUT _ingest/pipeline/hp-soa-geoip-pipeline +{ + "description" : "process hp-soa geoip info", + "processors" : [ + { + "geoip" : { + "field" : "clientAddr" + } + } + ] +} \ No newline at end of file diff --git a/misc/elk/2-hp-soa-lifecycle-policy.json.put b/misc/elk/2-hp-soa-lifecycle-policy.json.put new file mode 100644 index 0000000..4c30c20 --- /dev/null +++ b/misc/elk/2-hp-soa-lifecycle-policy.json.put @@ -0,0 +1,32 @@ + +PUT _ilm/policy/hp-soa-lifecycle-policy +{ + "policy": { + "phases": { + "hot": { + "min_age": "0ms", + "actions": { + "set_priority": { + "priority": 100 + } + } + }, + "warm": { + "min_age": "3d", + "actions": { + "set_priority": { + "priority": 50 + } + } + }, + "delete": { + "min_age": "15d", + "actions": { + "delete": { + "delete_searchable_snapshot": true + } + } + } + } + } +} \ No newline at end of file diff --git a/misc/elk/3-hp-soa-template.json.put b/misc/elk/3-hp-soa-template.json.put new file mode 100644 index 0000000..0c646c7 --- /dev/null +++ b/misc/elk/3-hp-soa-template.json.put @@ -0,0 +1,329 @@ + +PUT _index_template/hp-soa-template +{ + "priority": 100, + "version": 1, + "index_patterns": [ + "hp-soa-*" + ], + "template": { + "aliases": { + "hp-soa": { + + } + }, + "settings": { + "number_of_shards": 3, + "number_of_replicas": 0, + "index.lifecycle.name": "hp-soa-lifecycle-policy" + }, + "mappings": { + "_source": { + "enabled": true + }, + "dynamic_templates": [ + { + "time_template": { + "match_mapping_type": "string", + "match_pattern": "regex", + "match": "^[0-9-a-z_]*(time|Time)$", + "mapping": { + "type": "date", + "format": "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd HH:mm:ss.SSS || yyyy-MM-dd'T'HH:mm:ss.SSS'Z' || yyyy-MM-dd'T'HH:mm:ss'Z' || yyyy-MM-dd || epoch_millis" + } + } + }, + { + "long_id_template": { + "match_mapping_type": "long", + "match_pattern": "regex", + "match": "^[0-9-a-z_]*(id|Id)$", + "mapping": { + "type": "long" + } + } + }, + { + "str_id_template": { + "match_mapping_type": "string", + "match_pattern": "regex", + "match": "^[0-9-a-z_]*(id|Id)$", + "mapping": { + "type": "keyword" + } + } + }, + { + "long_template": { + "match_mapping_type": "long", + "match": "*", + "mapping": { + "type": "long" + } + } + }, + { + "double_template": { + "match_mapping_type": "double", + "match": "*", + "mapping": { + "type": "double" + } + } + }, + { + "text_template": { + "match_mapping_type": "string", + "match": "*", + "mapping": { + "type": "text" + } + } + } + ], + "properties": { + "geoip": { + "properties": { + "location": { + "type": "geo_point" + }, + "region_name": { + "type": "keyword" + }, + "region_iso_code": { + "type": "keyword" + }, + "country_name": { + "type": "keyword" + }, + "country_iso_code": { + "type": "keyword" + }, + "continent_name": { + "type": "keyword" + }, + "city_name": { + "type": "keyword" + } + } + }, + "@timestamp": { + "type": "date" + }, + "@version": { + "type": "keyword" + }, + "apiName": { + "type": "keyword" + }, + "appCode": { + "type": "keyword" + }, + "appId": { + "type": "keyword" + }, + "appName": { + "type": "keyword" + }, + "class": { + "type": "text" + }, + "clientAddr": { + "type": "keyword" + }, + "clientId": { + "type": "keyword" + }, + "costTime": { + "type": "long" + }, + "exception": { + "properties": { + "exception_class": { + "type": "text" + }, + "exception_message": { + "type": "text" + }, + "stacktrace": { + "type": "text" + } + } + }, + "facility": { + "type": "keyword" + }, + "file": { + "type": "text" + }, + "groupId": { + "type": "keyword" + }, + "level": { + "type": "keyword" + }, + "line_number": { + "type": "long" + }, + "log_time": { + "type": "date" + }, + "log_type": { + "type": "keyword" + }, + "logger_name": { + "type": "text" + }, + "mdc": { + "properties": { + "__appCode": { + "type": "keyword" + }, + "__appId": { + "type": "keyword" + }, + "__appName": { + "type": "keyword" + }, + "__clientId": { + "type": "keyword" + }, + "__fromServiceAddr": { + "type": "keyword" + }, + "__fromServiceId": { + "type": "keyword" + }, + "__fromServiceName": { + "type": "keyword" + }, + "__groupId": { + "type": "keyword" + }, + "__organization": { + "type": "keyword" + }, + "__owner": { + "type": "keyword" + }, + "__requestId": { + "type": "keyword" + }, + "__messageId": { + "type": "keyword" + }, + "__sourceRequestId": { + "type": "keyword" + }, + "__traceId": { + "type": "keyword" + }, + "__serviceAddr": { + "type": "keyword" + }, + "__serviceId": { + "type": "keyword" + }, + "__serviceName": { + "type": "keyword" + }, + "__token": { + "type": "keyword" + }, + "__userId": { + "type": "keyword" + } + } + }, + "message": { + "type": "text" + }, + "method": { + "type": "text" + }, + "monitor_type": { + "type": "keyword" + }, + "msg": { + "type": "text" + }, + "organization": { + "type": "keyword" + }, + "owner": { + "type": "keyword" + }, + "request": { + "type": "text" + }, + "requestId": { + "type": "keyword" + }, + "requestMethod": { + "type": "keyword" + }, + "requestPath": { + "type": "text" + }, + "requestUri": { + "type": "text" + }, + "response": { + "type": "text" + }, + "resultCode": { + "type": "long" + }, + "serviceAddr": { + "type": "keyword" + }, + "serviceId": { + "type": "keyword" + }, + "serviceName": { + "type": "keyword" + }, + "source_host": { + "type": "keyword" + }, + "source_ip": { + "type": "keyword" + }, + "statusCode": { + "type": "long" + }, + "thread_name": { + "type": "text" + }, + "timestamp": { + "type": "long" + }, + "token": { + "type": "keyword" + }, + "ua": { + "properties": { + "browser": { + "type": "keyword" + }, + "browserType": { + "type": "keyword" + }, + "deviceType": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "platform": { + "type": "keyword" + } + } + }, + "userId": { + "type": "long" + } + } + } + } +} \ No newline at end of file diff --git a/misc/jvm/java-opts.txt b/misc/jvm/java-opts.txt new file mode 100644 index 0000000..1447997 --- /dev/null +++ b/misc/jvm/java-opts.txt @@ -0,0 +1,3 @@ +Example: + +-Dhp.soa.system.properties.file=/data/hp-soa/config/system-config.properties -Dhp.soa.external.properties.file=/data/hp-soa/config/soa-config.properties -Ddubbo.resolve.file=/data/hp-soa/config/dubbo-resolve.properties -DskipTests=true -Djava.security.egd=file:/dev/./urandom --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/javax.net.ssl=ALL-UNNAMED \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-mysql.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-mysql.yml new file mode 100644 index 0000000..76d7397 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-mysql.yml @@ -0,0 +1 @@ +xxx.yyy: 789 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-nacos.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-nacos.yml new file mode 100644 index 0000000..76d7397 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-nacos.yml @@ -0,0 +1 @@ +xxx.yyy: 789 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-sentinel.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-sentinel.yml new file mode 100644 index 0000000..76d7397 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-sentinel.yml @@ -0,0 +1 @@ +xxx.yyy: 789 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-skywalking.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-skywalking.yml new file mode 100644 index 0000000..76d7397 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-bff-skywalking.yml @@ -0,0 +1 @@ +xxx.yyy: 789 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-mysql-service.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-mysql-service.yml new file mode 100644 index 0000000..142c278 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-mysql-service.yml @@ -0,0 +1,34 @@ +spring.datasource.dynamic.datasource: + master: + url: "jdbc:mysql://192.168.56.23:3306/soa-demo_db?serverTimezone=Asia/Shanghai&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&useSSL=false&failOverReadOnly=false&allowPublicKeyRetrieval=true" + username: soa + # encrypt password by druid public-key + #password: "Vt4ZT5CSHb9wpO4b0fx7uSX8UxGkyTEc3ycZZhWZcz++Ehkg0Dtzhi7s2XB8fLFAcjgKilUv0iN5Fasb4dNQ1w==" + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + slave_01: + url: "jdbc:mysql://192.168.56.23:3306/soa-demo-1_db?serverTimezone=Asia/Shanghai&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&useSSL=false&failOverReadOnly=false&allowPublicKeyRetrieval=true" + username: soa + # encrypt password by druid public-key + #password: "Vt4ZT5CSHb9wpO4b0fx7uSX8UxGkyTEc3ycZZhWZcz++Ehkg0Dtzhi7s2XB8fLFAcjgKilUv0iN5Fasb4dNQ1w==" + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + slave_02: + url: "jdbc:mysql://192.168.56.23:3306/soa-demo-2_db?serverTimezone=Asia/Shanghai&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&useSSL=false&failOverReadOnly=false&allowPublicKeyRetrieval=true" + username: soa + # encrypt password by druid public-key + #password: "Vt4ZT5CSHb9wpO4b0fx7uSX8UxGkyTEc3ycZZhWZcz++Ehkg0Dtzhi7s2XB8fLFAcjgKilUv0iN5Fasb4dNQ1w==" + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + +global-transaction-management: + enabled: true + #timeout: 3000 + #isolation: ISOLATION_READ_COMMITTED + #propagation: PROPAGATION_REQUIRED + #rollback-for: java.lang.Exception + #read-only-timeout: 3000 + #read-only-isolation: ISOLATION_READ_COMMITTED + #read-only-propagation: PROPAGATION_REQUIRED + #pointcut-expression: "execution(* ${hp.soa.web.component-scan.base-package}..service..*.*(..))" + \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-nacos-service.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-nacos-service.yml new file mode 100644 index 0000000..3da8cde --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-nacos-service.yml @@ -0,0 +1 @@ +aaa.bbb: 666 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-sentinel-service.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-sentinel-service.yml new file mode 100644 index 0000000..3da8cde --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-sentinel-service.yml @@ -0,0 +1 @@ +aaa.bbb: 666 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-skywalking-service.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-skywalking-service.yml new file mode 100644 index 0000000..3da8cde --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-infra-skywalking-service.yml @@ -0,0 +1 @@ +aaa.bbb: 666 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-job.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-job.yml new file mode 100644 index 0000000..46c6d1b --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-job.yml @@ -0,0 +1,2 @@ +xxx.yyy: 789 +#hp.soa.web.app.read-only: true \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-mq-consumer.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-mq-consumer.yml new file mode 100644 index 0000000..459e8f9 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-mq-consumer.yml @@ -0,0 +1,12 @@ +global-transaction-management: + enabled: true + #timeout: 3000 + #isolation: ISOLATION_READ_COMMITTED + #propagation: PROPAGATION_REQUIRED + #rollback-for: java.lang.Exception + #read-only-timeout: 3000 + #read-only-isolation: ISOLATION_READ_COMMITTED + #read-only-propagation: PROPAGATION_REQUIRED + #pointcut-expression: "execution(* ${hp.soa.web.component-scan.base-package}..service..*.*(..))" + +hp.soa.web.app.read-only: false \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-mq-producer.yml b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-mq-producer.yml new file mode 100644 index 0000000..081d9f6 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/DEMO_GROUP/hp-demo-mq-producer.yml @@ -0,0 +1,19 @@ +spring.datasource.dynamic.datasource: + master: + url: "jdbc:mysql://192.168.56.23:3306/soa-demo_db?serverTimezone=Asia/Shanghai&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&useSSL=false&failOverReadOnly=false&allowPublicKeyRetrieval=true" + username: soa + # encrypt password by druid public-key + #password: "Vt4ZT5CSHb9wpO4b0fx7uSX8UxGkyTEc3ycZZhWZcz++Ehkg0Dtzhi7s2XB8fLFAcjgKilUv0iN5Fasb4dNQ1w==" + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + +global-transaction-management: + enabled: true + #timeout: 3000 + #isolation: ISOLATION_READ_COMMITTED + #propagation: PROPAGATION_REQUIRED + #rollback-for: java.lang.Exception + #read-only-timeout: 3000 + #read-only-isolation: ISOLATION_READ_COMMITTED + #read-only-propagation: PROPAGATION_REQUIRED + #pointcut-expression: "execution(* ${hp.soa.web.component-scan.base-package}..service..*.*(..))" \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/dubbo.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/dubbo.yml new file mode 100644 index 0000000..9662551 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/dubbo.yml @@ -0,0 +1,10 @@ +dubbo: + scan.base-packages: ${hp.soa.web.component-scan.base-package} + registry: + file: "/data/hp-soa/.cache/dubbo-registry_${hp.soa.web.app.name}.properties" + application: + #qos-enable: false + name: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + owner: ${hp.soa.web.app.owner:} + organization: ${hp.soa.web.app.organization:} \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/hp-soa-web.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/hp-soa-web.yml new file mode 100644 index 0000000..efc8c4e --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/hp-soa-web.yml @@ -0,0 +1,19 @@ +hp.soa.web: + cookie: + max-age: 315360000 + cors: + mapping: "/**" + allowed-origins: "*" + allowed-headers: "*" + allowed-methods: "*" + exposed-headers: + allow-credentials: false + maxAge: 3600 + async: + enabled: true + core-pool-size: 4 + max-pool-size: 16 + keep-alive-seconds: 30 + queue-capacity: 2000 + rejection-policy: CALLER_RUNS + allow-core-thread-time-out: true \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/mysql.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/mysql.yml new file mode 100644 index 0000000..465272e --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/mysql.yml @@ -0,0 +1,93 @@ +spring.datasource: + druid: + web-stat-filter: + enabled: true + session-stat-enable: true + url-pattern: /* + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/__druid/*" + stat-view-servlet: + enabled: true + url-pattern: "/__druid/*" + login-username: admin + login-password: 123456 + allow: 127.0.0.1,192.168.0.0/16 + reset-enable: true + aop-patterns: + - ${hp.soa.web.component-scan.base-package:} + filter: + stat: + enabled: true + log-slow-sql: true + slow-sql-millis: 500 + wall: + enabled: true + slf4j: + enabled: true + config: + enabled: true + +spring.datasource.dynamic: + primary: master + strict: true + lazy: false + seata: false + druid: + max-active: 10 + min-idle: 1 + max-wait: 3000 + initial-size: 3 + keep-alive: true + test-while-idle: true + time-between-eviction-runs-millis: 60000 + validation-query: SELECT 1 + validation-query-timeout: 1 + filters: stat,wall,slf4j + #query-timeout: (seconds, 0: infinite) + #transaction-query-timeout: (seconds, 0: infinite) + default-transaction-isolation: 2 + connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=3000 + # public-key uses to encrypt database access password + #public-key: "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJRhKGNr3IAJcs4+0ys/4zmwUJoBYqAdx6EcyiWIBt7cIFS5QdruakTmGk8XOVPMVz9jOLOvhPfjLzgPgwaKo6UCAwEAAQ==" + +mybatis-plus: + mapper-locations: + type-aliases-package: + check-config-location: false + executor-type: reuse + configuration: + # prod env: org.apache.ibatis.logging.nologging.NoLoggingImpl + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + map-underscore-to-camel-case: true + lazy-loading-enabled: true + aggressive-lazy-loading: false + multiple-result-sets-enabled: true + use-column-label: true + use-generated-keys: true + default-statement-timeout: 3 + local-cache-scope: STATEMENT + cache-enabled: false + global-config: + banner: true + enable-sql-runner: false + db-config: + id-type: ASSIGN_ID + table-prefix: t_ + table-underline: true + capital-mode: false + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + insert-strategy: not_null + update-strategy: not_null + select-strategy: not_null + +global-transaction-management: + enabled: false + timeout: 3000 + isolation: ISOLATION_READ_COMMITTED + propagation: PROPAGATION_REQUIRED + rollback-for: java.lang.Exception + read-only-timeout: 3000 + read-only-isolation: ISOLATION_READ_COMMITTED + read-only-propagation: PROPAGATION_REQUIRED + pointcut-expression: "execution(* ${hp.soa.web.component-scan.base-package}..service..*.*(..))" \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/rabbitmq.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/rabbitmq.yml new file mode 100644 index 0000000..43d1de4 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/rabbitmq.yml @@ -0,0 +1,89 @@ +spring.rabbitmq: + addresses: 192.168.56.23:5672 + username: rabbit + password: 123456 + virtual-host: "/" + publisher-confirm-type: correlated + publisher-returns: true + listener: + simple: + missing-queues-fatal: false + direct: + missing-queues-fatal: false + stream: + native-listener: true + template: + mandatory: true + stream: + host: 192.168.56.23 + port: 5552 + name: default-stream + +spring.rabbitmq-first: + addresses: 192.168.56.23:5672 + username: rabbit + password: 123456 + virtual-host: "first" + publisher-confirm-type: correlated + publisher-returns: true + listener: + simple: + missing-queues-fatal: false + direct: + missing-queues-fatal: false + stream: + native-listener: true + template: + mandatory: true + stream: + host: 192.168.56.23 + port: 5552 + name: first-stream + +spring.rabbitmq-second: + addresses: 192.168.56.23:5672 + username: rabbit + password: 123456 + virtual-host: "second" + publisher-confirm-type: correlated + publisher-returns: true + listener: + # stream listener + type: stream + simple: + missing-queues-fatal: false + direct: + missing-queues-fatal: false + stream: + # native listener + native-listener: true + template: + mandatory: true + stream: + host: 192.168.56.23 + port: 5552 + name: second-stream + +spring.rabbitmq-third: + addresses: 192.168.56.23:5672 + username: rabbit + password: 123456 + virtual-host: "third" + publisher-confirm-type: correlated + publisher-returns: true + listener: + # stream listener + type: stream + simple: + missing-queues-fatal: false + direct: + missing-queues-fatal: false + stream: + # no-native listener + native-listener: false + template: + mandatory: true + stream: + host: 192.168.56.23 + port: 5552 + name: third-stream diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/redis.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/redis.yml new file mode 100644 index 0000000..078590c --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/redis.yml @@ -0,0 +1,6 @@ +spring.data.redis: + host: 192.168.56.23 + port: 6379 + #username: + #password: + clientName: ${spring.application.name} \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/sentinel.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/sentinel.yml new file mode 100644 index 0000000..5bc775a --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/sentinel.yml @@ -0,0 +1,82 @@ +spring.cloud.sentinel: + enabled: true + eager: true + filter: + enabled: true + log: + dir: /data/logs/sentinel + switch-pid: true + transport: + dashboard: 192.168.56.23:8383 + heartbeat-interval-ms: 30000 + datasource: + flow: + nacos: + server-addr: 192.168.56.23:8848 + username: nacos + password: 123456 + namespace: DEV + groupId: SENTINEL_GROUP + dataId: ${spring.application.name}-flow-rules + data-type: json + rule-type: flow + degrade: + nacos: + server-addr: 192.168.56.23:8848 + username: nacos + password: 123456 + namespace: DEV + groupId: SENTINEL_GROUP + dataId: ${spring.application.name}-degrade-rules + data-type: json + rule-type: degrade + param-flow: + nacos: + server-addr: 192.168.56.23:8848 + username: nacos + password: 123456 + namespace: DEV + groupId: SENTINEL_GROUP + dataId: ${spring.application.name}-param-rules + data-type: json + rule-type: param-flow + system: + nacos: + server-addr: 192.168.56.23:8848 + username: nacos + password: 123456 + namespace: DEV + groupId: SENTINEL_GROUP + dataId: ${spring.application.name}-system-rules + data-type: json + rule-type: system + authority: + nacos: + server-addr: 192.168.56.23:8848 + username: nacos + password: 123456 + namespace: DEV + groupId: SENTINEL_GROUP + dataId: ${spring.application.name}-authority-rules + data-type: json + rule-type: authority + #gw-flow: + # nacos: + # server-addr: 192.168.56.23:8848 + # username: nacos + # password: 123456 + # namespace: DEV + # groupId: SENTINEL_GROUP + # dataId: ${spring.application.name}-gw-flow-rules + # data-type: json + # rule-type: gw-flow + #gw-api-group: + # nacos: + # server-addr: 192.168.56.23:8848 + # username: nacos + # password: 123456 + # namespace: DEV + # groupId: SENTINEL_GROUP + # dataId: ${spring.application.name}-gw-api-group-rules + # data-type: json + # rule-type: gw-api-group diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/spring-boot.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/spring-boot.yml new file mode 100644 index 0000000..4e1c303 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/spring-boot.yml @@ -0,0 +1,81 @@ +# logging +logging.config: classpath:log4j2-kafka.xml + +# server +server: + servlet: + context-path: / + tomcat: + max-connections: 10000 + connection-timeout: 30000 + threads: + max: 200 + min-spare: 5 + undertow: + buffer-size: 1024 + direct-buffers: true + eager-filter-init: true + max-http-post-size: 10MB + threads: + worker: 64 + io: 4 + +# spring +spring: + application.name: ${hp.soa.web.app.name} + cloud.config: + # dev env -> enable override + allow-override: true + override-system-properties: true + override-none: true + # prod env -> enable override + #allow-override: false + #override-system-properties: false + #override-none: false + mvc: + servlet: + load-on-startup: 1 + path: / + throw-exception-if-no-hand1ler-found: true + web: + resources: + add-mappings: false + security: + user: + name: admin + password: "123456" + roles: ADMIN + servlet: + multipart: + enabled: false + max-file-size: 10MB + max-request-size: 10MB + +# management +management: + endpoint: + health.show-details: when-authorized + shutdown.enabled: false + endpoints: + enabled-by-default: true + jmx.exposure.include: "*" + web.base-path: /__admin + web.exposure.include: "*" + metrics: + export.influx.enabled: false + tags.application: ${hp.soa.web.app.name} + +# api doc +springdoc: + api-docs: + enabled: true + path: /v3/api-docs + swagger-ui: + enabled: true + path: /swagger-ui + packagesToScan: com.github.hpsocket.soa,${hp.soa.web.component-scan.base-package} + api-infos: + group-name: ${hp.soa.web.app.name} + title: ${hp.soa.web.app.name} + version: ${hp.soa.web.app.version} + description: "Spring Boot Project >> ${hp.soa.web.app.name} (v${hp.soa.web.app.version})" \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/spring-task.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/spring-task.yml new file mode 100644 index 0000000..bab3855 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/spring-task.yml @@ -0,0 +1,27 @@ +spring.task: + execution: + thread-name-prefix: "task-" + pool: + core-size: 4 + max-size: 16 + queue-capacity: 1000 + # milliseconds: 60000, seconds: 60s + keep-alive: 60000 + allow-core-thread-timeout: true + # CALLER_RUNS, ABORT, DISCARD, DISCARD_OLDEST, SYNC + rejection-policy: CALLER_RUNS + shutdown: + await-termination: false + # milliseconds + await-termination-period: 5000 + scheduling: + thread-name-prefix: "scheduling-" + pool: + size: 4 + # CALLER_RUNS, ABORT, DISCARD, DISCARD_OLDEST, SYNC + rejection-policy: CALLER_RUNS + shutdown: + await-termination: false + # milliseconds + await-termination-period: 5000 + \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/xxl-job.yml b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/xxl-job.yml new file mode 100644 index 0000000..e474431 --- /dev/null +++ b/misc/nacos/config/namespace-DEV/GLOBAL_GROUP/xxl-job.yml @@ -0,0 +1,12 @@ +xxl.job: + enabled: true + access-token: 0123456789abcdef + admin.addresses: http://192.168.56.23:8585/xxl-job-admin + executor: + appname: ${spring.application.name} + address: + ip: + # use static port, which is config by application config file + #port: + log-path: /data/logs/xxl-job/${spring.application.name} + log-retention-days: 7 \ No newline at end of file diff --git a/misc/nacos/config/namespace-DEV/nacos_config.zip b/misc/nacos/config/namespace-DEV/nacos_config.zip new file mode 100644 index 0000000..daad541 Binary files /dev/null and b/misc/nacos/config/namespace-DEV/nacos_config.zip differ diff --git a/misc/opt/deploy/scripts/env.sh b/misc/opt/deploy/scripts/env.sh new file mode 100644 index 0000000..bb276f7 --- /dev/null +++ b/misc/opt/deploy/scripts/env.sh @@ -0,0 +1,89 @@ +#!/bin/bash +BIN_DIR=$(cd $(dirname $0); pwd) +export PRG_HOME=$(cd $(dirname $BIN_DIR); pwd) +export CONF_FILE="$BIN_DIR/application.properties" + +if [ ! -f "$CONF_FILE" ]; then + echo " > environment check failed -> (config file '$CONF_FILE' not exists)" + exit 1 +fi + +source /etc/profile + +APP_ID_KEY='app.id' +APP_NAME_KEY='app.name' +SERVER_PORT_KEY='server.port' +JMX_PORT_KEY='jmx.port' +DUBBO_PORT_KEY='dubbo.protocol.port' +XXL_PORT_KEY='xxl.job.executor.port' +RUNTIME_ENV=${environment:-local} +JVM_XMS_KEY="env.${RUNTIME_ENV}.jvm.xms" +JVM_XMX_KEY="env.${RUNTIME_ENV}.jvm.xmx" +STARTUP_SECONDS_KEY="startup.max.wait.seconds" + +export APP_ID=$(cat $CONF_FILE | grep -E '^'$APP_ID_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +export APP_NAME=$(cat $CONF_FILE | grep -E '^'$APP_NAME_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +export SERVER_PORT=$(cat $CONF_FILE | grep -E '^'$SERVER_PORT_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +export JMX_PORT=$(cat $CONF_FILE | grep -E '^'$JMX_PORT_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +export JVM_XMS=$(cat $CONF_FILE | grep -E '^'$JVM_XMS_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +export JVM_XMX=$(cat $CONF_FILE | grep -E '^'$JVM_XMX_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +export DUBBO_PORT=$(cat $CONF_FILE | grep -E '^'$DUBBO_PORT_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +export XXL_PORT=$(cat $CONF_FILE | grep -E '^'$XXL_PORT_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") +STARTUP_SECONDS=$(cat $CONF_FILE | grep -E '^'$STARTUP_SECONDS_KEY'=' | cut -d "=" -f 2 | tr -d "[ \r\n]") + +if [[ -z "$STARTUP_SECONDS" || "$STARTUP_SECONDS" -le 0 ]]; then + STARTUP_SECONDS=90 +fi + +export STARTUP_SECONDS + +if [[ -z "$APP_ID" || -z "$APP_NAME" || -z "$SERVER_PORT" || -z "$JMX_PORT" ]]; then + echo " > environment check failed -> (can't find '$APP_ID_KEY' / '$APP_NAME_KEY' / '$SERVER_PORT_KEY' / '$JMX_PORT_KEY' property in config file '$CONF_FILE')" + exit 1 +fi + +LOG_LEVEL= +GELF_HOST= + +if [ "$RUNTIME_ENV" == "production" ]; then + LOG_LEVEL="INFO" +else + LOG_LEVEL="DEBUG" +fi + +if [ "$RUNTIME_ENV" == "local" ]; then + GELF_HOST="udp:localhost" +else + GELF_HOST="tcp:localhost" +fi + +export LOG_LEVEL +export GELF_HOST +export GELF_PORT="12201" +export LOG_FILE_PATH="/data/logs/access" +export LOG_FILE="${LOG_FILE_PATH}/${APP_NAME}/service.log" +export DUBBO_PROPERTIES_FILE="/data/dubbo/dubbo.properties" +export DUBBO_RESOLVE_FILE="/data/dubbo/dubbo-resolve.properties" +export JAVA_AGENT_FILE="/opt/settings/javaagent.config" + +JAVA_AGENT= + +if [ -f "$JAVA_AGENT_FILE" ]; then + while read LINE + do + if [[ -n "$LINE" && ${LINE:0:1} != "#" ]]; then + LINE=${LINE//\$\{APP_NAME\}/$APP_NAME} + LINE=${LINE//\$APP_NAME/$APP_NAME} + LINE=${LINE//\$\{APP_ID\}/$APP_ID} + LINE=${LINE//\$APP_ID/$APP_ID} + + if [ -z "$JAVA_AGENT" ]; then + JAVA_AGENT="$LINE" + else + JAVA_AGENT="$JAVA_AGENT $LINE" + fi + fi + done < $JAVA_AGENT_FILE +fi + +export JAVA_AGENT diff --git a/misc/opt/deploy/scripts/restart.sh b/misc/opt/deploy/scripts/restart.sh new file mode 100644 index 0000000..4b14fab --- /dev/null +++ b/misc/opt/deploy/scripts/restart.sh @@ -0,0 +1,5 @@ +#!/bin/bash +BIN_DIR=$(dirname $0) + +"$BIN_DIR"/stop.sh +"$BIN_DIR"/start.sh diff --git a/misc/opt/deploy/scripts/start.sh b/misc/opt/deploy/scripts/start.sh new file mode 100644 index 0000000..704ead4 --- /dev/null +++ b/misc/opt/deploy/scripts/start.sh @@ -0,0 +1,113 @@ +#!/bin/bash +BIN_DIR=$(dirname $0) +source $BIN_DIR/env.sh + +if [ $? -ne 0 ]; then + exit 1 +fi + +PRG_JAR= + +for i in $PRG_HOME/deploy/*.jar; do + if [ -z "$PRG_JAR" ]; then + PRG_JAR=$i + break + fi +done + +if [[ -z "$PRG_JAR" || ! -f "$PRG_JAR" ]]; then + echo " > start failed -> (main jar file '$PRG_JAR' not exists)" + exit 2 +fi + +JAVA_OPTS="-server -Xss256k -Dlog4j2.formatMsgNoLookups=true -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dsun.net.inetaddr.ttl=10 -Dsun.net.inetaddr.negative.ttl=1" +JAVA_OPTS="$JAVA_OPTS -Dapp.id=${APP_ID} -Dapp.name=${APP_NAME} -Dapp.home=${PRG_HOME} -Dspring.profiles.active=${RUNTIME_ENV} -Dserver.port=${SERVER_PORT}" +JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=$JMX_PORT -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" +JAVA_OPTS="$JAVA_OPTS -Ddruid.mysql.usePingMethod=false -Dlog4j.gelf.host=${GELF_HOST} -Dlog4j.gelf.port=${GELF_PORT} -Dlog4j.logfile.path=${LOG_FILE_PATH} -Dlog4j.log.level=${LOG_LEVEL}" +JAVA_OPTS="$JAVA_OPTS -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:LargePageSizeInBytes=64m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70" +#JAVA_OPTS="$JAVA_OPTS -XX:+UseCMSCompactAtFullCollection" +JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError" + +if [ -n "$JVM_XMS" ]; then + JAVA_OPTS="$JAVA_OPTS -Xms${JVM_XMS}" +fi + +if [ -n "$JVM_XMX" ]; then + JAVA_OPTS="$JAVA_OPTS -Xmx${JVM_XMX}" +fi + +if [ -n "$DUBBO_PORT" ]; then + JAVA_OPTS="$JAVA_OPTS -Ddubbo.protocol.port=${DUBBO_PORT}" +fi + +if [ -n "$XXL_PORT" ]; then + JAVA_OPTS="$JAVA_OPTS -Dxxl.job.executor.port=${XXL_PORT}" +fi + +if [ -n "$JAVA_AGENT" ]; then + JAVA_OPTS="$JAVA_OPTS $JAVA_AGENT" +fi + +if [ -f "$DUBBO_RESOLVE_FILE" ]; then + JAVA_OPTS="-Ddubbo.resolve.file=$DUBBO_RESOLVE_FILE $JAVA_OPTS" +fi + +if [ -f "$DUBBO_PROPERTIES_FILE" ]; then + JAVA_OPTS="-Ddubbo.properties.file=$DUBBO_PROPERTIES_FILE $JAVA_OPTS" +fi + +check_port() +{ + rs=0 + val=$(netstat -ntlp | awk '{print $4}' | grep -E ':'$1'$') + + if [ -z "$val" ]; then + rs=1 + fi + + return $rs +} + +echo " > starting ... ($SERVER_PORT)" + +if check_port $SERVER_PORT; then + echo " > start failed -> (SERVER_PORT $SERVER_PORT being in used)" + exit 3 +fi + +echo "\$JAVA_OPTS: $JAVA_OPTS" + +tail -0f "$LOG_FILE" 2>/dev/null & +trap "kill $! > /dev/null 2>&1" EXIT + +nohup java $JAVA_OPTS -jar $PRG_JAR > /dev/null 2>&1 & + +PID=$! +RS=5 +SEP=3 +CNT=$((STARTUP_SECONDS/SEP)) + +for ((i=1; i<=$CNT; i++)) +do + if ! check_port $SERVER_PORT; then + ps -p $PID > /dev/null 2>&1 + if [ $? -eq 0 ]; then + sleep $SEP + else + echo " > start failed -> (exception occured, process '$PID' exit)" + exit 4 + fi + else + RS=0 + break + fi +done + +if [ $RS -eq 0 ]; then + echo " > start ok !" +else + echo " > start failed -> (SERVER_PORT $SERVER_PORT check time out, forcibly stop process '$PID')" + kill -9 $PID +fi + +exit $RS diff --git a/misc/opt/deploy/scripts/status.sh b/misc/opt/deploy/scripts/status.sh new file mode 100644 index 0000000..a2f15ed --- /dev/null +++ b/misc/opt/deploy/scripts/status.sh @@ -0,0 +1,18 @@ +#!/bin/bash +BIN_DIR=$(dirname $0) +source $BIN_DIR/env.sh + +if [ $? -ne 0 ]; then + exit 1 +fi + +LINE=$(netstat -ntlp | awk '{print $7,$4}' | grep -E ':'$SERVER_PORT'$') + +if [ -n "$LINE" ]; then + PID=${LINE%/*} + echo " > '$APP_NAME' has started -> (PID: $PID, SERVER_PORT: $SERVER_PORT)" + exit 0 +fi + +echo " > '$APP_NAME' has stopped -> (SERVER_PORT: $SERVER_PORT)" +exit 1 diff --git a/misc/opt/deploy/scripts/stop.sh b/misc/opt/deploy/scripts/stop.sh new file mode 100644 index 0000000..8c7e435 --- /dev/null +++ b/misc/opt/deploy/scripts/stop.sh @@ -0,0 +1,71 @@ +#!/bin/bash +BIN_DIR=$(dirname $0) +source $BIN_DIR/env.sh + +if [ $? -ne 0 ]; then + exit 1 +fi + +LINE= +PORT=$SERVER_PORT + +check_pid() +{ + if [ -z "$1" ]; then + return 1 + fi + + LINE=$(netstat -ntlp | awk '{print $7,$4}' | grep -E ':'$1'$') + + if [ -z "$LINE" ]; then + return 2 + fi + + PORT=$1 + return 0 +} + +if ! check_pid "$SERVER_PORT"; then + if ! check_pid "$JMX_PORT"; then + if ! check_pid "$DUBBO_PORT"; then + if ! check_pid "$XXL_PORT"; then + : + fi + fi + fi +fi + +echo " > stopping ... ($PORT)" + +if [ -n "$LINE" ]; then + tail -0f "$LOG_FILE" 2>/dev/null & + trap "kill $! > /dev/null 2>&1" EXIT + + PID=${LINE%/*} + + OK='false' + kill $PID + + SEP=3 + CNT=$((STARTUP_SECONDS/SEP)) + + for ((i=1; i<=$CNT; i++)) + do + FLAG=$(ps -p $PID | awk '{print $1}' | grep -E '^'$PID'$') + + if [ -n "$FLAG" ]; then + sleep $SEP + else + OK='true' + break + fi + done + + if [ $OK != 'true' ]; then + echo " > time out, forcibly stopping ... (PID: $PID, SERVER_PORT: $PORT)" + kill -9 $PID + fi +fi + +echo " > stop ok !" +exit 0 diff --git a/misc/skywalking/java-agent-params.txt b/misc/skywalking/java-agent-params.txt new file mode 100644 index 0000000..084da50 --- /dev/null +++ b/misc/skywalking/java-agent-params.txt @@ -0,0 +1,3 @@ +Example: + +-javaagent:E:\Servers\skywalking-agent\skywalking-agent.jar=agent.service_name=hp-demo-infra-skywalking-service,collector.backend_service=192.168.56.23:11800,logging.level=WARN,logging.dir=/data/logs/skywalking,logging.file_name=skywalking-agent_hp-demo-infra-skywalking-service.log,logging.max_file_size=10485760,logging.max_history_files=10 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7acdf34 --- /dev/null +++ b/pom.xml @@ -0,0 +1,534 @@ + + 4.0.0 + + io.github.hpsocket + hp-soa-parent + ${project.artifactId} + ${revision} + pom + hp-soa: a fully functional, easy-to-use, and highly scalable microservice framework + https://github.com/ldcsaa/hp-soa + + + 0.0.1 + ${revision} + UTF-8 + UTF-8 + 17 + 17 + 17 + ${maven.build.timestamp} + yyyyMMddHHmmss + + io.github.hpsocket.soa.framework.web.server.main.AppStarter + + 1.18.28 + 0.2.0 + 1.5.5.Final + + 4.9.10 + 3.3.1 + 3.11.0 + 3.3.0 + 3.3.0 + 3.5.0 + 3.0.1 + 3.1.1 + 3.1.1 + 3.6.0 + 3.1.2 + 3.1.0 + 3.1.1 + 1.5.0 + 0.43.0 + + 1C + true + all + 1.6.13 + true + ${env.MAVEN_FORK_JVM_ARGS} + + + + hp-soa-dependencies + hp-soa-framework + hp-soa-starter + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:git@github.com:ldcsaa/hp-soa.git + scm:git@github.com:ldcsaa/hp-soa.git + https://github.com/ldcsaa/hp-soa + + + + ldcsaa + 17044073@qq.com + https://github.com/ldcsaa + +8 + + + + + + ossrh-snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots + + always + true + + + + ossrh-releases + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + false + + + + + + + + false + + alimaven + aliyun maven + http://maven.aliyun.com/nexus/content/groups/public/ + + + + + false + + central + Maven Repository Switchboard + http://repo1.maven.org/maven2 + + + + JBoss repository public + http://repository.jboss.org/nexus/content/groups/public/ + + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + src/main/java + src/test/java + + + + src/main/resources + true + + **/*.xml + **/*.yml + **/*.yaml + **/*.properties + **/*.conf + + + + src/main/resources + false + + **/*.json + **/*.imports + **/*.factories + **/*.Filter + + + + src/main/java + false + + **/*.java + + + + + + + pl.project13.maven + git-commit-id-plugin + ${git-commit-id-plugin.version} + + + get-the-git-infos + + revision + + initialize + + + validate-the-git-infos + + validateRevision + + package + + + + + git.branch + git.build.version + git.commit.id + git.commit.id.abbrev + git.commit.message.short + git.commit.time + git.commit.user.name + + yyyyMMddHHmmss + 8 + true + false + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${project.build.sourceEncoding} + + -parameters + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + ${project.build.sourceEncoding} + + + + compile + + + + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + default-jar + package + + jar + + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + none + + + + package + + jar + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + src/main/assembly/assembly.xml + + + + + make-assembly + package + + single + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + ${project.build.sourceEncoding} + ${maven.surefire.conf.forkCount} + ${maven.surefire.conf.reuseForks} + ${maven.surefire.conf.parallel} + ${maven.surefire.conf.useSystemClassLoader} + ${maven.surefire.conf.argLine} + + + + + test + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + verify + + sign + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot-maven-plugin.version} + + ${spring-boot.main_class} + + + + + repackage + + + + + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + + flatten + process-resources + + flatten + + + true + resolveCiFriendliesOnly + + + + flatten.clean + clean + + clean + + + + true + + true + resolveCiFriendliesOnly + + + + + io.fabric8 + docker-maven-plugin + ${docker-maven-plugin.version} + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + + + org.sonatype.plugins + nexus-staging-maven-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + + + + + +