GeXiangDong

精通Java、SQL、Spring的拼写,擅长Linux、Windows的开关机

0%

现象

错误日志

org.elasticsearch.cluster.block.ClusterBlockException: blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];
    at org.elasticsearch.cluster.block.ClusterBlocks.indexBlockedException(ClusterBlocks.java:183)
    at org.elasticsearch.action.support.replication.TransportReplicationAction.blockExceptions(TransportReplicationAction.java:255)
    at org.elasticsearch.action.support.replication.TransportReplicationAction.access$500(TransportReplicationAction.java:100)
    at org.elasticsearch.action.support.replication.TransportReplicationAction$ReroutePhase.doRun(TransportReplicationAction.java:780)
    at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37)
    at org.elasticsearch.action.support.replication.TransportReplicationAction.doExecute(TransportReplicationAction.java:172)
    at org.elasticsearch.action.support.replication.TransportReplicationAction.doExecute(TransportReplicationAction.java:100)
    at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:167)
    at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.apply(SecurityActionFilter.java:124)
    at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:165)
    at org.elasticsearch.action.support.TransportAction.execute(TransportAction.java:139)
    at org.elasticsearch.action.support.TransportAction.execute(TransportAction.java:81)
    at org.elasticsearch.action.bulk.TransportBulkAction$BulkOperation.doRun(TransportBulkAction.java:420)
    at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37)
    at org.elasticsearch.action.bulk.TransportBulkAction.executeBulk(TransportBulkAction.java:533)
    at org.elasticsearch.action.bulk.TransportBulkAction.executeIngestAndBulk(TransportBulkAction.java:271)
    at org.elasticsearch.action.bulk.TransportBulkAction.doExecute(TransportBulkAction.java:188)
    at org.elasticsearch.action.bulk.TransportBulkAction.doExecute(TransportBulkAction.java:90)
    at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:167)
    at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.apply(SecurityActionFilter.java:124)
    at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:165)
    at org.elasticsearch.action.support.TransportAction.execute(TransportAction.java:139)
    at org.elasticsearch.action.bulk.TransportSingleItemBulkWriteAction.doExecute(TransportSingleItemBulkWriteAction.java:69)
    at org.elasticsearch.action.bulk.TransportSingleItemBulkWriteAction.doExecute(TransportSingleItemBulkWriteAction.java:44)
    at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:167)
    at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.apply(SecurityActionFilter.java:124)
    at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:165)
    at org.elasticsearch.action.support.TransportAction.execute(TransportAction.java:139)
    at org.elasticsearch.action.support.replication.TransportReplicationAction$OperationTransportHandler.messageReceived(TransportReplicationAction.java:284)
    at org.elasticsearch.action.support.replication.TransportReplicationAction$OperationTransportHandler.messageReceived(TransportReplicationAction.java:276)
    at org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor$ProfileSecuredRequestHandler$1.doRun(SecurityServerTransportInterceptor.java:250)
    at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37)
    at org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor$ProfileSecuredRequestHandler.messageReceived(SecurityServerTransportInterceptor.java:308)
    at org.elasticsearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:66)
    at org.elasticsearch.transport.TcpTransport$RequestHandler.doRun(TcpTransport.java:1289)
    at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37)
    at org.elasticsearch.common.util.concurrent.EsExecutors$1.execute(EsExecutors.java:140)
    at org.elasticsearch.transport.TcpTransport.handleRequest(TcpTransport.java:1247)
    at org.elasticsearch.transport.TcpTransport.messageReceived(TcpTransport.java:1111)
    at org.elasticsearch.transport.TcpTransport.inboundMessage(TcpTransport.java:914)
    at org.elasticsearch.transport.netty4.Netty4MessageChannelHandler.channelRead(Netty4MessageChannelHandler.java:53)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.handler.logging.LoggingHandler.channelRead(LoggingHandler.java:241)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:656)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:556)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:510)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:470)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:909)
    at java.lang.Thread.run(Thread.java:834)

