前言:该博客主要是记录自己学习的过程,方便以后查看,当然也希望能够帮到大家。
Easytool 的目标是干掉大部分冗余的复杂代码,从而最大限度的避免“复制粘贴”代码的问题,使我们能去更专注业务,提升我们的代码质量。
Easytool 是一个小型的Java工具类库,封装了一些常用的通用的方法,降低了相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅。
Easytool 中的大部分方法来自开发过程中的真实需求,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当。
| 名称 | 介绍 |
|---|---|
| easytool-all | 包含所有模块的引用 |
| easytool-core | 核心包,包括对集合处理、日期、各类Util等 |
| easytool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 |
| easytool-process | 基于spring封装的任务编排轻量级框架 |
easytool-process是一个基于spring封装的任务编排轻量级框架
主要包含:上下文,节点,链路,其他组件

一个节点代表一个任务,节点通过编排组成链路。创建节点需要继承AbstractNode抽象类,并实现相关抽象方法,由于依赖spring,所以需要加@Component注解。

| 名称 | 说明 |
|---|---|
| getDependsOnNodes() | 抽象方法,返回当前节点依赖的前置节点集合,必须实现。通过该方法进行链路任务编排,注意:只需要返回直接前置节点,比如链路为A->B->C,此时C节点只需要返回B节点即可,另外如果无依赖的节点,比如A节点,此时返回空或空集合即可 |
| isSkip(ChainContext<DemoContext> chainContext) | 抽象方法,用于跳过当前节点,必须实现。返回ture则跳过,false则不跳过,比如某种状态下不需要执行当前节点的场景 |
| execute(ChainContext<DemoContext> chainContext) | 抽象方法,用于编写当前节点的逻辑,必须实现。重点在此通过上下文chainContext与其他节点进行数据传递,会按照链路编排的顺序执行各个节点,最终获取相应的数据/操作 |
| businessFail(String msg)/businessFail(int code, String msg) | 内置方法,当遇到业务异常时,比如某个数据不存在,此时可以直接中断整个链路,返回业务错误提示,需要用到时调用即可 |
| onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) | 抽象方法,是一个回调方法,必须实现。需要搭配businessFail方法使用,当执行了上述方法后会执行 |
| onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) | 抽象方法,是一个回调方法,必须实现。当节点执行遇到未知错误时会执行 |
| onTimeoutFail(ChainContext<DemoContext> chainContext) | 抽象方法,是一个回调方法,必须实现。当节点执行遇到超时错误时会执行 |
| onSuccess(@NonNull ChainContext<T> chainContext) | 内置方法,是一个回调方法,当节点执行成功时会执行,需要用到时重写即可 |
| afterExecute(@NonNull ChainContext<T> chainContext) | 内置方法,是一个回调方法,当节点执行遇到完成时会执行。注意,是执行完成,就是无论有没有出现异常,需要用到时重写即可 |
一个链路包含了多个已经按照顺序编排好的节点,创建链路需要继承AbstractChain抽象类,并实现相关抽象方法,由于依赖spring,所以需要加@Component注解。

