Repository: x-ream/reliable Branch: master Commit: d2d96cd552ec Files: 113 Total size: 198.6 KB Directory structure: gitextract_szvgdmr5/ ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── acku-api/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── io/ │ └── xream/ │ └── acku/ │ ├── api/ │ │ ├── AckuOnConsumed.java │ │ └── AckuProducer.java │ ├── exception/ │ │ └── BusyException.java │ └── interner/ │ ├── AckuBackend.java │ ├── AckuOnConsumedAspect.java │ ├── AckuProducerAspect.java │ ├── MessageIdGenerator.java │ └── MessageTraceable.java ├── acku-core/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── io/ │ └── xream/ │ └── acku/ │ ├── api/ │ │ └── acku/ │ │ ├── AckuMessageService.java │ │ ├── DtoConverter.java │ │ ├── FailedService.java │ │ └── MessageResultService.java │ ├── backend/ │ │ └── AckuBackendImpl.java │ └── remote/ │ └── acku/ │ ├── AckuServiceRemote.java │ └── FailedServiceRemote.java ├── acku-entity/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── io/ │ └── xream/ │ └── acku/ │ ├── TCCTopic.java │ └── bean/ │ ├── constant/ │ │ └── MessageStatus.java │ ├── dto/ │ │ ├── AckuDto.java │ │ └── ConsumedAckuDto.java │ ├── entity/ │ │ ├── AckuMessage.java │ │ └── MessageResult.java │ └── exception/ │ └── AckuExceptioin.java ├── acku-message-center/ │ ├── .gitignore │ ├── acku-app/ │ │ ├── Dockerfile │ │ ├── pom.xml │ │ ├── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── io/ │ │ │ │ └── xream/ │ │ │ │ └── acku/ │ │ │ │ ├── App.java │ │ │ │ ├── config/ │ │ │ │ │ ├── DefaultKafkaProducer.java │ │ │ │ │ ├── KafkaConfig.java │ │ │ │ │ ├── MQConfig.java │ │ │ │ │ ├── NextKafkaProducer.java │ │ │ │ │ ├── NextKafkaProperties.java │ │ │ │ │ ├── ProducerCustomizer.java │ │ │ │ │ └── ProducerWrapper.java │ │ │ │ ├── controller/ │ │ │ │ │ ├── AckuController.java │ │ │ │ │ ├── FailedController.java │ │ │ │ │ ├── NextBusiness.java │ │ │ │ │ ├── ScheduleAckuController.java │ │ │ │ │ └── TccBusiness.java │ │ │ │ └── produce/ │ │ │ │ └── Producer.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring.factories │ │ │ └── application.properties │ │ └── test/ │ │ └── java/ │ │ └── io/ │ │ └── xream/ │ │ └── reliable/ │ │ └── AppTest.java │ ├── acku-dashboard/ │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── xream/ │ │ │ └── acku/ │ │ │ ├── App.java │ │ │ ├── controller/ │ │ │ │ ├── AuthorizationBusiness.java │ │ │ │ └── MessageFailedController.java │ │ │ └── remote/ │ │ │ └── AuthorizationServiceRemote.java │ │ └── resources/ │ │ ├── application.properties │ │ └── static/ │ │ └── index.html │ ├── acku-schedule/ │ │ ├── Dockerfile │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── xream/ │ │ │ └── acku/ │ │ │ ├── App.java │ │ │ ├── codetemplate/ │ │ │ │ ├── ScheduleTemplate.java │ │ │ │ └── inner/ │ │ │ │ └── ScheduleTemplateImpl.java │ │ │ ├── config/ │ │ │ │ └── CodeTemplateConfig.java │ │ │ ├── remote/ │ │ │ │ └── ScheduledAckuServiceRemote.java │ │ │ └── schedule/ │ │ │ ├── CleanSchedule.java │ │ │ ├── ReTrySchedule.java │ │ │ └── TrySchedule.java │ │ └── resources/ │ │ └── application.properties │ ├── acku-service/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── io/ │ │ └── xream/ │ │ └── acku/ │ │ ├── repository/ │ │ │ └── acku/ │ │ │ ├── AckuMessageRepository.java │ │ │ └── MessageResultRepository.java │ │ └── service/ │ │ └── acku/ │ │ ├── AckuMessageServiceImpl.java │ │ ├── FailedServiceImpl.java │ │ └── MessageResultServiceImpl.java │ └── pom.xml ├── acku-spring-boot-starter/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── io/ │ │ └── xream/ │ │ └── acku/ │ │ ├── AckuStarter.java │ │ └── api/ │ │ └── EnableAckuManagement.java │ └── resources/ │ └── META-INF/ │ └── spring.factories ├── debug ├── demo/ │ ├── demo-listener/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── io/ │ │ │ └── xream/ │ │ │ └── acku/ │ │ │ ├── App.java │ │ │ ├── bean/ │ │ │ │ ├── Cat.java │ │ │ │ ├── CatOrder.java │ │ │ │ ├── CatSettle.java │ │ │ │ └── CatStatement.java │ │ │ ├── config/ │ │ │ │ └── AckuConfig.java │ │ │ ├── controller/ │ │ │ │ ├── SettleController.java │ │ │ │ └── StatementController.java │ │ │ ├── listener/ │ │ │ │ ├── SettleListenerOfPayment.java │ │ │ │ └── StatementListenerOfSettle.java │ │ │ └── repository/ │ │ │ ├── CatOrderRepository.java │ │ │ ├── CatRepository.java │ │ │ ├── CatSettleRepository.java │ │ │ └── CatStatementRepository.java │ │ └── resources/ │ │ └── application.properties │ └── demo-producer-listener/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── io/ │ │ └── xream/ │ │ └── acku/ │ │ ├── App.java │ │ ├── AppTest.java │ │ ├── PaymentServiceRemote.java │ │ ├── bean/ │ │ │ ├── Cat.java │ │ │ ├── CatOrder.java │ │ │ └── CatSettle.java │ │ ├── config/ │ │ │ └── AckuConfig.java │ │ ├── controller/ │ │ │ ├── OrderController.java │ │ │ └── PaymentController.java │ │ ├── listener/ │ │ │ └── OrderListenerOfPayment.java │ │ └── repository/ │ │ ├── CatOrderRepository.java │ │ ├── CatRepository.java │ │ └── CatSettleRepository.java │ └── resources/ │ └── application.properties └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ target/ */target/ */classes/ *.jar *.class *.iml */application-test.properties */application-prod.properties ================================================ FILE: LICENSE ================================================ 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 [2016] [io.xream] 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 ================================================ FILE: README.md ================================================ # acku [http://acku.xream.io](http://acku.xream.io) [![license](https://img.shields.io/github/license/x-ream/acku.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [![maven](https://img.shields.io/maven-central/v/io.xream.acku/acku.svg)](https://search.maven.org/search?q=io.xream) mq transaction, with tcc option ## code annotation @EnableAckuManagement @AckuProducer @AckuOnConsumed ## code config implements DtoConverter ## spring boot properties acku.app=acku-app (k8s service name) #acku.app=http://ip:7717 (ip:port) ## maven dependency ```xml 0.2.0 io.xream.acku acku-spring-boot-starter ${acku.version} ``` ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-architect ================================================ FILE: acku-api/pom.xml ================================================ acku io.xream.acku 0.2.0 4.0.0 acku-api ================================================ FILE: acku-api/src/main/java/io/xream/acku/api/AckuOnConsumed.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.api; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author Sim */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AckuOnConsumed { String svc() default ""; /** * next produce */ String nextTopic() default ""; int nextRetryMax() default 0; /** * Atleast one svc need, to confirm consumed ok, */ String[] nextSvcs() default {}; } ================================================ FILE: acku-api/src/main/java/io/xream/acku/api/AckuProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.api; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 1. retryMax=0,useTcc=false,async=false, mean: simple prepare, * producer local tx create preparing status record or log record * listener local tx create preparing status record or log record * 2. useTcc=true, anyway, framework will set retryMax = 0 * 3. retryMax=3, final consistent; if underConstruction, will save the message * * usage: * 1. create orderBean: (useTcc=true) or (retryMax=0,useTcc=false,async=false) * 2. pay: preparing(retryMax=0,useTcc=false,async=false), paid callback(retryMax=3,async=true) * 3. logistics warehousing: (retryMax=3), and client ui vision compensation * * @author Sim */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AckuProducer { boolean useTcc() default false; String topic() default ""; /** * define which arg is the message body, while has multi args */ Class type() default Void.class; String[] svcs() default {}; int retryMax() default 0; /** * to avoid long tx waiting,
* if async == false, not suggest to write data in db of the method annotated by ReliableProducer
*/ boolean async() default false; boolean underConstruction() default false; } ================================================ FILE: acku-api/src/main/java/io/xream/acku/exception/BusyException.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.exception; /** * @author Sim */ public class BusyException extends RuntimeException{ public BusyException(String message) { super(message); } public BusyException(Exception e) { super(e); } } ================================================ FILE: acku-api/src/main/java/io/xream/acku/interner/AckuBackend.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.interner; import java.util.concurrent.Callable; /** * @author Sim 8966188@qq.com * @apiNote to use @EnableReliabilityManagement, @ReliableProducer and @ReliableOnConsumed,
* has to implements ReliableBackend, develop and deploy 'Reliable Message Center' */ public interface AckuBackend { /** * @param useTcc * as regards to TCC, TCC implemented to check all resources,
* at first, produce 'TOPIC'_TCC_TRY
* if all resouces ok, produce 'TOPIC'_TCC_CONFIRM
* any exception occured, produce 'TOPIC'_TCC_CANCEL
* anyway, when isTcc = true, has to prepare 3 listeners
* @param id framework generate id * @param retryMax message retryMax * @param underConstruction message underConstruction * @param topic message topic * @param body message body * @param messageTraceable getMsgId(), set tracingId * @param svcs the nameList of other listening domain service * @param callable the service or controller handle the bisiness */ Object produceReliably(Boolean useTcc, String id, int retryMax, boolean underConstruction, String topic, Object body, MessageTraceable messageTraceable, String[] svcs, Callable callable); /** * * @param svc the name of listening domain service * @param message message body * @param runnable listener handle the bisiness */ void onConsumed(String svc, Object message, Runnable runnable); boolean createNext(String id, int retryMax, String topic, Object body, Object preMessage, String[] svcs); /** * TCC * @param msgId */ boolean tryToConfirm(String msgId); /** * TCC * @param msgId */ boolean cancel(String msgId); } ================================================ FILE: acku-api/src/main/java/io/xream/acku/interner/AckuOnConsumedAspect.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.interner; import io.xream.acku.api.AckuOnConsumed; import io.xream.internal.util.ExceptionUtil; import io.xream.internal.util.StringUtil; import io.xream.internal.util.VerifyUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** * @author Sim */ @Aspect public class AckuOnConsumedAspect { private final static Logger logger = LoggerFactory.getLogger(AckuBackend.class); public AckuOnConsumedAspect() { logger.info("Acku OnConsumed Enabled"); } @Autowired private AckuBackend backend; @Pointcut("@annotation(io.xream.acku.api.AckuOnConsumed))") public void cut() { } @Around("cut() && @annotation(ackuOnConsumed) ") public void around(ProceedingJoinPoint proceedingJoinPoint, AckuOnConsumed ackuOnConsumed) { Object[] args = proceedingJoinPoint.getArgs(); Object message = args[0]; Signature signature = proceedingJoinPoint.getSignature(); String logStr = signature.getDeclaringTypeName() + "." + signature.getName(); String nextTopic = ackuOnConsumed.nextTopic(); String[] svcs = ackuOnConsumed.nextSvcs(); if (StringUtil.isNotNull(nextTopic)){ if (svcs == null || svcs.length == 0){ throw new IllegalArgumentException(logStr + ", if config nextTopic, svcs of io.xream.x7.acku.AckuOnConsumed can not null, nextTopic: " + nextTopic); } } String svc = ackuOnConsumed.svc(); if (StringUtil.isNullOrEmpty(svc)){ svc = VerifyUtil.toMD5(logStr).substring(0,10); } this.backend.onConsumed(svc, message, () -> { try { MethodSignature ms = ((MethodSignature) signature); if (ms.getReturnType() == void.class) { proceedingJoinPoint.proceed(); } else { Object nextBody = proceedingJoinPoint.proceed(); String id = MessageIdGenerator.get(); int maxTry = ackuOnConsumed.nextRetryMax(); if (StringUtil.isNotNull(nextTopic)){ boolean flag = this.backend.createNext(id,maxTry,nextTopic,nextBody,message,svcs); if (!flag){ throw new RuntimeException(logStr + ", produce next topic failed: topic: " + nextTopic + ", message:"+ message + ",next body: " + nextBody); } } } } catch (Throwable e) { if (e instanceof RuntimeException){ throw (RuntimeException) e; } throw new RuntimeException(ExceptionUtil.getMessage(e)); } } ); } } ================================================ FILE: acku-api/src/main/java/io/xream/acku/interner/AckuProducerAspect.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.interner; import io.xream.acku.api.AckuProducer; import io.xream.acku.exception.BusyException; import io.xream.internal.util.ExceptionUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.concurrent.TimeUnit; /** * @author Sim */ @Aspect public class AckuProducerAspect { private final static Logger logger = LoggerFactory.getLogger(AckuBackend.class); public AckuProducerAspect() { logger.info("Acku Producer Enabled"); } @Autowired private AckuBackend backend; @Pointcut("@annotation(io.xream.acku.api.AckuProducer))") public void cut() { } @Around("cut() && @annotation(ackuProducer) ") public Object around(ProceedingJoinPoint proceedingJoinPoint, AckuProducer ackuProducer) { long startTime = System.currentTimeMillis(); Object[] args = proceedingJoinPoint.getArgs(); Signature signature = proceedingJoinPoint.getSignature(); String logStr = signature.getDeclaringTypeName() + "." + signature.getName(); if (args == null || args.length == 0) throw new IllegalArgumentException(logStr + ", for AckuMessage body can not be null, no args"); Object body = null; if (ackuProducer.type() != Void.class) { for (Object arg : args) { if (arg.getClass() == ackuProducer.type()) { body = arg; break; } } if (body == null) throw new IllegalArgumentException(logStr + ", for AckuMessage body can not be null, reliableProducer.type: " + ackuProducer.type()); } if (body == null) { body = args[0]; } MessageTraceable tracing = null; for (Object arg : args) { if (arg instanceof MessageTraceable) { tracing = (MessageTraceable) arg; break; } } final int retryMax = ackuProducer.useTcc() ? 0 : ackuProducer.retryMax(); final String msgId = MessageIdGenerator.get(); String[] svcs = ackuProducer.svcs(); for (String svc : svcs) { if (svc.contains(",")) { throw new IllegalArgumentException(logStr + ", " + AckuProducer.class.getName() + ", svcs: " + svcs); } } Object result = this.backend.produceReliably( ackuProducer.useTcc(),// msgId,// retryMax,// ackuProducer.underConstruction(),// ackuProducer.topic(),// body,// tracing,// ackuProducer.svcs(),// () -> { try { MethodSignature ms = ((MethodSignature) signature); if (ms.getReturnType() == void.class) { proceedingJoinPoint.proceed(); return null; } else { return proceedingJoinPoint.proceed(); } } catch (Throwable e) { if (e instanceof RuntimeException){ throw (RuntimeException) e; } throw new RuntimeException(ExceptionUtil.getMessage(e)); } } ); if (ackuProducer.async() && !ackuProducer.useTcc()) return result; final long intervalBaseOne = 100;//FIXME: require test boolean isOk = false; int replayMax = 3; long interval = intervalBaseOne; int replay = 0; while (replay < replayMax) { try { TimeUnit.MILLISECONDS.sleep(interval); isOk = this.backend.tryToConfirm(msgId); if (isOk) { logger.info("handled OK time: {} ,replay = {} ,for {}", System.currentTimeMillis() - startTime, replay, logStr); return result; } replay++; } catch (Exception e) { break; } interval += intervalBaseOne; } final long intervalBaseTwo = 1000; interval = intervalBaseTwo; replayMax = replay + 3; while (replay < replayMax) { try { TimeUnit.MILLISECONDS.sleep(interval); isOk = this.backend.tryToConfirm(msgId); if (isOk) { logger.info("handled OK, time: {} ,replay = {} ,for {}", System.currentTimeMillis() - startTime, replay, logStr); return result; } replay++; } catch (Exception e) { break; } interval += intervalBaseTwo; } if (retryMax == 0) { if (ackuProducer.useTcc()) { boolean flag = this.backend.cancel(msgId); while (!flag) { // has to wait for a long time to try to cancel try { TimeUnit.MILLISECONDS.sleep(intervalBaseTwo); isOk = this.backend.tryToConfirm(msgId); if (isOk) { logger.info("handled OK, time: {} ,replay = {} ,for {}", System.currentTimeMillis() - startTime, replay, logStr); return result; } flag = this.backend.cancel(msgId); }catch (Exception e){ flag = true; } } } logger.info("handled FAIL, time: {} ,replay = {} ,for {}", System.currentTimeMillis() - startTime, replay, logStr); throw new BusyException("TIMEOUT, X TRANSACTION UN FINISHED"); } return result; } } ================================================ FILE: acku-api/src/main/java/io/xream/acku/interner/MessageIdGenerator.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.interner; import java.util.UUID; /** * @author Sim */ public class MessageIdGenerator { protected static String get(){ return UUID.randomUUID().toString().replace("-",""); } } ================================================ FILE: acku-api/src/main/java/io/xream/acku/interner/MessageTraceable.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.interner; /** * @author Sim 8966188@qq.com * @apiNote to use @EnableReliabilityManagement, @ReliableProducer and @ReliableOnConsumed,
* reliable center's DTO has to implements MessageTraceable
* sql update in order, need getTime()
*/ public interface MessageTraceable { String getTracingId(); /** * * full tx: after finishing all write, then produce next message(if necessary)
* at first persist the next message, after persist all biz data, then find the message
* by parentId,and produce it
*/ String getParentId(); /** * for server, scheduling retry, java code like:
* if ( message.getTime() < now-interval)
* for client, as condition of sql update for 2pc business, sql code like:
* update inventory set refreshAt = ${message.time} where id = 1 and refreshAt < ${message.time}
*/ long getTime(); } ================================================ FILE: acku-core/pom.xml ================================================ acku io.xream.acku 0.2.0 4.0.0 acku-core io.xream.acku acku-api 0.2.0 io.xream.acku acku-entity 0.2.0 org.springframework spring-web ================================================ FILE: acku-core/src/main/java/io/xream/acku/api/acku/AckuMessageService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.api.acku; import io.xream.acku.bean.entity.AckuMessage; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.Qr; import java.util.List; import java.util.Map; /** * @author Sim */ public interface AckuMessageService { boolean create(AckuMessage message); boolean refresh(Qr condition); boolean remove(String id); List listByCond(Q criteria); List> listByX(Q.X x); AckuMessage get(String msgId); } ================================================ FILE: acku-core/src/main/java/io/xream/acku/api/acku/DtoConverter.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.api.acku; import io.xream.acku.bean.dto.AckuDto; /** * @author Sim */ public interface DtoConverter { AckuDto convertOnConsumed(Object message); } ================================================ FILE: acku-core/src/main/java/io/xream/acku/api/acku/FailedService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.api.acku; import io.xream.acku.bean.entity.AckuMessage; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.Qr; import java.util.List; import java.util.Map; /** * @author Sim */ public interface FailedService { boolean refresh(Qr condition); boolean refreshUnSafe(Qr condition); List> listByX(Q.X x); } ================================================ FILE: acku-core/src/main/java/io/xream/acku/api/acku/MessageResultService.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.api.acku; import io.xream.acku.bean.entity.MessageResult; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.Qr; import java.util.List; /** * @author Sim */ public interface MessageResultService { boolean create(MessageResult result); boolean refresh(Qr qr); boolean removeByMessageId(String id); List listByCond(Q q); } ================================================ FILE: acku-core/src/main/java/io/xream/acku/backend/AckuBackendImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.backend; import io.xream.acku.TCCTopic; import io.xream.acku.api.acku.DtoConverter; import io.xream.acku.bean.constant.MessageStatus; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.bean.dto.ConsumedAckuDto; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.bean.entity.MessageResult; import io.xream.acku.bean.exception.AckuExceptioin; import io.xream.acku.interner.AckuBackend; import io.xream.acku.interner.MessageTraceable; import io.xream.acku.remote.acku.AckuServiceRemote; import io.xream.internal.util.ExceptionUtil; import io.xream.x7.base.GenericObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.concurrent.Callable; /** * @author Sim */ public class AckuBackendImpl implements AckuBackend { @Autowired private AckuServiceRemote ackuServiceRemote; @Autowired private DtoConverter dtoConverter; @Override @Transactional public Object produceReliably(Boolean useTcc, String id, int retryMax, boolean underConstruction, String topic, Object body, MessageTraceable MessageTraceable, String[] svcs, Callable callable) { String tracingId = null; if (MessageTraceable != null){ tracingId = MessageTraceable.getTracingId(); } AckuMessage reliableMessage = new AckuMessage(id,retryMax,underConstruction,tracingId,topic, body, Arrays.asList(svcs)); reliableMessage.setTcc(useTcc ? TCCTopic._TCC_TRY.name() : TCCTopic._TCC_NONE.name()); if (useTcc) { reliableMessage.resetTopic(TCCTopic._TCC_TRY); } AckuDto dto = new AckuDto(reliableMessage); dto = this.ackuServiceRemote.create(dto); //STEP 1 Object result = null; try { result = callable.call(); //STEP 2 } catch (Exception e) { throw new AckuExceptioin(ExceptionUtil.getMessage(e)); } this.ackuServiceRemote.produce(dto); //STEP 3 return result; } @Override @Transactional public void onConsumed(String svc, Object message, Runnable runnable) { AckuDto dto = this.dtoConverter.convertOnConsumed(message); if (dto.isConsumed(svc)) return; List list = dto.getResultList(); MessageResult mr = null; for (MessageResult messageResult : list) { if (messageResult.getSvc().equals(svc)) { //可以是spring.application.name mr = messageResult; break; } } try { runnable.run(); //STEP 1 } catch (Exception e) { throw new AckuExceptioin(ExceptionUtil.getMessage(e)); } ConsumedAckuDto cdto = dto.consume(svc,dto.getMessage().getTcc(), mr); ackuServiceRemote.consume(cdto); //STEP 2 } @Override public boolean createNext(String id, int retryMax, String nextTopic, Object nextBody,Object preMessage,String[] svcs) { AckuDto dto = this.dtoConverter.convertOnConsumed(preMessage); String parentId = dto.getParentId();//先get parentId GenericObject go = new GenericObject(nextBody); Date date = new Date(); AckuMessage reliableMessage = dto.getMessage(); reliableMessage.setId(id); reliableMessage.setTracingId(dto.getTracingId()); reliableMessage.setParentId(parentId); reliableMessage.setTopic(nextTopic); reliableMessage.setBody(go); reliableMessage.setTcc(TCCTopic._TCC_NONE.name()); reliableMessage.setRetryMax(retryMax); reliableMessage.setUnderConstruction(false); reliableMessage.setSvcDone("&"); reliableMessage.setSvcList(Arrays.asList(svcs)); reliableMessage.setSendAt(0); reliableMessage.setRetryCount(0L); reliableMessage.setCreateAt(date); reliableMessage.setRefreshAt(date); reliableMessage.setStatus(MessageStatus.NEXT.name()); dto.setMessage(reliableMessage); dto.setResultList(new ArrayList<>()); this.ackuServiceRemote.create(dto); return true; } @Override public boolean tryToConfirm(String msgId) { return this.ackuServiceRemote.tryToConfirm(msgId); } @Override public boolean cancel(String msgId) { return this.ackuServiceRemote.cancel(msgId); } } ================================================ FILE: acku-core/src/main/java/io/xream/acku/remote/acku/AckuServiceRemote.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.remote.acku; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.bean.dto.ConsumedAckuDto; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.service.annotation.HttpExchange; import org.springframework.web.service.annotation.PostExchange; /** * @author Sim */ @HttpExchange("http://acku-app/message" ) public interface AckuServiceRemote { @PostExchange("/create") AckuDto create(@RequestBody AckuDto dto); @PostExchange("/produce") boolean produce(@RequestBody AckuDto dto); @PostExchange("/consume") boolean consume(@RequestBody ConsumedAckuDto dto); @PostExchange("/tryToConfirm") boolean tryToConfirm(@RequestBody String msgId); @PostExchange("/cancel") boolean cancel(@RequestBody String msgId); } ================================================ FILE: acku-core/src/main/java/io/xream/acku/remote/acku/FailedServiceRemote.java ================================================ package io.xream.acku.remote.acku; import io.xream.acku.bean.entity.AckuMessage; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; import java.util.List; /** * @author Sim */ @HttpExchange("http://acku-app/failed" ) public interface FailedServiceRemote { @GetExchange(value = "/find") List findFailed(); @GetExchange(value = "/find/{topic}") List findFailedByTopic(@PathVariable("topic") String topic); @GetExchange(value = "/retryAll") boolean retryAll(); @GetExchange(value = "/retry/{messageId}") boolean retry(@PathVariable("messageId") String messageId); } ================================================ FILE: acku-entity/pom.xml ================================================ acku io.xream.acku 0.2.0 4.0.0 acku-entity io.xream.acku acku-api 0.2.0 ================================================ FILE: acku-entity/src/main/java/io/xream/acku/TCCTopic.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku; /** * @author Sim */ public enum TCCTopic { _TCC_NONE, _TCC_TRY, _TCC_CONFIRM, _TCC_CANCEL; } ================================================ FILE: acku-entity/src/main/java/io/xream/acku/bean/constant/MessageStatus.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.bean.constant; /** * @author Sim */ public enum MessageStatus { NEXT, BLANK, SEND, OK, //定时任务删除记录 FAIL; //可人工补偿数据 } ================================================ FILE: acku-entity/src/main/java/io/xream/acku/bean/dto/AckuDto.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.bean.dto; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.bean.entity.MessageResult; import io.xream.acku.interner.MessageTraceable; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * @author Sim */ public class AckuDto implements MessageTraceable, Serializable { private static final long serialVersionUID = 4467713246475538455L; private AckuMessage message; private List resultList = new ArrayList<>(); public AckuDto(){ } public AckuDto(AckuMessage message){ this.message = message; } public AckuDto(AckuMessage message, List resultList ){ this.message = message; this.resultList = resultList; } public AckuMessage getMessage() { return message; } public void setMessage(AckuMessage message) { this.message = message; } public List getResultList() { return resultList; } public void setResultList(List resultList) { this.resultList = resultList; } public boolean isConsumed(String svc){ if (message == null) return true; return message.getSvcDone().contains(svc); } public boolean isUnRegistered(String svc){ if (message == null) return true; return ! message.getSvcList().contains(svc); } public ConsumedAckuDto consume(String consumedSvc, String tcc, MessageResult consumedMessageResult ) { ConsumedAckuDto dto = new ConsumedAckuDto(); dto.setMsgId(this.message.getId()); dto.setSvc(consumedSvc); dto.setTcc(tcc); if (consumedMessageResult != null){ dto.setResultId(consumedMessageResult.getId()); } return dto; } public ConsumedAckuDto consume(String consumedSvc) { ConsumedAckuDto dto = new ConsumedAckuDto(); dto.setMsgId(this.message.getId()); dto.setSvc(consumedSvc); return dto; } @Override public String toString() { return "ReliableDto{" + "message=" + message + ", resultList=" + resultList + '}'; } @Override public String getTracingId() { return this.message.getTracingId(); } @Override public String getParentId() { return this.message.getId(); } @Override public long getTime() { return this.message.getSendAt(); } } ================================================ FILE: acku-entity/src/main/java/io/xream/acku/bean/dto/ConsumedAckuDto.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.bean.dto; import io.xream.acku.TCCTopic; import java.io.Serializable; /** * @author Sim */ public class ConsumedAckuDto implements Serializable { private static final long serialVersionUID = 4467713246475543155L; private String msgId; private String svc; private String resultId; private String tcc; public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getSvc() { return svc; } public void setSvc(String svc) { this.svc = svc; } public String getResultId() { return resultId; } public void setResultId(String resultId) { this.resultId = resultId; } public Boolean getUseTcc() { return ! tcc.equals(TCCTopic._TCC_NONE); } public String getTcc() { return tcc; } public void setTcc(String tcc) { this.tcc = tcc; } @Override public String toString() { return "ConsumedReliableDto{" + "msgId='" + msgId + '\'' + ", svc='" + svc + '\'' + ", resultId='" + resultId + '\'' + ", tcc='" + tcc + '\'' + '}'; } } ================================================ FILE: acku-entity/src/main/java/io/xream/acku/bean/entity/AckuMessage.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.bean.entity; import io.xream.acku.TCCTopic; import io.xream.sqli.annotation.X; import io.xream.x7.base.GenericObject; import java.io.Serializable; import java.util.Date; import java.util.List; /** * @author Sim */ public class AckuMessage implements Serializable { private static final long serialVersionUID = 2002563420656592953L; @X.Key private String id; private String tracingId; private String parentId; private int retryMax; private Boolean underConstruction; private String status; private List svcList; private String tcc; private String svcDone; private String topic; private GenericObject body; private Date refreshAt; private Date createAt; private Long retryCount; private long sendAt; public AckuMessage(){} public AckuMessage(String topic, Object body, List svcList){ this.topic = topic; if (body instanceof GenericObject){ this.body = (GenericObject)body; }else { this.body = new GenericObject<>((T) body); } this.svcList = svcList; } public AckuMessage(String tracingId, String topic, Object body, List svcList){ this.tracingId = tracingId; this.topic = topic; if (body instanceof GenericObject){ this.body = (GenericObject)body; }else { this.body = new GenericObject<>((T) body); } this.svcList = svcList; } public AckuMessage(String id, int retryMax, boolean underConstruction, String tracingId, String topic, Object body, List svcList) { this.id = id; this.retryMax = retryMax; this.underConstruction = underConstruction; this.tracingId = tracingId; this.topic = topic; if (body instanceof GenericObject){ this.body = (GenericObject)body; }else { this.body = new GenericObject((T) body); } this.svcList = svcList; } public void resetTopic(TCCTopic tccTopic) { switch (tccTopic){ case _TCC_TRY: if (!this.topic.endsWith(tccTopic.name())){ this.topic += tccTopic.name(); } break; default: if (!this.topic.endsWith(tccTopic.name())){ this.topic = this.topic.replace(TCCTopic._TCC_TRY.name(),""); this.topic += tccTopic.name(); } } } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTracingId() { return tracingId; } public void setTracingId(String tracingId) { this.tracingId = tracingId; } public String getParentId() { return parentId; } public void setParentId(String parentId) { this.parentId = parentId; } public int getRetryMax() { return retryMax; } public void setRetryMax(int retryMax) { this.retryMax = retryMax; } public Boolean getUnderConstruction() { return underConstruction; } public void setUnderConstruction(Boolean underConstruction) { this.underConstruction = underConstruction; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getTcc() { return tcc; } public void setTcc(String tcc) { this.tcc = tcc; } public List getSvcList() { return svcList; } public void setSvcList(List svcList) { this.svcList = svcList; } public String getSvcDone() { return svcDone; } public void setSvcDone(String svcDone) { this.svcDone = svcDone; } public Boolean getUseTcc() { if (this.tcc == null) return false; return ! this.tcc.equals(TCCTopic._TCC_NONE.name()); } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public GenericObject getBody() { return body; } public void setBody(GenericObject body) { this.body = body; } public Date getRefreshAt() { return refreshAt; } public void setRefreshAt(Date refreshAt) { this.refreshAt = refreshAt; } public Date getCreateAt() { return createAt; } public void setCreateAt(Date createAt) { this.createAt = createAt; } public Long getRetryCount() { return retryCount; } public void setRetryCount(Long retryCount) { this.retryCount = retryCount; } public long getSendAt() { return sendAt; } public void setSendAt(long sendAt) { this.sendAt = sendAt; } @Override public String toString() { return "ReliableMessage{" + "id='" + id + '\'' + ", tracingId='" + tracingId + '\'' + ", parentId='" + parentId + '\'' + ", retryMax='" + retryMax + '\'' + ", underConstruction='" + underConstruction + '\'' + ", status='" + status + '\'' + ", svcList='" + svcList + '\'' + ", svcDone='" + svcDone + '\'' + ", tcc='" + tcc + '\'' + ", topic='" + topic + '\'' + ", body='" + body + '\'' + ", refreshAt=" + refreshAt + ", createAt=" + createAt + ", retryCount=" + retryCount + ", sendAt=" + sendAt + '}'; } } ================================================ FILE: acku-entity/src/main/java/io/xream/acku/bean/entity/MessageResult.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.bean.entity; import io.xream.sqli.annotation.X; import java.io.Serializable; import java.util.Date; /** * @author Sim */ public class MessageResult implements Serializable { private static final long serialVersionUID = 4901898223573975818L; @X.Key private String id; private String msgId; private String status; private String svc; private Date createAt; private Date refreshAt; public MessageResult(){ } public MessageResult(String svc){ this.svc = svc; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getSvc() { return svc; } public void setSvc(String svc) { this.svc = svc; } public Date getCreateAt() { return createAt; } public void setCreateAt(Date createAt) { this.createAt = createAt; } public Date getRefreshAt() { return refreshAt; } public void setRefreshAt(Date refreshAt) { this.refreshAt = refreshAt; } @Override public String toString() { return "MessageResult{" + "id='" + id + '\'' + ", msgId='" + msgId + '\'' + ", svc='" + svc + '\'' + ", status='" + status + '\'' + ", createAt=" + createAt + ", refreshAt=" + refreshAt + '}'; } } ================================================ FILE: acku-entity/src/main/java/io/xream/acku/bean/exception/AckuExceptioin.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.bean.exception; /** * @author Sim */ public class AckuExceptioin extends RuntimeException { private String message; public AckuExceptioin(){} public AckuExceptioin(String message){ this.message = message; } @Override public String getMessage() { return message; } } ================================================ FILE: acku-message-center/.gitignore ================================================ */target/ */classes/ *.jar *.class *.iml */application-test.properties */application-prod.properties ================================================ FILE: acku-message-center/acku-app/Dockerfile ================================================ FROM java:openjdk-8-jre-alpine LABEL version="0.2.0" MAINTAINER Sim <8966188@qq.com> ENV ACTIVE test ENV JAVA_OPTS -Xms1024m -Xmx1024m -Xmn320m ADD target/acku-app-0.2.0.jar /data/deploy/acku-app.jar CMD ["mkdir -p /data/logs"] VOLUME /data/logs /data/logs EXPOSE 80 ENTRYPOINT ["java","-Dspring.profiles.active=${ACTIVE}","-jar", "/data/deploy/acku-app.jar"] ================================================ FILE: acku-message-center/acku-app/pom.xml ================================================ acku-message-center io.xream.acku 0.2.0 4.0.0 acku-app io.xream.acku acku-service 0.2.0 org.springframework.boot spring-boot-maven-plugin 3.2.2 repackage io.xream.acku.App src/main/resources ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/App.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku; import io.xream.x7.EnableDateToLongForJackson; import io.xream.x7.EnableX7Repository; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * @author Sim */ @SpringBootApplication @EnableTransactionManagement @EnableX7Repository(baseTypeSupported = true) @EnableDateToLongForJackson public class App { public static void main(String[] args) { SpringApplication.run(App.class); } // @Bean // public ProducerCustomizer producerCustomizer(){ // // return new ProducerCustomizer() { // @Override // public Producer customize() { // return null; //Default is io.xream.acku.config.KafkaProducer, you can use RocketMQ,RabbitMQ .... // } // }; // } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/config/DefaultKafkaProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.config; import io.xream.acku.bean.exception.AckuExceptioin; import io.xream.acku.produce.Producer; import io.xream.internal.util.ExceptionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.kafka.core.KafkaTemplate; /** * @author Sim */ public class DefaultKafkaProducer implements Producer { private final static Logger logger = LoggerFactory.getLogger(DefaultKafkaProducer.class); private KafkaTemplate kafkaTemplate; public DefaultKafkaProducer(KafkaTemplate kafkaTemplate) { this.kafkaTemplate = kafkaTemplate; } @Override public boolean send(String topic, String message) { try { kafkaTemplate.send(topic, message).get(); if (logger.isDebugEnabled()) { logger.debug("Kafka produce, topic = {}, data = {}", topic, message); } }catch (Throwable ex) { String str = ExceptionUtil.getMessage(ex); logger.error("Kafka produce error, ex = {}, topic = {}, data = {}", str, topic, message); throw new AckuExceptioin(str); } return true; } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/config/KafkaConfig.java ================================================ package io.xream.acku.config; import io.xream.acku.produce.Producer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.annotation.Order; @Configuration /** * @author Sim */ public class KafkaConfig { @Autowired private NextKafkaProperties nextKafkaProperties; @Bean(name = "producer") @Primary @Order(2) public Producer producer(){ return new ProducerWrapper(); } @Bean(name = "nextProducer") @Order(3) public Producer nextProducer() { NextKafkaProducer nextKafkaProducer = new NextKafkaProducer(); nextKafkaProducer.init(this.nextKafkaProperties.getProducer().buildProperties(null)); return nextKafkaProducer; } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/config/MQConfig.java ================================================ package io.xream.acku.config; import io.xream.acku.produce.Producer; import org.springframework.beans.FatalBeanException; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.kafka.core.KafkaTemplate; /** * @author Sim */ public class MQConfig implements ApplicationListener { @Override public void onApplicationEvent(ApplicationStartedEvent event) { ProducerWrapper wrapper = event.getApplicationContext().getBean(ProducerWrapper.class); ProducerCustomizer customizer = null; try { customizer = event.getApplicationContext().getBean(ProducerCustomizer.class); } catch (Exception e) { } Producer producer = null; if (customizer != null){ producer = customizer.customize(); } if (producer == null) { KafkaTemplate kafkaTemplate = null; try { kafkaTemplate = event.getApplicationContext().getBean(KafkaTemplate.class); } catch (Exception e) { throw new FatalBeanException("can't find any producer, no config like Kafka"); } producer = new DefaultKafkaProducer(kafkaTemplate); } wrapper.setProducer(producer); } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/config/NextKafkaProducer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.config; import io.xream.acku.produce.Producer; import io.xream.internal.util.ExceptionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.ProducerFactory; import java.util.Map; /** * @author Sim */ public class NextKafkaProducer implements Producer { private final static Logger logger = LoggerFactory.getLogger(NextKafkaProducer.class); private KafkaTemplate kafkaTemplate; protected void init(Map map){ KafkaProperties kafkaProperties = new KafkaProperties(); Map configMap = kafkaProperties.buildProducerProperties(); configMap.putAll(map); ProducerFactory producerFactory = new DefaultKafkaProducerFactory<>(configMap); kafkaTemplate = new KafkaTemplate<>(producerFactory); } @Override public boolean send(String topic, String message) { try { this.kafkaTemplate.send(topic,message).get(); logger.info("Kafka produce, topic = {}, data = {}", topic, message); }catch (Throwable ex) { String str = ExceptionUtil.getMessage(ex); logger.error("Kafka produce error, ex = {}, topic = {}, data = {}", str, topic, message); return false; } return true; } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/config/NextKafkaProperties.java ================================================ package io.xream.acku.config; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * @author Sim */ @EnableConfigurationProperties @Configuration @ConfigurationProperties(prefix = "next.kafka") public class NextKafkaProperties { private final KafkaProperties.Producer producer = new KafkaProperties.Producer(); public KafkaProperties.Producer getProducer() { return producer; } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/config/ProducerCustomizer.java ================================================ package io.xream.acku.config; import io.xream.acku.produce.Producer; /** * @author Sim */ public interface ProducerCustomizer { Producer customize(); } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/config/ProducerWrapper.java ================================================ package io.xream.acku.config; import io.xream.acku.produce.Producer; /** * @author Sim */ public class ProducerWrapper implements Producer { private Producer producer; public void setProducer(Producer producer) { this.producer = producer; } @Override public boolean send(String topic, String message) { return producer.send(topic, message); } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/controller/AckuController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.controller; import io.xream.acku.TCCTopic; import io.xream.acku.api.acku.AckuMessageService; import io.xream.acku.api.acku.MessageResultService; import io.xream.acku.bean.constant.MessageStatus; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.bean.dto.ConsumedAckuDto; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.bean.entity.MessageResult; import io.xream.acku.bean.exception.AckuExceptioin; import io.xream.acku.produce.Producer; import io.xream.internal.util.JsonX; import io.xream.internal.util.StringUtil; import io.xream.sqli.builder.QrB; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Date; import java.util.List; import java.util.UUID; @Transactional @RestController @RequestMapping("/message") /** * @author Sim */ public class AckuController { @Autowired private AckuMessageService AckuMessageService; @Autowired private MessageResultService messageResultService; @Autowired private Producer producer; @Resource(name = "nextProducer") private Producer nextProducer; @Autowired private io.xream.acku.controller.TccBusiness tccBusiness; @Autowired private io.xream.acku.controller.NextBusiness nextBusiness; @RequestMapping("/create") public AckuDto create(@RequestBody AckuDto dto) { AckuMessage AckuMessage = dto.getMessage(); if (AckuMessage == null) throw new AckuExceptioin("AckuMessage == null"); Date date = new Date(); String messageId = AckuMessage.getId(); if (messageId == null) { messageId = UUID.randomUUID().toString(); messageId = messageId.replace("-", ""); } AckuMessage.setId(messageId); AckuMessage.setCreateAt(date); AckuMessage.setSendAt(date.getTime()); AckuMessage.setSvcDone(io.xream.acku.controller.TccBusiness.SVC_DONE_PREFIX); if (StringUtil.isNullOrEmpty(AckuMessage.getTracingId())) { AckuMessage.setTracingId(messageId); } if (StringUtil.isNullOrEmpty(AckuMessage.getStatus())){ AckuMessage.setStatus(MessageStatus.BLANK.toString()); } List resultList = dto.getResultList(); this.AckuMessageService.create(AckuMessage); for (MessageResult result : resultList) { result.setId(result.getSvc() + "_" + messageId); result.setMsgId(messageId); result.setStatus(MessageStatus.BLANK.toString()); result.setCreateAt(date); this.messageResultService.create(result); } return dto; } @RequestMapping("/produce") public boolean produce(@RequestBody AckuDto dto) { AckuMessage AckuMessage = dto.getMessage(); if (AckuMessage == null) throw new AckuExceptioin("AckuMessage == null"); Date date = new Date(); AckuMessage.setRefreshAt(date); AckuMessage.setStatus(MessageStatus.SEND.toString()); boolean flag = this.AckuMessageService.refresh( QrB.of(AckuMessage.class) .refresh("status",AckuMessage.getStatus()) .refresh("sendAt", AckuMessage.getSendAt()) .refresh("refreshAt", AckuMessage.getRefreshAt()) .eq("id", AckuMessage.getId()).build() ); if (!flag) throw new AckuExceptioin("AckuMessage refresh persist failed"); /* * MQ */ String topic = AckuMessage.getTopic(); return producer.send(topic, JsonX.toJson(dto)); } @RequestMapping("/consume") public boolean consume(@RequestBody ConsumedAckuDto dto) { String msgId = dto.getMsgId(); String svc = dto.getSvc(); if (StringUtil.isNullOrEmpty(msgId)) throw new AckuExceptioin("ConsumedAckuDto lack of msgId: " + dto); if (StringUtil.isNullOrEmpty(svc)) return true; Date date = new Date(); String resultId = dto.getResultId(); if (StringUtil.isNotNull(resultId)) { boolean flag = this.messageResultService.refresh( QrB.of(MessageResult.class) .refresh("status", dto.getTcc()) .refresh("refreshAt", date) .eq("id", resultId).eq("status", MessageStatus.BLANK).build() ); if (!flag) throw new AckuExceptioin("Problem with refresh resultMessage, id = " + resultId); }else { String id = svc + "_" + msgId; if (id.length() > 55) { id = id.substring(0,55); } MessageResult messageResult = new MessageResult(); messageResult.setId(id); messageResult.setMsgId(msgId); messageResult.setStatus(dto.getTcc()); messageResult.setSvc(svc); messageResult.setCreateAt(date); messageResult.setRefreshAt(date); try { boolean flag = this.messageResultService.create(messageResult); if (!flag) throw new AckuExceptioin("Problem with create resultMessage, id = " + id); }catch (Exception e){ throw new AckuExceptioin("Problem with create resultMessage, id = " + id); } } return this.AckuMessageService.refresh( QrB.of(AckuMessage.class) .refresh("svcDone = CONCAT(svcDone, ? , '" + io.xream.acku.controller.TccBusiness.SVC_DONE_PREFIX +"' )", svc) .refresh("refreshAt", date) .eq("id", msgId) .eq("tcc", dto.getUseTcc() ? dto.getTcc() : null).build() ); } @RequestMapping("/tryToConfirm") public boolean tryToConfirm(@RequestBody String msgId) { AckuMessage message = this.AckuMessageService.get(msgId); if (message == null) return true; for (Object svc : message.getSvcList()){ if (!message.getSvcDone().contains(svc.toString())) return false; } if (message.getTcc().equals(TCCTopic._TCC_NONE.name())) return true; boolean flag = this.tccBusiness.confirm(message,AckuMessageService,producer); if (!flag) throw new RuntimeException("ERROR, at AckuProducer TCC confirm"); try { this.nextBusiness.produce(message.getId(), AckuMessageService, nextProducer); }catch (Exception e) { // 需要任务补偿 } return flag; } @RequestMapping("/cancel") public boolean cancel(@RequestBody String msgId) { AckuMessage message = this.AckuMessageService.get(msgId); if (message == null) return false; if (message.getTcc().equals(TCCTopic._TCC_NONE.name())) return false; return this.tccBusiness.cancel(message,AckuMessageService,producer); } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/controller/FailedController.java ================================================ package io.xream.acku.controller; import io.xream.acku.api.acku.FailedService; import io.xream.acku.bean.constant.MessageStatus; import io.xream.acku.bean.entity.AckuMessage; import io.xream.sqli.builder.Direction; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.QB; import io.xream.sqli.builder.QrB; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; @RestController @RequestMapping("/failed") /** * @author Sim */ public class FailedController { @Autowired private FailedService failedService; @RequestMapping(value = "/find", method = RequestMethod.GET) public List> findFailed() { QB.X builder = QB.x(); builder.select("id","status","retryMax","topic").from(AckuMessage.class); builder.eq("status", MessageStatus.FAIL).gt("retryMax", 0); builder.sort("topic", Direction.DESC); Q.X x = builder.build(); List> mapList = this.failedService.listByX(x); return mapList; } @RequestMapping(value = "/find/{topic}", method = RequestMethod.GET) public List> findFailedByTopic(@PathVariable("topic") String topic) { QB.X builder = QB.x(); builder.select("id","status","retryMax","topic").from(AckuMessage.class); builder.eq("status", MessageStatus.FAIL).eq("topic",topic).gt("retryMax", 0); Q.X x = builder.build(); List> mapList = this.failedService.listByX(x); return mapList; } @RequestMapping(value = "/retryAll", method = RequestMethod.GET) public boolean retryAll(){ return this.failedService.refreshUnSafe( QrB.of(AckuMessage.class).refresh("status",MessageStatus.SEND) .refresh("retryCount",0) .eq("status",MessageStatus.FAIL).gt("retryMax",0).build() ); } @RequestMapping(value = "/retry/{messageId}", method = RequestMethod.GET) public boolean retry(@PathVariable("messageId") String messageId){ return this.failedService.refresh( QrB.of(AckuMessage.class).refresh("status",MessageStatus.SEND) .refresh("retryCount",0) .eq("id",messageId).build() ); } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/controller/NextBusiness.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.controller; import io.xream.acku.api.acku.AckuMessageService; import io.xream.acku.bean.constant.MessageStatus; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.bean.exception.AckuExceptioin; import io.xream.acku.produce.Producer; import io.xream.internal.util.JsonX; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.QB; import io.xream.sqli.builder.QrB; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; @Component /** * @author Sim */ public class NextBusiness { @Transactional public boolean produce(String parentId, AckuMessageService AckuMessageService, Producer producer){ QB builder = QB.of(AckuMessage.class); builder.eq("parentId",parentId); builder.eq("status",MessageStatus.NEXT); Q q = builder.build(); List list = AckuMessageService.listByCond(q); Date date = new Date(); for (AckuMessage AckuMessage : list) { AckuMessageService.refresh( QrB.of(AckuMessage.class).refresh("status",MessageStatus.SEND) .refresh("sendAt",date.getTime()) .refresh("refreshAt", date) .eq("id", AckuMessage.getId()).build() ); } for (AckuMessage AckuMessage : list) { AckuDto dto = new AckuDto(); dto.setMessage(AckuMessage); String message = JsonX.toJson(dto); String topic = AckuMessage.getTopic(); boolean flag = producer.send(topic, message); if (!flag) { throw new AckuExceptioin("Next produce failed, topic: " + topic + ", message: " + message); } } return true; } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/controller/ScheduleAckuController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.controller; import io.xream.acku.TCCTopic; import io.xream.acku.api.acku.AckuMessageService; import io.xream.acku.api.acku.MessageResultService; import io.xream.acku.bean.constant.MessageStatus; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.bean.entity.MessageResult; import io.xream.acku.produce.Producer; import io.xream.internal.util.JsonX; import io.xream.internal.util.StringUtil; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.QB; import io.xream.sqli.builder.QrB; import io.xream.x7.base.GenericObject; import jakarta.annotation.Resource; import org.apache.commons.collections.MapUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.*; @RestController @RequestMapping("/schedule") /** * @author Sim */ public class ScheduleAckuController { @Autowired private AckuMessageService AckuMessageService; @Autowired private MessageResultService messageResultService; @Autowired private Producer producer; @Resource(name = "nextProducer") private Producer nextProducer; @Autowired private TccBusiness tccBusiness; @Autowired private NextBusiness nextBusiness; @Value("${acku.retry.duration:5000}") private long ackuRetryDuration; private long checkStatusDuration = 1400; @RequestMapping(value = "/tryToProduceNext",method = RequestMethod.GET) public boolean tryToProduceNext(){ QB builder = QB.of(AckuMessage.class); builder.eq("status",MessageStatus.NEXT); Q q = builder.build(); List list = this.AckuMessageService.listByCond(q); Map> map = new HashMap<>(); for (AckuMessage AckuMessage : list) { String parentId = AckuMessage.getParentId(); List valueList = map.get(parentId); if (valueList == null) { valueList = new ArrayList<>(); map.put(parentId,valueList); } valueList.add(AckuMessage); } for (String parentId : map.keySet()){ AckuMessage AckuMessage = this.AckuMessageService.get(parentId); if (AckuMessage.getStatus().equals(MessageStatus.OK.name())){ this.nextBusiness.produce(parentId,AckuMessageService,nextProducer); } } return true; } @RequestMapping(value = "/tryToFinish", method = RequestMethod.GET) public boolean tryToFinish() { Date createAt = new Date(System.currentTimeMillis() - checkStatusDuration); QB.X builder = QB.x(); builder.select("id","svcDone","svcList","retryCount","retryMax","tcc","body"); builder.from(AckuMessage.class); builder.eq("status", MessageStatus.SEND); builder.lt("createAt", createAt); Q.X x = builder.build(); List> list = this.AckuMessageService.listByX(x); if (list.isEmpty()) return true; Date date = new Date(); for (Map map : list) { List svcList = (List)MapUtils.getObject(map, "svcList"); String tcc = MapUtils.getString(map, "tcc"); Object bodyObj = MapUtils.getObject(map, "body"); GenericObject go = (GenericObject) bodyObj; AckuMessage AckuMessage = new AckuMessage(); AckuMessage.setId(MapUtils.getString(map, "id")); AckuMessage.setSvcDone(MapUtils.getString(map, "svcDone")); AckuMessage.setSvcList(svcList); AckuMessage.setBody(go); AckuMessage.setTcc(tcc); AckuMessage.setRetryCount(MapUtils.getLongValue(map, "retryCount")); AckuMessage.setRetryMax(MapUtils.getIntValue(map, "retryMax")); String svcDone = AckuMessage.getSvcDone(); if (StringUtil.isNullOrEmpty(svcDone)) continue; boolean flag = true; for (String svc : svcList) { flag &= svcDone.contains(svc); } if (AckuMessage.getTcc().equals(TCCTopic._TCC_TRY.name())) { if (!flag) { if (AckuMessage.getRetryCount() >= AckuMessage.getRetryMax()) { cancel(AckuMessage.getId()); } } } else { if (flag) { this.AckuMessageService.refresh( QrB.of(AckuMessage.class) .refresh("status", MessageStatus.OK) .refresh("refreshAt", date) .eq("id", AckuMessage.getId()).build() ); try { this.nextBusiness.produce(AckuMessage.getId(),AckuMessageService,nextProducer); }catch (Exception e) { } } } } return true; } @RequestMapping(value = "/listForRetry", method = RequestMethod.GET) public List listForRetry() { long rrd = ackuRetryDuration < 5000 ? 5000 : ackuRetryDuration; long now = System.currentTimeMillis(); final long sendAt = now - rrd; QB.X builder = QB.x(); builder.select("id","svcList","svcDone","retryCount","retryMax","tcc","topic","body"); builder.from(AckuMessage.class); builder.eq("status", MessageStatus.SEND); // builder.x("retryCount < retryMax"); //需要人工补单 builder.lt("sendAt", sendAt); List> list = this.AckuMessageService.listByX(builder.build()); List rmList = new ArrayList<>(); if (list.isEmpty()) return rmList; for (Map map : list) { Object bodyObj = MapUtils.getObject(map, "body"); GenericObject go = (GenericObject) bodyObj; List svcList = (List) MapUtils.getObject(map, "svcList"); AckuMessage AckuMessage = new AckuMessage(); AckuMessage.setId(MapUtils.getString(map, "id")); AckuMessage.setTopic(MapUtils.getString(map, "topic")); AckuMessage.setSvcList(svcList); AckuMessage.setSvcDone(MapUtils.getString(map, "svcDone")); AckuMessage.setRetryCount(MapUtils.getLongValue(map, "retryCount")); AckuMessage.setRetryMax(MapUtils.getIntValue(map, "retryMax")); AckuMessage.setBody(go); AckuMessage.setTcc(MapUtils.getString(map, "tcc")); AckuMessage.setSendAt(sendAt); rmList.add(AckuMessage); } return rmList; } @Transactional @RequestMapping(value = "/retry") public boolean retry(@RequestBody AckuMessage AckuMessage) { Date date = new Date(); if (AckuMessage.getRetryCount() < AckuMessage.getRetryMax()) { QB builder = QB.of(MessageResult.class); builder.eq("msgId", AckuMessage.getId()); Q q = builder.build(); List list = this.messageResultService.listByCond(q); AckuDto dto = new AckuDto(); for (MessageResult messageResult : list) { if (MessageStatus.BLANK.toString().equals(messageResult.getStatus())) { dto.getResultList().add(messageResult); } } dto.setMessage(AckuMessage); AckuMessage.setRetryCount(AckuMessage.getRetryCount() + 1); AckuMessage.setSendAt(date.getTime());// IMPORTANT AckuMessage.setRefreshAt(date); this.AckuMessageService.refresh( QrB.of(AckuMessage.class) .refresh("retryCount", AckuMessage.getRetryCount() + 1) .refresh("sendAt", AckuMessage.getSendAt()) .refresh("refreshAt", AckuMessage.getRefreshAt()) .eq("id", AckuMessage.getId()).build() ); /* * MQ */ String topic = AckuMessage.getTopic(); producer.send(topic, JsonX.toJson(dto)); } else { if (AckuMessage.getTcc().equals(TCCTopic._TCC_TRY.name())) { // TODO: confirm step, exception no rollback cancel(AckuMessage.getId()); } else { //进入人工补单审核流程 this.AckuMessageService.refresh( QrB.of(AckuMessage.class) .refresh("status", MessageStatus.FAIL) .refresh("refreshAt", date) .eq("id", AckuMessage.getId()).build() ); } } return true; } @RequestMapping(value = "/clean", method = RequestMethod.GET) public boolean clean() { List cleanStatusList = new ArrayList<>(); cleanStatusList.add(MessageStatus.OK.toString()); cleanStatusList.add(MessageStatus.BLANK.toString()); QB.X builder = QB.x(); builder.select("id").from(AckuMessage.class); builder.eq("underConstruction", false); builder.in("status", cleanStatusList); List> list = this.AckuMessageService.listByX(builder.build()); for (Map map : list) { String id = MapUtils.getString(map, "id"); if (StringUtil.isNullOrEmpty(id)) continue; this.messageResultService.removeByMessageId(id); this.AckuMessageService.remove(id); } return true; } @Transactional public boolean cancel(String id) { AckuMessage AckuMessage = this.AckuMessageService.get(id); return this.tccBusiness.cancel(AckuMessage, AckuMessageService, producer); } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/controller/TccBusiness.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.controller; import io.xream.acku.TCCTopic; import io.xream.acku.api.acku.AckuMessageService; import io.xream.acku.bean.constant.MessageStatus; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.produce.Producer; import io.xream.internal.util.JsonX; import io.xream.sqli.builder.QrB; import io.xream.x7.base.GenericObject; import org.springframework.stereotype.Component; import java.util.Date; import java.util.UUID; @Component /** * @author Sim */ public class TccBusiness { public final static String SVC_DONE_PREFIX = "&"; public boolean confirm(AckuMessage AckuMessage, AckuMessageService ackuMessageService, Producer producer) { Date date = new Date(); boolean flag = ackuMessageService.refresh( QrB.of(AckuMessage.class) .refresh("status", MessageStatus.OK) .refresh("refreshAt", date) .refresh("tcc", TCCTopic._TCC_CONFIRM) .eq("id", AckuMessage.getId()) .eq("tcc", TCCTopic._TCC_TRY).build() ); //STEP 1 if (!flag) return flag; GenericObject body = AckuMessage.getBody(); String topic = AckuMessage.getTopic(); String tracingId = AckuMessage.getId(); AckuMessage AckuMessageConfirm = new AckuMessage(tracingId,topic,body,AckuMessage.getSvcList()); AckuMessageConfirm.setRetryMax(3 * 2); AckuMessageConfirm.resetTopic(TCCTopic._TCC_CONFIRM); String messageId = UUID.randomUUID().toString(); messageId = messageId.replace("-",""); AckuMessageConfirm.setId(messageId); AckuMessageConfirm.setCreateAt(date); AckuMessageConfirm.setRefreshAt(date); AckuMessageConfirm.setSendAt(date.getTime()); AckuMessageConfirm.setSvcDone(SVC_DONE_PREFIX); AckuMessageConfirm.setStatus(MessageStatus.SEND.toString());//初始化为已发送 AckuMessageConfirm.setTcc(TCCTopic._TCC_CONFIRM.name()); boolean b = ackuMessageService.create(AckuMessageConfirm); //STEP 2 if (b){ AckuDto dto = new AckuDto(AckuMessageConfirm); b &= producer.send(AckuMessageConfirm.getTopic(), JsonX.toJson(dto)); //STEP 3 } return b; } public boolean cancel(AckuMessage AckuMessage, AckuMessageService ackuMessageService, Producer producer) { Date date = new Date(); boolean flag = ackuMessageService.refresh( QrB.of(AckuMessage.class) .refresh("status", MessageStatus.FAIL) .refresh("refreshAt", date) .refresh("tcc", TCCTopic._TCC_CANCEL) .eq("id", AckuMessage.getId()) .eq("svcDone",AckuMessage.getSvcDone()) .ne("tcc", TCCTopic._TCC_CANCEL).build() ); //STEP 1 if (!flag) return flag; GenericObject body = AckuMessage.getBody(); String topic = AckuMessage.getTopic(); String tracingId = AckuMessage.getId(); AckuMessage AckuMessageCancel = new AckuMessage(tracingId,topic,body,AckuMessage.getSvcList()); AckuMessageCancel.setRetryMax(3 * 2); AckuMessageCancel.resetTopic(TCCTopic._TCC_CANCEL); String messageId = UUID.randomUUID().toString(); messageId = messageId.replace("-",""); AckuMessageCancel.setId(messageId); AckuMessageCancel.setCreateAt(date); AckuMessageCancel.setRefreshAt(date); AckuMessageCancel.setSendAt(date.getTime()); AckuMessageCancel.setSvcDone(SVC_DONE_PREFIX); AckuMessageCancel.setStatus(MessageStatus.SEND.toString());//初始化为已发送 AckuMessageCancel.setTcc(TCCTopic._TCC_CANCEL.name()); boolean b = ackuMessageService.create(AckuMessageCancel); //STEP 2 if (b){ AckuDto dto = new AckuDto(AckuMessageCancel); b &= producer.send(AckuMessageCancel.getTopic(), JsonX.toJson(dto)); //STEP 3 } return b; } } ================================================ FILE: acku-message-center/acku-app/src/main/java/io/xream/acku/produce/Producer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.produce; /** * @author Sim */ public interface Producer { boolean send(String topic, String message); } ================================================ FILE: acku-message-center/acku-app/src/main/resources/META-INF/spring.factories ================================================ org.springframework.context.ApplicationListener=\ io.xream.acku.config.MQConfig ================================================ FILE: acku-message-center/acku-app/src/main/resources/application.properties ================================================ spring.application.name=acku-app spring.profiles.active=dev server.port=7717 access.domain=* spring.jackson.serialization.write-dates-as-timestamps=true spring.jackson.time-zone=GMT+8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/acku_test?&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.default-auto-commit=true spring.datasource.auto-commit=true spring.datasource.maximum-pool-size=100 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5 spring.datasource.validation-query=SELECT 1 spring.datasource.test-on-borrow=false spring.datasource.test-while-idle=true spring.datasource.time-between-eviction-runs-millis=18800 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.hikari.idleTimeout=30000 spring.datasource.hikari.maximumPoolSize=10 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.pool.max-active=64 spring.redis.pool.min-idle=8 x7.repository.show-sql=true http.connectTimeout=15000 http.socketTimeout=60000 x7.reyc.fallback.remote-exception=RemoteServiceException resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=60000 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-closed-state=100 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-half-open-state=10 resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50 resilience4j.circuitbreaker.configs.default.event-consumer-buffer-size=100 endpoints.health.mapping.DOWN=OK endpoints.health.mapping.OUT_OF_SERVICE=OK #tracing.zipkin.url=http://127.0.0.1:9411 logging.level.io.xream=INFO loggin.pattern.console="%d - %msg%n" acku.retry.duration=5000 spring.kafka.producer.bootstrap-servers=127.0.0.1:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.batch-size=65535 spring.kafka.producer.buffer-memory=524288 spring.kafka.producer.acks=all spring.kafka.producer.retries=3 next.kafka.producer.bootstrap-servers=127.0.0.1:9092 next.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer next.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer next.kafka.producer.batch-size=65535 next.kafka.producer.buffer-memory=524288 next.kafka.producer.acks=all next.kafka.producer.retries=3 acku.app=localhost:7717 ================================================ FILE: acku-message-center/acku-app/test/java/io/xream/reliable/AppTest.java ================================================ package io.xream.acku; import io.xream.acku.produce.Producer; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; @SpringBootTest @RunWith(SpringRunner.class) /** * @author Sim */ public class AppTest { @Autowired private Producer producer; @Resource(name = "nextProducer") private Producer nextProducer; @Test public void testAll(){ System.out.println(this.producer); System.out.println(this.nextProducer); } } ================================================ FILE: acku-message-center/acku-dashboard/Dockerfile ================================================ FROM java:openjdk-8-jre-alpine LABEL version="0.2.0" MAINTAINER Sim <8966188@qq.com> ENV ACTIVE test ENV JAVA_OPTS -Xms1024m -Xmx1024m -Xmn320m ADD target/acku-dashboard-1.1.0.jar /data/deploy/acku-dashboard.jar CMD ["mkdir -p /data/logs"] VOLUME /data/logs /data/logs EXPOSE 80 ENTRYPOINT ["java","-Dspring.profiles.active=${ACTIVE}","-jar", "/data/deploy/acku-dashboard.jar"] ================================================ FILE: acku-message-center/acku-dashboard/README.md ================================================ # acku-dashboard support consistence retry manually, if retryMax > 0 can import the page into your iframe tag ================================================ FILE: acku-message-center/acku-dashboard/pom.xml ================================================ acku-message-center io.xream.acku 0.2.0 4.0.0 acku-dashboard org.springframework spring-web org.springframework.boot spring-boot-maven-plugin 3.2.2 repackage io.xream.acku.App src/main/resources ================================================ FILE: acku-message-center/acku-dashboard/src/main/java/io/xream/acku/App.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku; import io.xream.x7.EnableDateToLongForJackson; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * @author Sim */ @SpringBootApplication @EnableTransactionManagement @EnableDateToLongForJackson public class App { public static void main(String[] args) { SpringApplication.run(App.class); } } ================================================ FILE: acku-message-center/acku-dashboard/src/main/java/io/xream/acku/controller/AuthorizationBusiness.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.controller; import io.xream.acku.remote.AuthorizationServiceRemote; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * @author Sim */ @Component public class AuthorizationBusiness { @Value("acku.dashboard.authorization.required") private String authorizationRequired; @Value("acku.dashboard.authorization.url.redirect-to-sign-in") private String urlRedirectToSignIn = "NONE"; @Value("acku.dashboard.authorization.url.server") private String urlAuthorization = "NONE"; @Resource private AuthorizationServiceRemote authorizationServiceRemote; public boolean requireAuthorization(){ return Boolean.parseBoolean(this.authorizationRequired); } public boolean isAccessble(String token, String userId) { if (!requireAuthorization()) return true; return this.authorizationServiceRemote.verify(token, userId); } public String getRedirect(){ return "redirect:"+this.urlRedirectToSignIn; } } ================================================ FILE: acku-message-center/acku-dashboard/src/main/java/io/xream/acku/controller/MessageFailedController.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.controller; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.remote.acku.FailedServiceRemote; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author Sim */ @RestController @RequestMapping("/message/failed") public class MessageFailedController { private Logger logger = LoggerFactory.getLogger(MessageFailedController.class); @Resource private FailedServiceRemote failedServiceRemote; @Autowired private AuthorizationBusiness authorizationBusiness; @RequestMapping(value = "/find",method = RequestMethod.GET) public ResponseEntity find() { return this.find(null,null); } @RequestMapping(value = "/retry/all",method = RequestMethod.GET) public ResponseEntity retryAll() { return this.retryAll(null,null); } @RequestMapping(value = "/retry/{messageId}",method = RequestMethod.GET) public ResponseEntity retry(@PathVariable String messageId) { return this.retry(messageId,null,null); } @RequestMapping(value = "/find/{token}/{userId}",method = RequestMethod.GET) public ResponseEntity find(@PathVariable String token, @PathVariable String userId) { if (! authorizationBusiness.isAccessble(token,userId)) { String redirect = this.authorizationBusiness.getRedirect(); return ResponseEntity.ok(redirect); } List list = this.failedServiceRemote.findFailed(); return ResponseEntity.ok(list); } @RequestMapping(value = "/retry/all/{token}/{userId}",method = RequestMethod.GET) public ResponseEntity retryAll(@PathVariable String token, @PathVariable String userId) { logger.info("retry all failed message"); if (! authorizationBusiness.isAccessble(token,userId)) { String redirect = this.authorizationBusiness.getRedirect(); return ResponseEntity.ok(redirect); } this.failedServiceRemote.retryAll(); return ResponseEntity.ok("all"); } @RequestMapping(value = "/retry/{messageId}/{token}/{userId}",method = RequestMethod.GET) public ResponseEntity retry(@PathVariable String messageId, @PathVariable String token, @PathVariable String userId) { logger.info("retry failed message: " + messageId); if (! authorizationBusiness.isAccessble(token,userId)) { String redirect = this.authorizationBusiness.getRedirect(); return ResponseEntity.ok(redirect); } this.failedServiceRemote.retry(messageId); return ResponseEntity.ok(messageId); } } ================================================ FILE: acku-message-center/acku-dashboard/src/main/java/io/xream/acku/remote/AuthorizationServiceRemote.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.remote; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; /** * @author Sim */ @HttpExchange("${acku.dashboard.authorization.url.server}" ) public interface AuthorizationServiceRemote { @GetExchange(value = "/{token}/{userId}") boolean verify(String token, String userId); } ================================================ FILE: acku-message-center/acku-dashboard/src/main/resources/application.properties ================================================ spring.application.name=acku-dashboard spring.profiles.active=dev server.port=7737 access.domain=* spring.jackson.serialization.write-dates-as-timestamps=true spring.jackson.time-zone=GMT+8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/acku_test?&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.default-auto-commit=true spring.datasource.auto-commit=true spring.datasource.maximum-pool-size=100 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5 spring.datasource.validation-query=SELECT 1 spring.datasource.test-on-borrow=false spring.datasource.test-while-idle=true spring.datasource.time-between-eviction-runs-millis=18800 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.hikari.idleTimeout=30000 spring.datasource.hikari.maximumPoolSize=10 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.pool.max-active=64 spring.redis.pool.min-idle=8 x7.repository.show-sql=true http.connectTimeout=15000 http.socketTimeout=60000 x7.reyc.fallback.remote-exception=RemoteServiceException resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=60000 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-closed-state=100 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-half-open-state=10 resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50 resilience4j.circuitbreaker.configs.default.event-consumer-buffer-size=100 endpoints.health.mapping.DOWN=OK endpoints.health.mapping.OUT_OF_SERVICE=OK #tracing.zipkin.url=http://127.0.0.1:9411 logging.level.root=INFO loggin.pattern.console="%d - %msg%n" spring.kafka.producer.bootstrap-servers=127.0.0.1:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.batch-size=65535 spring.kafka.producer.buffer-memory=524288 spring.kafka.producer.acks=all spring.kafka.producer.retries=3 spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration acku.retry.duration=5000 acku.app=acku-app:7717 acku.dashboard.authorization.required=false acku.dashboard.authorization.url.redirect-to-sign-in=http://127.0.0.1 acku.dashboard.authorization.url.server=http://127.0.0.1 ================================================ FILE: acku-message-center/acku-dashboard/src/main/resources/static/index.html ================================================ TEST ================================================ FILE: acku-message-center/acku-schedule/Dockerfile ================================================ FROM java:openjdk-8-jre-alpine LABEL version="0.2.0" MAINTAINER Sim<8966188@qq.com> ENV ACTIVE test ENV JAVA_OPTS -Xms1024m -Xmx1024m -Xmn320m ADD target/acku-schedule-0.2.0.jar /data/deploy/acku-schedule.jar CMD ["mkdir -p /data/logs"] VOLUME /data/logs /data/logs EXPOSE 80 ENTRYPOINT ["java","-Dspring.profiles.active=${ACTIVE}","-jar", "/data/deploy/acku-schedule.jar"] ================================================ FILE: acku-message-center/acku-schedule/pom.xml ================================================ acku-message-center io.xream.acku 0.2.0 4.0.0 acku-schedule org.springframework spring-web org.springframework.boot spring-boot-maven-plugin 3.2.2 repackage io.xream.acku.App src/main/resources ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/App.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku; import io.xream.x7.EnableDateToLongForJackson; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; /** * @author Sim */ @SpringBootApplication @EnableDateToLongForJackson @EnableScheduling public class App { public static void main(String[] args) { SpringApplication.run(App.class); } } ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/codetemplate/ScheduleTemplate.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.codetemplate; import java.util.concurrent.Callable; /** * @author Sim */ public interface ScheduleTemplate { boolean schedule(Class scheduleClazz, Callable callable); } ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/codetemplate/inner/ScheduleTemplateImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.codetemplate.inner; import io.xream.acku.codetemplate.ScheduleTemplate; import io.xream.internal.util.ExceptionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.concurrent.Callable; /** * @author Sim */ public class ScheduleTemplateImpl implements ScheduleTemplate { public boolean schedule(Class scheduleClazz, Callable callable) { Logger logger = LoggerFactory.getLogger(scheduleClazz); String loggingName = scheduleClazz.getSimpleName(); boolean flag = false; long startTime = System.currentTimeMillis(); String taskId = loggingName + "_" + startTime; logger.info("Executing {}, At: {}, id: {}", loggingName, new Date(), taskId); try { flag = callable.call(); }catch (Exception e) { logger.info("Exception Occured: {}" , ExceptionUtil.getMessage(e)); } long endTime = System.currentTimeMillis(); String result = flag == true?"OK," : "FAILED,"; logger.info("Executing {}, {}, Cost: {}ms id: {}", loggingName, result, (endTime - startTime), taskId); return flag; } } ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/config/CodeTemplateConfig.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.config; import io.xream.acku.codetemplate.ScheduleTemplate; import io.xream.acku.codetemplate.inner.ScheduleTemplateImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Sim */ @Configuration public class CodeTemplateConfig { @Bean public ScheduleTemplate getScheduleTemplate(){ return new ScheduleTemplateImpl(); } } ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/remote/ScheduledAckuServiceRemote.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.remote; import io.xream.acku.bean.entity.AckuMessage; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; import org.springframework.web.service.annotation.PostExchange; import java.util.List; /** * @author Sim */ @HttpExchange("http://${acku.app}/schedule" ) public interface ScheduledAckuServiceRemote { @PostExchange(value = "/retry") boolean retry(AckuMessage message); @GetExchange(value = "/listForRetry") List listForRetry(); @GetExchange(value = "/tryToFinish") boolean tryToFinish(); @GetExchange(value = "/tryToProduceNext") boolean tryToProduceNext(); @GetExchange(value = "/clean") boolean clean(); } ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/schedule/CleanSchedule.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.schedule; import io.xream.acku.codetemplate.ScheduleTemplate; import io.xream.acku.remote.ScheduledAckuServiceRemote; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; /** * @author Sim */ @Configuration public class CleanSchedule { private final static Logger logger = LoggerFactory.getLogger(CleanSchedule.class); public CleanSchedule(){ logger.info("Clean Schedule Started"); } @Resource private ScheduledAckuServiceRemote remote; @Autowired private ScheduleTemplate scheduleTemplate; @Scheduled(cron = "0 0 0/1 * * ?") public void clean(){ scheduleTemplate.schedule(CleanSchedule.class, () -> remote.clean()); } } ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/schedule/ReTrySchedule.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.schedule; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.codetemplate.ScheduleTemplate; import io.xream.acku.remote.ScheduledAckuServiceRemote; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; import java.util.List; /** * @author Sim */ @Configuration public class ReTrySchedule { private final static Logger logger = LoggerFactory.getLogger(ReTrySchedule.class); public ReTrySchedule(){ logger.info("ReProduce Schedule Started"); } @Resource private ScheduledAckuServiceRemote remote; @Autowired private ScheduleTemplate scheduleTemplate; @Scheduled(cron = "0/7 * * * * ?") public void reProduce(){ scheduleTemplate.schedule(ReTrySchedule.class, () -> { boolean flag = false; List list = remote.listForRetry(); flag = true; for (AckuMessage message : list) { flag &= remote.retry(message); } return flag; }); } } ================================================ FILE: acku-message-center/acku-schedule/src/main/java/io/xream/acku/schedule/TrySchedule.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.schedule; import io.xream.acku.codetemplate.ScheduleTemplate; import io.xream.acku.remote.ScheduledAckuServiceRemote; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; /** * @author Sim */ @Configuration public class TrySchedule { private final static Logger logger = LoggerFactory.getLogger(TrySchedule.class); public TrySchedule(){ logger.info("CheckStatus Schedule Started"); } @Resource private ScheduledAckuServiceRemote remote; @Autowired private ScheduleTemplate scheduleTemplate; @Scheduled(cron = "0/2 * * * * ?") public void tryToFinish(){ scheduleTemplate.schedule(TrySchedule.class, () -> remote.tryToFinish()); } @Scheduled(cron = "0/57 * * * * ?") public void tryToProduceNext(){ scheduleTemplate.schedule(TrySchedule.class, () -> remote.tryToProduceNext()); } } ================================================ FILE: acku-message-center/acku-schedule/src/main/resources/application.properties ================================================ spring.application.name=acku-schedule spring.profiles.active=dev server.port=7727 access.domain=* spring.jackson.serialization.write-dates-as-timestamps=true spring.jackson.time-zone=GMT+8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/acku_test?&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.default-auto-commit=true spring.datasource.auto-commit=true spring.datasource.maximum-pool-size=100 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5 spring.datasource.validation-query=SELECT 1 spring.datasource.test-on-borrow=false spring.datasource.test-while-idle=true spring.datasource.time-between-eviction-runs-millis=18800 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.hikari.idleTimeout=30000 spring.datasource.hikari.maximumPoolSize=10 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.pool.max-active=64 spring.redis.pool.min-idle=8 x7.repository.show-sql=true http.connectTimeout=15000 http.socketTimeout=60000 x7.reyc.fallback.remote-exception=RemoteServiceException resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=60000 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-closed-state=100 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-half-open-state=10 resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50 resilience4j.circuitbreaker.configs.default.event-consumer-buffer-size=100 endpoints.health.mapping.DOWN=OK endpoints.health.mapping.OUT_OF_SERVICE=OK #tracing.zipkin.url=http://127.0.0.1:9411 acku.retry.duration=5000 spring.kafka.producer.bootstrap-servers=127.0.0.1:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.batch-size=65535 spring.kafka.producer.buffer-memory=524288 spring.kafka.producer.acks=all spring.kafka.producer.retries=3 acku.app=acku-app:7717 logging.level.root=INFO loggin.pattern.console="%d - %msg%n" spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration ================================================ FILE: acku-message-center/acku-service/pom.xml ================================================ acku-message-center io.xream.acku 0.2.0 4.0.0 acku-service io.xream.acku acku-entity 0.2.0 io.xream.acku acku-core 0.2.0 ================================================ FILE: acku-message-center/acku-service/src/main/java/io/xream/acku/repository/acku/AckuMessageRepository.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.repository.acku; import io.xream.acku.bean.entity.AckuMessage; import io.xream.sqli.api.BaseRepository; import io.xream.sqli.api.RepositoryX; import org.springframework.stereotype.Repository; /** * @author Sim */ @Repository public interface AckuMessageRepository extends BaseRepository, RepositoryX { } ================================================ FILE: acku-message-center/acku-service/src/main/java/io/xream/acku/repository/acku/MessageResultRepository.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.repository.acku; import io.xream.acku.bean.entity.MessageResult; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; /** * @author Sim */ @Repository public interface MessageResultRepository extends BaseRepository { } ================================================ FILE: acku-message-center/acku-service/src/main/java/io/xream/acku/service/acku/AckuMessageServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.service.acku; import io.xream.acku.api.acku.AckuMessageService; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.repository.acku.AckuMessageRepository; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.Qr; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; /** * @author Sim */ @Service public class AckuMessageServiceImpl implements AckuMessageService { @Autowired private AckuMessageRepository repository; @Override public boolean create(AckuMessage message) { this.repository.create(message); return true; } @Override public boolean refresh(Qr refreshCondition) { return this.repository.refresh(refreshCondition); } @Override public boolean remove(String id) { return this.repository.remove(id); } @Override public List listByCond(Q q) { return this.repository.list(q); } @Override public List> listByX(Q.X x) { return this.repository.listX(x); } @Override public AckuMessage get(String msgId) { return this.repository.get(msgId); } } ================================================ FILE: acku-message-center/acku-service/src/main/java/io/xream/acku/service/acku/FailedServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.service.acku; import io.xream.acku.api.acku.FailedService; import io.xream.acku.bean.entity.AckuMessage; import io.xream.acku.repository.acku.AckuMessageRepository; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.Qr; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; /** * @author Sim */ @Service public class FailedServiceImpl implements FailedService { @Autowired private AckuMessageRepository ackuMessageRepository; @Override public boolean refresh(Qr condition) { return this.ackuMessageRepository.refresh(condition); } @Override public boolean refreshUnSafe(Qr condition) { return this.ackuMessageRepository.refreshUnSafe(condition); } @Override public List> listByX(Q.X x) { return this.ackuMessageRepository.listX(x); } } ================================================ FILE: acku-message-center/acku-service/src/main/java/io/xream/acku/service/acku/MessageResultServiceImpl.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.service.acku; import io.xream.acku.api.acku.MessageResultService; import io.xream.acku.bean.entity.MessageResult; import io.xream.acku.repository.acku.MessageResultRepository; import io.xream.sqli.api.NativeRepository; import io.xream.sqli.builder.Q; import io.xream.sqli.builder.Qr; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @author Sim */ @Service public class MessageResultServiceImpl implements MessageResultService { @Autowired private MessageResultRepository repository; @Autowired private NativeRepository nativeRepository; public boolean create(MessageResult result) { this.repository.create(result); return true; } public boolean refresh(Qr condition) { return this.repository.refresh(condition); } public boolean remove(String id) { return this.repository.remove(id); } @Override public boolean removeByMessageId(String id) { String sql = "delete from messageResult where msgId = ?"; return nativeRepository.execute(sql ,id); } @Override public List listByCond(Q q) { return this.repository.list(q); } } ================================================ FILE: acku-message-center/pom.xml ================================================ io.xream.acku 0.2.0 4.0.0 acku-message-center pom UTF-8 17 17 17 true 1.2.79 8.0.17 0.2.0 acku-service acku-app acku-schedule acku-dashboard org.springframework.boot spring-boot-dependencies 3.2.2 pom import io.xream.acku acku-entity ${acku.version} io.xream.acku acku-core ${acku.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat org.springframework.boot spring-boot-starter-undertow org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java ${mysql.version} com.alibaba fastjson ${fastjson.version} org.springframework.kafka spring-kafka ================================================ FILE: acku-spring-boot-starter/pom.xml ================================================ acku io.xream.acku 0.2.0 4.0.0 acku-spring-boot-starter io.xream.acku acku-api 0.2.0 io.xream.acku acku-entity 0.2.0 io.xream.acku acku-core 0.2.0 ================================================ FILE: acku-spring-boot-starter/src/main/java/io/xream/acku/AckuStarter.java ================================================ package io.xream.acku; import org.springframework.context.annotation.Configuration; /** * @author Sim */ @Configuration public class AckuStarter { } ================================================ FILE: acku-spring-boot-starter/src/main/java/io/xream/acku/api/EnableAckuManagement.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package io.xream.acku.api; import io.xream.acku.interner.AckuOnConsumedAspect; import io.xream.acku.interner.AckuProducerAspect; import org.springframework.context.annotation.Import; import java.lang.annotation.*; /** * @author Sim */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import({AckuProducerAspect.class, AckuOnConsumedAspect.class}) public @interface EnableAckuManagement { } ================================================ FILE: acku-spring-boot-starter/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ io.xream.acku.AckuStarter,\ io.xream.acku.backend.AckuBackendImpl ================================================ FILE: debug ================================================ [master 776ee82] info- 13 files changed, 115 insertions(+), 113 deletions(-) ================================================ FILE: demo/demo-listener/pom.xml ================================================ acku io.xream.acku 0.2.0 4.0.0 demo-listener UTF-8 1.8 1.8 1.8 true 1.2.79 8.0.17 org.springframework.boot spring-boot-dependencies 2.6.13 pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat org.springframework.boot spring-boot-starter-undertow org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-jdbc io.xream.acku acku-spring-boot-starter 0.2.0 mysql mysql-connector-java ${mysql.version} com.alibaba fastjson ${fastjson.version} org.springframework.kafka spring-kafka org.springframework.boot spring-boot-maven-plugin repackage io.xream.acku.App src/main/resources ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/App.java ================================================ package io.xream.acku; import io.xream.acku.api.EnableAckuManagement; import io.xream.x7.EnableCorsConfig; import io.xream.x7.EnableDateToLongForJackson; import io.xream.rey.EnableReyClient; import io.xream.x7.EnableX7Repository; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; @SpringBootApplication @EnableTransactionManagement @EnableAckuManagement @EnableX7Repository(baseTypeSupported = true) @EnableDateToLongForJackson @EnableReyClient @EnableCorsConfig /** * @author Sim */ public class App { public static void main(String[] args) { SpringApplication.run(App.class); } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/bean/Cat.java ================================================ package io.xream.acku.bean; import io.xream.sqli.annotation.X; /** * @author Sim */ public class Cat { @X.Key private String id; private String name; private String desc; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "Cat{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/bean/CatOrder.java ================================================ package io.xream.acku.bean; import io.xream.sqli.annotation.X; /** * @author Sim */ public class CatOrder { @X.Key private String id; private String status; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return "CatOrder{" + "id='" + id + '\'' + ", status='" + status + '\'' + '}'; } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/bean/CatSettle.java ================================================ package io.xream.acku.bean; import io.xream.sqli.annotation.X; /** * @author Sim */ public class CatSettle { @X.Key private String id; private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "CatSettle{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/bean/CatStatement.java ================================================ package io.xream.acku.bean; import io.xream.sqli.annotation.X; /** * @author Sim */ public class CatStatement { @X.Key private String id; private String test; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTest() { return test; } public void setTest(String test) { this.test = test; } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/config/AckuConfig.java ================================================ package io.xream.acku.config; import io.xream.internal.util.JsonX; import io.xream.acku.api.acku.DtoConverter; import io.xream.acku.bean.dto.AckuDto; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Sim */ @Configuration public class AckuConfig { @Bean public DtoConverter dtoConverter() { return message -> { String body = ((ConsumerRecord) message).value(); //KAFKA AckuDto dto = JsonX.toObject(body, AckuDto.class); return dto; }; } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/controller/SettleController.java ================================================ package io.xream.acku.controller; import io.xream.acku.bean.CatSettle; import io.xream.acku.repository.CatSettleRepository; import io.xream.sqli.builder.RefreshBuilder; import io.xream.x7.base.web.ViewEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/settle") /** * @author Sim */ public class SettleController { @Autowired private CatSettleRepository repository; @RequestMapping("/create") public ViewEntity create(@RequestBody CatSettle catSettle) { this.repository.create(catSettle); return ViewEntity.ok(); } @RequestMapping("/confirm") public ViewEntity confirm(CatSettle catSettle) { repository.refresh( RefreshBuilder.builder() .refresh("name",catSettle.getName()) .eq("id",catSettle.getId()).build() ); return ViewEntity.ok(); } @RequestMapping("/cancel") public ViewEntity cancel(CatSettle catSettle) { repository.refresh( RefreshBuilder.builder() .refresh("name",catSettle.getName()) .eq("id",catSettle.getId()).build() ); return ViewEntity.ok(); } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/controller/StatementController.java ================================================ package io.xream.acku.controller; import io.xream.acku.bean.CatStatement; import io.xream.acku.repository.CatStatementRepository; import io.xream.x7.base.web.ViewEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/statement") /** * @author Sim */ public class StatementController { @Autowired private CatStatementRepository catStatementRepository; @RequestMapping("/create") public ViewEntity create(@RequestBody CatStatement catStatement) { this.catStatementRepository.create(catStatement); return ViewEntity.ok(); } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/listener/SettleListenerOfPayment.java ================================================ package io.xream.acku.listener; import io.xream.acku.api.AckuOnConsumed; import io.xream.acku.api.acku.DtoConverter; import io.xream.acku.bean.CatSettle; import io.xream.acku.bean.CatStatement; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.controller.SettleController; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.KafkaListener; /** * @author Sim */ @Configuration public class SettleListenerOfPayment { @Autowired private SettleController settleController; @Autowired private DtoConverter dtoConverter; @AckuOnConsumed(svc = "cat-settle", nextTopic = "CAT_SETTLE_CREATED", nextRetryMax = 2, nextSvcs = {"cat-statement"}) @KafkaListener(topics = "CAT_PAID") public CatStatement onCatPaid(ConsumerRecord record) { AckuDto dto = dtoConverter.convertOnConsumed(record); //-------------- CatSettle catSettle = new CatSettle(); catSettle.setId("CAT_SETTLE_TEST"); this.settleController.create(catSettle); CatStatement catStatement = new CatStatement(); catStatement.setId("TEST_STATEMENT"); catStatement.setTest("CAT_SETTLE_CREATED"); return catStatement; } @AckuOnConsumed(svc = "cat-settle",nextTopic = "CAT_SETTLE_CREATED", nextRetryMax = 2, nextSvcs = {"cat-statement"}) @KafkaListener(topics = "CAT_PAID_TCC_TRY") public CatStatement onCatPaid_TCC_TRY(ConsumerRecord record) { AckuDto dto = dtoConverter.convertOnConsumed(record); //-------------- CatSettle catSettle = new CatSettle(); catSettle.setId("CAT_SETTLE_TEST"); catSettle.setName("TRY"); settleController.create(catSettle); CatStatement catStatement = new CatStatement(); catStatement.setId("TEST_STATEMENT"); catStatement.setTest("CAT_SETTLE_CREATED"); return catStatement; } @AckuOnConsumed(svc = "cat-settle") @KafkaListener(topics = "CAT_PAID_TCC_CONFIRM") public void onCatPaid_TCC_CONFIRM(ConsumerRecord record) { AckuDto dto = dtoConverter.convertOnConsumed(record); //-------------- CatSettle catsettle = new CatSettle(); catsettle.setId("CAT_SETTLE_TEST"); catsettle.setName("CONFIRM"); settleController.confirm(catsettle); } @AckuOnConsumed(svc = "cat-settle") @KafkaListener(topics = "CAT_PAID_TCC_CANCEL") public void onCatPaid_TCC_CANCEL(ConsumerRecord record) { AckuDto dto = dtoConverter.convertOnConsumed(record); //-------------- CatSettle catsettle = new CatSettle(); catsettle.setId("CAT_SETTLE_TEST"); catsettle.setName("CANCEL"); settleController.cancel(catsettle); } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/listener/StatementListenerOfSettle.java ================================================ package io.xream.acku.listener; import io.xream.acku.api.AckuOnConsumed; import io.xream.acku.api.acku.DtoConverter; import io.xream.acku.bean.CatStatement; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.controller.StatementController; import io.xream.x7.base.GenericObject; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.KafkaListener; /** * @author Sim */ @Configuration public class StatementListenerOfSettle { private static final Logger logger = LoggerFactory.getLogger(StatementListenerOfSettle.class); @Autowired private StatementController statementController; @Autowired private DtoConverter dtoConverter; @AckuOnConsumed(svc = "cat-statement") @KafkaListener(topics = "CAT_SETTLE_CREATED") public void onSettleCreated(ConsumerRecord record) { AckuDto dto = dtoConverter.convertOnConsumed(record); GenericObject go = dto.getMessage().getBody(); CatStatement catStatement = go.get(); this.statementController.create(catStatement); logger.info("------------------------------------>>>>>>>>>>>>>>>>>>>>>>"); } } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/repository/CatOrderRepository.java ================================================ package io.xream.acku.repository; import io.xream.acku.bean.CatOrder; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; @Repository /** * @author Sim */ public interface CatOrderRepository extends BaseRepository { } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/repository/CatRepository.java ================================================ package io.xream.acku.repository; import io.xream.acku.bean.Cat; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; @Repository /** * @author Sim */ public interface CatRepository extends BaseRepository { } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/repository/CatSettleRepository.java ================================================ package io.xream.acku.repository; import io.xream.acku.bean.CatSettle; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; @Repository /** * @author Sim */ public interface CatSettleRepository extends BaseRepository { } ================================================ FILE: demo/demo-listener/src/main/java/io/xream/acku/repository/CatStatementRepository.java ================================================ package io.xream.acku.repository; import io.xream.acku.bean.CatStatement; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; @Repository /** * @author Sim */ public interface CatStatementRepository extends BaseRepository { } ================================================ FILE: demo/demo-listener/src/main/resources/application.properties ================================================ spring.application.name=acku-demo spring.profiles.active=dev server.port=7757 access.domain=* spring.jackson.serialization.write-dates-as-timestamps=true spring.jackson.time-zone=GMT+8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/acku_test?&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.default-auto-commit=true spring.datasource.auto-commit=true spring.datasource.maximum-pool-size=100 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5 spring.datasource.validation-query=SELECT 1 spring.datasource.test-on-borrow=false spring.datasource.test-while-idle=true spring.datasource.time-between-eviction-runs-millis=18800 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.hikari.idleTimeout=30000 spring.datasource.hikari.maximumPoolSize=10 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.pool.max-active=64 spring.redis.pool.min-idle=8 http.connectTimeout=15000 http.socketTimeout=60000 resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=60000 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-closed-state=100 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-half-open-state=10 resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50 resilience4j.circuitbreaker.configs.default.event-consumer-buffer-size=100 endpoints.health.mapping.DOWN=OK endpoints.health.mapping.OUT_OF_SERVICE=OK #tracing.zipkin.url=http://127.0.0.1:9411 logging.level.root=INFO loggin.pattern.console="%d - %msg%n" acku.retry.duration=5000 spring.kafka.producer.bootstrap-servers=127.0.0.1:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.batch-size=65535 spring.kafka.producer.buffer-memory=524288 spring.kafka.producer.acks=1 spring.kafka.producer.retries=3 spring.kafka.listener.missing-topics-fatal=false spring.kafka.consumer.bootstrap-servers=127.0.0.1:9092 spring.kafka.consumer.group-id=acku-test-settle spring.kafka.consumer.auto-offset-reset=earliest spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.consumer.max-poll-records=30 spring.kafka.properties.max.poll.interval.ms=400000 spring.kafka.consumer.enable-auto-commit=true spring.kafka.listener.ack-mode=record #spring.kafka.consumer.enable-auto-commit=false #spring.kafka.listener.ack-mode=manual_immediate acku.app=acku-app:7717 acku.demo=acku-demo:7747 ================================================ FILE: demo/demo-producer-listener/pom.xml ================================================ acku io.xream.acku 0.2.0 4.0.0 demo-producer-listener UTF-8 1.8 1.8 1.8 true 1.2.79 8.0.17 org.springframework.boot spring-boot-dependencies 2.6.13 pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat org.springframework.boot spring-boot-starter-undertow org.springframework.boot spring-boot-starter-logging org.springframework.boot spring-boot-starter-jdbc io.xream.acku acku-spring-boot-starter 0.2.0 mysql mysql-connector-java ${mysql.version} com.alibaba fastjson ${fastjson.version} org.springframework.kafka spring-kafka org.springframework.boot spring-boot-maven-plugin repackage io.xream.acku.App src/main/resources ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/App.java ================================================ package io.xream.acku; import io.xream.acku.api.EnableAckuManagement; import io.xream.x7.EnableCorsConfig; import io.xream.x7.EnableDateToLongForJackson; import io.xream.rey.EnableReyClient; import io.xream.x7.EnableX7Repository; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * @author Sim */ @SpringBootApplication @EnableTransactionManagement @EnableAckuManagement @EnableX7Repository(baseTypeSupported = true) @EnableDateToLongForJackson @EnableReyClient @EnableCorsConfig public class App { public static void main(String[] args) { SpringApplication.run(App.class); } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/AppTest.java ================================================ package io.xream.acku; import io.xream.acku.bean.Cat; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * @author Sim */ @SpringBootTest @RunWith(SpringRunner.class) public class AppTest { private static Logger logger = LoggerFactory.getLogger(AppTest.class); @Autowired private PaymentServiceRemote paymentServiceRemote; @Test public void test(){ Cat cat = new Cat(); cat.setId("CAT_TEST_SSSSS"); cat.setName("ddse"); cat.setDesc("RWER"); this.paymentServiceRemote.pay(cat); } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/PaymentServiceRemote.java ================================================ package io.xream.acku; import io.xream.acku.bean.Cat; import io.xream.rey.annotation.ReyClient; import io.xream.x7.base.web.ViewEntity; import org.springframework.web.bind.annotation.RequestMapping; @ReyClient(value = "${acku.demo}/payment") /** * @author Sim */ public interface PaymentServiceRemote { @RequestMapping("/pay") ViewEntity pay(Cat cat); } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/bean/Cat.java ================================================ package io.xream.acku.bean; import io.xream.sqli.annotation.X; /** * @author Sim */ public class Cat { @X.Key private String id; private String name; private String desc; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "Cat{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/bean/CatOrder.java ================================================ package io.xream.acku.bean; import io.xream.sqli.annotation.X; /** * @author Sim */ public class CatOrder { @X.Key private String id; private String status; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } @Override public String toString() { return "CatOrder{" + "id='" + id + '\'' + ", status='" + status + '\'' + '}'; } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/bean/CatSettle.java ================================================ package io.xream.acku.bean; import io.xream.sqli.annotation.X; /** * @author Sim */ public class CatSettle { @X.Key private String id; private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "CatSettle{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/config/AckuConfig.java ================================================ package io.xream.acku.config; import io.xream.internal.util.JsonX; import io.xream.acku.api.acku.DtoConverter; import io.xream.acku.bean.dto.AckuDto; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration /** * @author Sim */ public class AckuConfig { @Bean public DtoConverter dtoConverter() { return message -> { String body = ((ConsumerRecord) message).value(); //KAFKA AckuDto dto = JsonX.toObject(body, AckuDto.class); return dto; }; } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/controller/OrderController.java ================================================ package io.xream.acku.controller; import io.xream.acku.bean.CatOrder; import io.xream.acku.repository.CatOrderRepository; import io.xream.sqli.builder.RefreshBuilder; import io.xream.x7.base.web.ViewEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Transactional @RestController @RequestMapping("/order") /** * @author Sim */ public class OrderController { @Autowired private CatOrderRepository repository; @RequestMapping("/create") public ViewEntity create(@RequestBody CatOrder order) { repository.create(order); return ViewEntity.ok(); } @RequestMapping("/confirm") public ViewEntity confirm(CatOrder catOrder) { repository.refresh( RefreshBuilder.builder() .refresh("status",catOrder.getStatus()) .eq("id",catOrder.getId()).build() ); return ViewEntity.ok(); } @RequestMapping("/cancel") public ViewEntity cancel(CatOrder catOrder) { repository.refresh(RefreshBuilder.builder() .refresh("status",catOrder.getStatus()) .eq("id",catOrder.getId()).build() ); return ViewEntity.ok(); } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/controller/PaymentController.java ================================================ package io.xream.acku.controller; import io.xream.acku.api.AckuProducer; import io.xream.acku.bean.Cat; import io.xream.acku.repository.CatRepository; import io.xream.x7.base.web.ViewEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Transactional @RestController @RequestMapping("/payment") /** * @author Sim */ public class PaymentController { @Autowired private CatRepository repository; @AckuProducer(useTcc=true,topic = "CAT_PAID", svcs = {"cat-order","cat-settle"}) @RequestMapping("/pay") public ViewEntity pay(@RequestBody Cat cat) { repository.create(cat); return ViewEntity.ok(); } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/listener/OrderListenerOfPayment.java ================================================ package io.xream.acku.listener; import io.xream.internal.util.JsonX; import io.xream.acku.api.AckuOnConsumed; import io.xream.acku.api.acku.DtoConverter; import io.xream.acku.bean.CatOrder; import io.xream.acku.bean.dto.AckuDto; import io.xream.acku.controller.OrderController; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.annotation.KafkaListener; /** * @author Sim */ @Configuration public class OrderListenerOfPayment { @Autowired private OrderController orderController; @Autowired private DtoConverter dtoConverter; @AckuOnConsumed(svc = "cat-order") @KafkaListener(topics = "CAT_PAID") public void onCatPaid(ConsumerRecord record) { String json = record.value(); AckuDto dto = JsonX.toObject(json,AckuDto.class); //-------------- CatOrder catOrder = new CatOrder(); catOrder.setId("CAT_ORDER_TEST"); orderController.create(catOrder); } @AckuOnConsumed(svc = "cat-order") @KafkaListener(topics = "CAT_PAID_TCC_TRY") public void onCatPaid_TCC_TRY(ConsumerRecord record) { String json = record.value(); AckuDto dto = JsonX.toObject(json,AckuDto.class); //-------------- CatOrder catOrder = new CatOrder(); catOrder.setId("CAT_ORDER_TEST"); catOrder.setStatus("TRY"); orderController.create(catOrder); } @AckuOnConsumed(svc = "cat-order") @KafkaListener(topics = "CAT_PAID_TCC_CONFIRM") public void onCatPaid_TCC_CONFIRM(ConsumerRecord record) { String json = record.value(); AckuDto dto = JsonX.toObject(json,AckuDto.class); //-------------- CatOrder catOrder = new CatOrder(); catOrder.setId("CAT_ORDER_TEST"); catOrder.setStatus("CONFIRM"); orderController.confirm(catOrder); } @AckuOnConsumed(svc = "cat-order") @KafkaListener(topics = "CAT_PAID_TCC_CANCEL") public void onCatPaid_TCC_CANCEL(ConsumerRecord record) { String json = record.value(); AckuDto dto = JsonX.toObject(json,AckuDto.class); //-------------- CatOrder catOrder = new CatOrder(); catOrder.setId("CAT_ORDER_TEST"); catOrder.setStatus("CANCEL"); orderController.cancel(catOrder); } } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/repository/CatOrderRepository.java ================================================ package io.xream.acku.repository; import io.xream.acku.bean.CatOrder; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; @Repository /** * @author Sim */ public interface CatOrderRepository extends BaseRepository { } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/repository/CatRepository.java ================================================ package io.xream.acku.repository; import io.xream.acku.bean.Cat; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; @Repository /** * @author Sim */ public interface CatRepository extends BaseRepository { } ================================================ FILE: demo/demo-producer-listener/src/main/java/io/xream/acku/repository/CatSettleRepository.java ================================================ package io.xream.acku.repository; import io.xream.acku.bean.CatSettle; import io.xream.sqli.api.BaseRepository; import org.springframework.stereotype.Repository; @Repository /** * @author Sim */ public interface CatSettleRepository extends BaseRepository { } ================================================ FILE: demo/demo-producer-listener/src/main/resources/application.properties ================================================ spring.application.name=acku-demo spring.profiles.active=dev server.port=7747 access.domain=* spring.jackson.serialization.write-dates-as-timestamps=true spring.jackson.time-zone=GMT+8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/acku_test?&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.default-auto-commit=true spring.datasource.auto-commit=true spring.datasource.maximum-pool-size=100 spring.datasource.max-idle=10 spring.datasource.max-wait=10000 spring.datasource.min-idle=5 spring.datasource.initial-size=5 spring.datasource.validation-query=SELECT 1 spring.datasource.test-on-borrow=false spring.datasource.test-while-idle=true spring.datasource.time-between-eviction-runs-millis=18800 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.hikari.idleTimeout=30000 spring.datasource.hikari.maximumPoolSize=10 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.pool.max-active=64 spring.redis.pool.min-idle=8 http.connectTimeout=15000 http.socketTimeout=60000 resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=60000 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-closed-state=100 resilience4j.circuitbreaker.configs.default.ring-buffer-size-in-half-open-state=10 resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50 resilience4j.circuitbreaker.configs.default.event-consumer-buffer-size=100 endpoints.health.mapping.DOWN=OK endpoints.health.mapping.OUT_OF_SERVICE=OK #tracing.zipkin.url=http://127.0.0.1:9411 logging.level.root=INFO loggin.pattern.console="%d - %msg%n" acku.retry.duration=5000 spring.kafka.producer.bootstrap-servers=127.0.0.1:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.batch-size=65535 spring.kafka.producer.buffer-memory=524288 spring.kafka.producer.acks=1 spring.kafka.producer.retries=3 spring.kafka.listener.missing-topics-fatal=false spring.kafka.consumer.bootstrap-servers=127.0.0.1:9092 spring.kafka.consumer.group-id=acku-test-order spring.kafka.consumer.auto-offset-reset=earliest spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer spring.kafka.consumer.max-poll-records=30 spring.kafka.properties.max.poll.interval.ms=400000 spring.kafka.consumer.enable-auto-commit=true spring.kafka.listener.ack-mode=record #spring.kafka.consumer.enable-auto-commit=false #spring.kafka.listener.ack-mode=manual_immediate acku.app=acku-app:7717 acku.demo=localhost:7747 ================================================ FILE: pom.xml ================================================ 4.0.0 io.xream.acku acku acku pom 0.2.0 http://acku.xream.io http://acku.xream.io UTF-8 17 17 17 true 3.1.7 acku-entity acku-core acku-spring-boot-starter acku-api scm:git:https://github.com/x-ream/acku scm:git:https://github.com/x-ream/acku scm:git:https://github.com/x-ream/acku 0.2.0 Apache 2 http://www.apache.org/licenses/LICENSE-2.0 repo A business-friendly OSS license Sim Wang 8966188@qq.com oss-s xream.io snopshots repo https://oss.sonatype.org/content/repositories/snapshots oss-r xream.io release repo https://oss.sonatype.org/service/local/staging/deploy/maven2 org.springframework.boot spring-boot-dependencies 3.2.2 pom import io.xream.x7 x7-spring-boot-starter ${x7.boot.version} org.codehaus.mojo versions-maven-plugin 2.7 false org.apache.maven.plugins maven-javadoc-plugin 3.1.1 UTF-8 UTF-8 UTF-8 -Xdoclint:none attach-javadocs jar org.apache.maven.plugins maven-source-plugin 3.2.0 true source-jar compile jar src/main/resources