原因

日志里已经很明显了,elasticsearch 进入了只读模式,不能执行删除操作。

Elasticsearch进入只读模式是因为磁盘空间不足造成的,磁盘可用空间过低后(默认低于5%),elasticsearch会进入只读模式,即使增加了磁盘可用空间也不会自动恢复,需要通过如下 curl 命令恢复。

1
curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_all/_settings -d '{"index.blocks.read_only_allow_delete": null}'

Reference

https://stackoverflow.com/questions/50609417/elasticsearch-error-cluster-block-exception-forbidden-12-index-read-only-all

现象

java程序调用https url的服务时,会出错,错误堆栈如下。 不论是使用spring resttemplate,还是自己用jdk里的httpsurlconnection 写段程序访问,都出现这个问题,而这个url在浏览器里调用是好的。

java.lang.RuntimeException: javax.net.ssl.SSLHandshakeException: Could not generate secret

    at cn.devmgr.mall.mms.sync.CommonService.login(CommonService.java:64)
    at cn.devmgr.mall.mms.test.AppTests.testSyncClerks(AppTests.java:40)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1510)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1510)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: javax.net.ssl.SSLHandshakeException: Could not generate secret
    at java.base/sun.security.ssl.KAKeyDerivation.t12DeriveKey(KAKeyDerivation.java:91)
    at java.base/sun.security.ssl.KAKeyDerivation.deriveKey(KAKeyDerivation.java:61)
    at java.base/sun.security.ssl.ECDHClientKeyExchange$ECDHEClientKeyExchangeProducer.produce(ECDHClientKeyExchange.java:419)
    at java.base/sun.security.ssl.ClientKeyExchange$ClientKeyExchangeProducer.produce(ClientKeyExchange.java:65)
    at java.base/sun.security.ssl.SSLHandshake.produce(SSLHandshake.java:440)
    at java.base/sun.security.ssl.ServerHelloDone$ServerHelloDoneConsumer.consume(ServerHelloDone.java:182)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:445)
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:423)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:167)
    at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1462)
    at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1370)
    at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:437)
    at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:567)
    at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:171)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1370)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1345)
    at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:220)
    at cn.devmgr.mall.mms.sync.CommonService.login(CommonService.java:51)
    ... 64 more
Caused by: java.security.InvalidKeyException: cannot identify XDH private key
    at org.bouncycastle.jcajce.provider.asymmetric.edec.KeyAgreementSpi.engineDoPhase(Unknown Source)
    at java.base/javax.crypto.KeyAgreement.doPhase(KeyAgreement.java:579)
    at java.base/sun.security.ssl.KAKeyDerivation.t12DeriveKey(KAKeyDerivation.java:75)
    ... 83 more

排查

单独用 HttpUrlConnection 来写main方法访问url,脱离项目环境,单独运行Java main方法,发现是好的。
因此可以排除是本地java环境和服务器证书问题,断定是堆栈中对应的org.bouncycastle导致的。

通过 mvn -X test 运行项目,找到org.boundcycastle,找到使用它的上级jar,在pom里 exclusion 掉,再次运行,解决。

解决

maven pom 中排除掉 org.bouncycastle:bcprov-jdk15on:jar:1.65

如果你的项目需要这个包,升级到最新版。

有2个java的库可以生成jwt,maven依赖分别如下

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>

有在一个项目中用jjwt使用HS512方式做签名的token,使用同一个字符串作为key,但是在java-jwt中无法验证,出现错误:

Exception in thread “main” com.auth0.jwt.exceptions.SignatureVerificationException: The Token’s Signature resulted invalid when verified using the Algorithm: HmacSHA512

出错的代码

1
2
3
4
5
6
Jwts.builder()
.setClaims(map)
.setExpiration(new Date(t))
.signWith(SignatureAlgorithm.HS512, "helloworld")
.compact();