| 名称 | 说明 |
|---|---|
| getChainTimeout() | 内置方法,返回的是该链路的超时时间,执行超过该时间则中断链路并返回超时异常结果,默认值是200ms,需要替换时重写即可 |
| getThreadPool() | 内置方法,返回的是执行该链路的线程池,内置默认线程池,需要替换时重写即可 |
| execute(ChainContext<DemoContext> chainContext) | 抽象方法,用于编写当前节点的逻辑,必须实现。重点在此通过上下文chainContext与其他节点进行数据传递,会按照链路编排的顺序执行各个节点,最终获取相应的数据/操作 |
| KeyThreadContextConfig/SingletonThreadContextConfig | 内置类,用于配置线程上下文信息,包含新增,获取,删除三个动作 |
| getThreadContextInitConfigs() | 内置方法,返回的是用于配置线程上下文信息集合,也就是4中的两种,一种需要key,一种不需要。最常见的场景就是用于链路追踪的traceId,需要用到时重写即可。 |
| checkParams(ChainContext<DemoContext> chainContext) | 抽象方法,用于在执行链路之前做参数校验使用,必须实现 |
| businessFail(String msg)/businessFail(int code, String msg) | 内置方法,当遇到业务异常时,比如某个数据不存在,此时可以直接中断整个链路,返回业务错误提示,需要用到时调用即可行 |
| onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) | 抽象方法,是一个回调方法,必须实现。需要搭配businessFail方法使用,当执行了上述方法后会执行 |
| onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) | 抽象方法,是一个回调方法,必须实现。当链路执行遇到未知错误时会执行 |
| onTimeoutFail(ChainContext<DemoContext> chainContext) | 抽象方法,是一个回调方法,必须实现。当链路执行遇到超时错误时会执行 |
| onSuccess(@NonNull ChainContext<T> chainContext) | 内置方法,是一个回调方法,当链路执行成功时会执行,需要用到时重写即可 |
| afterExecute(@NonNull ChainContext<T> chainContext) | 内置方法,是一个回调方法,当链路执行遇到完成时会执行。注意,是执行完成,就是无论有没有出现异常,需要用到时重写即可 |
| openMonitor() | 内置方法,用于开启链路监控,会记录并定时打印链路及各节点执行的耗时等信息。返回ture则开启,false则不开启,需要用到时重写即可 |
| setNodeInfo() | 抽象方法,用于添加链路节点,必须实现。内部通过下面方法添加节点,注意:链路的执行顺序跟此处的添加顺序无关 |
| addInterruptNode/addInterruptNodes | 内置方法,增加一个中断节点。执行遇到异常时中断链路,并返回异常信息。参数:节点类,获取节点超时时间方法 |
| addAbandonNode/addAbandonNodes | 内置方法,增加一个抛弃节点。执行遇到异常时抛弃该节点,继续执行后续节点。参数:节点类,获取节点超时时间方法 |
| addRetryNode/addRetryNodes | 内置方法,增加一个重试节点。执行遇到异常时重试执行该节点,达到最大重试次数后还未执行成功,则中断链路,并返回异常信息。参数:节点类,获取节点超时时间方法,重试次数 |
## 会有整个执行过程的细节日志
grep '链路id' xx.log
## 会有整个链路过程的细节日志,过滤一些可能无关的日志
grep '链路id' xx.log | grep 'process'
## 会有整个链路过程的细节日志,包括一些错误日志(错误栈信息可能换行)
grep -A 50 '链路id' xx.log | grep 'process'

