diff --git a/easytrans-core/pom.xml b/easytrans-core/pom.xml index addbc13..7dea33c 100644 --- a/easytrans-core/pom.xml +++ b/easytrans-core/pom.xml @@ -46,7 +46,7 @@ com.alibaba.fescar fescar-rm-datasource - 0.2.0 + 0.3.1 diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/log/TransactionLogReader.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/log/TransactionLogReader.java index a189cc3..52d5f82 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/log/TransactionLogReader.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/log/TransactionLogReader.java @@ -4,6 +4,7 @@ import java.util.List; import com.yiqiniu.easytrans.log.vo.LogCollection; +import com.yiqiniu.easytrans.protocol.TransactionId; public interface TransactionLogReader { @@ -16,4 +17,13 @@ public interface TransactionLogReader { * @return */ List getUnfinishedLogs(LogCollection locationId,int pageSize,Date createTimeCeiling); + + /** + * 获取当前服务的未完成的日志 + * get current service's unfinished logs by id + * @param ids transaction ids + * @return + */ + List getTransactionLogById(List ids); + } diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/EtMonitor.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/EtMonitor.java new file mode 100644 index 0000000..13a94e8 --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/EtMonitor.java @@ -0,0 +1,12 @@ +package com.yiqiniu.easytrans.monitor; + +/** + * 本接口用户表明某个接口是EasyTransaction的Monitor接口 + * 其只能被接口继承,否则不生效 + * Monitor的接口方法的入参请用简单类型,如字符串和数字,否则可能出现问题 + * + * @author deyou + * @date 2019.04.03 + */ +public interface EtMonitor { +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/MonitorConsumerFactory.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/MonitorConsumerFactory.java new file mode 100644 index 0000000..726fe15 --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/MonitorConsumerFactory.java @@ -0,0 +1,18 @@ +package com.yiqiniu.easytrans.monitor; + +/** + * + * @author deyou + * @date 2019/04/02 + * + */ +public interface MonitorConsumerFactory { + + /** + * use for methods to call + * @param monitorInterface + * @return + */ + public T getRemoteProxy(String appId, Class monitorInterface); + +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/StringCodecMonitor.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/StringCodecMonitor.java new file mode 100644 index 0000000..4ad210e --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/StringCodecMonitor.java @@ -0,0 +1,10 @@ +package com.yiqiniu.easytrans.monitor; + +/** + * + * @author deyou + * + */ +public interface StringCodecMonitor extends EtMonitor { + Object getString2IdMap(); +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/TransactionLogMonitor.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/TransactionLogMonitor.java new file mode 100644 index 0000000..55a8a46 --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/TransactionLogMonitor.java @@ -0,0 +1,13 @@ +package com.yiqiniu.easytrans.monitor; + +/** + * + * @author deyou + * + */ +public interface TransactionLogMonitor extends EtMonitor { + Object getUnfinishedLogs(int pageSize,Long latestTimeStamp); + + Object consistentProcess(String busCode, long trxId); + +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideStringCodecMonitor.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideStringCodecMonitor.java new file mode 100644 index 0000000..4526a51 --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideStringCodecMonitor.java @@ -0,0 +1,29 @@ +package com.yiqiniu.easytrans.monitor.server; + +import com.yiqiniu.easytrans.monitor.StringCodecMonitor; +import com.yiqiniu.easytrans.stringcodec.ListableStringCodec; +import com.yiqiniu.easytrans.stringcodec.StringCodec; + +public class ServerSideStringCodecMonitor implements StringCodecMonitor { + + + private StringCodec stringCodec; + + public ServerSideStringCodecMonitor(StringCodec stringCodec) { + super(); + this.stringCodec = stringCodec; + } + + @Override + public Object getString2IdMap() { + + if(stringCodec instanceof ListableStringCodec) { + ListableStringCodec lsc = (ListableStringCodec) stringCodec; + return lsc.getMapStr2Id(); + } + + return null; + } + + +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideTransactionLogMonitor.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideTransactionLogMonitor.java new file mode 100644 index 0000000..96caeb4 --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/monitor/server/ServerSideTransactionLogMonitor.java @@ -0,0 +1,44 @@ +package com.yiqiniu.easytrans.monitor.server; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import com.yiqiniu.easytrans.core.ConsistentGuardian; +import com.yiqiniu.easytrans.log.TransactionLogReader; +import com.yiqiniu.easytrans.log.vo.LogCollection; +import com.yiqiniu.easytrans.monitor.TransactionLogMonitor; +import com.yiqiniu.easytrans.protocol.TransactionId; + +public class ServerSideTransactionLogMonitor implements TransactionLogMonitor { + + private String appId; + private TransactionLogReader logReader; + private ConsistentGuardian consistentGuardian; + + + public ServerSideTransactionLogMonitor(String appId, TransactionLogReader logReader, ConsistentGuardian consistentGuardian) { + super(); + this.appId = appId; + this.logReader = logReader; + this.consistentGuardian = consistentGuardian; + } + + @Override + public Object getUnfinishedLogs(int pageSize, Long latestTimeStamp) { + return logReader.getUnfinishedLogs(null, pageSize, latestTimeStamp != null?new Date(latestTimeStamp):new Date()); + } + + @Override + public Object consistentProcess(String busCode, long trxId) { + List transactionLogById = logReader.getTransactionLogById(Arrays.asList(new TransactionId(appId,busCode,trxId))); + if(CollectionUtils.isEmpty(transactionLogById)) { + return false; + } + + consistentGuardian.process(transactionLogById.get(0)); + return true; + } + +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java index 8acf3da..e998c56 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/protocol/autocps/EtDataSourceManager.java @@ -66,7 +66,7 @@ public Long branchRegister(BranchType branchType, String resourceId, String clie Integer callSeq = MetaDataFilter.getMetaData(EasytransConstant.CallHeadKeys.CALL_SEQ); // check locks - if (StringUtils.isEmpty(lockKey)) { + if (StringUtils.isNullOrEmpty(lockKey)) { return callSeq== null?-1:callSeq.longValue(); } diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/provider/factory/DefaultListableProviderFactory.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/provider/factory/DefaultListableProviderFactory.java index fdf273d..d88e2e9 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/provider/factory/DefaultListableProviderFactory.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/provider/factory/DefaultListableProviderFactory.java @@ -35,7 +35,8 @@ public class DefaultListableProviderFactory implements ListableProviderFactory,I private Map> mapBusinessInterface = new HashMap>(); private ApplicationContext ctx; - private Map, List>> businessProviderTypeBeanMap; + @SuppressWarnings("rawtypes") + private Map, List> businessProviderTypeBeanMap; // public DefaultListableProviderFactory(Map, List>> businessProviderTypeBeanMap){ // this.businessProviderTypeBeanMap = businessProviderTypeBeanMap; @@ -54,7 +55,7 @@ public void afterPropertiesSet() throws Exception { @SuppressWarnings("rawtypes") private void initDefaultTypes(){ - businessProviderTypeBeanMap = new HashMap, List>>(4); + businessProviderTypeBeanMap = new HashMap, List>(4); Map rpcList = ctx.getBeansOfType(RpcBusinessProvider.class); Map msgList = ctx.getBeansOfType(MessageBusinessProvider.class); @@ -84,7 +85,7 @@ private void initDefaultTypes(){ //handle for(Entry, Map, List>> entry :mapBusinessProvider.entrySet()){ - List> beansOfType = businessProviderTypeBeanMap.get(entry.getKey()); + List beansOfType = businessProviderTypeBeanMap.get(entry.getKey()); for(Object bean :beansOfType){ for(Entry, List> transactionTypeList:entry.getValue().entrySet()){ Class transactionType = transactionTypeList.getKey(); diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/stringcodec/ListableStringCodec.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/stringcodec/ListableStringCodec.java new file mode 100644 index 0000000..4ce6742 --- /dev/null +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/stringcodec/ListableStringCodec.java @@ -0,0 +1,12 @@ +package com.yiqiniu.easytrans.stringcodec; + +import java.util.Map; + +/** + * @author xu deyou + * + */ +public interface ListableStringCodec extends StringCodec { + Map> getMapStr2Id(); + +} diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/stringcodec/impl/ZooKeeperStringCodecImpl.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/stringcodec/impl/ZooKeeperStringCodecImpl.java index a884aa4..0902873 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/stringcodec/impl/ZooKeeperStringCodecImpl.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/stringcodec/impl/ZooKeeperStringCodecImpl.java @@ -1,6 +1,7 @@ package com.yiqiniu.easytrans.stringcodec.impl; import java.nio.ByteBuffer; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.curator.framework.CuratorFramework; @@ -15,9 +16,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.yiqiniu.easytrans.stringcodec.StringCodec; +import com.yiqiniu.easytrans.stringcodec.ListableStringCodec; -public class ZooKeeperStringCodecImpl implements StringCodec { +public class ZooKeeperStringCodecImpl implements ListableStringCodec { private static Logger LOG = LoggerFactory.getLogger(ZooKeeperStringCodecImpl.class); @@ -184,4 +185,11 @@ public String findString(String stringType, int id) { } throw new RuntimeException("get string failed!" + stringType + "," + id); } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Map> getMapStr2Id() { + Map map = mapStr2Id; + return map; + } } diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/CallWrapUtil.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/CallWrapUtil.java index 2e91501..e8b43bf 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/CallWrapUtil.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/CallWrapUtil.java @@ -24,7 +24,6 @@ public static class Result implements Serializable{ private static final long serialVersionUID = 1L; } - @SuppressWarnings("unchecked") public T createTransactionCallInstance(Class transactionApiClass, Class> cfgClass) { @@ -39,7 +38,7 @@ public T createTransactio //create proxy that delegates to EasyTransFacade - Object instance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{transactionApiClass}, new InvocationHandler() { + Object instance = Proxy.newProxyInstance(transactionApiClass.getClassLoader(), new Class[]{transactionApiClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { diff --git a/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java b/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java index 672a799..93c6bb0 100644 --- a/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java +++ b/easytrans-core/src/main/java/com/yiqiniu/easytrans/util/ReflectUtil.java @@ -182,5 +182,27 @@ public static List> getTypeArguments(Class baseClass, Class getClassWithMark(Class monitorClass, Class mark) { + + Class[] interfaces = monitorClass.getInterfaces(); + for(Class interfaze : interfaces) { + if(interfaze == mark) { + return monitorClass; + } else { + Class result = getClassWithMark(interfaze, mark); + if(result != null) { + return result; + } + } + } + + Class superclass = monitorClass.getSuperclass(); + if(superclass == null) { + return null; + } else { + return getClassWithMark(superclass, mark); + } + } } diff --git a/easytrans-dashboard/.gitignore b/easytrans-dashboard/.gitignore new file mode 100644 index 0000000..a22b96d --- /dev/null +++ b/easytrans-dashboard/.gitignore @@ -0,0 +1,14 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store +.springBeans \ No newline at end of file diff --git a/easytrans-dashboard/pom.xml b/easytrans-dashboard/pom.xml new file mode 100644 index 0000000..77df774 --- /dev/null +++ b/easytrans-dashboard/pom.xml @@ -0,0 +1,88 @@ + + 4.0.0 + EasyTransaction dashboard + + easytrans-dashboard + + + com.yiqiniu.easytrans + easytrans + ${revision} + ../pom.xml + + + + UTF-8 + 1.8 + + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + + com.yiqiniu.easytrans + easytrans-rpc-rest-ribbon-starter + + + + com.yiqiniu.easytrans + easytrans-rpc-dubbo-starter + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/easytrans-dashboard/readme.md b/easytrans-dashboard/readme.md new file mode 100644 index 0000000..57e5ba0 --- /dev/null +++ b/easytrans-dashboard/readme.md @@ -0,0 +1,9 @@ +## dashboard +本工程仅仅提供最基本的未完成事务查看、重试 以及 字符编码映射关系查询 + +若对功能有更多功能要求、美观性要求、访问安全性要求的 可参考本实现自行开发对应的dashboard,也欢迎自行开发后PR到本项目 + +1.3.0+以后dashboard可用,需要根据项目使用的RPC框架,调整对应代码配置 + +项目的关键依赖为MonitorConsumerFactory这个类 + diff --git a/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java b/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java new file mode 100644 index 0000000..382675d --- /dev/null +++ b/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java @@ -0,0 +1,4 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +public class Constant { +} diff --git a/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/DashboardApplication.java b/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/DashboardApplication.java new file mode 100644 index 0000000..323fc20 --- /dev/null +++ b/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/DashboardApplication.java @@ -0,0 +1,43 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +import java.util.List; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.netflix.ribbon.RibbonClientSpecification; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; + +import com.yiqiniu.easytrans.monitor.MonitorConsumerFactory; +import com.yiqiniu.easytrans.rpc.impl.rest.RestRibbonEasyTransRpcConsumerImpl; +import com.yiqiniu.easytrans.rpc.impl.rest.RestRibbonEasyTransRpcProperties; +import com.yiqiniu.easytrans.rpc.impl.rest.RestRibbonMonitorConsumerFactory; + +@SpringBootApplication +@EnableConfigurationProperties(RestRibbonEasyTransRpcProperties.class) +//@EnableDubboConfig +public class DashboardApplication { + public static void main(String[] args) { + SpringApplication.run(DashboardApplication.class, args); + } + + + //------------- for rest ribbon --------------- + @Bean + public RestRibbonEasyTransRpcConsumerImpl restRibbonEasyTransRpcConsumerImpl(RestRibbonEasyTransRpcProperties properties, ApplicationContext ctx, List configurations) { + return new RestRibbonEasyTransRpcConsumerImpl(properties, null, ctx, configurations); + } + + @Bean + public MonitorConsumerFactory monitorConsumerFactory(RestRibbonEasyTransRpcConsumerImpl consumer) { + return new RestRibbonMonitorConsumerFactory(consumer); + } + + //------------- for dubbo --------------- +// @Bean +// public MonitorConsumerFactory monitorConsumerFactory(Optional applicationConfig, Optional registryConfig, Optional protocolConfig, Optional consumerConfig, Optional moduleConfig, +// Optional monitorConfig, Optional customizationer) { +// return new DubboMonitorConsumerFactory(applicationConfig, registryConfig, protocolConfig, consumerConfig, moduleConfig, monitorConfig, customizationer); +// } +} diff --git a/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/DashboardController.java b/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/DashboardController.java new file mode 100644 index 0000000..001a1ba --- /dev/null +++ b/easytrans-dashboard/src/main/java/com/yiqiniu/easytrans/demos/order/impl/DashboardController.java @@ -0,0 +1,85 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.yiqiniu.easytrans.monitor.MonitorConsumerFactory; +import com.yiqiniu.easytrans.monitor.StringCodecMonitor; +import com.yiqiniu.easytrans.monitor.TransactionLogMonitor; + +@Controller +public class DashboardController { + + private static final String YYYY_M_MDD_H_HMMSS = "yyyyMMddHHmmssSSS"; + private static final String VIEW_INDEX = "index"; + private static final String VIEW_STRING_TO_ID_MAP = "string2IdMap"; + private static final String VIEW_UNFINISHED_LOGS = "unfinishedLogs"; + + @Autowired + private MonitorConsumerFactory factory; + + @RequestMapping(path = "/", method = RequestMethod.GET) + public String index() { + return VIEW_INDEX; + } + + + @RequestMapping(path = "/string2IdMap", method = RequestMethod.GET) +// @ResponseBody + public Object getString2IdMap(@RequestParam(required=false) String appId, Model model) { + + model.addAttribute("appId", appId); + + if(appId != null) { + Object string2IdMap = factory.getRemoteProxy(appId, StringCodecMonitor.class).getString2IdMap(); + model.addAttribute("result", string2IdMap); + } + return VIEW_STRING_TO_ID_MAP; + } + + @RequestMapping(path = "/unfinishedLogs", method = RequestMethod.GET) + public String getUnfinishedLog(@RequestParam(required=false) String appId, @RequestParam(required=false, defaultValue="10") Integer pageSize , @RequestParam(required=false) @DateTimeFormat(pattern=YYYY_M_MDD_H_HMMSS) Date timestamp, Model model) { + + model.addAttribute("appId", appId); + model.addAttribute("pageSize", pageSize); + if(timestamp != null) { + model.addAttribute("timestamp", new SimpleDateFormat(YYYY_M_MDD_H_HMMSS).format(timestamp)); + } + + + if(appId != null) { + Object unfinishedLogs = factory.getRemoteProxy(appId, TransactionLogMonitor.class).getUnfinishedLogs(pageSize, timestamp==null?null:timestamp.getTime()); + model.addAttribute("result", unfinishedLogs); + } + return VIEW_UNFINISHED_LOGS; + } + + @RequestMapping(path = "/consistentGuardian", method = RequestMethod.GET,produces="application/json") + @ResponseBody + public String getUnfinishedLog(@RequestParam String appId, @RequestParam String busCode , @RequestParam long trxId) { + factory.getRemoteProxy(appId, TransactionLogMonitor.class).consistentProcess(busCode, trxId); + return "Success"; + } + + +// public static void main(String[] args) { +// SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); +// try { +// Date format = sdf.parse("20190405082912"); +// System.out.println(format); +// } catch (ParseException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } + +} diff --git a/easytrans-dashboard/src/main/resources/application.yml b/easytrans-dashboard/src/main/resources/application.yml new file mode 100644 index 0000000..2a82688 --- /dev/null +++ b/easytrans-dashboard/src/main/resources/application.yml @@ -0,0 +1,39 @@ +spring: + application: + name: easytrans-dashboard # the same with com.yiqiniu.easytrans.demos.order.Constant.APPID + +server: + port: 8888 + +easytrans: + recovery: + enabled: false + master: + zk: + enabled: false + +# RIBBON用,也可以直接开启Eureka +order-service: + ribbon: + listOfServers: localhost:8080 + +wallet-service: + ribbon: + listOfServers: localhost:8081 + +# DUBBO 配置 +dubbo: + application: + name: easytrans-dashboard + protocol: + name: dubbo + port: 20880 + registry: + address: zookeeper://localhost:2281 + provider: + timeout: 1000 + consumer: + timeout: 1000 + + +debug: true \ No newline at end of file diff --git a/easytrans-dashboard/src/main/resources/createDatabase.sql b/easytrans-dashboard/src/main/resources/createDatabase.sql new file mode 100644 index 0000000..48bef3b --- /dev/null +++ b/easytrans-dashboard/src/main/resources/createDatabase.sql @@ -0,0 +1,69 @@ +CREATE DATABASE `order` ; +USE `order`; +CREATE TABLE `order` ( + `order_id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `money` bigint(20) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8; + + + -- 用于记录业务发起方的最终业务有没有执行 + -- p_开头的,代表本事务对应的父事务id + -- select for update查询时,若事务ID对应的记录不存在则事务一定失败了 + -- 记录存在,但status为0表示事务成功,为1表示事务失败(包含父事务和本事务) + -- 记录存在,但status为2表示本方法存在父事务,且父事务的最终状态未知 + -- 父事务的状态将由发起方通过 优先同步告知 失败则 消息形式告知 + CREATE TABLE `executed_trans` ( + `app_id` smallint(5) unsigned NOT NULL, + `bus_code` smallint(5) unsigned NOT NULL, + `trx_id` bigint(20) unsigned NOT NULL, + `p_app_id` smallint(5) unsigned DEFAULT NULL, + `p_bus_code` smallint(5) unsigned DEFAULT NULL, + `p_trx_id` bigint(20) unsigned DEFAULT NULL, + `status` tinyint(1) NOT NULL, + PRIMARY KEY (`app_id`,`bus_code`,`trx_id`), + KEY `parent` (`p_app_id`,`p_bus_code`,`p_trx_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `idempotent` ( + `src_app_id` smallint(5) unsigned NOT NULL COMMENT '来源AppID', + `src_bus_code` smallint(5) unsigned NOT NULL COMMENT '来源业务类型', + `src_trx_id` bigint(20) unsigned NOT NULL COMMENT '来源交易ID', + `app_id` smallint(5) NOT NULL COMMENT '调用APPID', + `bus_code` smallint(5) NOT NULL COMMENT '调用的业务代码', + `call_seq` smallint(5) NOT NULL COMMENT '同一事务同一方法内调用的次数', + `handler` smallint(5) NOT NULL COMMENT '处理者appid', + `called_methods` varchar(64) NOT NULL COMMENT '被调用过的方法名', + `md5` binary(16) NOT NULL COMMENT '参数摘要', + `sync_method_result` blob COMMENT '同步方法的返回结果', + `create_time` datetime NOT NULL COMMENT '执行时间', + `update_time` datetime NOT NULL, + `lock_version` smallint(32) NOT NULL COMMENT '乐观锁版本号', + PRIMARY KEY (`src_app_id`,`src_bus_code`,`src_trx_id`,`app_id`,`bus_code`,`call_seq`,`handler`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + + + + +CREATE DATABASE `order_translog` ; +USE `order_translog`; + +CREATE TABLE `trans_log_detail` ( + `log_detail_id` int(11) NOT NULL AUTO_INCREMENT, + `trans_log_id` binary(12) NOT NULL, + `log_detail` blob, + `create_time` datetime NOT NULL, + PRIMARY KEY (`log_detail_id`), + KEY `app_id` (`trans_log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +CREATE TABLE `trans_log_unfinished` ( + `trans_log_id` binary(12) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`trans_log_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SELECT * FROM translog.trans_log_detail; \ No newline at end of file diff --git a/easytrans-dashboard/src/main/resources/log4j.properties b/easytrans-dashboard/src/main/resources/log4j.properties new file mode 100644 index 0000000..a296f5b --- /dev/null +++ b/easytrans-dashboard/src/main/resources/log4j.properties @@ -0,0 +1,28 @@ +log4j.rootLogger=info,stdout,logfile,errfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n +#log4j.appender.stdout.Threshold = DEBUG + +log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender +log4j.appender.file.DatePattern='.'yyyy-MM-dd +log4j.appender.logfile.File=logs/info.log +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.appender.errfile=org.apache.log4j.RollingFileAppender +log4j.appender.errfile.MaxFileSize=5000KB +log4j.appender.errfile.MaxBackupIndex=3 +log4j.appender.errfile.File=logs/err.log +log4j.appender.errfile.layout=org.apache.log4j.PatternLayout +log4j.appender.errfile.layout.ConversionPattern=%d %p [%c] - %m%n +log4j.appender.errfile.Threshold = ERROR + +log4j.logger.org.apache.zookeeper=OFF +log4j.logger.com.alibaba=OFF +log4j.logger.druid.sql=OFF +log4j.logger.org.springframework=OFF +log4j.logger.com.yiqiniu.easytrans=ON +log4j.logger.com.yiqiniu.easytrans.core=TRACE diff --git a/easytrans-dashboard/src/main/resources/templates/index.html b/easytrans-dashboard/src/main/resources/templates/index.html new file mode 100644 index 0000000..33e291f --- /dev/null +++ b/easytrans-dashboard/src/main/resources/templates/index.html @@ -0,0 +1,13 @@ + + + + Getting Started: Serving Web Content + + + + 获取字符串与其对应id的映射关系 + 获取未完成的事务记录 + + 本界面过于简陋,求前端大牛协助优化 + + \ No newline at end of file diff --git a/easytrans-dashboard/src/main/resources/templates/string2IdMap.html b/easytrans-dashboard/src/main/resources/templates/string2IdMap.html new file mode 100644 index 0000000..df8e319 --- /dev/null +++ b/easytrans-dashboard/src/main/resources/templates/string2IdMap.html @@ -0,0 +1,20 @@ + + + + Getting Started: Serving Web Content + + + + 获取字符串与其对应id的映射关系 + 获取未完成的事务记录 + +
+ AppId + +
+
根据数字及ID可以反查在 事务日志、已执行事务、幂等信息 相关数据
+
+

+    
+ + \ No newline at end of file diff --git a/easytrans-dashboard/src/main/resources/templates/unfinishedLogs.html b/easytrans-dashboard/src/main/resources/templates/unfinishedLogs.html new file mode 100644 index 0000000..2ea2af8 --- /dev/null +++ b/easytrans-dashboard/src/main/resources/templates/unfinishedLogs.html @@ -0,0 +1,45 @@ + + + + Getting Started: Serving Web Content + + + + 获取字符串与其对应id的映射关系 + 获取未完成的事务记录 + +
+ AppId + 拉取数量 + 事务创建最大时间戳(long型) + +
+
从这里可以看哪些事务没有完成
+
+ + + + + + + + + + + + + + + + + + + + + + +
appIdbusCodetrxIdcreateTime事务日志内容
重试
+ +
+ + \ No newline at end of file diff --git a/easytrans-demo/interface-call/pom.xml b/easytrans-demo/interface-call/pom.xml index 536913b..e828d4b 100644 --- a/easytrans-demo/interface-call/pom.xml +++ b/easytrans-demo/interface-call/pom.xml @@ -19,7 +19,7 @@ UTF-8 UTF-8 1.8 - 1.2.0 + 1.3.0 diff --git a/easytrans-demo/log-redis/.gitignore b/easytrans-demo/log-redis/.gitignore new file mode 100644 index 0000000..9f0b34d --- /dev/null +++ b/easytrans-demo/log-redis/.gitignore @@ -0,0 +1,13 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store diff --git a/easytrans-demo/log-redis/logredis-order-service/.gitignore b/easytrans-demo/log-redis/logredis-order-service/.gitignore new file mode 100644 index 0000000..a22b96d --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/.gitignore @@ -0,0 +1,14 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store +.springBeans \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-order-service/pom.xml b/easytrans-demo/log-redis/logredis-order-service/pom.xml new file mode 100644 index 0000000..1ad33fa --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/pom.xml @@ -0,0 +1,85 @@ + + 4.0.0 + + logredis-order-service + + + com.yiqiniu.easytrans.demos + logredis + ${revision} + ../pom.xml + + + + UTF-8 + 1.8 + + + + + com.yiqiniu.easytrans + easytrans-starter + + + + + com.yiqiniu.easytrans + easytrans-queue-kafka-starter + + + com.yiqiniu.easytrans + + easytrans-log-database-starter + + + + + + + com.yiqiniu.easytrans + easytrans-log-redis-starter + + + + com.yiqiniu.easytrans.demos + logredis-wallet-api + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + mysql + mysql-connector-java + + + + org.apache.httpcomponents + httpclient + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java new file mode 100644 index 0000000..bdea2ad --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/Constant.java @@ -0,0 +1,5 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +public class Constant { + public static final String APPID="order-service"; +} diff --git a/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderApplication.java b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderApplication.java new file mode 100644 index 0000000..880ddfa --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderApplication.java @@ -0,0 +1,18 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import com.yiqiniu.easytrans.EnableEasyTransaction; +import com.yiqiniu.easytrans.log.impl.redis.EnableLogRedisImpl; + +@SpringBootApplication +@EnableEasyTransaction +@EnableTransactionManagement +@EnableLogRedisImpl +public class OrderApplication { + public static void main(String[] args) { + SpringApplication.run(OrderApplication.class, args); + } +} diff --git a/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderController.java b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderController.java new file mode 100644 index 0000000..b3e3b85 --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderController.java @@ -0,0 +1,21 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class OrderController { + + @Autowired + private OrderService orderService; + + @RequestMapping("/buySth") + @ResponseBody + public Integer buySomething(@RequestParam int userId,@RequestParam int money){ + + return orderService.buySomething(userId, money); + } +} diff --git a/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderService.java b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderService.java new file mode 100644 index 0000000..353fdaa --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/src/main/java/com/yiqiniu/easytrans/demos/order/impl/OrderService.java @@ -0,0 +1,119 @@ +package com.yiqiniu.easytrans.demos.order.impl; + +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.concurrent.Future; + +import javax.annotation.Resource; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.yiqiniu.easytrans.core.EasyTransFacade; +import com.yiqiniu.easytrans.demos.wallet.api.vo.WalletPayVO.WalletPayRequestVO; +import com.yiqiniu.easytrans.demos.wallet.api.vo.WalletPayVO.WalletPayResponseVO; + +@Component +public class OrderService { + + public static final String BUSINESS_CODE = "buySth"; + + + @Resource + private EasyTransFacade transaction; + @Resource + private JdbcTemplate jdbcTemplate; + + + @Transactional + public int buySomething(int userId,long money){ + + /** + * finish the local transaction first, in order for performance and generated of business id + * + * 优先完成本地事务以 1. 提高性能(减少异常时回滚消耗)2. 生成事务内交易ID + */ + Integer id = saveOrderRecord(jdbcTemplate,userId,money); + + /** + * annotation the global transactionId, it is combined of appId + bussiness_code + id + * it can be omit,then framework will use "default" as businessCode, and will generate an id + * but it will make us harder to associate an global transaction to an concrete business + * + * 声明全局事务ID,其由appId,业务代码,业务代码内ID构成 + * 本代码可以省略,框架会自动生成ID及使用一个默认的业务代码 + * 但这样的话,会使得我们难以把全局事务ID与一个具体的事务关联起来 + */ + transaction.startEasyTrans(BUSINESS_CODE, id); + + /** + * call remote service to deduct money, it's a TCC service, + * framework will maintains the eventually constancy based on the final transaction status of method buySomething + * if you think introducing object transaction(EasyTransFacade) is an unacceptable coupling + * then you can refer to another demo(interfacecall) in the demos directory, it will show you how to execute transaction by user defined interface + * + * 调用远程服务扣除所需的钱,这个远程服务实现了TCC接口, + * 框架会根据buySomething方法的事务结果来维护远程服务的最终一致性 + * 如果你认为引入transaction(EasyTransFacde)是一个无法接受的耦合 + * 那么你可以参考在demos目录下另外一个样例(interfacecall),它会告诉你如何用用户自定义的接口来执行远程事务 + */ + WalletPayRequestVO deductRequest = new WalletPayRequestVO(); + deductRequest.setUserId(userId); + deductRequest.setPayAmount(money); + /** + * return future for the benefits of performance enhance(batch write execute log and batch execute RPC) + * 返回future是为了能方便的优化性能(批量写日志及批量调用RPC) + */ + @SuppressWarnings("unused") + Future deductFuture = transaction.execute(deductRequest); + + /** + * you can add more types of transaction calls here, e.g. TCC,reliable message, SAGA-TCC and so on + * framework will maintains the eventually consistent + * + * 你可以额外加入其它类型的事务,如TCC,可靠消息,SAGA-TCC等等 + * 框架会维护全局事务的最终一致性 + */ + + + /** + * we can get remote service result to determine whether to commit this transaction + * + * 可以获取远程返回的结果用以判断是继续往下走 还是 抛异常结束 + * + * deductFuture.get(); + */ + + return id; + } + + + private Integer saveOrderRecord(JdbcTemplate jdbcTemplate, final int userId, final long money) { + + final String INSERT_SQL = "INSERT INTO `order` (`order_id`, `user_id`, `money`, `create_time`) VALUES (NULL, ?, ?, ?);"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update( + new PreparedStatementCreator() { + @Override + public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { + PreparedStatement ps = + connection.prepareStatement(INSERT_SQL, new String[] {"id"}); + ps.setInt(1, userId); + ps.setLong(2, money); + ps.setDate(3, new Date(System.currentTimeMillis())); + return ps; + } + }, + keyHolder); + + return keyHolder.getKey().intValue(); + } + + +} diff --git a/easytrans-demo/log-redis/logredis-order-service/src/main/resources/application.yml b/easytrans-demo/log-redis/logredis-order-service/src/main/resources/application.yml new file mode 100644 index 0000000..5df2073 --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/src/main/resources/application.yml @@ -0,0 +1,43 @@ +spring: + application: + name: order-service # the same with com.yiqiniu.easytrans.demos.order.Constant.APPID + datasource: # order service datasource config + url: jdbc:mysql://localhost:3306/order?characterEncoding=UTF-8&useSSL=false + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver + +server: + port: 8080 + +# RIBBON用,也可以直接开启Eureka +order-service: + ribbon: + listOfServers: localhost:8080 + +wallet-service: + ribbon: + listOfServers: localhost:8081 + +easytrans: + master: + zk: + zooKeeperUrl: localhost:2281 + stringcodec: + zk: + zooKeeperUrl: ${easytrans.master.zk.zooKeeperUrl} + idgen: + trxId: + zkSnow: + zooKeeperUrl: ${easytrans.master.zk.zooKeeperUrl} + log: + redis: + enabled: true + keyPrefix: "et:" + redisUri: redis://localhost/ # 具体格式请参考 https://lettuce.io/core/release/reference/#redisuri.uri-syntax + + + + + + \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-order-service/src/main/resources/createDatabase.sql b/easytrans-demo/log-redis/logredis-order-service/src/main/resources/createDatabase.sql new file mode 100644 index 0000000..48bef3b --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/src/main/resources/createDatabase.sql @@ -0,0 +1,69 @@ +CREATE DATABASE `order` ; +USE `order`; +CREATE TABLE `order` ( + `order_id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `money` bigint(20) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`order_id`) +) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8; + + + -- 用于记录业务发起方的最终业务有没有执行 + -- p_开头的,代表本事务对应的父事务id + -- select for update查询时,若事务ID对应的记录不存在则事务一定失败了 + -- 记录存在,但status为0表示事务成功,为1表示事务失败(包含父事务和本事务) + -- 记录存在,但status为2表示本方法存在父事务,且父事务的最终状态未知 + -- 父事务的状态将由发起方通过 优先同步告知 失败则 消息形式告知 + CREATE TABLE `executed_trans` ( + `app_id` smallint(5) unsigned NOT NULL, + `bus_code` smallint(5) unsigned NOT NULL, + `trx_id` bigint(20) unsigned NOT NULL, + `p_app_id` smallint(5) unsigned DEFAULT NULL, + `p_bus_code` smallint(5) unsigned DEFAULT NULL, + `p_trx_id` bigint(20) unsigned DEFAULT NULL, + `status` tinyint(1) NOT NULL, + PRIMARY KEY (`app_id`,`bus_code`,`trx_id`), + KEY `parent` (`p_app_id`,`p_bus_code`,`p_trx_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `idempotent` ( + `src_app_id` smallint(5) unsigned NOT NULL COMMENT '来源AppID', + `src_bus_code` smallint(5) unsigned NOT NULL COMMENT '来源业务类型', + `src_trx_id` bigint(20) unsigned NOT NULL COMMENT '来源交易ID', + `app_id` smallint(5) NOT NULL COMMENT '调用APPID', + `bus_code` smallint(5) NOT NULL COMMENT '调用的业务代码', + `call_seq` smallint(5) NOT NULL COMMENT '同一事务同一方法内调用的次数', + `handler` smallint(5) NOT NULL COMMENT '处理者appid', + `called_methods` varchar(64) NOT NULL COMMENT '被调用过的方法名', + `md5` binary(16) NOT NULL COMMENT '参数摘要', + `sync_method_result` blob COMMENT '同步方法的返回结果', + `create_time` datetime NOT NULL COMMENT '执行时间', + `update_time` datetime NOT NULL, + `lock_version` smallint(32) NOT NULL COMMENT '乐观锁版本号', + PRIMARY KEY (`src_app_id`,`src_bus_code`,`src_trx_id`,`app_id`,`bus_code`,`call_seq`,`handler`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + + + + +CREATE DATABASE `order_translog` ; +USE `order_translog`; + +CREATE TABLE `trans_log_detail` ( + `log_detail_id` int(11) NOT NULL AUTO_INCREMENT, + `trans_log_id` binary(12) NOT NULL, + `log_detail` blob, + `create_time` datetime NOT NULL, + PRIMARY KEY (`log_detail_id`), + KEY `app_id` (`trans_log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +CREATE TABLE `trans_log_unfinished` ( + `trans_log_id` binary(12) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`trans_log_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SELECT * FROM translog.trans_log_detail; \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-order-service/src/main/resources/log4j.properties b/easytrans-demo/log-redis/logredis-order-service/src/main/resources/log4j.properties new file mode 100644 index 0000000..a296f5b --- /dev/null +++ b/easytrans-demo/log-redis/logredis-order-service/src/main/resources/log4j.properties @@ -0,0 +1,28 @@ +log4j.rootLogger=info,stdout,logfile,errfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n +#log4j.appender.stdout.Threshold = DEBUG + +log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender +log4j.appender.file.DatePattern='.'yyyy-MM-dd +log4j.appender.logfile.File=logs/info.log +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.appender.errfile=org.apache.log4j.RollingFileAppender +log4j.appender.errfile.MaxFileSize=5000KB +log4j.appender.errfile.MaxBackupIndex=3 +log4j.appender.errfile.File=logs/err.log +log4j.appender.errfile.layout=org.apache.log4j.PatternLayout +log4j.appender.errfile.layout.ConversionPattern=%d %p [%c] - %m%n +log4j.appender.errfile.Threshold = ERROR + +log4j.logger.org.apache.zookeeper=OFF +log4j.logger.com.alibaba=OFF +log4j.logger.druid.sql=OFF +log4j.logger.org.springframework=OFF +log4j.logger.com.yiqiniu.easytrans=ON +log4j.logger.com.yiqiniu.easytrans.core=TRACE diff --git a/easytrans-demo/log-redis/logredis-wallet-api/.gitignore b/easytrans-demo/log-redis/logredis-wallet-api/.gitignore new file mode 100644 index 0000000..9f0b34d --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-api/.gitignore @@ -0,0 +1,13 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store diff --git a/easytrans-demo/log-redis/logredis-wallet-api/pom.xml b/easytrans-demo/log-redis/logredis-wallet-api/pom.xml new file mode 100644 index 0000000..b62595f --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-api/pom.xml @@ -0,0 +1,42 @@ + + 4.0.0 + + + + logredis-wallet-api + + + com.yiqiniu.easytrans.demos + logredis + ${revision} + ../pom.xml + + + + UTF-8 + 1.8 + + + + + com.yiqiniu.easytrans + easytrans-core + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletServiceApiConstant.java b/easytrans-demo/log-redis/logredis-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletServiceApiConstant.java new file mode 100644 index 0000000..8752d1c --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/WalletServiceApiConstant.java @@ -0,0 +1,5 @@ +package com.yiqiniu.easytrans.demos.wallet.api; + +public class WalletServiceApiConstant { + public static final String APPID="wallet-service"; +} diff --git a/easytrans-demo/log-redis/logredis-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/vo/WalletPayVO.java b/easytrans-demo/log-redis/logredis-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/vo/WalletPayVO.java new file mode 100644 index 0000000..c321b40 --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-api/src/main/java/com/yiqiniu/easytrans/demos/wallet/api/vo/WalletPayVO.java @@ -0,0 +1,55 @@ +package com.yiqiniu.easytrans.demos.wallet.api.vo; + +import java.io.Serializable; + +import com.yiqiniu.easytrans.demos.wallet.api.WalletServiceApiConstant; +import com.yiqiniu.easytrans.protocol.BusinessIdentifer; +import com.yiqiniu.easytrans.protocol.tcc.TccMethodRequest; + +public class WalletPayVO { + + + @BusinessIdentifer(appId=WalletServiceApiConstant.APPID,busCode="pay",rpcTimeOut=2000) + public static class WalletPayRequestVO implements TccMethodRequest { + + private static final long serialVersionUID = 1L; + + private Integer userId; + + private Long payAmount; + + public Long getPayAmount() { + return payAmount; + } + + public void setPayAmount(Long payAmount) { + this.payAmount = payAmount; + } + + public Integer getUserId() { + return userId; + } + + public void setUserId(Integer userId) { + this.userId = userId; + } + } + + public static class WalletPayResponseVO implements Serializable{ + private static final long serialVersionUID = 1L; + private Long freezeAmount; + public Long getFreezeAmount() { + return freezeAmount; + } + public void setFreezeAmount(Long freezeAmount) { + this.freezeAmount = freezeAmount; + } + + @Override + public String toString() { + return "WalletPayTccMethodResult [freezeAmount=" + freezeAmount + + "]"; + } + } + +} diff --git a/easytrans-demo/log-redis/logredis-wallet-service/.gitignore b/easytrans-demo/log-redis/logredis-wallet-service/.gitignore new file mode 100644 index 0000000..9f0b34d --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/.gitignore @@ -0,0 +1,13 @@ +server_back.properties +*.class +*.log +*.log.* +.factorypath +/target/* +/.settings/* +/bin/* +/.project +/.classpath +/logs +/target/ +.DS_Store diff --git a/easytrans-demo/log-redis/logredis-wallet-service/pom.xml b/easytrans-demo/log-redis/logredis-wallet-service/pom.xml new file mode 100644 index 0000000..ee32f4c --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + logredis-wallet-service + + com.yiqiniu.easytrans.demos + logredis + ${revision} + ../pom.xml + + + + + UTF-8 + 1.8 + + + + + com.yiqiniu.easytrans + easytrans-starter + + + + + com.yiqiniu.easytrans + easytrans-queue-kafka-starter + + + com.yiqiniu.easytrans + + easytrans-log-database-starter + + + + + + + + com.yiqiniu.easytrans + easytrans-log-redis-starter + + + + com.yiqiniu.easytrans.demos + logredis-wallet-api + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + mysql + mysql-connector-java + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletApplication.java b/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletApplication.java new file mode 100644 index 0000000..1de4fe4 --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletApplication.java @@ -0,0 +1,18 @@ +package com.yiqiniu.easytrans.demos.wallet.impl; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import com.yiqiniu.easytrans.EnableEasyTransaction; +import com.yiqiniu.easytrans.log.impl.redis.EnableLogRedisImpl; + +@SpringBootApplication +@EnableEasyTransaction +@EnableTransactionManagement +@EnableLogRedisImpl +public class WalletApplication { + public static void main(String[] args) { + SpringApplication.run(WalletApplication.class, args); + } +} diff --git a/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletPayTccService.java b/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletPayTccService.java new file mode 100644 index 0000000..422a42b --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletPayTccService.java @@ -0,0 +1,40 @@ +package com.yiqiniu.easytrans.demos.wallet.impl; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Component; + +import com.yiqiniu.easytrans.demos.wallet.api.vo.WalletPayVO.WalletPayRequestVO; +import com.yiqiniu.easytrans.demos.wallet.api.vo.WalletPayVO.WalletPayResponseVO; +import com.yiqiniu.easytrans.protocol.tcc.TccMethod; + +@Component +public class WalletPayTccService implements TccMethod{ + + public static final String METHOD_NAME="pay"; + + @Resource + private WalletService wlletService; + + @Override + public WalletPayResponseVO doTry(WalletPayRequestVO param) { + return wlletService.doTryPay(param); + } + + @Override + public void doConfirm(WalletPayRequestVO param) { + wlletService.doConfirmPay(param); + } + + + @Override + public void doCancel(WalletPayRequestVO param) { + wlletService.doCancelPay(param); + } + + + @Override + public int getIdempotentType() { + return IDENPOTENT_TYPE_FRAMEWORK; + } +} diff --git a/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletService.java b/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletService.java new file mode 100644 index 0000000..d290768 --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/src/main/java/com/yiqiniu/easytrans/demos/wallet/impl/WalletService.java @@ -0,0 +1,55 @@ +package com.yiqiniu.easytrans.demos.wallet.impl; + +import javax.annotation.Resource; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import com.yiqiniu.easytrans.core.EasyTransFacade; +import com.yiqiniu.easytrans.demos.wallet.api.vo.WalletPayVO.WalletPayRequestVO; +import com.yiqiniu.easytrans.demos.wallet.api.vo.WalletPayVO.WalletPayResponseVO; + +@Component +public class WalletService { + + @Resource + private EasyTransFacade transaction; + @Resource + private JdbcTemplate jdbcTemplate; + + @Transactional + public WalletPayResponseVO doTryPay(WalletPayRequestVO param) { + int update = jdbcTemplate.update("update `wallet` set freeze_amount = freeze_amount + ? where user_id = ? and (total_amount - freeze_amount) >= ?;", + param.getPayAmount(),param.getUserId(),param.getPayAmount()); + + if(update != 1){ + throw new RuntimeException("can not find specific user id or have not enought money"); + } + + WalletPayResponseVO walletPayTccMethodResult = new WalletPayResponseVO(); + walletPayTccMethodResult.setFreezeAmount(param.getPayAmount()); + return walletPayTccMethodResult; + } + + @Transactional + public void doConfirmPay(WalletPayRequestVO param) { + int update = jdbcTemplate.update("update `wallet` set freeze_amount = freeze_amount - ?, total_amount = total_amount - ? where user_id = ?;", + param.getPayAmount(),param.getPayAmount(),param.getUserId()); + + if(update != 1){ + throw new RuntimeException("unknow Exception!"); + } + } + + @Transactional + public void doCancelPay(WalletPayRequestVO param) { + int update = jdbcTemplate.update("update `wallet` set freeze_amount = freeze_amount - ? where user_id = ?;", + param.getPayAmount(),param.getUserId()); + if(update != 1){ + throw new RuntimeException("unknow Exception!"); + } + } + + +} diff --git a/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/application.yml b/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/application.yml new file mode 100644 index 0000000..156b820 --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/application.yml @@ -0,0 +1,44 @@ +spring: + application: + name: order-service # the same with com.yiqiniu.easytrans.demos.order.Constant.APPID + datasource: # order service datasource config + url: jdbc:mysql://localhost:3306/wallet?characterEncoding=UTF-8&useSSL=false + username: root + password: 123456 + driver-class-name: com.mysql.jdbc.Driver + +server: + port: 8081 + +# RIBBON用,也可以直接开启Eureka +order-service: + ribbon: + listOfServers: localhost:8080 + +wallet-service: + ribbon: + listOfServers: localhost:8081 + +easytrans: + master: + zk: + zooKeeperUrl: localhost:2281 + stringcodec: + zk: + zooKeeperUrl: ${easytrans.master.zk.zooKeeperUrl} + idgen: + trxId: + zkSnow: + zooKeeperUrl: ${easytrans.master.zk.zooKeeperUrl} + log: + redis: + enabled: true + keyPrefix: "et:" + redisUri: redis://localhost/ # 具体格式请参考 https://lettuce.io/core/release/reference/#redisuri.uri-syntax + + + +debug: true + + + \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/createDatabase.sql b/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/createDatabase.sql new file mode 100644 index 0000000..f200387 --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/createDatabase.sql @@ -0,0 +1,67 @@ +CREATE DATABASE `wallet` ; +USE `wallet`; +CREATE TABLE `wallet` ( + `user_id` int(11) NOT NULL, + `total_amount` bigint(20) NOT NULL, + `freeze_amount` bigint(20) NOT NULL, + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `wallet`.`wallet` (`user_id`, `total_amount`, `freeze_amount`) VALUES ('1', '10000000', '0'); + + + -- 用于记录业务发起方的最终业务有没有执行 + -- p_开头的,代表本事务对应的父事务id + -- select for update查询时,若事务ID对应的记录不存在则事务一定失败了 + -- 记录存在,但status为0表示事务成功,为1表示事务失败(包含父事务和本事务) + -- 记录存在,但status为2表示本方法存在父事务,且父事务的最终状态未知 + -- 父事务的状态将由发起方通过 优先同步告知 失败则 消息形式告知 + CREATE TABLE `executed_trans` ( + `app_id` smallint(5) unsigned NOT NULL, + `bus_code` smallint(5) unsigned NOT NULL, + `trx_id` bigint(20) unsigned NOT NULL, + `p_app_id` smallint(5) unsigned DEFAULT NULL, + `p_bus_code` smallint(5) unsigned DEFAULT NULL, + `p_trx_id` bigint(20) unsigned DEFAULT NULL, + `status` tinyint(1) NOT NULL, + PRIMARY KEY (`app_id`,`bus_code`,`trx_id`), + KEY `parent` (`p_app_id`,`p_bus_code`,`p_trx_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `idempotent` ( + `src_app_id` smallint(5) unsigned NOT NULL COMMENT '来源AppID', + `src_bus_code` smallint(5) unsigned NOT NULL COMMENT '来源业务类型', + `src_trx_id` bigint(20) unsigned NOT NULL COMMENT '来源交易ID', + `app_id` smallint(5) NOT NULL COMMENT '调用APPID', + `bus_code` smallint(5) NOT NULL COMMENT '调用的业务代码', + `call_seq` smallint(5) NOT NULL COMMENT '同一事务同一方法内调用的次数', + `handler` smallint(5) NOT NULL COMMENT '处理者appid', + `called_methods` varchar(64) NOT NULL COMMENT '被调用过的方法名', + `md5` binary(16) NOT NULL COMMENT '参数摘要', + `sync_method_result` blob COMMENT '同步方法的返回结果', + `create_time` datetime NOT NULL COMMENT '执行时间', + `update_time` datetime NOT NULL, + `lock_version` smallint(32) NOT NULL COMMENT '乐观锁版本号', + PRIMARY KEY (`src_app_id`,`src_bus_code`,`src_trx_id`,`app_id`,`bus_code`,`call_seq`,`handler`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + +CREATE DATABASE `wallet_translog` ; +USE `wallet_translog`; + +CREATE TABLE `trans_log_detail` ( + `log_detail_id` int(11) NOT NULL AUTO_INCREMENT, + `trans_log_id` binary(12) NOT NULL, + `log_detail` blob, + `create_time` datetime NOT NULL, + PRIMARY KEY (`log_detail_id`), + KEY `app_id` (`trans_log_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +CREATE TABLE `trans_log_unfinished` ( + `trans_log_id` binary(12) NOT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`trans_log_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +SELECT * FROM translog.trans_log_detail; \ No newline at end of file diff --git a/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/log4j.properties b/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/log4j.properties new file mode 100644 index 0000000..a296f5b --- /dev/null +++ b/easytrans-demo/log-redis/logredis-wallet-service/src/main/resources/log4j.properties @@ -0,0 +1,28 @@ +log4j.rootLogger=info,stdout,logfile,errfile + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n +#log4j.appender.stdout.Threshold = DEBUG + +log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender +log4j.appender.file.DatePattern='.'yyyy-MM-dd +log4j.appender.logfile.File=logs/info.log +log4j.appender.logfile.layout=org.apache.log4j.PatternLayout +log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n + +log4j.appender.errfile=org.apache.log4j.RollingFileAppender +log4j.appender.errfile.MaxFileSize=5000KB +log4j.appender.errfile.MaxBackupIndex=3 +log4j.appender.errfile.File=logs/err.log +log4j.appender.errfile.layout=org.apache.log4j.PatternLayout +log4j.appender.errfile.layout.ConversionPattern=%d %p [%c] - %m%n +log4j.appender.errfile.Threshold = ERROR + +log4j.logger.org.apache.zookeeper=OFF +log4j.logger.com.alibaba=OFF +log4j.logger.druid.sql=OFF +log4j.logger.org.springframework=OFF +log4j.logger.com.yiqiniu.easytrans=ON +log4j.logger.com.yiqiniu.easytrans.core=TRACE diff --git a/easytrans-demo/log-redis/pom.xml b/easytrans-demo/log-redis/pom.xml new file mode 100644 index 0000000..cf19cba --- /dev/null +++ b/easytrans-demo/log-redis/pom.xml @@ -0,0 +1,61 @@ + + + + org.springframework.boot + spring-boot-starter-parent + 1.5.13.RELEASE + + + 4.0.0 + com.yiqiniu.easytrans.demos + logredis + ${revision} + pom + + + + UTF-8 + UTF-8 + 1.8 + 1.3.0 + + + + + logredis-wallet-api + logredis-wallet-service + logredis-order-service + + + + + + + com.yiqiniu.easytrans + easytrans + ${revision} + pom + import + + + + com.yiqiniu.easytrans.demos + logredis-wallet-api + ${revision} + + + com.yiqiniu.easytrans.demos + logredis-wallet-service + ${revision} + + + com.yiqiniu.easytrans.demos + logredis-order-service + ${revision} + + + + + \ No newline at end of file diff --git a/easytrans-demo/log-redis/readme.md b/easytrans-demo/log-redis/readme.md new file mode 100644 index 0000000..a4876f2 --- /dev/null +++ b/easytrans-demo/log-redis/readme.md @@ -0,0 +1,14 @@ +# English +## tcc-only +this demo show an simple usage with REDIS log implement + +more usage you can refer to other demos or the UT case in easytrans-starter + +to run this demo, you will need zookeeper and mysql,change the configuration in applicaiton.yml,you can start the services + + +# 中文 +## tcc-only +本demo只演示了在本框架中如使用REDIS替代MYSQL作为日志存储 + +本demo运行起来需要zk、关系数据库及 REDIS,修改applicaiton.yml文件里相关zk及数据库配置后,即可启动。 \ No newline at end of file diff --git a/easytrans-demo/rpc-dubbo/pom.xml b/easytrans-demo/rpc-dubbo/pom.xml index f6e46cf..1d585d1 100644 --- a/easytrans-demo/rpc-dubbo/pom.xml +++ b/easytrans-demo/rpc-dubbo/pom.xml @@ -19,7 +19,7 @@ UTF-8 UTF-8 1.8 - 1.2.0 + 1.3.0 diff --git a/easytrans-demo/sagatcc/pom.xml b/easytrans-demo/sagatcc/pom.xml index 9386200..c6aff3f 100644 --- a/easytrans-demo/sagatcc/pom.xml +++ b/easytrans-demo/sagatcc/pom.xml @@ -19,7 +19,7 @@ UTF-8 UTF-8 1.8 - 1.2.0 + 1.3.0 diff --git a/easytrans-demo/tcc-and-fescar/pom.xml b/easytrans-demo/tcc-and-fescar/pom.xml index 707caeb..43d5ea3 100644 --- a/easytrans-demo/tcc-and-fescar/pom.xml +++ b/easytrans-demo/tcc-and-fescar/pom.xml @@ -19,7 +19,7 @@ UTF-8 UTF-8 1.8 - 1.2.0 + 1.3.0 diff --git a/easytrans-demo/tcc-and-msg/pom.xml b/easytrans-demo/tcc-and-msg/pom.xml index 06712d2..a046a11 100644 --- a/easytrans-demo/tcc-and-msg/pom.xml +++ b/easytrans-demo/tcc-and-msg/pom.xml @@ -19,7 +19,7 @@ UTF-8 UTF-8 1.8 - 1.2.0 + 1.3.0 diff --git a/easytrans-demo/tcc-only/pom.xml b/easytrans-demo/tcc-only/pom.xml index 3066698..9ad2554 100644 --- a/easytrans-demo/tcc-only/pom.xml +++ b/easytrans-demo/tcc-only/pom.xml @@ -19,7 +19,7 @@ UTF-8 UTF-8 1.8 - 1.2.0 + 1.3.0 diff --git a/easytrans-demo/tcc-only/tcconly-wallet-service/src/main/resources/application.yml b/easytrans-demo/tcc-only/tcconly-wallet-service/src/main/resources/application.yml index 19be3f2..c2c29da 100644 --- a/easytrans-demo/tcc-only/tcconly-wallet-service/src/main/resources/application.yml +++ b/easytrans-demo/tcc-only/tcconly-wallet-service/src/main/resources/application.yml @@ -42,7 +42,7 @@ easytrans: password: 123456 - +debug: true \ No newline at end of file diff --git a/easytrans-log-database-starter/src/main/java/com/yiqiniu/easytrans/log/impl/database/DataBaseTransactionLogReaderImpl.java b/easytrans-log-database-starter/src/main/java/com/yiqiniu/easytrans/log/impl/database/DataBaseTransactionLogReaderImpl.java index eb2248c..6fc9fee 100644 --- a/easytrans-log-database-starter/src/main/java/com/yiqiniu/easytrans/log/impl/database/DataBaseTransactionLogReaderImpl.java +++ b/easytrans-log-database-starter/src/main/java/com/yiqiniu/easytrans/log/impl/database/DataBaseTransactionLogReaderImpl.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import javax.sql.DataSource; @@ -27,8 +28,8 @@ public class DataBaseTransactionLogReaderImpl implements TransactionLogReader { private String selectTransDetailsByIds = "select * from trans_log_detail where trans_log_id in (:ids) order by trans_log_id,log_detail_id;"; - private String selectUnfinishedTransWithPos = "select trans_log_id from trans_log_unfinished where trans_log_id <= ? and trans_log_id >= ? and create_time <= ? ORDER BY trans_log_id LIMIT ?"; - private String selectUnfinishedTransWithoutPos = "select trans_log_id from trans_log_unfinished where trans_log_id <= ? and trans_log_id >= ? and create_time <= ? and trans_log_id > ? ORDER BY trans_log_id LIMIT ?"; + private String selectUnfinishedTransWithoutPos= "select trans_log_id from trans_log_unfinished where trans_log_id <= ? and trans_log_id >= ? and create_time <= ? ORDER BY trans_log_id LIMIT ?"; + private String selectUnfinishedTransWithPos = "select trans_log_id from trans_log_unfinished where trans_log_id <= ? and trans_log_id >= ? and create_time <= ? and trans_log_id > ? ORDER BY trans_log_id LIMIT ?"; public DataBaseTransactionLogReaderImpl(String appId, ObjectSerializer serializer,DataSource dataSource, ByteFormIdCodec idCodec, String tablePrefix) { @@ -67,21 +68,24 @@ public List getUnfinishedLogs(LogCollection locationId, JdbcTemplate localJdbcTemplate = getJdbcTemplate(); - List query; - List transIdList = null; if(locationId != null){ byte[] transIdLocation = idCodec.getTransIdByte(new TransactionId(locationId.getAppId(), locationId.getBusCode(), locationId.getTrxId())); - transIdList = localJdbcTemplate.queryForList(selectUnfinishedTransWithoutPos, new Object[]{idCodec.getAppIdCeil(appId), idCodec.getAppIdFloor(appId), createTimeCeiling,transIdLocation,pageSize},byte[].class); - }else{ - transIdList = localJdbcTemplate.queryForList(selectUnfinishedTransWithPos, new Object[]{idCodec.getAppIdCeil(appId), idCodec.getAppIdFloor(appId), createTimeCeiling,pageSize},byte[].class); + transIdList = localJdbcTemplate.queryForList(selectUnfinishedTransWithPos, new Object[]{idCodec.getAppIdCeil(appId), idCodec.getAppIdFloor(appId), createTimeCeiling,transIdLocation,pageSize},byte[].class); + } else { + transIdList = localJdbcTemplate.queryForList(selectUnfinishedTransWithoutPos, new Object[]{idCodec.getAppIdCeil(appId), idCodec.getAppIdFloor(appId), createTimeCeiling,pageSize},byte[].class); } if(transIdList == null || transIdList.size() ==0){ return new ArrayList(); } - NamedParameterJdbcTemplate namedTemplate = new NamedParameterJdbcTemplate(localJdbcTemplate); + return getTransactionLogByIds(localJdbcTemplate, transIdList); + } + + private List getTransactionLogByIds(JdbcTemplate localJdbcTemplate, List transIdList) { + List query; + NamedParameterJdbcTemplate namedTemplate = new NamedParameterJdbcTemplate(localJdbcTemplate); MapSqlParameterSource paramSource = new MapSqlParameterSource(); paramSource.addValue("ids", transIdList); query = namedTemplate.query(selectTransDetailsByIds, paramSource,new BeanPropertyRowMapper(DataBaseTransactionLogDetail.class)); @@ -103,9 +107,8 @@ public List getUnfinishedLogs(LogCollection locationId, currentContentList.addAll(deserializer(detailDo)); } addToResult(result, currentDoList, currentContentList); - - return result; - } + return result; + } private List deserializer(DataBaseTransactionLogDetail detailDo) { return serializer.deserialize(detailDo.getLogDetail()); } @@ -122,5 +125,14 @@ private void addToResult(List result, new ArrayList(currentContentList), first.getCreateTime())); } } + @Override + public List getTransactionLogById(List ids) { + + List keys = ids.stream().map( + locationId->idCodec.getTransIdByte(new TransactionId(locationId.getAppId(), locationId.getBusCode(), locationId.getTrxId()))) + .collect(Collectors.toList()); + + return getTransactionLogByIds(getJdbcTemplate(), keys); + } } diff --git a/easytrans-log-redis-starter/src/main/java/com/yiqiniu/easytrans/log/impl/redis/RedisTransactionLogReaderImpl.java b/easytrans-log-redis-starter/src/main/java/com/yiqiniu/easytrans/log/impl/redis/RedisTransactionLogReaderImpl.java index 672fa05..2dcfa64 100644 --- a/easytrans-log-redis-starter/src/main/java/com/yiqiniu/easytrans/log/impl/redis/RedisTransactionLogReaderImpl.java +++ b/easytrans-log-redis-starter/src/main/java/com/yiqiniu/easytrans/log/impl/redis/RedisTransactionLogReaderImpl.java @@ -115,4 +115,28 @@ public List getUnfinishedLogs(LogCollection locationId, int pageS return result; } + + @Override + public List getTransactionLogById(List ids) { + + Map mapStringTrx = ids.stream().collect(Collectors.toMap(trxId -> ObjectDigestUtil.byteArrayToHexString(idCodec.getTransIdByte(trxId)), trxId->trxId)); + + Map> mapContent = mapStringTrx.keySet().stream() + .collect(Collectors.toMap( + id->id, + id->{ + try { + return async.lrange(keyPrefix + id, 0,-1).get().stream().map(objectSerializer::deserialize).collect(Collectors.toList()); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + })); + + return mapContent.entrySet().stream().map( + entry -> { + TransactionId transactionId = mapStringTrx.get(entry.getKey()); + return new LogCollection(transactionId.getAppId(), transactionId.getBusCode(), transactionId.getTrxId(), entry.getValue(), null); + }) + .collect(Collectors.toList()); + } } diff --git a/easytrans-rpc-dubbo-starter/pom.xml b/easytrans-rpc-dubbo-starter/pom.xml index b630d78..4a2a5f1 100644 --- a/easytrans-rpc-dubbo-starter/pom.xml +++ b/easytrans-rpc-dubbo-starter/pom.xml @@ -27,6 +27,12 @@ dubbo 2.6.3 + + + com.alibaba + fastjson + 1.2.54 + com.101tec diff --git a/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboEasyTransRpcConfiguration.java b/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboEasyTransRpcConfiguration.java index ead34f2..34c7e0b 100644 --- a/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboEasyTransRpcConfiguration.java +++ b/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboEasyTransRpcConfiguration.java @@ -1,7 +1,9 @@ package com.yiqiniu.easytrans.rpc.impl.dubbo; +import java.util.List; import java.util.Optional; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -17,6 +19,8 @@ import com.alibaba.dubbo.config.RegistryConfig; import com.alibaba.dubbo.config.spring.context.annotation.EnableDubboConfig; import com.yiqiniu.easytrans.filter.EasyTransFilterChainFactory; +import com.yiqiniu.easytrans.monitor.EtMonitor; +import com.yiqiniu.easytrans.monitor.MonitorConsumerFactory; import com.yiqiniu.easytrans.rpc.EasyTransRpcConsumer; import com.yiqiniu.easytrans.rpc.EasyTransRpcProvider; @@ -28,6 +32,9 @@ @EnableConfigurationProperties(DubboEasyTransRpcProperties.class) @EnableDubboConfig public class DubboEasyTransRpcConfiguration { + + @Value("${spring.application.name}") + private String applicationName; @Bean @ConditionalOnMissingBean(EasyTransRpcConsumer.class) @@ -42,4 +49,18 @@ public DubboEasyTransRpcProviderImpl onsEasyTransMsgPublisherImpl(EasyTransFilte return new DubboEasyTransRpcProviderImpl(filterChainFactory, applicationConfig, registryConfig, protocolConfig, providerConfig, moduleConfig, monitorConfig, customizationer); } + @Bean + @ConditionalOnProperty(name="easytrans.rpc.dubbo.monitor.enabled",havingValue="true",matchIfMissing=true) + public DubboMonitorProvider dubboMonitorProvider(EasyTransFilterChainFactory filterChainFactory, Optional applicationConfig, + Optional registryConfig,Optional protocolConfig,Optional providerConfig,Optional moduleConfig,Optional monitorConfig, Optional customizationer,List etMonitorList) { + return new DubboMonitorProvider(applicationName, filterChainFactory, applicationConfig, registryConfig, protocolConfig, providerConfig, moduleConfig, monitorConfig, customizationer, etMonitorList); + } + + @Bean + @ConditionalOnMissingBean(MonitorConsumerFactory.class) + public MonitorConsumerFactory monitorConsumerFactory(Optional applicationConfig, Optional registryConfig,Optional protocolConfig,Optional consumerConfig,Optional moduleConfig,Optional monitorConfig, Optional customizationer) { + return new DubboMonitorConsumerFactory(applicationConfig, registryConfig, protocolConfig, consumerConfig, moduleConfig, monitorConfig, customizationer); + } + + } diff --git a/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboMonitorConsumerFactory.java b/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboMonitorConsumerFactory.java new file mode 100644 index 0000000..8c7cc54 --- /dev/null +++ b/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboMonitorConsumerFactory.java @@ -0,0 +1,114 @@ +package com.yiqiniu.easytrans.rpc.impl.dubbo; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ConsumerConfig; +import com.alibaba.dubbo.config.ModuleConfig; +import com.alibaba.dubbo.config.MonitorConfig; +import com.alibaba.dubbo.config.ProtocolConfig; +import com.alibaba.dubbo.config.ReferenceConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.rpc.service.GenericService; +import com.yiqiniu.easytrans.monitor.EtMonitor; +import com.yiqiniu.easytrans.monitor.MonitorConsumerFactory; + +public class DubboMonitorConsumerFactory implements MonitorConsumerFactory { + + private ApplicationConfig applicationConfig; + private RegistryConfig registryConfig; + private ProtocolConfig protocolConfig; + private ConsumerConfig consumerConfig; + private ModuleConfig moduleConfig; + private MonitorConfig monitorConfig; + private DubboReferanceCustomizationer customizationer; + + public DubboMonitorConsumerFactory(Optional applicationConfig, Optional registryConfig,Optional protocolConfig,Optional consumerConfig,Optional moduleConfig,Optional monitorConfig, Optional customizationer) { + super(); + this.applicationConfig = applicationConfig.orElse(null); + this.registryConfig = registryConfig.orElse(null); + this.protocolConfig = protocolConfig.orElse(null); + this.customizationer = customizationer.orElse(null); + this.consumerConfig = consumerConfig.orElse(null); + this.moduleConfig = moduleConfig.orElse(null); + this.monitorConfig = monitorConfig.orElse(null); + } + + private ConcurrentHashMap mapMonitor = new ConcurrentHashMap<>(); + + @SuppressWarnings("unchecked") + public T getRemoteProxy(String appId, Class monitorInterface) { + return (T) mapMonitor.computeIfAbsent(getKey(appId, monitorInterface), k -> { + return generateProxy(appId, monitorInterface); + }); + + } + + private EtMonitor generateProxy(String appId, Class monitorInterface) { + return (EtMonitor) Proxy.newProxyInstance(monitorInterface.getClassLoader(), new Class[] { monitorInterface }, new InvocationHandler() { + + private GenericService service = generateService(appId, monitorInterface); + private ConcurrentHashMap mapParameterCLassString = new ConcurrentHashMap<>(); + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + String[] paramTypeStr = mapParameterCLassString.computeIfAbsent(method, m->{ + return Arrays.stream(m.getParameterTypes()).map(clazz->clazz.getName()).toArray(String[]::new); + }); + + return service.$invoke(method.getName(), paramTypeStr, args); + } + }); + } + + private String getKey(String appId, Class monitorInterface) { + return appId + "|" + monitorInterface.getSimpleName(); + } + + private GenericService generateService(String appId, Class monitorInterface) { + + ReferenceConfig referenceConfig = new ReferenceConfig(); + referenceConfig.setInterface(monitorInterface); // 弱类型接口名 + referenceConfig.setVersion("1.0.0"); + referenceConfig.setGeneric(true); // 声明为泛化接口 + referenceConfig.setGroup(appId + "-" + monitorInterface.getSimpleName()); + referenceConfig.setCheck(false); + + if(applicationConfig != null) { + referenceConfig.setApplication(applicationConfig); + } + + if(registryConfig != null) { + referenceConfig.setRegistry(registryConfig); + } + + if(protocolConfig != null) { + referenceConfig.setProtocol(protocolConfig.getName()); + } + + if(moduleConfig != null) { + referenceConfig.setModule(moduleConfig); + } + + if(monitorConfig != null) { + referenceConfig.setMonitor(monitorConfig); + } + + if(consumerConfig != null) { + referenceConfig.setConsumer(consumerConfig); + } + + if(customizationer != null) { + customizationer.customDubboReferance(appId,null,referenceConfig); + } + + return referenceConfig.get(); + } + +} diff --git a/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboMonitorProvider.java b/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboMonitorProvider.java new file mode 100644 index 0000000..ec8e62e --- /dev/null +++ b/easytrans-rpc-dubbo-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/dubbo/DubboMonitorProvider.java @@ -0,0 +1,142 @@ +package com.yiqiniu.easytrans.rpc.impl.dubbo; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.dubbo.config.ApplicationConfig; +import com.alibaba.dubbo.config.ModuleConfig; +import com.alibaba.dubbo.config.MonitorConfig; +import com.alibaba.dubbo.config.ProtocolConfig; +import com.alibaba.dubbo.config.ProviderConfig; +import com.alibaba.dubbo.config.RegistryConfig; +import com.alibaba.dubbo.config.ServiceConfig; +import com.alibaba.dubbo.rpc.service.GenericException; +import com.alibaba.dubbo.rpc.service.GenericService; +import com.alibaba.fastjson.JSON; +import com.yiqiniu.easytrans.filter.EasyTransFilterChainFactory; +import com.yiqiniu.easytrans.monitor.EtMonitor; +import com.yiqiniu.easytrans.util.ReflectUtil; + +public class DubboMonitorProvider { + + private List etMonitorList; + private ApplicationConfig applicationConfig; + private RegistryConfig registryConfig; + private ProtocolConfig protocolConfig; + private ProviderConfig providerConfig; + private ModuleConfig moduleConfig; + private MonitorConfig monitorConfig; + private DubboServiceCustomizationer customizationer; + private String appId; + + private static Logger logger = LoggerFactory.getLogger(DubboEasyTransRpcProviderImpl.class); + + public DubboMonitorProvider(String appId, EasyTransFilterChainFactory filterChainFactory, Optional applicationConfig, + Optional registryConfig,Optional protocolConfig,Optional providerConfig,Optional moduleConfig,Optional monitorConfig, Optional customizationer,List etMonitorList) { + super(); + this.appId = appId; + this.applicationConfig = applicationConfig.orElse(null); + this.registryConfig = registryConfig.orElse(null); + this.protocolConfig = protocolConfig.orElse(null); + this.providerConfig = providerConfig.orElse(null); + this.moduleConfig = moduleConfig.orElse(null); + this.monitorConfig = monitorConfig.orElse(null); + this.customizationer = customizationer.orElse(null); + this.etMonitorList = etMonitorList; + } + + @PostConstruct + public void initHandler() throws NoSuchMethodException, SecurityException { + + for(EtMonitor monitor : etMonitorList) { + + Class monitorClass = monitor.getClass(); + Class markClass = ReflectUtil.getClassWithMark(monitorClass,EtMonitor.class); + if(!markClass.isInterface()) { + throw new RuntimeException("EtMonitor should mark in interface but not class!"); + } + + GenericService genericService = new GenericService() { + + @Override + public Object $invoke(String method, String[] parameterTypes, Object[] args) + throws GenericException { + Method m = getMethod(markClass, method, parameterTypes); + if(m == null) { + throw new RuntimeException("can not find method !" + markClass + method + parameterTypes) ; + } + + try { + if(logger.isDebugEnabled()) { + logger.debug("Monitor method called! {} {} {}",markClass.getSimpleName(),method,args); + } + + //to JSON string to avoid serialize problem + return JSON.toJSON(m.invoke(monitor, args)); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private Method getMethod(Class interfaceClass, String methodName, String[] parameterTypes) { + Method[] methods = interfaceClass.getMethods(); + for(Method m : methods) { + if(m.getName().equals(methodName)) { + String[] methodParameters = Arrays.stream(m.getParameterTypes()).map(t->t.getName()).toArray(String[]::new); + if(Arrays.equals(methodParameters, parameterTypes)) { + return m; + } + } + } + return null; + } + }; + + ServiceConfig service = new ServiceConfig(); + service.setInterface(markClass); + service.setGroup(appId + "-" + markClass.getSimpleName()); + service.setVersion("1.0.0"); + service.setRef(genericService); + service.setCluster("failfast"); + + if(applicationConfig != null) { + service.setApplication(applicationConfig); + } + + if(registryConfig != null) { + service.setRegistry(registryConfig); + } + + if(protocolConfig != null) { + service.setProtocol(protocolConfig); + } + + if(monitorConfig != null) { + service.setMonitor(monitorConfig); + } + + if(moduleConfig != null) { + service.setModule(moduleConfig); + } + + if(providerConfig != null) { + service.setProvider(providerConfig); + } + + if(customizationer != null) { + customizationer.customDubboService(null,service); + } + + service.export(); + } + + } +} diff --git a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConfiguration.java b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConfiguration.java index f453b68..c5a9149 100644 --- a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConfiguration.java +++ b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConfiguration.java @@ -11,6 +11,7 @@ import org.springframework.context.annotation.Configuration; import com.yiqiniu.easytrans.filter.EasyTransFilterChainFactory; +import com.yiqiniu.easytrans.monitor.MonitorConsumerFactory; import com.yiqiniu.easytrans.rpc.EasyTransRpcConsumer; import com.yiqiniu.easytrans.rpc.EasyTransRpcProvider; import com.yiqiniu.easytrans.serialization.ObjectSerializer; @@ -35,4 +36,16 @@ public RestRibbonEasyTransRpcProviderImpl restRibbonEasyTransRpcProviderImpl(Eas return new RestRibbonEasyTransRpcProviderImpl(filterChainFactory, serializer); } + @Bean + @ConditionalOnProperty(name="easytrans.rpc.rest-ribbon.monitor.enabled",havingValue="true",matchIfMissing=true) + public RestRibbonMonitorProvider restRibbonMonitorProvider() { + return new RestRibbonMonitorProvider(); + } + + @Bean + @ConditionalOnMissingBean(MonitorConsumerFactory.class) + public MonitorConsumerFactory monitorConsumerFactory(RestRibbonEasyTransRpcConsumerImpl consumer) { + return new RestRibbonMonitorConsumerFactory(consumer); + } + } diff --git a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConsumerImpl.java b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConsumerImpl.java index da3d7ed..15de045 100644 --- a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConsumerImpl.java +++ b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcConsumerImpl.java @@ -1,6 +1,8 @@ package com.yiqiniu.easytrans.rpc.impl.rest; import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; @@ -28,6 +30,7 @@ import com.netflix.loadbalancer.BestAvailableRule; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.Server; +import com.yiqiniu.easytrans.monitor.EtMonitor; import com.yiqiniu.easytrans.protocol.EasyTransRequest; import com.yiqiniu.easytrans.rpc.EasyTransRpcConsumer; import com.yiqiniu.easytrans.rpc.impl.rest.RestRibbonEasyTransRpcProperties.RestConsumerProperties; @@ -112,11 +115,7 @@ public RestTemplate getLoadBalancedRestTemplate() { @Override public

, R extends Serializable> R call(String appId, String busCode, String innerMethod, Map header, P params) { - RestConsumerProperties restConsumerProperties = properties.getConsumer().get(appId); - String context = RestRibbonEasyTransConstants.DEFAULT_URL_CONTEXT; - if(restConsumerProperties!= null && restConsumerProperties.getContext() != null){ - context = restConsumerProperties.getContext(); - } + String context = getHttpContext(appId); Class paramsClass = params.getClass(); Class resultClass = ReflectUtil.getResultClass(paramsClass); @@ -134,6 +133,41 @@ public RestTemplate getLoadBalancedRestTemplate() { return (R) exchangeResult.getBody(); } + private String getHttpContext(String appId) { + RestConsumerProperties restConsumerProperties = properties.getConsumer().get(appId); + String context = RestRibbonEasyTransConstants.DEFAULT_URL_CONTEXT; + if(restConsumerProperties!= null && restConsumerProperties.getContext() != null){ + context = restConsumerProperties.getContext(); + } + return context; + } + + public Object sendMonitorRequest(String appId, Class monitorClass, Method m,Object[] params) { + String httpContext = getHttpContext(appId); + + StringBuilder sb = new StringBuilder(); + sb.append("http://").append(appId) + .append("/").append(httpContext) + .append("/").append(RestRibbonMonitorProvider.MONITOR_CONTEXT) + .append("/").append(monitorClass.getSimpleName()) + .append("/").append(m.getName()) + .append("?"); + + int pos = 0; + for(Parameter param : m.getParameters()) { + if(params[pos] != null) { + sb.append(param.getName()).append("=").append(params[pos++]).append("&"); + } + } + + ResponseEntity exchangeResult = loadBalancedRestTemplate.exchange(sb.toString(), HttpMethod.GET, null, Object.class ); + if(!exchangeResult.getStatusCode().is2xxSuccessful()){ + throw new RuntimeException("远程请求发生错误:" + exchangeResult); + } + + return exchangeResult.getBody(); + } + private String encodeEasyTransHeader(Map header) { return new String(Base64.getEncoder().encode(serializer.serialization(header)), StandardCharsets.ISO_8859_1); } diff --git a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java index 976fd4f..aa0c115 100644 --- a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java +++ b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonEasyTransRpcProviderImpl.java @@ -172,8 +172,7 @@ public void startService(Class businessInterface,Map filters) { this.filters.addAll(filters); diff --git a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonMonitorConsumerFactory.java b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonMonitorConsumerFactory.java new file mode 100644 index 0000000..292a3b7 --- /dev/null +++ b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonMonitorConsumerFactory.java @@ -0,0 +1,37 @@ +package com.yiqiniu.easytrans.rpc.impl.rest; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.concurrent.ConcurrentHashMap; + +import com.yiqiniu.easytrans.monitor.EtMonitor; +import com.yiqiniu.easytrans.monitor.MonitorConsumerFactory; + +public class RestRibbonMonitorConsumerFactory implements MonitorConsumerFactory { + + private RestRibbonEasyTransRpcConsumerImpl consumer; + private ConcurrentHashMap mapMonitor = new ConcurrentHashMap<>(); + + public RestRibbonMonitorConsumerFactory(RestRibbonEasyTransRpcConsumerImpl consumer) { + super(); + this.consumer = consumer; + } + + @SuppressWarnings("unchecked") + public T getRemoteProxy(String appId, Class monitorInterface) { + + return (T) mapMonitor.computeIfAbsent(appId + "|" + monitorInterface.getSimpleName() , k->{ + return (EtMonitor)Proxy.newProxyInstance(monitorInterface.getClassLoader(), new Class[] {monitorInterface}, new InvocationHandler() { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return consumer.sendMonitorRequest(appId, monitorInterface, method, args); + } + }); + }); + + } + + +} diff --git a/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonMonitorProvider.java b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonMonitorProvider.java new file mode 100644 index 0000000..4382453 --- /dev/null +++ b/easytrans-rpc-rest-ribbon-starter/src/main/java/com/yiqiniu/easytrans/rpc/impl/rest/RestRibbonMonitorProvider.java @@ -0,0 +1,88 @@ +package com.yiqiniu.easytrans.rpc.impl.rest; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import com.yiqiniu.easytrans.monitor.EtMonitor; +import com.yiqiniu.easytrans.util.ReflectUtil; + +public class RestRibbonMonitorProvider { + +// @Autowired +// private RestRibbonEasyTransRpcProviderImpl controller; + + public static final String MONITOR_CONTEXT = "_monitors"; + + @Autowired + private RequestMappingHandlerMapping requestMappingHandlerMapping; + + @Autowired + private List etMonitorList; + + @Value("${easytrans.rpc.rest-ribbon.provider.context:" + RestRibbonEasyTransConstants.DEFAULT_URL_CONTEXT + "}") + private String rootUrl; + + @PostConstruct + public void initHandler() throws NoSuchMethodException, SecurityException { + + Set objectMethods = new HashSet<>(Arrays.asList(Object.class.getMethods())); + + for(EtMonitor monitor : etMonitorList) { + + Class monitorClass = monitor.getClass(); + Class markClass = ReflectUtil.getClassWithMark(monitorClass,EtMonitor.class); + if(!markClass.isInterface()) { + throw new RuntimeException("EtMonitor should mark in interface but not class!"); + } + + for(Method m : monitor.getClass().getMethods()) { + + if(objectMethods.contains(m)) { + continue; + } + + Object proxy = Proxy.newProxyInstance(monitorClass.getClassLoader(), new Class[]{markClass}, new InvocationHandler() { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if(!method.getName().equals(m.getName())) { + throw new RuntimeException("Illegal call! " + method.getName()); + } + + Object result = method.invoke(monitor, args); + + return new ResponseEntity(result,HttpStatus.OK); + }}); + + Method markClassMethod = markClass.getMethod(m.getName(), m.getParameterTypes()); + + RequestMappingInfo requestMappingInfo = RequestMappingInfo + .paths(rootUrl + "/" + MONITOR_CONTEXT + "/" + markClass.getSimpleName() + "/" + m.getName()) + .methods(RequestMethod.GET) + .produces(MediaType.APPLICATION_JSON_VALUE) + .build(); + + requestMappingHandlerMapping.registerMapping(requestMappingInfo, proxy, markClassMethod); + } + + } + + } +} diff --git a/easytrans-starter/pom.xml b/easytrans-starter/pom.xml index 0549859..59b782b 100644 --- a/easytrans-starter/pom.xml +++ b/easytrans-starter/pom.xml @@ -22,7 +22,7 @@ easytrans-core - + com.yiqiniu.easytrans easytrans-log-database-starter @@ -48,11 +48,10 @@ mysql mysql-connector-java + 5.1.46 test -