1
2
3
4
Algorithm algorithm = Algorithm.HMAC512("helloworld");
JWTVerifier verifier =
JWT.require(algorithm).acceptExpiresAt(5).build(); // Reusable verifier instance
DecodedJWT jwt = verifier.verify(t2);

原因

仔细查看代码后发现jjwt的签名参数传递错误导致,signWith的第二个参数时字符串,但是是需要的做base64后的字符串,在jjwt的源码中对传递进来的字符串参数做了base64 decode

参照: https://github.com/jwtk/jjwt/blob/master/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java

1
2
3
4
5
6
7
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey);
return signWith(alg, bytes);
}

解决方案

知道原因了就很容易解决了,最简单的方式两边加密解密的都用成 byte[] 做key,不会有base64 啥事。

或者jjwt加密时先把明文密码字符串做base64.Encode

1
2
3
4
5
6
String t2 =
Jwts.builder()
.setClaims(buyerMap)
.setExpiration(new Date(t))
.signWith(SignatureAlgorithm.HS512, Base64.encode("helloworld".getBytes()))
.compact();

在使用Springboot 时遇到错误,服务端日志如下:

1
WARN  o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported]

RestController里

1
2
3
4
@RequestMapping(
value = "/xxxxxs",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)

看上去这个错误原因很简单,客户端传递的 Content-Type 没有在服务器端支持。

检查发现,客户端和服务器端都是正确,而且同一个controller里有的POST是好的,不出问题,有的出问题。

网上搜到的办法也都试过,pom中依赖也改过….

后来把日志级别 org.springframeword 改为 TRACE,然后发现日志中有这么一段(有堆栈,很容发现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
12:49:56.112 [http-nio-8050-exec-3] DEBUG o.s.h.c.j.MappingJackson2HttpMessageConverter - Failed to evaluate Jackson deserialization for type [[simple type, class cn.devmgr.mall.product.pojo.Category]]
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot find a (Map) Key deserializer for type [simple type, class cn.devmgr.mall.product.pojo.ProductPriceKey]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1589)
at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:599)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findKeyDeserializer(DeserializerCache.java:168)
at com.fasterxml.jackson.databind.DeserializationContext.findKeyDeserializer(DeserializationContext.java:499)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.createContextual(MapDeserializer.java:248)
at com.fasterxml.jackson.databind.DeserializationContext.handlePrimaryContextualization(DeserializationContext.java:650)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:484)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
at com.fasterxml.jackson.databind.DeserializationContext.findContextualValueDeserializer(DeserializationContext.java:443)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.createContextual(CollectionDeserializer.java:183)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.createContextual(CollectionDeserializer.java:27)
at com.fasterxml.jackson.databind.DeserializationContext.handlePrimaryContextualization(DeserializationContext.java:650)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:484)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
at com.fasterxml.jackson.databind.deser.DeserializerCache.hasValueDeserializerFor(DeserializerCache.java:191)
at com.fasterxml.jackson.databind.DeserializationContext.hasValueDeserializerFor(DeserializationContext.java:421)

现在原因就很明显了,ProductPriceKey作为map的key导致的。这个属性我本不需要客户端传递,于是在属性增加了只读。

1
2
3
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Map<ProductPriceKey, ProductPrice> salePrices;

Java类和JSON字符串互转,map需要key是string,这个很容易理解,不是string的没法转。(java->json部分在这个类内之前有处理;反过来没处理)

这样就解决了。

这个问题本应该更容易发现,只是spring/fasterxml内把这个错误的日志级别用了DEBUG,不太容易被发现。

在使用 Spring RESTDocs 时,有如下代码