<dependency>
<groupId>cc.jinhx</groupId>
<artifactId>easytool-all</artifactId>
<version>自行查看最新版本</version>
</dependency>
可以根据需求对每个模块单独引入,也可以通过引入easytool-all方式引入所有模块。
DemoContext
import lombok.Data;
/**
* DemoContext
*
* @author jinhx
* @since 2022-03-29
*/
@Data
public class DemoContext {
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 入参 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 入参 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
private String req;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 中间数据 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private String dataA;
private String dataB;
private String dataC;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 中间数据 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 结果 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
private String dataD;
private String dataE;
private String dataF;
private String dataG;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 结果 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
}
DemoGetDataANode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* DemoGetDataANode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataANode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return null;
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("req".equals(demoContextInfo.getReq())){
demoContextInfo.setDataA(demoService.getDataA());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataBNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataBNode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataBNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataANode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataA".equals(demoContextInfo.getDataA())){
demoContextInfo.setDataB(demoService.getDataB());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataCNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataBNode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataCNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataANode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataA".equals(demoContextInfo.getDataA())){
demoContextInfo.setDataC(demoService.getDataC());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataDNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataDNode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataDNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataANode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataA".equals(demoContextInfo.getDataA())){
demoContextInfo.setDataD(demoService.getDataD());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataENode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataENode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataENode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Arrays.asList(DemoGetDataBNode.class, DemoGetDataCNode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataB".equals(demoContextInfo.getDataB()) && "dataC".equals(demoContextInfo.getDataC())){
demoContextInfo.setDataE(demoService.getDataE());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataFNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataENode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataFNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataCNode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataC".equals(demoContextInfo.getDataC())){
demoContextInfo.setDataF(demoService.getDataF());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoGetDataGNode
import cc.jinhx.easytool.process.BusinessException;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.service.DemoService;
import cc.jinhx.easytool.process.node.AbstractNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* DemoGetDataENode
*
* @author jinhx
* @since 2022-03-29
*/
@Component
public class DemoGetDataGNode extends AbstractNode<DemoContext> {
@Autowired
private DemoService demoService;
@Override
public Set<Class<? extends AbstractNode>> getDependsOnNodes() {
return new HashSet<>(Collections.singletonList(DemoGetDataENode.class));
}
@Override
protected boolean isSkip(ChainContext<DemoContext> chainContext) {
return false;
}
@Override
protected void execute(ChainContext<DemoContext> chainContext) {
DemoContext demoContextInfo = chainContext.getContextInfo();
if ("dataE".equals(demoContextInfo.getDataE())){
demoContextInfo.setDataG(demoService.getDataG());
}
}
@Override
public void onUnknowFail(ChainContext<DemoContext> chainContext, Exception e) {
}
@Override
public void onBusinessFail(ChainContext<DemoContext> chainContext, BusinessException e) {
}
@Override
public void onTimeoutFail(ChainContext<DemoContext> chainContext) {
}
}
DemoChain
import cc.jinhx.easytool.process.chain.*;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import cc.jinhx.easytool.process.demo.node.*;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* DemoChain
*
* @author jinhx
* @since 2022-03-29
*/
@Slf4j
@Component
public class DemoChain extends AbstractChain<DemoContext> {
private static final AtomicInteger CHAIN_THREAD_POOL_COUNTER = new AtomicInteger(0);
private static final int CPU_NUM = Runtime.getRuntime().availableProcessors();
/**
* 自定义链路线程池
*/
public static final ThreadPoolExecutor CHAIN_THREAD_POOL =
new ThreadPoolExecutor(
2, CPU_NUM * 2,
10, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(1024),
(Runnable r) -> new Thread(r, "chain_thread_" + CHAIN_THREAD_POOL_COUNTER.incrementAndGet()),
(r, executor) -> log.info("chain has bean rejected" + r));
private final static long DEFAULT_CHAIN_TIMEOUT = 1000L;
private final static long DEFAULT_NODE_TIMEOUT = 500L;
@Override
protected long getChainTimeout() {
return DEFAULT_CHAIN_TIMEOUT;
}
@Override
protected ExecutorService getThreadPool() {
// 执行该链路的线程池
return CHAIN_THREAD_POOL;
}
@Override
protected Set<AbstractThreadContextConfig> getThreadContextInitConfigs() {
return new HashSet<>(Collections.singletonList(new KeyThreadContextConfig<>("traceId", MDC::get, MDC::put, MDC::remove)));
}
@Override
protected void checkParams(ChainContext<DemoContext> chainContext) {
}
/**
* 设置节点信息,链路的执行顺序跟此处的添加顺序无关
*/
@Override
protected void setNodeInfo() {
// 添加重试节点DemoGetDataANode,重试次数为1,并配置节点超时时间。执行遇到异常时重试执行该节点,达到最大重试次数后还未执行成功,则中断链路,并返回异常信息
this.addRetryNode(DemoGetDataANode.class, ChainNode.RetryTimesEnum.ONE, DemoChain::getNodeTimeout);
// 添加中断节点DemoGetDataBNode,DemoGetDataCNode,并配置节点超时时间。执行遇到异常时中断链路,并返回异常信息
this.addInterruptNodes(Arrays.asList(DemoGetDataBNode.class, DemoGetDataCNode.class), DemoChain::getNodeTimeout);
// 添加抛弃节点DemoGetDataDNode,并配置节点超时时间。执行遇到异常时抛弃该节点,继续执行后续节点
this.addAbandonNode(DemoGetDataDNode.class, DemoChain::getNodeTimeout);
// 添加中断节点DemoGetDataENode,DemoGetDataFNode,DemoGetDataGNode,并配置节点超时时间。执行遇到异常时中断链路,并返回异常信息
this.addInterruptNodes(Arrays.asList(DemoGetDataENode.class, DemoGetDataFNode.class, DemoGetDataGNode.class), DemoChain::getNodeTimeout);
}
public static long getNodeTimeout() {
return DEFAULT_NODE_TIMEOUT;
}
}
DemoTest
import cc.jinhx.easytool.process.ProcessResult;
import cc.jinhx.easytool.process.SpringContextConfig;
import cc.jinhx.easytool.process.chain.ChainContext;
import cc.jinhx.easytool.process.chain.ChainHandler;
import cc.jinhx.easytool.process.demo.chain.DemoChain;
import cc.jinhx.easytool.process.demo.context.DemoContext;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* DemoTest
*
* @author jinhx
* @since 2022-03-21
*/
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringContextConfig.class)
public class DemoTest {
@Test
public void test1() {
// 创建上下文
ChainContext<DemoContext> chainContext = ChainContext.create(DemoContext.class);
// 设置入参
chainContext.getContextInfo().setReq("req");
// 执行指定链路,并返回所有数据
ProcessResult<DemoContext> processResult1 = ChainHandler.execute(DemoChain.class, chainContext);
System.out.println(processResult1);
// 执行指定链路,并返回指定数据
ProcessResult<String> processResult2 = ChainHandler.execute(DemoChain.class, chainContext, DemoContext::getDataG);
System.out.println(processResult2);
}
}
## 会有整个执行过程的细节日志
grep '链路id' xx.log
## 会有整个链路过程的细节日志,过滤一些可能无关的日志
grep '链路id' xx.log | grep 'process'
## 会有整个链路过程的细节日志,包括一些错误日志(错误栈信息可能换行)
grep -A 50 '链路id' xx.log | grep 'process'
后记:本次分享到此结束,本人水平有限,难免有错误或遗漏之处,望大家指正和谅解,欢迎评论留言。
英文版英文链接关注公众号在“亚特兰蒂斯的回声”中踏上一段难忘的冒险之旅,深入未知的海洋深处。足智多谋的考古学家AriaSeaborne偶然发现了一件古代神器,揭示了一张通往失落之城亚特兰蒂斯的隐藏地图。在她神秘的导师内森·兰登教授的指导和勇敢的冒险家亚历克斯·默瑟的帮助下,阿丽亚开始了一段危险的旅程,以揭开这座传说中城市的真相。他们的冒险之旅带领他们穿越险恶的大海、神秘的岛屿和充满陷阱和谜语的致命迷宫。随着Aria潜在的魔法能力的觉醒,她被睿智勇敢的QueenNeria的幻象所指引,她让她为即将到来的挑战做好准备。三人组揭开亚特兰蒂斯令人惊叹的隐藏文明,并了解到邪恶的巫师马拉卡勋爵试图利用其古
matlab打开matlab,用最简单的imread方法读取一个图像clcclearimg_h=imread('hua.jpg');返回一个数组(矩阵),往往是a*b*cunit8类型解释一下这个三维数组的意思,行数、数和层数,unit8:指数据类型,无符号八位整形,可理解为0~2^8的数三个层数分别代表RGB三个通道图像rgb最常用的是24-位实现方法,即RGB每个通道有256色阶(2^8)。基于这样的24-位RGB模型的色彩空间可以表现256×256×256≈1670万色当imshow传入了一个二维数组,它将以灰度方式绘制;可以把图像拆分为rgb三层,可以以灰度的方式观察它figure(1
Ruby有一些不错的文档生成器,例如Yard、rDoc,甚至Glyph。问题是Sphinx可以做网站、PDF、epub、LaTex等。它在重组文本中完成所有这些事情。在Ruby世界中有替代方案吗?也许是程序的组合?如果我也能使用Markdown就更好了。 最佳答案 自1.0版以来,Sphinx有了“域”的概念,它是从Python和/或C以外的语言标记代码实体(如方法调用、对象、函数等)的方法。有一个rubydomain,所以你可以只使用Sphinx本身。您唯一会缺少的(我认为)是Sphinx使用autodoc从源代码自动创建文档
我怀念ipython的一件事是它有一个?为特定功能挖掘文档的运算符。我知道ruby有一个类似的命令行工具,但是我在irb中调用它非常不方便。ruby/irb有类似的东西吗? 最佳答案 Pry是IPython的Ruby版本,它支持?命令来查找有关方法的文档,但语法略有不同:pry(main)>?File.dirnameFrom:file.cinRubyCore(CMethod):Numberoflines:6visibility:publicsignature:dirname()Returnsallcomponentsofthef
我正在尝试使用nokogirigem提取页面上的所有url及其链接文本,并将链接文本和url存储在散列中。FooBar我想回去{"Foo"=>"#foo","Bar"=>"#bar"} 最佳答案 这是一个单行:Hash[doc.xpath('//a[@href]').map{|link|[link.text.strip,link["href"]]}]#=>{"Foo"=>"#foo","Bar"=>"#bar"}拆分一点可以说更具可读性:h={}doc.xpath('//a[@href]').eachdo|link|h[link.t
这是一些奇怪的例子:#!/usr/bin/rubyrequire'rubygems'require'open-uri'require'nokogiri'print"withoutread:",Nokogiri(open('http://weblog.rubyonrails.org/')).class,"\n"print"withread:",Nokogiri(open('http://weblog.rubyonrails.org/').read).class,"\n"运行此返回:withoutread:Nokogiri::XML::Documentwithread:Nokogiri::
目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式
这是我的YAML文件“test.yml”:---alpha:100.0beta:200.0gama:300.0---3...第一个文档是一个散列。第二个文档是一个整数。我正在尝试将它们作为散列和整数加载到Ruby程序中。这是我目前的尝试:require'yaml'variables=YAML.load_file('test.yml')putsvariables.inspect 最佳答案 要访问单个文件中的多个YAML文档,请使用load_stream方法(正如“matt”在对其他答案之一的评论中提到的):YAML.load_stre
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭4年前。Improvethisquestion我希望能够将模板化的YARD文档样式注释插入到我现有的Rails遗留应用程序中。目前它的评论很少。我想要具有指定参数的类header和方法header(通过从我假定的方法签名中提取)和返回值的占位符。在PHP代码中,我有一些工具可以检查代码并在适当的位置创建插入到代码中的文档header注释。在带有Ducktyping等的Ruby中,我确信诸如@params等类型之类
我必须使用AdobeInDesign来创建文档。我们基本上需要在整个文档中有几个变量(公司名称、项目名称等)需要填写。我想知道是否有一种方法可以采用现有模板,并使用我熟悉的语言(Ruby、Python等)以编程方式填充这些模板。我尝试使用文本编辑器打开AdobeInDesign文件-但是当我使用Notepad++进行修改然后在InDesign中打开文件时,它告诉我文件已损坏。如果您对以编程方式构建InDesign文档有任何见解或了解任何教程,我们将不胜感激。附言我尝试查看Adobe的ExtendScript,但没有找到太多适用于InDesign的文档。感谢您的帮助!