Vim中使用The Platinum Searcher

在vim中如何使用The Platinum Searcher呢?

我们看到readme中写道

1
2
3
4
5
6
7
nnoremap <silent> ,g :<C-u>Unite grep:. -buffer-name=search-buffer<CR>
if executable('pt')
let g:unite_source_grep_command = 'pt'
let g:unite_source_grep_default_opts = '--nogroup --nocolor'
let g:unite_source_grep_recursive_opt = ''
let g:unite_source_grep_encoding = 'utf-8'
endif

这个贴到.vimrc就没问题了吗?

显然不是。我们还需要安装unite.vimvimproc.vim

1
2
3
4
Bundle "Shougo/unite.vim"
Bundle "Shougo/vimproc.vim"

# run :BundleInstall

之后需要安装vimproc,cd ~/.vim/bundle/vimproc.vim/ && make

然后就可以在normal mode下使用,g,这样会弹出一个让你输入Pattern:的对话条,输入需要搜索的pattern就行了。

接下来问题是,这个config到底是什么意思?

: 防止输出到命令行上

: 试一下就知道,卷上画面一半

vim

Java 的 @FunctionalInterface 以及Lamda

背景

在使用MockMvc的时候我们通常会见到类似下面的表达

1
2
3
4
mockMvc.perform(MockMvcRequestBuilders.get("/some_api_endpoint")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].user_id").value("123"));

andExpect()到底是个什么东西?为什么里面传了函数之后返回结果还能继续调用自身?

经过翻阅文档我们发现,MockMvc.perform 返回一个ResultActions的instance。
而这个ResultActions则是一个接口。

1
2
3
4
5
6
7
8
9
public interface ResultActions {

ResultActions andExpect(ResultMatcher matcher) throws Exception;

ResultActions andDo(ResultHandler handler) throws Exception;

MvcResult andReturn();

}

这样就解释了为什么可以继续调用自身。

那么这个ResultMatcher是个什么东西?

1
2
3
4
5
6
7
8
9
10
11
@FunctionalInterface
public interface ResultMatcher {
void match(MvcResult result) throws Exception;
static ResultMatcher matchAll(ResultMatcher... matchers) {
return result -> {
for (ResultMatcher matcher : matchers) {
matcher.match(result);
}
};
}
}

于是引出了这个文章的最终话题,@FunctionalInterface到底是什么?

@FunctionalInterface和Java8 Lamda

其实理解了@FunctionalInterface对理解Lamda很有帮助,因为Lamda的本质是匿名函数。
@FunctionalInterface字如其名,是函数的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
@FunctionalInterface
interface FunctionPointer {
void helloWorld(String subject, String name);
}

private static void helloFromFunction(String subject, String name, FunctionPointer functionPointer){
functionPointer.helloWorld(subject, name);
}

private static void hello(String subject, String name){
System.out.println("Hello " + subject + ", I'm " + name + ". ");
}

public static void main(String[] args) {
helloFromFunction("world", "wrongwrong", (subject, name) -> System.out.println("Hello " + subject + ", I'm " + name + "."));
helloFromFunction("world", "wrongwrong", Main::hello);
}
}

上面的代码,我们用@FunctionalInterface标注了FunctionPointer, 于是任何我们定义的函数,只要符合其中定义的void helloWorld(string, string)的调用规则,都可以用FunctionPointer.helloWorld来调用。

概念上来说,一个@FunctionalInterface只能有一个抽象函数, 所以我们定义void hello(string, string)的时候并不需要声明我这个函数实现了FunctionPointer中的helloWorld这个方法。

甚至,我们只需在使用的时候带入一个匿名函数。

1
helloFromFunction("world", "wrongwrong", (subject, name) -> System.out.println("Hello " + subject + ", I'm " + name + "."));

这个就是Lamda的本质了。

PHP strtotime函数计算下个月时候的一个坑

我们用php获取下个月的月份的时候通常会想当然

1
echo date('Ym', strtotime('next month'));

平时看来并没有什么问题,但是当今天是1月31号的时候会发生什么呢?

1
2
3
4
5
6
7
8
9
10
11
<?php 
$date = "2018-01-31";

$ym1 = date('Y-m', strtotime('+1 month ' . $date));
$ym2 = date('Y-m', strtotime('+2 month ' . $date));
$ym3 = date('Y-m', strtotime('+3 month ' . $date));

var_dump($date);
var_dump($ym1);
var_dump($ym2);
var_dump($ym3);

我们期待的输出结果可能是

1
2
3
4
string(10) “2018-01-31”
string(7) “2018-02”
string(7) “2018-03”
string(7) “2018-04”