1
2
3
4
5
6
this.mockMvc
.perform(
get("/books/123/images/5")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(...);

运行正常

当把其中url替换成参数形式,改成如下代码时,

1
2
3
4
5
6
7
8
this.mockMvc
.perform(
get("/books/{bookId}/images/{imageId}",
bookId,
imageId)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(...);

执行时出现错误:

java.lang.IllegalArgumentException: urlTemplate not found. If you are using MockMvc did you use RestDocumentationRequestBuilders to build the request?

这个提示也比较清晰,但对于不太了解 RESTDocs 的初学者,可能会迷惑一会,这是由于,静态方法有2个

1
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

上面这个是MockMvc里提供的,不支持URLTemplate这种形式,下面这个是RESTDocs里提供的,支持URLTemplate这种形式,需要用下面这个

1
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;

替换掉import,倒入正确的get方法即可正常运行。

PostgreSQL里有两种特殊的字段类型,数组和JSON,这2个字段在处理一些数据类型时会非常方便。不用多建表还能方便的查询其中内容。

数组

数据中的多对多,我们之前一般需要建一个映射表来处理;或者是在某方建一个比较长的字段(例如text类型)然后用某种分隔符(例如逗号)保存数据。

一对多如果在在「一」方保存「多」的信息,也只能类似多对多的处理方式。

有了数组类型,就可以直接建个数组字段保存这些值了,很简单的实现了一对多。

数组字段的查询

例如,我们创建一个表,产品表,每个产品可以有多张图片,图片用数组字段

1
2
3
4
5
create table product(
id serial primary key,
name char(50) not null,
imgs varchar(50) not null
)

添加一条记录,有3张图p1.jpg, p2.jpg, p3.jpg

1
2
insert into product(name, imgs) 
values('手机', '{"p1.jpg", "p2.jpg", "p3.jpg"}');

查询imgs字段包含 p1.jpg 的记录

1
select * from product where 'p2.jpg' = ANY(imgs)

更多

PostgreSQL 支持多维数组,而且是各种类型的数组、int[], timestamp[] 等等,不仅仅是字符串数组,多维数组形式例如: varchar(100)[][]

JSON

JSON字段用于保存更复杂的数据类型。

json vs. jsonb

PostgreSQL中有2种JSON类型: json 和 jsonb ,两种类型的区别是:

  • json 保存原始值,不会对值进行处理(例如去空格,去重等)。insert/update后 select出来的值的字符串形式是相同的
  • jsonb 保存是处理过的值,会对值去空格去重;insert/update后 select出来的值的字符串形式会不同
  • jsonb 保存(insert/update)速度会稍慢于json
  • 如果使用PostgreSQL 自身的JSON函数查询字段,jsonb会快于json

根据这个特点,如果我们的JSON数据只是在程序中处理,可以选择json类型;如果需要PostgreSQL处理则选择jsonb更佳。

官方文档

Postgresql 的官方文档做得非常好,更多信息看

PostgreSQL还有些其它很有意思的字段,例如:区间类型(日期区间、数值区间等)、复合类型、枚举类型、甚至点线多边形等几何图形等字段类型,而且对于这些类型都有相应的一些辅助运算函数的支持。数据类型

现象

spring gateway 遇到异常,无法启动,异常如下:

14:07:09.901 [main] ERROR o.s.boot.SpringApplication - Application run failed
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:156)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:544)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
    at cn.devmgr.mall.gateway.GatewayMpApplication.main(GatewayMpApplication.java:20)
Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory(ServletWebServerApplicationContext.java:203)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:179)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:153)
    ... 8 common frames omitted

原因

出现这种状况,是由于项目中混入了 spring-webmvc 的包导致的,spring gateway 使用 webflux, 而webflux和webmvc不兼容,就会出现上面的错误。

解决方法

一般不是直接包含了 spring-webmvc,是引入的某个包需要依赖webmvc,找到并排除就好了,但找起来,需要点方法。

用命令 mvn -X package增加一个 -X 参数去打包,会把详细的依赖路径全部都显示出来。例如:

