-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 57.5 KB
/
content.json
1
{"meta":{"title":"叫我肖哥就好了","subtitle":"Don't Worry Be Happy~","description":"叫我肖哥就好了","author":"肖哥","url":"https://blog.imyxiao.com"},"pages":[{"title":"About Me","date":"2016-07-11T10:42:22.000Z","updated":"2018-04-02T03:20:46.465Z","comments":false,"path":"about/index.html","permalink":"https://blog.imyxiao.com/about/index.html","excerpt":"","text":"Hi, I’m Yang Xiao~~ 1234567891011121314151617181920212223{ \"name\": \"Yang Xiao\", \"url\": \"https://blog.imyxiao.com\", \"email\": \"i # imyxiao.com\", \"address\": { \"city\": \"北京\", \"country\": \"中国\" }, \"links\": [ { \"name\": \"Github\", \"url\": \"https://github.com/imyxiao\" }, { \"name\": \"Twitter\", \"url\": \"https://twitter.com/imyxiao\" }, { \"name\": \"zhiwu\", \"url\": \"https://www.zhihu.com/people/yangxiao91\" } ]}"},{"title":"Categories","date":"2016-07-11T10:18:55.000Z","updated":"2018-03-30T06:21:14.166Z","comments":false,"path":"categories/index.html","permalink":"https://blog.imyxiao.com/categories/index.html","excerpt":"","text":""},{"title":"Links","date":"2018-03-30T08:13:39.000Z","updated":"2018-03-30T08:33:03.465Z","comments":false,"path":"links/index.html","permalink":"https://blog.imyxiao.com/links/index.html","excerpt":"","text":"我的git仓库 Friends TonyDeng’s Blog Don"},{"title":"Tags","date":"2016-07-11T10:15:31.000Z","updated":"2018-03-30T06:43:20.460Z","comments":false,"path":"tags/index.html","permalink":"https://blog.imyxiao.com/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"使用Certbot给你的网站添加HTTPS,部署Let's Encrypt通配符证书","slug":"use-certbot-create-letsencrypt-free-certificate","date":"2018-03-16T02:50:15.000Z","updated":"2018-03-30T07:54:48.513Z","comments":true,"path":"certificate/use-certbot-create-letsencrypt-free-certificate.html","link":"","permalink":"https://blog.imyxiao.com/certificate/use-certbot-create-letsencrypt-free-certificate.html","excerpt":"SSL证书以往要在网站上启用HTTPS,是需要向一些证书颁发机构(CA)购买证书的,而如今很多证书机构都提供了免费证书,比如StartSSL、Let’s Encrypt、Symantec等等,在国内的话,可以用过阿里云、腾讯云等去申请Symantec DV SSL证书。 但是这些免费证书的过期时间大多数只有一年,而且大多只有DV证书(单域名证书)。单域名证书,顾名思义,只保护一个域名,使用起来就很不方便,比如我有两个子域名www.example.com,shop.example.com,那么我需要申请两个证书,子域名越多,管理起来越麻烦。 通配符证书通配符证书可以保护同一主域名下同一级所有的子域名,不限个数,申请证书时,域名填写为 *.example.com 比如某公司旗下有一个域名叫 example.com,但因为业务需要,解析了很多子域名,比如有www.example.com;login.example.com;shop.example.com;bill.example.com 可能多达几十上百个这样的子域名,但为每一个域名都申请一张证书,将会是很昂贵的费用。所以该公司申请了一张支持*.example.com的通配符证书,保护了任何前缀的子域名。","text":"SSL证书以往要在网站上启用HTTPS,是需要向一些证书颁发机构(CA)购买证书的,而如今很多证书机构都提供了免费证书,比如StartSSL、Let’s Encrypt、Symantec等等,在国内的话,可以用过阿里云、腾讯云等去申请Symantec DV SSL证书。 但是这些免费证书的过期时间大多数只有一年,而且大多只有DV证书(单域名证书)。单域名证书,顾名思义,只保护一个域名,使用起来就很不方便,比如我有两个子域名www.example.com,shop.example.com,那么我需要申请两个证书,子域名越多,管理起来越麻烦。 通配符证书通配符证书可以保护同一主域名下同一级所有的子域名,不限个数,申请证书时,域名填写为 *.example.com 比如某公司旗下有一个域名叫 example.com,但因为业务需要,解析了很多子域名,比如有www.example.com;login.example.com;shop.example.com;bill.example.com 可能多达几十上百个这样的子域名,但为每一个域名都申请一张证书,将会是很昂贵的费用。所以该公司申请了一张支持*.example.com的通配符证书,保护了任何前缀的子域名。 Let’s EncryptLet’s Encrypt是由非营利互联网安全研究组织(ISRG)支持的免费,自动化和开放的证书认证机构。他们搞了一个非常有创意的事情,设计了一个ACME协议,该协议通常在我们的Web主机上运行,目前该协议的版本是v1。 那什么是ACME协议,传统的 CA 机构是人工受理证书申请、证书更新、证书撤销,完全是手动处理的。而 ACME 协议规范化了证书申请、更新、撤销等流程,只要一个客户端实现了该协议的功能,通过客户端就可以向 Let’s Encrypt 申请证书,也就是说 Let’s Encrypt CA 完全是自动化操作的。 申请 Let’s Encrypt 通配符证书为了实现通配符证书,Let’s Encrypt 对 ACME 协议的实现进行了升级,只有 v2 协议才能支持通配符证书,相关的新闻和技术点见: ACME v2 and Wildcard Certificate Support is Live ACME v2 Production Environment & Wildcards Let’s Encrypt 建议大多数使用shell访问的人使用 Certbot ACME客户端。它可以自动执行证书颁发和安装,而不会停机,也为不想自动配置的人提供专家模式。它很容易使用,适用于许多操作系统,并且具有出色的文档。访问Certbot站点以获取适用于您的操作系统和Web服务器的定制说明。这个网址列举了不同的ACME客户端:client-options 唯一不好的地方在于,Let’s Encrypt 证书的持续时间只有3个月,所以证书快到期了需要我们renew一下。 Certbot由于从Certbot站点上获取的客户端,有些暂时不支持v2协议,所以我推荐用Certbot提供的certbot-auto脚本。12wget https://dl.eff.org/certbot-autochmod a+x certbot-auto 或者直接去github.com下载最新的代码123git clone https://github.com/certbot/certbotcd certbotchmod a+x certbot-auto 获取完整的命令行帮助,你可以输入:1./certbot-auto --help all 客户在申请 Let’s Encrypt 证书的时候,需要校验域名的所有权,证明操作者有权利为该域名申请证书,目前支持三种验证方式: dns-01:给域名添加一个 DNS TXT 记录。 http-01:在域名对应的 Web 服务器下放置一个 HTTP well-known URL 资源文件。 tls-sni-01:在域名对应的 Web 服务器下放置一个 HTTPS well-known URL 资源文件。 而申请通配符证书,只能使用 dns-01 的方式 实践简单介绍下申请步骤(域名:imyxiao.com) 1./certbot-auto certonly -d *.imyxiao.com --manual --manual-public-ip-logging-ok --preferred-challenges dns-01 --agree-tos --server https://acme-v02.api.letsencrypt.org/directory 参数说明: certonly,表示安装模式,Certbot 有安装模式和验证模式两种类型的插件。 –manual 表示手动安装插件,Certbot 有很多插件,不同的插件都可以申请证书,用户可以根据需要自行选择 -d 为那些主机申请证书,如果是通配符,输入*.imyxiao.com(可以替换为你自己的域名) –preferred-challenges dns-01,使用 DNS 方式校验域名所有权 –server,Let’s Encrypt ACME v2 版本使用的服务器不同于 v1 版本,需要显示指定。 –manual-public-ip-logging-ok 自动允许公共IP记录 –agree-tos 同意ACME服务器的用户协议 如果使用zsh,添加-d *.imyxiao.com可能会报错,请去除这个参数,交互时手动输入 输入命令后输出:12345678910111213141516Requesting to rerun ./certbot-auto with root privileges...Saving debug log to /var/log/letsencrypt/letsencrypt.logPlugins selected: Authenticator manual, Installer NoneObtaining a new certificatePerforming the following challenges:dns-01 challenge for imyxiao.com-------------------------------------------------------------------------------Please deploy a DNS TXT record under the name_acme-challenge.imyxiao.com with the following value:WE3b-3uNQra1erQ-lHUQQifuVvGWSQFAKjqa7F_NtcUBefore continuing, verify the record is deployed.-------------------------------------------------------------------------------Press Enter to Continue 要求配置 DNS TXT 记录,从而校验域名所有权,也就是判断证书申请者是否有域名的所有权。 上面输出要求给 _acme-challenge.imyxiao.com 配置一条 TXT 记录,在没有确认 TXT 记录生效之前不要回车执行。 添加完TXT记录后,输入下列命令,验证TXT记录是否生效: 1234567891011121314151617181920$ dig txt _acme-challenge.imyxiao.com @8.8.8.8; <<>> DiG 9.10.3-P4-Debian <<>> txt _acme-challenge.imyxiao.com @8.8.8.8;; global options: +cmd;; Got answer:;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1372;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1;; OPT PSEUDOSECTION:; EDNS: version: 0, flags:; udp: 512;; QUESTION SECTION:;_acme-challenge.imyxiao.com. IN TXT;; ANSWER SECTION:_acme-challenge.imyxiao.com. 119 IN TXT "WE3b-3uNQra1erQ-lHUQQifuVvGWSQFAKjqa7F_NtcU";; Query time: 164 msec;; SERVER: 8.8.8.8#53(8.8.8.8);; WHEN: Fri Mar 16 12:25:40 CST 2018;; MSG SIZE rcvd: 112 确认生效后,回车执行,输出如下: 12345678910111213IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/imyxiao.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/imyxiao.com/privkey.pem Your cert will expire on 2018-06-16. To obtain a new or tweaked version of this certificate in the future, simply run certbot-auto again. To non-interactively renew *all* of your certificates, run "certbot-auto renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le 证书申请成功,证书和密钥保存在下列目录 123456789$ sudo tree /etc/letsencrypt/live/imyxiao.com/etc/letsencrypt/live/imyxiao.com├── cert.pem -> ../../archive/imyxiao.com/cert2.pem├── chain.pem -> ../../archive/imyxiao.com/chain2.pem├── fullchain.pem -> ../../archive/imyxiao.com/fullchain2.pem├── privkey.pem -> ../../archive/imyxiao.com/privkey2.pem└── README0 directories, 5 files 这样证书就Ok了,由于证书持续时间只有三个月,所以需要在快到期的时候重新持行一下命令生成新的证书。或者: 1./certbot-auto renew 引用: Let’s Encrypt 终于支持通配符证书了","categories":[{"name":"certificate","slug":"certificate","permalink":"https://blog.imyxiao.com/categories/certificate/"}],"tags":[{"name":"certificate","slug":"certificate","permalink":"https://blog.imyxiao.com/tags/certificate/"},{"name":"certbot","slug":"certbot","permalink":"https://blog.imyxiao.com/tags/certbot/"},{"name":"letsencrypt","slug":"letsencrypt","permalink":"https://blog.imyxiao.com/tags/letsencrypt/"},{"name":"https","slug":"https","permalink":"https://blog.imyxiao.com/tags/https/"}]},{"title":"转换Java keytools的keystore证书到OPENSSL的PEM格式文件","slug":"keystore-convert-pem","date":"2018-02-08T07:20:48.000Z","updated":"2018-03-30T07:57:04.000Z","comments":true,"path":"java/keystore-convert-pem.html","link":"","permalink":"https://blog.imyxiao.com/java/keystore-convert-pem.html","excerpt":"使用https直接连接后端的tomcat,证书是通过JDK自带的keytool生成的.假如keytool生成密钥对server.keystore如下: 1keytool -genkey -alias tomcat-server -keyalg RSA -keypass xxxxxxx -storepass xxxxxxx -keystore server.keystore 现决定迁移到nginx做ssl,但是keytool生成的证书都是二进制data,nginx使用的是OPENSSL标准的PEM+key文件,即ascii文本格式的密钥,所以需要作证书转换. 首先将服务器证书导出为证书文件server.cer: 1keytool -export -alias tomcat-server -storepass xxxxxxx -file server.cer -keystore server.keystore cer文件到PEM文件的转换较简单。这两者都是X509证书,编码不同,使用openssl工具即可: 1openssl x509 -inform der -in server.cer -out server.pem","text":"使用https直接连接后端的tomcat,证书是通过JDK自带的keytool生成的.假如keytool生成密钥对server.keystore如下: 1keytool -genkey -alias tomcat-server -keyalg RSA -keypass xxxxxxx -storepass xxxxxxx -keystore server.keystore 现决定迁移到nginx做ssl,但是keytool生成的证书都是二进制data,nginx使用的是OPENSSL标准的PEM+key文件,即ascii文本格式的密钥,所以需要作证书转换. 首先将服务器证书导出为证书文件server.cer: 1keytool -export -alias tomcat-server -storepass xxxxxxx -file server.cer -keystore server.keystore cer文件到PEM文件的转换较简单。这两者都是X509证书,编码不同,使用openssl工具即可: 1openssl x509 -inform der -in server.cer -out server.pem 至于keystore转换就比较麻烦,首先使用http://download.csdn.net/detail/cwxzz/1072684这里的工具,PFX格式证书和JAVA keyStore证书相互转换,先将keystore转换为PFX证书。修改java代码,填入keystore路径,生成文件的路径,KEYSTORE_PASSWORD。运行后得到PFX证书server.pfx 1234567891011121314151617181920212223242526272829303132333435363738public static final String PFX_KEYSTORE_FILE = \"server.pfx\"; public static final String KEYSTORE_PASSWORD = \"xxxxxxx\"; public static final String JKS_KEYSTORE_FILE = \"server.keystore\"; private static void coverToPfx() { try { KeyStore inputKeyStore = KeyStore.getInstance(\"JKS\"); FileInputStream fis = new FileInputStream(JKS_KEYSTORE_FILE); char[] nPassword; if (KEYSTORE_PASSWORD.trim().equals(\"\")) { nPassword = null; } else { nPassword = KEYSTORE_PASSWORD.toCharArray(); } inputKeyStore.load(fis, nPassword); fis.close(); KeyStore outputKeyStore = KeyStore.getInstance(\"PKCS12\"); outputKeyStore.load(null, KEYSTORE_PASSWORD.toCharArray()); Enumeration enums = inputKeyStore.aliases(); while (enums.hasMoreElements()) { String keyAlias = (String) enums.nextElement(); System.out.println(\"alias=[\" + keyAlias + \"]\"); if (inputKeyStore.isKeyEntry(keyAlias)) { Key key = inputKeyStore.getKey(keyAlias, nPassword); Certificate[] certChain = inputKeyStore.getCertificateChain(keyAlias); outputKeyStore.setKeyEntry(keyAlias, key, KEYSTORE_PASSWORD.toCharArray(), certChain); } } FileOutputStream out = new FileOutputStream(PFX_KEYSTORE_FILE); outputKeyStore.store(out, nPassword); out.close(); } catch (Exception e) { e.printStackTrace(); } } 接下来使用openssl从PFX中提取私钥 1openssl pkcs12 -in server.pfx -nocerts -nodes -out server.key 这里也需要输入生成证书时使用的密码。这样ascii格式的key文件server.key也可以使用了。 如果私钥带有密码,则需要移除密码: 1openssl rsa -in server.key -out server.origin.key nginx配置ssl示例(ssl开头的属性与证书配置有直接关系): 1234567891011121314151617server { listen 443; server_name localhost; ssl on; root html; index index.html index.htm; ssl_certificate server.pem; ssl_certificate_key server.key; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; location / { root html; index index.html index.htm; }} 公钥提取可用如下方法: 1keytool -list -rfc --keystore server.keystore | openssl x509 -inform pem -pubkey 输入密码,即可输出公钥及证书 引用: 转换java keytools的keystore证书到OPENSSL的PEM格式文件 PFX格式证书和JAVA keyStore证书相互转换","categories":[{"name":"java","slug":"java","permalink":"https://blog.imyxiao.com/categories/java/"}],"tags":[{"name":"certificate","slug":"certificate","permalink":"https://blog.imyxiao.com/tags/certificate/"},{"name":"https","slug":"https","permalink":"https://blog.imyxiao.com/tags/https/"},{"name":"keystore","slug":"keystore","permalink":"https://blog.imyxiao.com/tags/keystore/"},{"name":"pem","slug":"pem","permalink":"https://blog.imyxiao.com/tags/pem/"},{"name":"nginx","slug":"nginx","permalink":"https://blog.imyxiao.com/tags/nginx/"}]},{"title":"SonarQube的安装与使用","slug":"sonarqube","date":"2017-09-27T09:50:54.000Z","updated":"2018-03-30T07:56:43.210Z","comments":true,"path":"docker/sonarqube.html","link":"","permalink":"https://blog.imyxiao.com/docker/sonarqube.html","excerpt":"","text":"Sonar是一个用于代码质量管理的开源平台,用于管理源代码的质量,帮助我们从源码中找出潜在的bug,未使用的代码,复杂的表达式,未覆盖的代码,糟糕的代码,重复的代码等等通过插件形式,可以支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管理与检测 更多介绍请看官方网站SonarQube,官方文档:SONAR Documentation, SonarQube安装推荐使用docker安装,官方docker地址:SonarQube, 简单的运行一个SonarQube服务: 1docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube 打开http://localhost:9000/,默认帐号和密码都为admin 数据库配置默认情况使用的一个内嵌的H2数据库,肯定不适合生产情境,数据库配置的三个变量 SONARQUBE_JDBC_USERNAME SONARQUBE_JDBC_PASSWORD SONARQUBE_JDBC_URL 官方示例(需要安装一个postgresql):123456docker run -d --name sonarqube \\ -p 9000:9000 -p 9092:9092 \\ -e SONARQUBE_JDBC_USERNAME=sonar \\ -e SONARQUBE_JDBC_PASSWORD=sonar \\ -e SONARQUBE_JDBC_URL=jdbc:postgresql://localhost/sonar \\ sonarqube 由于我已经有一个mysql容器:1docker run --name wlcx_mysql -p 3306:3306 -v /srv/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=password -d mysql 所以使用链接容器的方式使用数据库,容器名为wlcx_mysql 123456docker run -d --link wlcx_mysql:wlcx_mysql --name sonarqube \\ -p 9000:9000 -p 9092:9092 \\ -e SONARQUBE_JDBC_USERNAME=root \\ -e SONARQUBE_JDBC_PASSWORD=password \\ -e \"SONARQUBE_JDBC_URL=jdbc:mysql://wlcx_mysql:3306/sonar?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false\" \\ sonarqube 也可以使用docker-compose的方式,更加的方便,请参考:docker sonarqube recipes SonarQube使用使用默认的帐号登录之后,可以: 生成一个代替帐号的token 修改一个admin的密码 可以在Administration=>System=>Update Center,安装中文插件和其它要分析的语言的插件 拿到了token后,介绍如何用sonar分析Maven与Gradle项目 Maven项目123mvn clean verify sonar:sonar \\ -Dsonar.host.url=http://localhost:9000 \\ -Dsonar.login=token 运行这个命令即可,为了固定sonar的mvn插件版本,可以配置:1234567891011<build> <pluginManagement> <plugins> <plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> <version>3.3.0.603</version> </plugin> </plugins> </pluginManagement></build> 更多详细配置:Analyzing with SonarQube Scanner for Maven Gradle项目配置插件:123456789101112buildscript { repositories { maven { url \"https://plugins.gradle.org/m2/\" } } dependencies { classpath \"org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.5\" }}apply plugin: \"org.sonarqube\" 对于Gradle 2.1+ ,可以这样配置:123plugins { id "org.sonarqube" version "2.5"} 运行命令:123gradle sonarqube \\ -Dsonar.host.url=http://localhost:9000 \\ -Dsonar.login=token 或将sonar地址与token,配置到项目下的gradle.properties或~/.gradle/gradle.properties当中123systemProp.sonar.host.url=http://localhost:9000systemProp.sonar.login=token 然后执行gradle sonarqube即可,更多详细配置请看:Analyzing with SonarQube Scanner for Gradle","categories":[{"name":"docker","slug":"docker","permalink":"https://blog.imyxiao.com/categories/docker/"}],"tags":[{"name":"sonarqube","slug":"sonarqube","permalink":"https://blog.imyxiao.com/tags/sonarqube/"},{"name":"sonar","slug":"sonar","permalink":"https://blog.imyxiao.com/tags/sonar/"},{"name":"docker","slug":"docker","permalink":"https://blog.imyxiao.com/tags/docker/"}]},{"title":"自动挂载硬盘","slug":"auto-mount-disk","date":"2017-08-22T13:29:51.000Z","updated":"2018-03-30T07:57:35.977Z","comments":true,"path":"linux/auto-mount-disk.html","link":"","permalink":"https://blog.imyxiao.com/linux/auto-mount-disk.html","excerpt":"","text":"我的系统(deepin)装在了固态硬盘上,并且只分配了20G空间.并不是很够用,所以很多数据、程序放在了之前的机械硬盘上,但是硬盘不是自己挂载的,需要开机后点一下才会挂载,挺麻烦。所以需要开机自动挂载硬盘。 查询NTFS磁盘的UUID1sudo blkid 查询出所以磁盘的信息,其中一条如下:1/dev/sda5: LABEL=\"work\" UUID=\"0005F366000EF1F7\" TYPE=\"ntfs\" PARTUUID=\"e6f99ced-05\" 编辑/etc/fstab1sudo vim /etc/fstab 将下面这段添加到末尾即可,UUID为上一步查询所得,/media/yxiao/work是挂载点1UUID=0005F366000EF1F7 /media/yxiao/work ntfs defaults 0 0 保存退出即可。下次开机会自动挂载。","categories":[{"name":"linux","slug":"linux","permalink":"https://blog.imyxiao.com/categories/linux/"}],"tags":[{"name":"mount","slug":"mount","permalink":"https://blog.imyxiao.com/tags/mount/"},{"name":"disk","slug":"disk","permalink":"https://blog.imyxiao.com/tags/disk/"},{"name":"deepin","slug":"deepin","permalink":"https://blog.imyxiao.com/tags/deepin/"}]},{"title":"Spring Boot静态资源处理","slug":"Spring-Boot-Static-Content","date":"2016-12-16T14:19:58.000Z","updated":"2018-03-30T07:55:22.000Z","comments":true,"path":"java/Spring-Boot-Static-Content.html","link":"","permalink":"https://blog.imyxiao.com/java/Spring-Boot-Static-Content.html","excerpt":"Spring Boot 静态资源处理,默认配置路径为 classpath 下的/static (或 /public 或 /resources 或 /META-INF/resources),即将/**映射到这几个路径下。Spring Boot 启动的时候,我们会看到如下日志: 1232016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]2016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]2016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 可以看到,Spring Boot 静态资源处理使用 Spring MVC 的ResourceHttpRequestHandler来实现。这里除了将/**映射到默认资源路径下,还为我们配置一个favicon.ico以及后面会讲到的webjars。Spring Boot中可以通过添加一个配置类,继承WebMvcConfigurerAdapter来自定义Spring MVC的配置,其中addResourceHandlers方法可以用来自定义静态资源处理,比如修改静态资源的缓存时间。这里我们想要替换默认的静态资源列表:123456789@Configurationpublic class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\"/**\") .addResourceLocations(\"classpath:/mydir1/\",\"classpath:/mydir2/\",\"classpath:/mydir3/\"); }} 此时,/**被映射到了"classpath:/mydir1/","classpath:/mydir2/","classpath:/mydir3/"下,要注意,如果替换了默认静态资源地址,那么默认欢迎页面index.html也将会被替换到我们自定义配置的任何路径下,Spring Boot相关的实现代码在ResourceProperties,这个类使用了ConfigurationProperties注解,前缀(prefix)为spring.resources,静态资源字段为staticLocations,那么我们也可以通过配置spring.resources.staticLocations这个properties来替换静态资源地址。","text":"Spring Boot 静态资源处理,默认配置路径为 classpath 下的/static (或 /public 或 /resources 或 /META-INF/resources),即将/**映射到这几个路径下。Spring Boot 启动的时候,我们会看到如下日志: 1232016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]2016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]2016-12-16 22:27:56 INFO 9812 - [restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 可以看到,Spring Boot 静态资源处理使用 Spring MVC 的ResourceHttpRequestHandler来实现。这里除了将/**映射到默认资源路径下,还为我们配置一个favicon.ico以及后面会讲到的webjars。Spring Boot中可以通过添加一个配置类,继承WebMvcConfigurerAdapter来自定义Spring MVC的配置,其中addResourceHandlers方法可以用来自定义静态资源处理,比如修改静态资源的缓存时间。这里我们想要替换默认的静态资源列表:123456789@Configurationpublic class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\"/**\") .addResourceLocations(\"classpath:/mydir1/\",\"classpath:/mydir2/\",\"classpath:/mydir3/\"); }} 此时,/**被映射到了"classpath:/mydir1/","classpath:/mydir2/","classpath:/mydir3/"下,要注意,如果替换了默认静态资源地址,那么默认欢迎页面index.html也将会被替换到我们自定义配置的任何路径下,Spring Boot相关的实现代码在ResourceProperties,这个类使用了ConfigurationProperties注解,前缀(prefix)为spring.resources,静态资源字段为staticLocations,那么我们也可以通过配置spring.resources.staticLocations这个properties来替换静态资源地址。 1.使用webjars 我们在Web开发中,前端页面中用了越来越多的JS或CSS,如jQuery等等,平时我们是将这些Web资源拷贝到Java的目录下,这种通过人工方式拷贝可能会产生版本误差,拷贝版本错误,前端页面就无法正确展示。 WebJars 就是为了解决这种问题衍生的,将这些Web前端资源打包成Java的Jar包,然后借助Maven这些依赖库的管理,保证这些Web资源版本唯一性,升级也比较容易。 webjars中的资源所在路径为:/META-INF/resources/webjars/,其中/META-INF/resources是默认资源路径,从刚才的日志当中也可以看到,Spirng Boot为其配置一个/webjars/**的Url映射。我们可以去http://www.webjars.org/ 找到我们想要的资源,将其依赖加入我们项目当中,即可使用这个资源了。例如: 12345<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.4</version></dependency> 这里dependency的version即为静态资源的版本号,模板中(thymeleaf)使用:123<head> <script th:src=\"@{/webjars/jquery/2.1.4/jquery.js}\"></script></head> 2.静态资源处理Spring Boot 为我们提供了 Spring MVC 中比较高级的静态资源处理方法,比如为webjars添加动态版本号、清除缓存的静态资源等。 2.1.为Webjars添加动态版本号实际开发当中,我们不可避免的需要升级第三方js、css库的版本,如果我们有100个页面,那么一个一个页面的去替换版本号显然不是个好方法,Spring Boot项目中我们只需要简单的添加webjars-locator依赖,即可为Webjars添加动态版本号管理。1234<dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId></dependency> 此依赖使用spring-boot-dependencies中统一管理的版本号,然后模板中就不需要写版本号了:123<head> <script th:src=\"@{/webjars/jquery/jquery.js}\"></script></head> 生成的结果为:123<head> <script th:src=\"/webjars/jquery/2.1.4/jquery.js\"></script></head> 原理很简单,运行时使用ResourceUrlEncodingFilter这个过滤器,将response通过HttpServletResponseWrapper进行静态包装并重写encodeURL方法,然后模板中使用response的encodeURL将链接地址重写。并且一定要注意,Thymeleaf 和 FreeMarker这两个模板自动配置了ResourceUrlEncodingFilter,JSP则需要手动声明这个过滤器,而其它模板则需要自已去实现这个功能。下面说到的其它版本管理中,也是同理。 此时,如果想要更换jquery版本号,只需修改jquery的webjars的dependency版本即可。 2.2.缓存清除当静态资源发生变化时,为了清除用户客户端的缓存,我们一般会将静态资源加上版本号或者固定时间戮,每当资源变化时手动去更新版本或时间戳。比如: 1<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/style.css?v=1.0.0\"/> 这种方式也是需要我们一个页面一个页面的去修改,显然不合适。我们有更好的方式,Spring Boot中我们可以去配置VersionResourceResolver,它有两种策略: FixedVersionStrategy可以使用某项属性,或者日期,或者其它来作为版本. ContentVersionStrategy是使用资源内容计算出来的MD5哈希作为版本 与Webjars添加动态版本号一样,这两种方式都借助了ResourceUrlEncodingFilter来重写模板中的URL。 更详细配置可参考:Spring Framework’s reference documentation. 2.2.1.ContentVersionStrategyproperties文件配置方法如下:12spring.resources.chain.strategy.content.enabled=truespring.resources.chain.strategy.content.paths=/** 同样也可以在继承WebMvcConfigurerAdapter的配置类中配置 1234567@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\"/**\") .addResourceLocations(\"classpath:/static/\") .resourceChain(true) .addResolver(new VersionResourceResolver().addContentVersionStrategy(\"/**\"));} 配置成功后,页面中的:1<link rel=\"stylesheet\" type=\"text/css\" th:href=\"@{/css/style.css}\"/> 会被替换为:1<link rel=\"stylesheet\" type=\"text/css\" th:href=\"/css/style-2a2d595e6ed9a0b24f027f2b63b134d6.css\"/> 文件的hash会被缓存,使用的是Spring的ConcurrentMapCache,一个简单的缓存实现,文件改动后,只有重启服务再次访问才会重新生成hash 2.2.2.FixedVersionStrategy在动态的加载静态资源时,比如javascript模块加截器,这时更改文件名不是个好选择,可以使用FixedVersionStrategy,用某项属性,或者日期,或者其它来作为版本。 比如,在properties文件中这样配置12345spring.resources.chain.strategy.content.enabled=truespring.resources.chain.strategy.content.paths=/**spring.resources.chain.strategy.fixed.enabled=truespring.resources.chain.strategy.fixed.paths=/js/lib/spring.resources.chain.strategy.fixed.version=v12 这样配置后, 位于 "/js/lib/"下的 JavaScript 将会使用一个固定的版本号"/v12/js/lib/mymodule.js" ,而其它的静态资源仍然使用文件hash的方式。 同样可以在类中配置:123456789@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(\"/**\") .addResourceLocations(\"classpath:/static/\") .resourceChain(true) .addResolver(new VersionResourceResolver() .addFixedVersionStrategy(\"v12\", \"/js/lib/\") .addContentVersionStrategy(\"/**\"));} 2.3.URL中含有”#”字符的问题查看ResourceUrlEncodingFilter这个过滤器,发现当URL中含有”?”,比如1<script th:src=\"@{/jquery.js?v=1}\"></script> response的encodeURL执行时,会首先将”?”后面的部分?v=1去掉,然后再根据/jquery.js去资源目录去查找文件1234private int getQueryParamsIndex(String url) { int index = url.indexOf(\"?\"); return (index > 0 ? index : url.length());} 如果URL中有其它字符,比如/jquery.js#1,encodeURL执行时会拿着/jquery.js#1去查找文件,这样当然找不到,所以导致没法配置资源缓存清除这个功能了。 至于为什么我会遇到资源文件的URL中有”#”字符的情况呢?123<svg> <use xlink:href=\"/svg-symbols.svg#grbs\"></use></svg> 我们公司前端的svg引用就是这样写的,将很多svg放在一个文件中,通过”#”选择调用。 于是我重写了ResourceUrlEncodingFilter过滤器,添加”#”的判断,并优化了文件URL缓存。","categories":[{"name":"java","slug":"java","permalink":"https://blog.imyxiao.com/categories/java/"}],"tags":[{"name":"static","slug":"static","permalink":"https://blog.imyxiao.com/tags/static/"},{"name":"webjars","slug":"webjars","permalink":"https://blog.imyxiao.com/tags/webjars/"},{"name":"springboot","slug":"springboot","permalink":"https://blog.imyxiao.com/tags/springboot/"}]},{"title":"Spring Boot去除provided的依赖","slug":"Spring-Boot-exclude-dependency","date":"2016-12-04T12:50:15.000Z","updated":"2018-03-30T07:55:35.887Z","comments":true,"path":"java/Spring-Boot-exclude-dependency.html","link":"","permalink":"https://blog.imyxiao.com/java/Spring-Boot-exclude-dependency.html","excerpt":"Maven的Dependency scope是用来限制Dependency的作用范围的, 影响maven项目在各个生命周期时导入的package的状态。总共有6种范围,默认是compile,作用于整个生命周期。当scope设置为provided时,打包的时候可以不用包进去。该依赖理论上可以参与编译,测试,运行等周期,相当于compile,但是在打包阶段做了exclude的动作。 例如,Java EE中Servlet api,我们在编译的时候用到servlet-api和jsp-api,但在打包的时候不用这两个依赖,因为容器都有提供。 123456789101112<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency><dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> 但是在spring boot当中,当Dependency scope设置为provided时,例如: 123456<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <scope>provided</scope></dependency>","text":"Maven的Dependency scope是用来限制Dependency的作用范围的, 影响maven项目在各个生命周期时导入的package的状态。总共有6种范围,默认是compile,作用于整个生命周期。当scope设置为provided时,打包的时候可以不用包进去。该依赖理论上可以参与编译,测试,运行等周期,相当于compile,但是在打包阶段做了exclude的动作。 例如,Java EE中Servlet api,我们在编译的时候用到servlet-api和jsp-api,但在打包的时候不用这两个依赖,因为容器都有提供。 123456789101112<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency><dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> 但是在spring boot当中,当Dependency scope设置为provided时,例如: 123456<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <scope>provided</scope></dependency> spring-boot-devtools这个依赖帮助我们在开发的时候使用LiveReload或者Hot swapping,但是正式环境中用不到它(也有用但是我用不着),所以就没必要将这个依赖打入jar中,我将其设置为<scope>provided</scope>,但是生成executable jar中依旧含有这个jar包。 我们使用Spring Boot时,都会使用Spring Boot Maven Plugin插件,其中的一个作用通过spring-boot:repackage生成一个可执行的jar或者war。一个可执行的jar当中必然包含servlet容器,如默认的tomcat,所以repackage设计成:即使dependencies的scope为provided,依旧会将依赖打入jar当中。 为了可以excluded dependencies from the executable jar,所以Spring Boot Maven Plugin可以通过配置去排除依赖exclude dependency 通过特定的groupId和artifactId来排除 (有的依赖还有classifier) 通过指定artifactId来排除:任何符合给定的artifactId都被排除 通过指定groupId来排除属于这个groupId下的依赖 排除特定依赖com.foo:bar:1234567891011121314151617181920212223242526<project> ... <build> ... <plugins> ... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.4.2.RELEASE</version> <configuration> <excludes> <exclude> <groupId>com.foo</groupId> <artifactId>bar</artifactId> </exclude> </excludes> </configuration> ... </plugin> ... </plugins> ... </build> ...</project> 排除artifact为my-lib 或者 another-lib的依赖123456789101112131415161718192021<project> ... <build> ... <plugins> ... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.4.2.RELEASE</version> <configuration> <excludeArtifactIds>my-lib,another-lib</excludeArtifactIds> </configuration> ... </plugin> ... </plugins> ... </build> ...</project> 排除所有属于com.foo这个group的依赖123456789101112131415161718192021<project> ... <build> ... <plugins> ... <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.4.2.RELEASE</version> <configuration> <excludeGroupIds>com.foo</excludeGroupIds> </configuration> ... </plugin> ... </plugins> ... </build> ...</project>","categories":[{"name":"java","slug":"java","permalink":"https://blog.imyxiao.com/categories/java/"}],"tags":[{"name":"springboot","slug":"springboot","permalink":"https://blog.imyxiao.com/tags/springboot/"},{"name":"maven","slug":"maven","permalink":"https://blog.imyxiao.com/tags/maven/"},{"name":"provided","slug":"provided","permalink":"https://blog.imyxiao.com/tags/provided/"},{"name":"dependency","slug":"dependency","permalink":"https://blog.imyxiao.com/tags/dependency/"}]},{"title":"SpringBoot使用thymeleaf 3","slug":"Spring-Boot-thymeleaf-3","date":"2016-11-26T09:26:00.000Z","updated":"2018-03-30T07:55:09.000Z","comments":true,"path":"java/Spring-Boot-thymeleaf-3.html","link":"","permalink":"https://blog.imyxiao.com/java/Spring-Boot-thymeleaf-3.html","excerpt":"如果你的spring boot应用继承spring-boot-starter-parent,那么只需要添加spring-boot-starter-thymeleaf这个starter依赖,即可使用thymeleaf模板引擎 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>","text":"如果你的spring boot应用继承spring-boot-starter-parent,那么只需要添加spring-boot-starter-thymeleaf这个starter依赖,即可使用thymeleaf模板引擎 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> 升级为thymeleaf 3从spring-boot-dependencies中的dependencyManagement中可以看到:spring-boot-starter-thymeleaf,默认使用Thymeleaf 2.1 如果要改为Thymeleaf 3,只需要重写thymeleaf.version和thymeleaf-layout-dialect.version两个properties <properties> <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version> <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version> </properties> thymeleaf 3Thymeleaf3完全兼容Thymeleaf2,升级到Thymeleaf3的过程中,我只用到的部分特性,参考地址:Thymeleaf 3 迁移指南。 1.Full HTML5 markup support(完整的HTML5 标记支持)Thymeleaf 2.1中,html代码必须严格遵守XML规范,必须是XML-well-formed HTML5 code,比如: 标签必须闭合,<br> 是错误的 属性必须有值,<span v-cloak/> 是不被允许的 不是所有的人都会完全的遵守XML规范,Thymeleaf2中要解决这个问题,可以将spring.thymeleaf.mode这个属性改为LEGACYHTML5,然后添加nekoHTML这个库。如果使用Thymeleaf3,就不会存在这个问题,因为Thymeleaf3使用新的解析引擎。 2.Template modes(模板类型) HTML、XML、TEXT、JAVASCRIPT、CSS、RAW 分为三类:标记型模板(HTML,XML),文本型模板(TEXT, JAVASCRIPT和CSS),无操作(no-op)模板 (RAW)。 Thymeleaf2.1中的HTML5, XHTML, VALIDXHTML和LEGACYHTML5相当于3.0中的 HTML Thymeleaf2.1中的VALIDXML也就是3.0中的XML 所以在Thymeleaf3中使用HTML包括了HTML5,HTML4和XHTML在内的所有类型的HTML标记,此时,标记的作用范围按可能的最大化处理。 spring boot需要将默认的HTML5改为HTML,在application.properties文件中增加:spring.thymeleaf.mode=HTML Improved inlining mechanism(增强的内联机制)Thymeleaf3中可无需额外的标签,直接在文本中输出数据 <p>This product is called [[${product.name}]] and it's great!</p> Thymeleaf2.1中则需要使用内联标签th:inline <p th:inline="text"> This product is called [[${product.name}]] and it's great! </p> 上面的代码中也可以使用[(${product.name)]来代替,[[...]]和[(...)]区别在于[(...)]中的文本不会被Escape,就相当于th:text和th:utext的区别 3.Fragment Expressions(片段表达式)Thymeleaf 3.0 引入了一个新的Fragment Expressions。像是这样:~{commons::footer}。例如,我们定义一个模版页面base.html <head th:fragment="common_header(title,links)"> <title th:utext="${title}">The awesome application</title> <!-- Common styles and scripts --> <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}"> <link rel="shortcut icon" th:href="@{/images/favicon.ico}"> <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script> <!--/* Per-page placeholder for additional links */--> <th:block th:replace="${links} ?: ~{}" /> </head> 在我们页面中使用这个模板 ... <head th:replace="base :: common_header(~{::title},~{::link})"> <title>Awesome - Main</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}"> </head> ... 结果会输出: ... <head> <title>Awesome - Main</title> <!-- Common styles and scripts --> <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css"> <link rel="shortcut icon" href="/awe/images/favicon.ico"> <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script> <link rel="stylesheet" href="/awe/css/bootstrap.min.css"> <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css"> </head> ... 这个特性解决了通用的header和footer的问题,详细说明参考:https://github.com/thymeleaf/thymeleaf/issues/451 4.Decoupled Template Logic(模板逻辑解耦)定义一个完全的html模版home.html <!DOCTYPE html> <html> <body> <table id="usersTable"> <tr> <td class="username">Jeremy Grapefruit</td> <td class="usertype">Normal User</td> </tr> <tr> <td class="username">Alice Watermelon</td> <td class="usertype">Administrator</td> </tr> </table> </body> </html> 然后只需要定义一个home.th.xml <?xml version="1.0"?> <thlogic> <attr sel="#usersTable" th:remove="all-but-first"> <attr sel="/tr[0]" th:each="user : ${users}"> <attr sel="td.username" th:text="${user.name}" /> <attr sel="td.usertype" th:text="#{|user.type.${user.type}|}" /> </attr> </attr> </thlogic> 这样前端人员写的html与后端代码完全解耦,详细说明参考:https://github.com/thymeleaf/thymeleaf/issues/465","categories":[{"name":"java","slug":"java","permalink":"https://blog.imyxiao.com/categories/java/"}],"tags":[{"name":"springboot","slug":"springboot","permalink":"https://blog.imyxiao.com/tags/springboot/"},{"name":"thymeleaf","slug":"thymeleaf","permalink":"https://blog.imyxiao.com/tags/thymeleaf/"}]},{"title":"给jodd的beancopy注册Converter","slug":"jodd_beancopy_converter","date":"2016-11-25T14:50:00.000Z","updated":"2018-03-30T07:52:53.319Z","comments":true,"path":"java/jodd_beancopy_converter.html","link":"","permalink":"https://blog.imyxiao.com/java/jodd_beancopy_converter.html","excerpt":"公司的项目是微服务架构,使用facebook的thrift框架作为微服务之间rpc通信的协议,thrift是一个跨语言的服务框架。 Thrift通过接口定义语言 (interface definition language,IDL) 来定义数据类型和服务,Thrift接口定义文件由Thrift代码编译器生成thrift目标语言的代码。 Thrift编译器生成API文件,我们分别实现其server与client。在server端,将服务需要响应的数据(如数据库中查询所得)封装到thrift生成的java对象中序列化后返回,在client端收到之后,反序列化成thrift生成的java对象。thrift框架的序列化及反序列化使用的binary protocol,即二进制协议,速度快,性能高。","text":"公司的项目是微服务架构,使用facebook的thrift框架作为微服务之间rpc通信的协议,thrift是一个跨语言的服务框架。 Thrift通过接口定义语言 (interface definition language,IDL) 来定义数据类型和服务,Thrift接口定义文件由Thrift代码编译器生成thrift目标语言的代码。 Thrift编译器生成API文件,我们分别实现其server与client。在server端,将服务需要响应的数据(如数据库中查询所得)封装到thrift生成的java对象中序列化后返回,在client端收到之后,反序列化成thrift生成的java对象。thrift框架的序列化及反序列化使用的binary protocol,即二进制协议,速度快,性能高。 BeanCopy我们在使用Thrift的过程中,需要使用到两次java对象属性的复制:server端再将对象复制到thrift生成的java对象中,client端将thrift对象复制到自己的定义的java对象中。java中使用bean copy的工具类,比较多是的jodd的beanCopy与Apache的BeanUtils,我们用的jodd。将A对象复制到B对象,jodd的BeanCopy使用如下: BeanCopy.beans(A, B).copy(); 然而由于thrift框架的基本数据类型中,没有java中的时间类型,即java.util.Date,我们会将时间定义为String或long类型。比较推荐使用long类型时间戳,但是之前代码中都使用的String类型,比较令人讨厌,旧的项目中大量的使用BeanCopy,这时候改动比较麻烦。 那么使用String来表示时间,配置BeanCopy的Converter。 Converterjodd中类型之间的转换大量的使用Converter,并且有一个”类型转换管理器”:jodd.typeconverter.TypeConverterManager。TypeConverterManager注册了大量的转换器。 将Date转化为String,使用的是jodd.typeconverter.impl.StringConverter 原理很简单,先判断被转换对象的类型,然后实现不同对象的转换,Date转换为String使用的是Date的toString方法 被转化的String格式为:"EEE MMM dd HH:mm:ss zzz yyyy",且Locale为US jodd中的使用jodd.typeconverter.impl.DateConverter将对象转化为Date 使用的默认格式为:YYYY-MM-DD hh:mm:ss.mss ,这时候我们有两种解决方案。 在server端重新注册StringConverter,BeanCopy时就将Date转为YYYY-MM-DD hh:mm:ss.mss格式的String,client端会被转换为Date 在client端重新注册DateConverter,BeanCopy时将”EEE MMM dd HH:mm:ss zzz yyyy”格式的String转换为Date DateConverter(项目启动后,执行即可):1234567891011121314151617TypeConverterManager.register(Date.class, value -> { if (value == null) { return null; } else if (value instanceof Date) { return value; } else if (value instanceof Number) { return new Date(((Number) value).longValue()); } else { String stringValue = value.toString().trim(); SimpleDateFormat slf = new SimpleDateFormat(\"EEE MMM dd HH:mm:ss zzz yyyy\", Locale.US); try { return slf.parse(stringValue); } catch (ParseException e) { return null; } }}) StringConverter类似,只需要增加一个Date类型的判断:123if (value instanceof Date) { return new SimpleDateFormat(\"YYYY-MM-DD hh:mm:ss.mss\").format(value);} thrift的binary类型公司有的项目直接使用thrift的binary做数据的返回类型,java中对应的是ByteBuffer。 这时候我们需要自己实现对象的序列化与反序列化,比如说MessagePack,一种比JSON更快、更小的格式 这时候就可以不用thrift生成的java对象了,也就不需要烦恼任何类型转换的问题了。","categories":[{"name":"java","slug":"java","permalink":"https://blog.imyxiao.com/categories/java/"}],"tags":[{"name":"springboot","slug":"springboot","permalink":"https://blog.imyxiao.com/tags/springboot/"},{"name":"jodd","slug":"jodd","permalink":"https://blog.imyxiao.com/tags/jodd/"},{"name":"beancopy","slug":"beancopy","permalink":"https://blog.imyxiao.com/tags/beancopy/"}]},{"title":"批量更新Git代码库","slug":"gitUpdater","date":"2016-07-21T08:36:45.000Z","updated":"2018-03-30T07:52:28.326Z","comments":true,"path":"git/gitUpdater.html","link":"","permalink":"https://blog.imyxiao.com/git/gitUpdater.html","excerpt":"每天上班前会所有的代码git pull,保持项目是最新的,但是由于公司使用的架构(thrift微服务),导致项目特别多,一个项目一个项目的git pull挺麻烦的,于是在想有没有一个好的办法,一次性更新所有的项目。于是在网上搜索发现了git-repo-updater这个小工具。","text":"每天上班前会所有的代码git pull,保持项目是最新的,但是由于公司使用的架构(thrift微服务),导致项目特别多,一个项目一个项目的git pull挺麻烦的,于是在想有没有一个好的办法,一次性更新所有的项目。于是在网上搜索发现了git-repo-updater这个小工具。gitup (the git-repo-updater) gitup is a tool designed to update a large number of git repositories at once.It is smart enough to handle multiple remotes, branches, dirty workingdirectories, and more, hopefully providing a great way to get everythingup-to-date for short periods of internet access between long periods of none. gitup should work on OS X, Linux, and Windows. You should have the latestversion of git and either Python 2.7 or Python 3 installed. InstallationWith Homebrew: brew install gitup From sourceFirst: git clone git://github.com/earwig/git-repo-updater.git cd git-repo-updater Then, to install for everyone: sudo python setup.py install …or for just yourself (make sure you have ~/.local/bin in your PATH): python setup.py install --user Finally, simply delete the git-repo-updater directory, and you’re done! Note: If you are using Windows, you may wish to add a macro so you caninvoke gitup in any directory. Note that C:\\python27\\ refers to thedirectory where Python is installed: DOSKEY gitup=c:\\python27\\python.exe c:\\python27\\Scripts\\gitup $* UsageThere are two ways to update repos: you can pass them as command arguments,or save them as “bookmarks”. For example: gitup ~/repos/foo ~/repos/bar ~/repos/baz will automatically pull to the foo, bar, and baz git repositories.Additionally, you can just type: gitup ~/repos to automatically update all git repositories in that directory. To add a bookmark (or bookmarks), either of these will work: gitup --add ~/repos/foo ~/repos/bar ~/repos/baz gitup --add ~/repos Then, to update all of your bookmarks, just run gitup without args: gitup Delete a bookmark: gitup --delete ~/repos View your current bookmarks: gitup --list You can mix and match bookmarks and command arguments: gitup --add ~/repos/foo ~/repos/bar gitup ~/repos/baz # update 'baz' only gitup # update 'foo' and 'bar' only gitup ~/repos/baz --update # update all three! Update all git repositories in your current directory: gitup . By default, gitup will fetch all remotes in a repository. Pass --current-only(or -c) to make it fetch only the remote tracked by the current branch. Also by default, gitup will try to fast-forward all branches that haveupstreams configured. It will always skip branches where this is not possible(e.g. dirty working directory or a merge/rebase is required). Pass--fetch-only (or -f) to only fetch remotes. After fetching, gitup will keep remote-tracking branches that no longer existupstream. Pass --prune (or -p) to delete them, or set fetch.prune orremote.<name>.prune in git config to do this by default. For a full list of all command arguments and abbreviations: gitup --help Finally, all paths can be either absolute (e.g. /path/to/repo) or relative(e.g. ../my/repo).","categories":[{"name":"git","slug":"git","permalink":"https://blog.imyxiao.com/categories/git/"}],"tags":[{"name":"git","slug":"git","permalink":"https://blog.imyxiao.com/tags/git/"}]},{"title":"First Post ~","slug":"firstpost","date":"2016-07-11T17:31:02.000Z","updated":"2018-03-30T07:52:42.614Z","comments":true,"path":"blog/firstpost.html","link":"","permalink":"https://blog.imyxiao.com/blog/firstpost.html","excerpt":"Wordpress2010年夏天开始学习并使用Wordpress写博客,已经有好几年了,当时还是在学校里面,学的是与计算机无关的专业。一开始接触的程序是学校的Discuz论坛,那时就对网站有关的知识产生了极大了兴趣,通过网络认识了Wordpress这个程序,然后开始慢慢的折腾,对网站的一些概念才有了一定的认识,所以才有了后来转到计算机专业的念头。","text":"Wordpress2010年夏天开始学习并使用Wordpress写博客,已经有好几年了,当时还是在学校里面,学的是与计算机无关的专业。一开始接触的程序是学校的Discuz论坛,那时就对网站有关的知识产生了极大了兴趣,通过网络认识了Wordpress这个程序,然后开始慢慢的折腾,对网站的一些概念才有了一定的认识,所以才有了后来转到计算机专业的念头。 当然由于一些原因,除了最开始的几年我会经常的去折腾主题,折腾特效,觉得蛮有意思外,后面几乎没有花时间在上面了。wordpress是php写,我使用的是最便宜的虚拟主机,运行的环镜:Apache+php+Mysql。一年的费用是100元。 Hexo前几天开始尝试使用hexo与markdown做一个静态博客,Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 并将博客托管到github pages上. markdown还是挺有用的,最近也在研究使用gitbook写交流文档。 http://book.imyxiao.com","categories":[{"name":"blog","slug":"blog","permalink":"https://blog.imyxiao.com/categories/blog/"}],"tags":[{"name":"firstpost","slug":"firstpost","permalink":"https://blog.imyxiao.com/tags/firstpost/"},{"name":"hexo","slug":"hexo","permalink":"https://blog.imyxiao.com/tags/hexo/"},{"name":"wordpress","slug":"wordpress","permalink":"https://blog.imyxiao.com/tags/wordpress/"}]},{"title":"Hello World","slug":"hello-world","date":"2016-07-11T05:58:02.000Z","updated":"2018-03-30T07:57:20.947Z","comments":true,"path":"blog/hello-world.html","link":"","permalink":"https://blog.imyxiao.com/blog/hello-world.html","excerpt":"","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","categories":[{"name":"blog","slug":"blog","permalink":"https://blog.imyxiao.com/categories/blog/"}],"tags":[{"name":"hello","slug":"hello","permalink":"https://blog.imyxiao.com/tags/hello/"}]}]}