0%

前言

最近项目上在接口json格式,前端传递日期是string,实体类是Date,格式化的时候报了个错 Failed to convert property value of type ‘java.lang.String‘ to required type ‘java.util.Date,和明显是string不能转到date

解决

在相应的属性上使用 @DateTimeFormat 注解,并指定格式,见第 14 或 16 行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 import java.util.Date;

import org.springframework.format.annotation.DateTimeFormat;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor // 无参构造方法
@AllArgsConstructor // 有参构造方法
@Data // Getters、Setters、toString() 等方法
public class BillsVo {

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
}

顺带说一下,返回json格式化日期,需要在相应的类的属性上使用 @JsonFormat 注解:

1
2
3
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date billtime;

如果使用的是fastjson,注解有所不同,但原理相同

前言

最近再项目上遇到了一个奇怪的问题,项目中前端页面是h5形式嵌入再第三方的小程序里,登录时将第三方token给到后台,后台调用第三方的获取用户信息接口,传递一个token,放在cookie里,今天客户反馈登录不上了,我查看了日志发现前端传递给后台的token没问题,但是第三方返回token失效且返回的token并不是我传递的token,第一感觉难道是第三方系统的问题,但是通过postman调用第三方那个接口发现没有问题,怀疑难道是缓存的问题,我重启了下服务,果真好了,但是没有解决根本问题,下午客户又反馈登录不上了,这次我好好看了下日志,下面记录下解决问题的过程!

发现问题

日志中打印了调用了线程名称,一开始启动后没有问题,反复的登录N次后就出现了问题,日志中的线程名称类似如下

1
2
3
2022-09-29 22:23:39.134 [http-nio-8087-exec-5]
2022-09-29 22:23:44.739 [http-nio-8087-exec-6]
2022-09-29 22:23:44.739 [http-nio-8087-exec-7]

我反复查看了日志,比如第一次我用线程5登录成功了,1个小时后token失效了,换了新的token,这次又命中了线程5去登录,第三方返回token无效且token并不是我传递的新token,发现返回的token是线程5上次获取到的token,那问题就很明了了,就是cookie传递的不对,我用的hutool的httputil工具类,关键代码如下

1
HttpResponse cookie = HttpUtil.createGet("https://www.test.com/login").header("Cookie", "token=12345").execute();

我用的header设置的cookie,我想难道是这里的问题?为了复现这个问题,我把tomcat的线程设置成1个

1
server.tomcat.threads.max=1

启动后第一次登录无问题,第二次登录返回无效,token果真是上次的token,看来不能让第三方背锅了

解决

然后看了下hutool的方法,如果不设置cookie,那默认每次都是取上次设置的cookie,因为第三方的接口不管成功失败,都在响应头里返回我传递的那个cookie,间接导致了这个问题!因为我设置cookie一开始用的header去设置的,但是hutool应该是最后请求时会默认再用cookie覆盖一遍,所以就导致了cookie设置无效,改用cookie设置后就好了,源码中注释也提示了如果不设置就会用默认行为,设置了就会覆盖

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 设置Cookie<br>
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
*
* @param cookie Cookie值,如果为{@code null}则设置无效,使用默认Cookie行为
* @return this
* @since 3.0.7
*/
public HttpRequest cookie(String cookie) {
this.cookie = cookie;
return this;
}
总结

如果要设置cookie,就一定要用cookie的方法去设置,不要用header,否则就会出现类似的问题

前言

最近再项目中集成easyexcel,导出数据报了个错Found interface org.apache.poi.util.POILogger, but class was expected,下面记录下解决办法

解决

具体的报错信息

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
2022-05-10 11:55:57.742 ERROR 12248 --- [nio-8020-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.IncompatibleClassChangeError: Found interface org.apache.poi.util.POILogger, but class was expected] with root cause