[INFO] <<< spring-boot-maven-plugin:2.2.1.RELEASE:run (default-cli) < test-compile @ gateway-mp-server <<<
[DEBUG] Dependency collection stats: {ConflictMarker.analyzeTime=1, ConflictMarker.markTime=0, ConflictMarker.nodeCount=909, ConflictIdSorter.graphTime=1, ConflictIdSorter.topsortTime=0, ConflictIdSorter.conflictIdCount=230, ConflictIdSorter.conflictIdCycleCount=0, ConflictResolver.totalTime=15, ConflictResolver.conflictItemCount=552, DefaultDependencyCollector.collectTime=27, DefaultDependencyCollector.transformTime=17}
[DEBUG] cn.devmgr.mall:gateway-mp-server:jar:1.0.0
[DEBUG]    cn.devmgr:common-api:jar:1.0.0:compile
[DEBUG]       org.springframework:spring-webmvc:jar:5.2.1.RELEASE:compile
[DEBUG]          org.springframework:spring-beans:jar:5.2.1.RELEASE:compile
[DEBUG]          org.springframework:spring-context:jar:5.2.1.RELEASE:compile
[DEBUG]          org.springframework:spring-expression:jar:5.2.1.RELEASE:compile
[DEBUG]          org.springframework:spring-web:jar:5.2.1.RELEASE:compile
[DEBUG]       org.springframework.security:spring-security-web:jar:5.2.1.RELEASE:compile
[DEBUG]          org.springframework.security:spring-security-core:jar:5.2.1.RELEASE:compile
[DEBUG]       com.auth0:java-jwt:jar:3.9.0:compile
[DEBUG]          com.fasterxml.jackson.core:jackson-databind:jar:2.10.0:compile (version managed from 2.10.0.pr3 by com.fasterxml.jackson:jackson-bom:2.10.0)
[DEBUG]          commons-codec:commons-codec:jar:1.13:compile (version managed from 1.12 by org.springframework.boot:spring-boot-dependencies:2.2.1.RELEASE)
[DEBUG]    org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:jar:2.2.0.RELEASE:compile
[DEBUG]       org.springframework.cloud:spring-cloud-starter:jar:2.2.0.RELEASE:compile
[DEBUG]          org.springframework.cloud:spring-cloud-context:jar:2.2.0.RELEASE:compile
[DEBUG]             org.springframework.security:spring-security-crypto:jar:5.2.1.RELEASE:compile
[DEBUG]          org.springframework.cloud:spring-cloud-commons:jar:2.2.0.RELEASE:compile
[DEBUG]          org.springframework.security:spring-security-rsa:jar:1.0.7.RELEASE:compile
[DEBUG]             org.bouncycastle:bcpkix-jdk15on:jar:1.60:compile
[DEBUG]                org.bouncycastle:bcprov-jdk15on:jar:1.60:compile
[DEBUG]       org.springframework.cloud:spring-cloud-netflix-hystrix:jar:2.2.0.RELEASE:compile
[DEBUG]          org.springframework.boot:spring-boot-starter-aop:jar:2.2.1.RELEASE:compile
[DEBUG]       org.springframework.cloud:spring-cloud-netflix-eureka-client:jar:2.2.0.RELEASE:compile
[DEBUG]       com.netflix.eureka:eureka-client:jar:1.9.13:compile
[DEBUG]          org.codehaus.jettison:jettison:jar:1.3.7:runtime
[DEBUG]             stax:stax-api:jar:1.0.1:runtime
[DEBUG]          com.netflix.netflix-commons:netflix-eventbus:jar:0.3.0:runtime
[DEBUG]             com.netflix.netflix-commons:netflix-infix:jar:0.3.0:runtime
[DEBUG]                commons-jxpath:commons-jxpath:jar:1.3:runtime
[DEBUG]                joda-time:joda-time:jar:2.10.5:runtime (version managed from 2.3 by org.springframework.boot:spring-boot-dependencies:2.2.1.RELEASE)
[DEBUG]                org.antlr:antlr-runtime:jar:3.4:runtime
[DEBUG]                   org.antlr:stringtemplate:jar:3.2.1:runtime
[DEBUG]                   antlr:antlr:jar:2.7.7:runtime
[DEBUG]             org.apache.commons:commons-math:jar:2.2:runtime
[DEBUG]          com.netflix.archaius:archaius-core:jar:0.7.6:compile
[DEBUG]          javax.ws.rs:jsr311-api:jar:1.1.1:runtime
[DEBUG]          com.netflix.servo:servo-core:jar:0.12.21:runtime
[DEBUG]          com.sun.jersey:jersey-core:jar:1.19.1:runtime
[DEBUG]          com.sun.jersey:jersey-client:jar:1.19.1:runtime
[DEBUG]          com.sun.jersey.contribs:jersey-apache-client4:jar:1.19.1:runtime
[DEBUG]          org.apache.httpcomponents:httpclient:jar:4.5.10:compile (version managed from 4.5.3 by org.springframework.boot:spring-boot-dependencies:2.2.1.RELEASE)