但是输出的结果确是

1
2
3
4
string(10) “2018-01-31”
string(7) “2018-03”
string(7) “2018-03”
string(7) “2018-05”

这到底是怎么会是呢?

原来,strtotime函数将1月31日的1个月以后计算成2月31日,显然,2月没有31日,于是,这个1个月后的时间就变成了相应的3月3日。去掉月份,于是得到了2018-03的结果。

那么要正确获取下个月的日期可以怎么做呢?

其实很简单。先取得这个月的第一天,然后再加上一个月就好了。

1
2
$date = date("Y-m-01");
$ym1 = date('Y-m', strtotime('+1 month ' . $date));
PHP

Spring Boot Redis Cluster的相关配置

搭建 Redis Cluster

直接使用docker创建一个Redis Cluster。

1
2
3
#docker-compose.yml
redisCluster:
image: grokzen/redis-cluster:3.2.13

这个Image默认将创建端口号从7000-7005的3Master3Slave的的Redis Cluster。

在SpringBoot的configuration中配置端口。

1
2
3
4
5
6
7
#application.properties
spring.redis.cluster.nodes[0]=notification_redis:7000
spring.redis.cluster.nodes[1]=notification_redis:7001
spring.redis.cluster.nodes[2]=notification_redis:7002
spring.redis.cluster.nodes[3]=notification_redis:7003
spring.redis.cluster.nodes[4]=notification_redis:7004
spring.redis.cluster.nodes[5]=notification_redis:7005

Java Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class RedisClusterConf {

List<String> nodes;

@Bean
public RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(new RedisClusterConfiguration(nodes));
}
@Bean
public RedisTemplate<String, MyObject> redisKvTemplate(@Qualifier("connectionFactory") RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate <>();
template.setConnectionFactory(factory);

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =new Jackson2JsonRedisSerializer<>(MyObject.class);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
//template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
//template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}

}

public RedisTemplate<String, MyObject> redisKvTemplate(@Qualifier("connectionFactory") RedisConnectionFactory factory)
定义一个将MyObject对象序列化然后保存到Redis中的Template。

Spring-boot-starter-redis默认使用JdkSerializationRedisSerializer将value序列化(而Key则是默认使用StringRedisSerializer),
这样的结果就是我们直接在redis-cli中查看value的内容极其不方便。

具体可以参考这里

我们需要根据实际的需要,将Key和Value以肉眼可以理解的形式序列化。所以就有了

1
2
3
4
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =new Jackson2JsonRedisSerializer<>(MyObject.class);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);

这样,我们的Key直接以plain的String进行存储,Value被序列化成Json的String进行存储。

那么setHashKeySerializersetHashValueSerializer分别是什么呢?
原来是当value中包含HashMap的时候,我们需要将HashMap的Key和Value也进行序列化,
这个时候,我们也需要制定Key和Value的序列化方式。
在没有用到HashMap的情况下,我们自然就不需要指定了。

1
2
//template.setHashKeySerializer(stringRedisSerializer);
//template.setHashValueSerializer(jackson2JsonRedisSerializer);

另外参考

 

Spring Boot 使用RabbitMQ的实例

目标

Publisher将Json消息发送至RabbitMQ中,
Comsumer能够将RabbitMQ中的消息自动映射成对应的Java POJO。

准备

使用docker-compose迅速运行RabbitMQ。

1
2
3
4
5
rabbitmq:
image: rabbitmq:management
ports:
- "5672:5672"
- "15672:15672"

添加spring-boot-starter-amqp

1
2
3
4
5
6
7
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-amqp'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'org.springframework.amqp:spring-rabbit-test'
}

添加AMQP的Java Configuration。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@EnableRabbit
public class AmqpConf implements RabbitListenerConfigurer {

@Bean
public MappingJackson2MessageConverter jackson2Converter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
return converter;
}

@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(jackson2Converter());
return factory;
}

@Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
}

}

添加Message Listener。

1
2
3
4
5
6
7
8
9
10
class Receiver {
@RabbitListener(queues = "queue")
public void receiveMessage(Foo foo) {
System.out.println("Received <" + foo.name + ">");
}
}

class Foo {
public String name;
}

解说

mappingJackson2MessageConverter能将接受到的Quque中的Message转换成对应的POJO。

Spring使用PayloadArgumentResolver去提取,转换Message为对应标记@RabbitListener Method中的Parameter。

我们需要自己实现一个RabbitListenerConfigurer,将Message converter设置成mappingJackson2MessageConverter。

具体参考这里