java.lang.IncompatibleClassChangeError: Found interface org.apache.poi.util.POILogger, but class was expected
at org.apache.poi.openxml4j.opc.ZipPackage.buildPartName(ZipPackage.java:366) ~[poi-ooxml-3.17.jar:3.17]
at org.apache.poi.openxml4j.opc.ZipPackage.getPartsImpl(ZipPackage.java:303) ~[poi-ooxml-3.17.jar:3.17]
at org.apache.poi.openxml4j.opc.OPCPackage.getParts(OPCPackage.java:756) ~[poi-ooxml-3.17.jar:3.17]
at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:327) ~[poi-ooxml-3.17.jar:3.17]
at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:184) ~[poi-ooxml-3.17.jar:4.1.2]
at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:149) ~[poi-ooxml-3.17.jar:4.1.2]
at cn.afterturn.easypoi.excel.imports.ExcelImportService.importExcelByIs(ExcelImportService.java:438) ~[easypoi-base-4.1.3.jar:na]
at cn.afterturn.easypoi.excel.ExcelImportUtil.importExcel(ExcelImportUtil.java:83) ~[easypoi-base-4.1.3.jar:na]
at cn.ffcs.cm.produce.controller.CreatorScoreImportController.importScore(CreatorScoreImportController.java:200) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449) ~[shiro-web-1.5.3.jar:1.5.3]
at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365) ~[shiro-web-1.5.3.jar:1.5.3]
at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) ~[shiro-core-1.5.3.jar:1.5.3]
at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) ~[shiro-core-1.5.3.jar:1.5.3]
at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387) ~[shiro-core-1.5.3.jar:1.5.3]
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362) ~[shiro-web-1.5.3.jar:1.5.3]
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) ~[shiro-web-1.5.3.jar:1.5.3]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200) ~[tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:836) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1747) [tomcat-embed-core-9.0.19.jar:9.0.19]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.19.jar:9.0.19]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.19.jar:9.0.19]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]

大概的报错原因就是指poi-ooxml版本引用的不对,一般都是项目上已经引用了其他版本的poi-ooxml导致的,所以我们需要找到项目中引入的依赖,例如我的模块中引入了

1
2
3
4
5
6
7
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
<scope>compile</scope>
</dependency>

需要改为4.17版本

1
2
3
4
5
6
7
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
<scope>compile</scope>
</dependency>

改完,刷新,完成!

前言

最近项目上需要导出excel,导出的字段里有个19位长的数字,导出后发现变成了科学计数法,需要加一个注解处理下数字格式

  • 方法一

    1
    @NumberFormat(value = “#”)

    如果多个Long类型长度太长,不推荐多个地方要加

  • 方法二

    1
    2
    //设置转换器
    EasyExcel.write(fileName, cls).registerConverter(new LongStringConverter())

前言

最近用android studio打包一个老项目的时候报错了,提示Execution failed for task ‘:app:lintVitalRelease‘. > Lint infrastructure err,意思就是代码检测报错了,下面记录下

解决

直接运行啥的都没错,就是打release包的时候会报错,经过查看资料,在appbuild.gradle下,android标签下加入如下配置即可

1
2
3
4
5
6
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}

大概意思就是忽略错误,即使发现错误也要继续构建

前言

最近再学习前端的一些知识,偶尔用到了字符串截图的函数,看到js中有三个方法都支持字符串截图,下面详细记录下这几个函数的区别

使用
方法 参数 返回值
slice(start, end) start(必需) -起始位置; end(可选)-结束位置,若未指定,则默认到末尾所有元素 返回 [start,end)之间的元素
substring(start, end) start(必需) -起始位置;end(可选)-结束位置,若未指定,则默认到末尾所有元素 返回 [start,end)之间的元素
substr(start, length) start(必需)-起始位置;length(可选)-所截取的元素的个数,若未指定,则默认到末尾 返回[start, start+length)之间的元素

当传的参数都为正数的时候,substring和slice没有区别。当参数为负数时,三个函数的行为不尽相同。

  • slice() - 将传入的负参数与字符串长度相加;
  • substring() - 把所有的负值置为0;
  • substr() - 将负的第一个参数与字符串长度相加,负的第二个参数置为0。
例子

1. 参数为正数

1
2
3
4
5
6
7
8
var str = 'hello world';
console.log(str.slice(3)); // lo world
console.log(str.substring(3));// lo world
console.log(str.substr(3));// lo world