在输入里面搜索 webmvc ,找到最上级后,在pom里排除掉即可,例如:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>cn.devmgr</groupId>
<artifactId>common-api</artifactId>
<exclusions>
<exclusion><!-- 这里是排除掉webmvc -->
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</exclusion>
</exclusions>
</dependency>

问题

在一个页面上对同一个自定义组件调用了2次,代码如下:

1
2
3
4
5
6
7
8
9
10
11

<MyComponent
v-if="type1 == 2"
v-model="data1"
name="标题A" />

<MyComponent
v-if="banner.type == 3"
v-model="data2"
name="标题B" />

在页面上还有一组单选按钮,用来选择 type,当 type==2 时,显示上面的 name 是“标题A”的自定义组件,当 type==3 时,则显示下面的 name 是“标题B”的组件。 乍看之下,运行良好,当组件扩充里面有data部分时,问题出现了,对上面的组件实例进行操作导致 data 的值变化后,下面的组件实例的data部分也会跟着变,加入日志显示,两个实例似乎共享了data部分,props部分还保持自己的,没有被更改。

这不科学,不符合 VUE.js 手册上对用户自定义组件的描述,而且组件的data部分是函数,也是按照 VUE.js 手册的标准写法写的。

解决方法

后来发现是两次调用都有 v-if 一次只能出现一个导致 vue 在使用时混乱了导致,给每个对组件的引用都增加了key属性,并使用了不同的key值,问题得以解决。

1
2
3
4
5
6
7
8
9
10
11
12
13

<MyComponent
v-if="type1 == 2"
key="componentA"
v-model="data1"
name="标题A" />

<MyComponent
v-if="banner.type == 3"
key="componentB"
v-model="data2"
name="标题B" />

本文不是为了比较双方优劣,是因为我经常在两类项目中切换,有时搞混,列出来方便自己记忆和速查。

对比项目 微信小程序 VUE.js
模版内显示变量
模版中元素属性使用变量 view class=”“ div :class=”var”
元素事件捕获 bindtap=”userClicked” @click=”userClicked”
给事件处理函数传递变量 在元素上写 data-varname=”“, js中用 event.target.dataset.varname获取 @click=”userClicked(var1, var2)”,直接写在方法名后,和调用相同
事件处理函数中获取event 方法可接收的唯一参数就是event @click=”userClicked($event)” 在模版中写上特殊变量 $event
阻止事件向父级元素传递 bindXxx改为catchXXX,例如:catchtap=”userclicked” 用.stop 例如: @click.stop=”userClicked” 或者@clicked.stop表明不处理直接阻止
模版内调用JS方法 只能调用wxs内声明的方法 可以直接调用vue内method段声明的方法
页面生命周期事件 onLoad created
onShow mounted
script部分方法 直接写在Page内和onload同级 写在methods部分下,methods和created同级
script部分对data的访问 this.data.xxx this.xxx
component中对props内属性访问 this.properties.xxx this.xxx
data 部分 可在程序中随时增加 先在data中声明,不声明的,动态增加 模版部分也不能使用
模版引用data遇到null 无显示或显示NULL 异常出错
模版内循环 wx:for=”array” wx:key=”index” wx:item=”item” v-for=”(item, index) in array” :key=”index”
模版内虚拟元素 block template 在v-for上应用时,需要把:key 应用与所有一级子元素并保持key唯一
自定义组件 created 方法不能调用 setData,(可用attached事件替代部分) created 方法可以使用this ,能改变data内数据

git用起来很方便,小型团队不需要权限区分时,仅仅使用git就够了。

需要权限区分时,可以使用gitolite来做权限控制。

安装

首先安装好 git openssh 等,这里不再累述。

ssh到安装好git的服务器上,

切换到git用户 sudo su - git ,之后确认下 .ssh 目录下没有 authorization_keys 文件或改文件为空。(以下这些命令都在git用户下执行)

运行下面的命令

1
2
3
git clone https://github.com/sitaramc/gitolite
mkdir -p $HOME/bin
gitolite/install -to $HOME/bin

这三个命令的作用是,从github.com克隆gitolite的安装仓库,在当前用户(git)的用户目录下创建一个bin目录,并且把gitolite安装到这个目录。

安装到这个目录后,由于path里没有这个目录,调用此目录下新安装的命令时,需要写全路径,不过这影响不大,因为这几个命令不会经常用。

下面这个很关键,不要错,把自己用的客户机上的公钥(.ssh目录下的id_rsa.pub)文件拷贝到服务器上git用户目录下,并且命名为admin.pub,之后再运行下面的命令

1
/home/git/bin/gitolite setup -pk admin.pub

此命令运行成功后,会出现如下内容:

1
2
Initialized empty Git repository in /home/git/repositories/gitolite-admin.git/
Initialized empty Git repository in /home/git/repositories/testing.git/

而且会在git用户目录下创建 repository目录和projects.list文件。
如果用的公钥不正确,可以删除这2个目录/文件,然后重新来过。

此时gitolite已经安装好了,要设置仓库、用户和权限,都需要在客户端(就是前面生成admin.pub的机器)上进行了

日常管理

首先把gitolite-admin这个仓库克隆到本地,所有管理工作都由这个仓库进行。

1
git clone ssh://git@host/gitolite-admin

如果出错,请首先检查客户端电脑的用户公钥是否就是安装过程中使用的admin.pub。

克隆下 gitolite-admin 这个仓库后,会发现有2个目录 conf 和 keydir,

  • conf是仓库和用户的配置目录,内有一个gitolite.conf 文件,存储用户和仓库的对应权限
  • keydir 是保存的用户的公钥,按照上述安装步骤进行的,应该已经有一个 admin.pub 文件了

创建仓库

不需要手工创建仓库,在 conf/gitolite.conf 文件中配置的仓库名,在commit并push后,会自动被创建。

仓库的URL

仓库的URL是

ssh://git@host/repo-name

例如:

ssh://git@192.168.1.2/testing

仓库名后面有或没有.git都可以。

gitolite 命令

除了安装时使用gitolite命令的setup参数初始化了 gitolite-admin 和 testing 两个仓库外,这个命令还可以接收以下这些参数

list-groups                 list all group names in conf
list-users                  list all users/user groups in conf
list-repos                  list all repos/repo groups in conf
list-phy-repos              list all repos actually on disk
list-memberships            list all groups a name is a member of
list-members                list all members of a group

这可以帮助我们确认服务器上到底有哪些仓库/用户等。

参考

gitolite github网址:https://github.com/sitaramc/gitolite