console.log(str.slice(3, 7)); // lo w
console.log(str.substring(3, 7)); // lo w
console.log(str.substr(3, 7));//lo worl

2 参数为负数

1
2
3
4
5
6
7
8
9
var str = 'hello world';
console.log(str.slice(-3)); // rld
console.log(str.substring(-3));// hello world
console.log(str.substr(-3));// rld

console.log(str.slice(3, -4)); // lo w
console.log(str.substring(3, -4)); // hel
console.log(str.substr(3, -4));// ""(空字符串)

以上示例和以下相等

  • slice(-3) => slice(8)
  • substring(-3) => substring(0)
  • substr(-3) => substr(8)
  • slice(3, -4) => slice(3, 7)
  • substring(3, -4) => substring(3, 0) =>substring(0, 3)
  • substr(3, -4) => substr(3, 0)

前言

之前的老项目用的jQuery,对这个不是太熟悉,今天项目遇到一个需求,要清除选中的行,顺便了解下datagrip

使用

easyui-datagrid取消所有选中行:

1
$("#userGrid").datagrid('clearSelections');

多选和单选属性:

1
singleSelect:true,

获取选中行的数据(单选)

1
2
3
4
var row = $('#tt').datagrid('getSelected');
if(row){
alert('ItemID:'+row.itemid+"Price:"+row.listprice);
}

获取选中行的数据(多选)

1
2
3
4
5
6
var ids = [];
var rows = $('#tt').datagrid('getSelections');
for(vari=0;i<</span>rows.length;i++){
ids.push(rows[i].itemid);
}
alert(ids.join(''));

获取datagrid中当前页所有的数据

1
var data = $('#rafficManager_datagrid').datagrid('getRows');

前言

Redis 的配置文件位于 Redis 安装目录下,文件名为 redis.conf。

可以通过 CONFIG 命令查看或设置配置项。

windows上默认点击redis-server.exe的话是用了个默认的配置,该包下的conf文件是不生效的,有两种方式,一是启动时指定conf文件,一种是启动redis后,config命名去设置,命名设置的重启可能失效,所以最好是指定conf文件

配置文件
1
redis-server  redis.windows-service.conf

在redis目录下,执行这个命令或者把这个命名新建一个bat文件,拷进去

config命令

redis-cli命令进入命令行,命令格式如下

1
CONFIG GET CONFIG_SETTING_NAME

示例

1
2
3
4
CONFIG GET loglevel

# 匹配所有项
CONFIG GET *

修改命令

1
CONFIG SET CONFIG_SETTING_NAME NEW_CONFIG_VALUE

示例

1
CONFIG SET loglevel "notice"

redis.conf 配置项说明如下:

\1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

daemonize no

\2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

pidfile /var/run/redis.pid

\3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

port 6379

\4. 绑定的主机地址

bind 127.0.0.1

5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

timeout 300

\6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

loglevel verbose

\7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

logfile stdout

\8. 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id

databases 16

\9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

save

Redis默认配置文件中提供了三个条件:

save 900 1

save 300 10

save 60 10000

分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

\10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

rdbcompression yes

\11. 指定本地数据库文件名,默认值为dump.rdb

dbfilename dump.rdb

\12. 指定本地数据库存放目录

dir ./

\13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

slaveof

\14. 当master服务设置了密码保护时,slav服务连接master的密码

masterauth

\15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码,默认关闭

requirepass foobared

\16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

maxclients 128

\17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

maxmemory

\18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

appendonly no

\19. 指定更新日志文件名,默认为appendonly.aof

appendfilename appendonly.aof

\20. 指定更新日志条件,共有3个可选值:

no:表示等操作系统进行数据缓存同步到磁盘(快)

always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)

everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec

\21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

vm-enabled no

\22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

vm-swap-file /tmp/redis.swap

\23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

vm-max-memory 0

\24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

vm-page-size 32

\25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

vm-pages 134217728

\26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

vm-max-threads 4

\27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

glueoutputbuf yes

\28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

hash-max-zipmap-entries 64

hash-max-zipmap-value 512

\29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

activerehashing yes

\30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

include /path/to/local.conf

Redis 安装 Redis 数据类型

前言

通过开启key过期的事件通知,当key过期时,会发布过期事件;我们定义key过期事件的监听器,当key过期时,就能收到回调通知。但是这个只能是测试或者简单的场景使用,最好不要在生产上使用,尤其是大项目,可能不太靠谱,下面讲一下如果使用

使用场景

消息作为key,将需要延迟的时间设置为key的TTL,当key过期时,在监听器收到通知,达到延迟的效果,不如订单过期通知,订单需要10分钟内支付,过期后需要变更为已取消

配置

需要修改redis.conf配置文件

1
notify-keyspace-events Ex

在配置文件搜索notify-keyspace-events,默认后面是 “”,两个双引号,删除,改为 Ex即可

使用

java代码,先订阅

1
2
3
4
5
6
7
8
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory) {
// redis 消息订阅(监听)者容器
RedisMessageListenerContainer messageListenerContainer = new RedisMessageListenerContainer();
messageListenerContainer.setConnectionFactory(redisConnectionFactory);
// messageListenerContainer.addMessageListener(new ProductUpdateListener(), new PatternTopic("*.product.update"));
return messageListenerContainer;
}

再监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

/**
* 创建RedisKeyExpirationListener bean时注入 redisMessageListenerContainer
*
* @param redisMessageListenerContainer RedisConfig中配置的消息监听者容器bean
*/
public RedisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) {
super(redisMessageListenerContainer);
}

@Override
public void onMessage(Message message, byte[] pattern) {
String channel = new String(message.getChannel()); // __keyevent@*__:expired
String pa = new String(pattern); // __keyevent@*__:expired
String expiredKey = message.toString();
System.out.println("监听到过期key:" + expiredKey);
}
}

注意:

  1)由于Redis key过期删除是定时+惰性,当key过多时,删除会有延迟,回调通知同样会有延迟。因此性能较低

  2)且通知是一次性的,没有ack机制,若收到通知后处理失败,将不再收到通知。需自行保证收到通知后处理成功。

  3)通知只能拿到key,拿不到value

  4)Redis将数据存储在内存中,如果遇到恶意下单或者刷单的将会给内存带来巨大压力

所有,如果生产上有需求,最好是使用带延迟功能消息中间件(比如pulsar)

前言

kotlin中如何判断一个字符串里没给字符是不是都是数字

方法
  • 使用all()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //Kotlin 不提供任何标准函数来检查给定字符串是否为数字。但是,这可以通过 all() 函数判断

    fun isNumber(s: String?): Boolean {
    return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
    }

    fun main() {
    val s = "100"
    if (isNumber(s)) print("Number") else print("Not a Number") // Number
    }
  • 使用toInt()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //这里的技巧是将给定的字符串转换为整数 toInt() 函数,它生成 NumberFormatException 对于非数字字符串。请注意,当值超出整数范围时,此函数将失败。
    fun isNumber(s: String): Boolean {
    return try {
    s.toInt()
    true
    } catch (ex: NumberFormatException) {
    false
    }
    }

    fun main() {
    val s = "100"
    if (isNumber(s)) print("Number") else print("Not a Number") // Number
    }
  • 使用toIntOrNull方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //可以使用 toIntOrNull() 函数,它将字符串解析为 Int 数字,如果字符串不是数字的有效表示形式,则返回结果或 null。
    fun isNumber(s: String): Boolean {
    return when(s.toIntOrNull())
    {
    null -> false
    else -> true
    }
    }

    fun main() {
    val s = "100"
    if (isNumber(s)) print("Number") else print("Not a Number") // Number
    }
  • 使用matches方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //使用正则表达式来检查字符串是否为数字 matches() 函数,它告诉这个字符串是否匹配给定的正则表达式。
    fun isNumber(s: String?): Boolean {
    return !s.isNullOrEmpty() && s.matches(Regex("\\d+"))
    }

    fun main() {
    val s = "100"
    if (isNumber(s)) print("Number") else print("Not a Number") // Number
    }