├── weixin └── build.gradle ├── tools ├── src │ ├── test │ │ ├── resources │ │ │ └── mustache │ │ │ │ └── t1.mustache │ │ └── java │ │ │ └── cn │ │ │ └── msjc │ │ │ └── utils │ │ │ ├── misc │ │ │ └── BitToolsTest.java │ │ │ └── ds │ │ │ ├── string │ │ │ └── mustache │ │ │ │ └── MustacheToolsTest.java │ │ │ └── json │ │ │ └── JsonUtilsTest.java │ └── main │ │ └── java │ │ └── cn │ │ └── msjc │ │ └── utils │ │ ├── lang │ │ ├── clone │ │ │ ├── package-info.java │ │ │ ├── EnhancedCloneable.java │ │ │ ├── AutoCloneable.java │ │ │ └── CloneRuntimeException.java │ │ ├── annotation │ │ │ ├── package-info.java │ │ │ └── Alias.java │ │ ├── ExceptionUtils.java │ │ └── concurrent │ │ │ └── FutureUtils.java │ │ ├── bean │ │ ├── HasId.java │ │ ├── HasName.java │ │ ├── HasTimeFields.java │ │ ├── pageable │ │ │ ├── PageNumBasedPageLink.java │ │ │ └── PageNumBasedPageData.java │ │ └── Response.java │ │ ├── misc │ │ ├── BitTools.java │ │ └── i18n │ │ │ └── phonenumber │ │ │ └── PhoneNumberTools.java │ │ ├── ds │ │ ├── ObjectUtils.java │ │ ├── string │ │ │ ├── mustache │ │ │ │ └── MustacheTools.java │ │ │ └── RandomGenerator.java │ │ ├── hash │ │ │ └── HashUtils.java │ │ ├── json │ │ │ └── JsonUtils.java │ │ └── date │ │ │ └── LocalDateTimeUtil.java │ │ ├── storage │ │ └── file │ │ │ └── SimpleFileUtils.java │ │ └── system │ │ └── CmdExecutor.java └── build.gradle ├── aliyun └── build.gradle ├── lombok.config ├── config ├── settings.zip ├── env.tpl ├── flyway-local.conf ├── spotbugs-exclude-filter.xml ├── checkstyle.xml └── intellij-java-google-style.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── rm-misc.sh ├── LICENSE ├── gradle.properties ├── gradlew.bat ├── README.md ├── .gitignore ├── gradlew └── header.svg /weixin/build.gradle: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/src/test/resources/mustache/t1.mustache: -------------------------------------------------------------------------------- 1 | {{name}}, {{description}} -------------------------------------------------------------------------------- /aliyun/build.gradle: -------------------------------------------------------------------------------- 1 | compile group: 'com.alipay.sdk', name: 'alipay-easysdk', version: '2.1.0' -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.accessors.chain=true 2 | lombok.extern.findbugs.addSuppressFBWarnings = true -------------------------------------------------------------------------------- /config/settings.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/ms-java-commons/master/config/settings.zip -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/clone/package-info.java: -------------------------------------------------------------------------------- 1 | /** 克隆封装 */ 2 | package cn.msjc.utils.lang.clone; 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx-chevalier/ms-java-commons/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/bean/HasId.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.bean; 2 | 3 | public interface HasId { 4 | T getId(); 5 | } 6 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/annotation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 注解包,提供增强型注解和注解工具类 */ 2 | package cn.msjc.utils.lang.annotation; 3 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/bean/HasName.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.bean; 2 | 3 | public interface HasName { 4 | String getName(); 5 | } 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ms-java-common-tools' 2 | 3 | // 公共 Tools 4 | include ':msjc-utils' 5 | project(':msjc-utils').projectDir = new File("$rootDir/msjc-utils") -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /config/env.tpl: -------------------------------------------------------------------------------- 1 | # build-locally.sh 2 | export DISABLE_TEST= 3 | export DISABLE_SPOTBUGS= 4 | export TAG= 5 | 6 | # deploy-locally.sh 7 | export RELEASE= 8 | export VALUES_FILE= 9 | export NAMESPACE= -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /config/flyway-local.conf: -------------------------------------------------------------------------------- 1 | flyway.url=jdbc:mysql://127.0.0.1:3306/test-db 2 | flyway.user=root 3 | flyway.defaultSchema =test-db 4 | flyway.schemas=test-db 5 | flyway.password=roottoor 6 | flyway.cleanDisabled=true 7 | -------------------------------------------------------------------------------- /rm-misc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find . -name '*.project' -exec rm -rf {} \; 4 | find . -name '*.idea' -exec rm -rf {} \; 5 | find . -name '*.iml' -exec rm -rf {} \; 6 | find . -name 'bin' -exec rm -rf {} \; 7 | find . -name 'target' -exec rm -rf {} \; 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/bean/HasTimeFields.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.bean; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public interface HasTimeFields { 6 | 7 | LocalDateTime getCreatedAt(); 8 | 9 | LocalDateTime getUpdatedAt(); 10 | } 11 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/clone/EnhancedCloneable.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.lang.clone; 2 | 3 | /** 4 | * 克隆支持接口 5 | * 6 | * @param 实现克隆接口的类型 7 | */ 8 | public interface EnhancedCloneable extends java.lang.Cloneable { 9 | 10 | /** 11 | * 克隆当前对象,浅复制 12 | * 13 | * @return 克隆后的对象 14 | */ 15 | T clone(); 16 | } 17 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/ExceptionUtils.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.lang; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | 6 | public class ExceptionUtils { 7 | 8 | public static String getStackTrace(Throwable throwable) { 9 | StringWriter sw = new StringWriter(); 10 | PrintWriter pw = new PrintWriter(sw, true); 11 | throwable.printStackTrace(pw); 12 | return sw.getBuffer().toString(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/clone/AutoCloneable.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.lang.clone; 2 | 3 | import cn.hutool.core.clone.CloneRuntimeException; 4 | 5 | /** 6 | * 克隆支持类,提供默认的克隆方法 7 | * 8 | * @param 9 | */ 10 | public class AutoCloneable implements Cloneable { 11 | 12 | @SuppressWarnings("unchecked") 13 | @Override 14 | public T clone() { 15 | try { 16 | return (T) super.clone(); 17 | } catch (CloneNotSupportedException e) { 18 | throw new CloneRuntimeException(e); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/bean/pageable/PageNumBasedPageLink.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.bean.pageable; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import lombok.Data; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Data 8 | @RequiredArgsConstructor 9 | public class PageNumBasedPageLink { 10 | 11 | // 0 页开始 12 | final int pageNum; 13 | 14 | // 每页大小 15 | final int pageSize; 16 | 17 | @JsonCreator 18 | public PageNumBasedPageLink nextPage() { 19 | return new PageNumBasedPageLink(getPageNum() + 1, getPageSize()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /config/spotbugs-exclude-filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/annotation/Alias.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.lang.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 别名注解,使用此注解的字段、方法、参数等会有一个别名,用于Bean拷贝、Bean转Map等 */ 10 | @Documented 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) 13 | public @interface Alias { 14 | 15 | /** 16 | * 别名值,即使用此注解要替换成的别名名称 17 | * 18 | * @return 别名值 19 | */ 20 | String value(); 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tools/src/test/java/cn/msjc/utils/misc/BitToolsTest.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.misc; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.google.common.collect.Sets; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | import org.junit.jupiter.api.Test; 9 | 10 | class BitToolsTest { 11 | 12 | @Test 13 | void testBitString() { 14 | String[] strs = new String[] {"ABCD", "CDEF", "MMMM", "XXXX", "EE"}; 15 | HashSet s1 = Sets.newHashSet("XXXX", "EE"); 16 | String bitS = BitTools.convertToBitString(s1, strs); 17 | System.out.println(bitS); 18 | Set s2 = BitTools.convertFromBitString(bitS, strs); 19 | assertEquals(s1, s2); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/misc/BitTools.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.misc; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | public class BitTools { 7 | 8 | public static String convertToBitString(Set strings, String[] strArray) { 9 | StringBuilder sb = new StringBuilder(); 10 | for (String s : strArray) { 11 | if (strings.contains(s)) { 12 | sb.append('1'); 13 | } else { 14 | sb.append('0'); 15 | } 16 | } 17 | return sb.toString(); 18 | } 19 | 20 | public static Set convertFromBitString(String bitString, String[] strArray) { 21 | Set res = new HashSet<>(); 22 | int i = 0; 23 | for (char ch : bitString.toCharArray()) { 24 | if (ch == '1') { 25 | res.add(strArray[i]); 26 | } 27 | i++; 28 | } 29 | return res; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/clone/CloneRuntimeException.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.lang.clone; 2 | 3 | import cn.hutool.core.exceptions.ExceptionUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | 6 | /** 克隆异常 */ 7 | public class CloneRuntimeException extends RuntimeException { 8 | private static final long serialVersionUID = 6774837422188798989L; 9 | 10 | public CloneRuntimeException(Throwable e) { 11 | super(ExceptionUtil.getMessage(e), e); 12 | } 13 | 14 | public CloneRuntimeException(String message) { 15 | super(message); 16 | } 17 | 18 | public CloneRuntimeException(String messageTemplate, Object... params) { 19 | super(StrUtil.format(messageTemplate, params)); 20 | } 21 | 22 | public CloneRuntimeException(String message, Throwable throwable) { 23 | super(message, throwable); 24 | } 25 | 26 | public CloneRuntimeException(Throwable throwable, String messageTemplate, Object... params) { 27 | super(StrUtil.format(messageTemplate, params), throwable); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/misc/i18n/phonenumber/PhoneNumberTools.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.misc.i18n.phonenumber; 2 | 3 | import com.google.i18n.phonenumbers.PhoneNumberUtil; 4 | import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; 5 | import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; 6 | import io.vavr.API; 7 | import io.vavr.control.Try; 8 | 9 | public class PhoneNumberTools { 10 | 11 | private static final PhoneNumberUtil UTIL = PhoneNumberUtil.getInstance(); 12 | 13 | public static Try parse(String phoneNumber) { 14 | return API.Try(() -> UTIL.parse(phoneNumber, "CN")); 15 | } 16 | 17 | public static Try parse(String phoneNumber, String defaultRegion) { 18 | return API.Try(() -> UTIL.parse(phoneNumber, defaultRegion)); 19 | } 20 | 21 | public static String format(PhoneNumber phoneNumber, PhoneNumberFormat phoneNumberFormat) { 22 | return UTIL.format(phoneNumber, phoneNumberFormat); 23 | } 24 | 25 | public static String formatE164(PhoneNumber phoneNumber) { 26 | return format(phoneNumber, PhoneNumberFormat.E164); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 王下邀月熊 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | springBootVersion=2.1.7.RELEASE 2 | mybatisPlusBootStarterVersion=3.2.0 3 | pahoMqttVersion=1.2.4 4 | guavaVersion=29.0-jre 5 | libphonenumberVersion=8.12.8 6 | slf4jVersion=1.7.30 7 | jetbrainsAnnotationVersion=19.0.0 8 | jupiterVersion=5.6.2 9 | hamcrestLibraryVersion=2.2 10 | findbugsAnnotationVersion=3.0.1 11 | lombokVersion=1.18.12 12 | javaJwtVersion=3.10.3 13 | validationApiVersion=2.0.1.Final 14 | swaggerVersion=1.5.22 15 | hsqldbVersion=2.5.0 16 | jacksonVersion=2.11.0 17 | orgJsonVersion=20200518 18 | mockitoVersion=3.3.3 19 | slf4jSimpleVersion=2.0.0-alpha1 20 | javaxMailVersion=1.6.2 21 | aliyunSdkOssVersion=3.5.0 22 | aliyunJavaSdkCoreVersion=4.1.0 23 | mysqlConnectorJavaVersion=8.0.20 24 | easyExcelVersion=2.1.4 25 | gexinVersion=4.1.0.5 26 | aliyunHitsdbClientVersion=0.2.6 27 | drillJdbcVersion=1.17.0 28 | akkaVersion=2.6.6 29 | wechatPaySdkVersion=3.7.8.B 30 | knifeDocVersion=2.0.2 31 | redissonVersion=3.13.0 32 | druidSpringBootStarterVersion=1.1.18 33 | micrometerRegistryPrometheusVersion=1.3.0 34 | vavrVersion=1.0.0-alpha-3 35 | modelmapperVersion=2.3.0 36 | mustacheJavaCompilerVersion=0.9.6 37 | hutoolVersion=5.5.1 -------------------------------------------------------------------------------- /tools/src/test/java/cn/msjc/utils/ds/string/mustache/MustacheToolsTest.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds.string.mustache; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import io.vavr.API; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class MustacheToolsTest { 9 | 10 | @Test 11 | void test_mustache_template_file() { 12 | String res = 13 | MustacheTools.mustacheTemplateFile( 14 | "mustache/t1.mustache", 15 | API.Map("name", "msjc", "description", "Industrial Internet of things platform")); 16 | 17 | assertEquals("msjc, Industrial Internet of things platform", res); 18 | } 19 | 20 | @Test 21 | void test_mustache_template() { 22 | String res = 23 | MustacheTools.mustache( 24 | "{{name}}, {{description}}", 25 | API.Map("name", "msjc", "description", "Industrial Internet of things platform")); 26 | 27 | assertEquals("msjc, Industrial Internet of things platform", res); 28 | } 29 | 30 | @Test 31 | void test_mustache_lack_of_value() { 32 | String res = MustacheTools.mustache("{{name}}, {{description}}", API.Map("name", "msjc")); 33 | assertEquals("msjc, ", res); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/bean/pageable/PageNumBasedPageData.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.bean.pageable; 2 | 3 | import io.vavr.collection.List; 4 | import java.util.function.Function; 5 | import lombok.Data; 6 | import lombok.RequiredArgsConstructor; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | @Data 10 | @RequiredArgsConstructor 11 | public class PageNumBasedPageData { 12 | 13 | final List data; 14 | final PageNumBasedPageLink pageLink; 15 | final Integer totalPage; 16 | final Long totalElements; 17 | 18 | @Nullable 19 | public PageNumBasedPageLink getNextPageLink() { 20 | if (pageLink != null && pageLink.getPageNum() < totalPage) { 21 | return pageLink.nextPage(); 22 | } else { 23 | return null; 24 | } 25 | } 26 | 27 | /** 28 | * 未分页数据 29 | * 30 | * @param data 所有数据 31 | */ 32 | public PageNumBasedPageData(List data) { 33 | this.data = data; 34 | this.pageLink = new PageNumBasedPageLink(0, data.size()); 35 | this.totalPage = 1; 36 | this.totalElements = (long) data.size(); 37 | } 38 | 39 | public PageNumBasedPageData map(Function mapper) { 40 | return new PageNumBasedPageData<>(data.map(mapper), pageLink, totalPage, totalElements); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/ds/ObjectUtils.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds; 2 | 3 | import cn.hutool.core.util.NumberUtil; 4 | import java.math.BigDecimal; 5 | import java.util.Objects; 6 | 7 | /** 对象工具类,包括判空、克隆、序列化等操作 */ 8 | public class ObjectUtils { 9 | 10 | /** 11 | * 比较两个对象是否相等,此方法是 {@link #equal(Object, Object)}的别名方法。
12 | * 相同的条件有两个,满足其一即可:
13 | * 14 | *
    15 | *
  1. obj1 == null && obj2 == null 16 | *
  2. obj1.equals(obj2) 17 | *
  3. 如果是 BigDecimal 比较,0 == obj1.compareTo(obj2) 18 | *
19 | * 20 | * @param obj1 对象1 21 | * @param obj2 对象2 22 | * @return 是否相等 23 | * @see #equal(Object, Object) 24 | */ 25 | public static boolean equals(Object obj1, Object obj2) { 26 | return equal(obj1, obj2); 27 | } 28 | 29 | /** 30 | * 比较两个对象是否相等。
31 | * 相同的条件有两个,满足其一即可:
32 | * 33 | *
    34 | *
  1. obj1 == null && obj2 == null 35 | *
  2. obj1.equals(obj2) 36 | *
  3. 如果是BigDecimal比较,0 == obj1.compareTo(obj2) 37 | *
38 | * 39 | * @param obj1 对象1 40 | * @param obj2 对象2 41 | * @return 是否相等 42 | * @see Objects#equals(Object, Object) 43 | */ 44 | public static boolean equal(Object obj1, Object obj2) { 45 | // 首先判断是否为 BigDecimal,如果是则进行模糊判断 46 | if (obj1 instanceof BigDecimal && obj2 instanceof BigDecimal) { 47 | return NumberUtil.equals((BigDecimal) obj1, (BigDecimal) obj2); 48 | } 49 | 50 | return Objects.equals(obj1, obj2); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/ds/string/mustache/MustacheTools.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds.string.mustache; 2 | 3 | import com.github.mustachejava.DefaultMustacheFactory; 4 | import com.github.mustachejava.Mustache; 5 | import com.github.mustachejava.MustacheFactory; 6 | import io.vavr.API; 7 | import io.vavr.collection.Map; 8 | import java.io.IOException; 9 | import java.io.StringReader; 10 | import java.io.StringWriter; 11 | import java.util.UUID; 12 | 13 | // https://mustache.github.io/ 14 | // https://github.com/spullara/mustache.java 15 | public class MustacheTools { 16 | private static final MustacheFactory mf = new DefaultMustacheFactory(); 17 | 18 | public static String mustacheTemplateFile(String templateFile, Object scope) { 19 | Mustache mustache = mf.compile(templateFile); 20 | StringWriter stringWriter = new StringWriter(); 21 | mustache.execute(stringWriter, scope); 22 | return stringWriter.toString(); 23 | } 24 | 25 | public static String mustacheTemplateFile(String templateFile, Map scopes) { 26 | return mustacheTemplateFile(templateFile, scopes.toJavaMap()); 27 | } 28 | 29 | public static String mustache(String template, Object scope) { 30 | Mustache mustache = mf.compile(new StringReader(template), UUID.randomUUID().toString()); 31 | StringWriter stringWriter = new StringWriter(); 32 | mustache.execute(stringWriter, scope); 33 | return stringWriter.toString(); 34 | } 35 | 36 | public static String mustache(String template, Map scopes) { 37 | return mustache(template, scopes.toJavaMap()); 38 | } 39 | 40 | public static void main(String[] args) throws IOException { 41 | System.out.println( 42 | mustache( 43 | "{{name}}, {{description}}", 44 | API.Map( 45 | "name", "lotuc", 46 | "description", "programmer"))); 47 | System.out.println( 48 | mustacheTemplateFile( 49 | "abc.mustache", 50 | API.Map( 51 | "hello", "damn", 52 | "world", "room", 53 | "he", "shitter", 54 | "it", "nobody"))); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/ds/hash/HashUtils.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds.hash; 2 | 3 | import com.google.common.io.BaseEncoding; 4 | import com.google.common.util.concurrent.UncheckedExecutionException; 5 | import java.io.BufferedInputStream; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.nio.charset.StandardCharsets; 11 | import java.security.MessageDigest; 12 | import java.security.NoSuchAlgorithmException; 13 | 14 | public class HashUtils { 15 | 16 | /** @return hex string with upper case */ 17 | public static String sha256(File file) { 18 | try (InputStream ins = new BufferedInputStream(new FileInputStream(file))) { 19 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 20 | byte[] buf = new byte[1024]; 21 | int nRead = 0; 22 | while ((nRead = ins.read(buf)) != -1) { 23 | md.update(buf, 0, nRead); 24 | } 25 | return BaseEncoding.base16().encode(md.digest()); 26 | } catch (IOException | NoSuchAlgorithmException e) { 27 | throw new UncheckedExecutionException(e); 28 | } 29 | } 30 | 31 | /** @return hex string with upper case */ 32 | public static String md5(File file) { 33 | try (InputStream ins = new BufferedInputStream(new FileInputStream(file))) { 34 | MessageDigest md = MessageDigest.getInstance("MD5"); 35 | byte[] buf = new byte[1024]; 36 | int nRead = 0; 37 | while ((nRead = ins.read(buf)) != -1) { 38 | md.update(buf, 0, nRead); 39 | } 40 | return BaseEncoding.base16().encode(md.digest()); 41 | } catch (IOException | NoSuchAlgorithmException e) { 42 | throw new UncheckedExecutionException(e); 43 | } 44 | } 45 | 46 | /** @return hex string with upper case */ 47 | public static String sha256(String s) { 48 | try { 49 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 50 | return BaseEncoding.base16().encode(md.digest(s.getBytes(StandardCharsets.UTF_8))); 51 | } catch (NoSuchAlgorithmException e) { 52 | throw new UncheckedExecutionException(e); 53 | } 54 | } 55 | 56 | /** @return hex string with upper case */ 57 | public static String md5(String s) { 58 | try { 59 | MessageDigest md = MessageDigest.getInstance("MD5"); 60 | return BaseEncoding.base16().encode(md.digest(s.getBytes(StandardCharsets.UTF_8))); 61 | } catch (NoSuchAlgorithmException e) { 62 | throw new UncheckedExecutionException(e); 63 | } 64 | } 65 | 66 | /** @return hex string with upper case */ 67 | public static String md5(byte[] bytes) { 68 | try { 69 | MessageDigest md = MessageDigest.getInstance("MD5"); 70 | return BaseEncoding.base16().encode(md.digest(bytes)); 71 | } catch (NoSuchAlgorithmException e) { 72 | throw new UncheckedExecutionException(e); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/ds/string/RandomGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds.string; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.RoundingMode; 5 | import java.util.Objects; 6 | import java.util.Random; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public class RandomGenerator { 10 | 11 | private static final Random rand = new Random(System.currentTimeMillis()); 12 | 13 | /** 注意此处移除1,i,I o o O等易混淆的符号 */ 14 | private static final String VERIFY_CODES = "1234567890"; 15 | 16 | public static String randomCode(int size) { 17 | int codesLen = VERIFY_CODES.length(); 18 | StringBuilder verifyCode = new StringBuilder(size); 19 | for (int i = 0; i < size; i++) { 20 | verifyCode.append(VERIFY_CODES.charAt(rand.nextInt(codesLen - 1))); 21 | } 22 | return verifyCode.toString(); 23 | } 24 | 25 | public static double randomRate() { 26 | // 生成随机比例 27 | double v = rand.nextDouble(); 28 | // 随机正负号 29 | int prefixFlag = rand.nextInt() % 3 == 0 ? -1 : 1; 30 | BigDecimal b = new BigDecimal(prefixFlag * v); 31 | return b.setScale(2, RoundingMode.HALF_UP).doubleValue(); 32 | } 33 | 34 | public static Long randomLong() { 35 | return rand.nextLong(); 36 | } 37 | 38 | public static int randomInt(int bound) { 39 | return rand.nextInt(bound); 40 | } 41 | 42 | public static int randomInt() { 43 | return rand.nextInt(); 44 | } 45 | 46 | public static Long randomLong(Integer start, Integer endExclusive) { 47 | return start + randomLong() % (endExclusive - start); 48 | } 49 | 50 | /** 51 | * 为指定Code添加校验位 52 | * 53 | * @apiNote 校验位和数据通过中划线分割 54 | * @apiNote 校验位计算公式(参考身份证号校验位算法): ∑((int)char * [index+1]) % 11 55 | */ 56 | public static String appendCheckData(String randomStr) { 57 | if (randomStr == null || randomStr.length() == 0) { 58 | return ""; 59 | } 60 | char[] chars = randomStr.toCharArray(); 61 | int s = 0; 62 | for (int i = 0; i < chars.length; i++) { 63 | char c = chars[i]; 64 | s += c * (i + 1); 65 | } 66 | 67 | String validateBitData = s % 11 == 10 ? "X" : String.valueOf(s % 11); 68 | return randomStr + "-" + validateBitData; 69 | } 70 | 71 | /** 72 | * @apiNote 支持校验格式为 20200409-476889-8 格式的单号 73 | * @apiNote 单号的校验位与数据位通过 - 分割 74 | */ 75 | public static boolean validate(String code) { 76 | if (!hasText(code)) { 77 | return false; 78 | } 79 | String rawCode = code.substring(0, code.length() - 2); 80 | String checkCode = appendCheckData(rawCode); 81 | return Objects.equals(checkCode, code); 82 | } 83 | 84 | private static boolean hasText(@Nullable String str) { 85 | return (str != null && !str.isEmpty() && containsText(str)); 86 | } 87 | 88 | private static boolean containsText(CharSequence str) { 89 | int strLen = str.length(); 90 | for (int i = 0; i < strLen; i++) { 91 | if (!Character.isWhitespace(str.charAt(i))) { 92 | return true; 93 | } 94 | } 95 | return false; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/storage/file/SimpleFileUtils.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.storage.file; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | import static com.google.common.base.Preconditions.checkState; 5 | 6 | import com.google.common.io.BaseEncoding; 7 | import java.io.BufferedInputStream; 8 | import java.io.BufferedReader; 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.IOException; 12 | import java.io.InputStreamReader; 13 | import java.io.UncheckedIOException; 14 | import java.nio.charset.StandardCharsets; 15 | import java.nio.file.Files; 16 | import java.util.Arrays; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | @Slf4j 21 | public class SimpleFileUtils { 22 | 23 | private static final int BUFFER_SIZE = 3 * 1024; 24 | 25 | public static File createTempFile(String suffix) { 26 | try { 27 | return Files.createTempFile("", suffix).toFile(); 28 | } catch (IOException e) { 29 | throw new UncheckedIOException(e); 30 | } 31 | } 32 | 33 | public static File createTempFile() { 34 | try { 35 | return Files.createTempFile("", "").toFile(); 36 | } catch (IOException e) { 37 | throw new UncheckedIOException(e); 38 | } 39 | } 40 | 41 | public static File createTempDir(String prefix) { 42 | try { 43 | return Files.createTempDirectory(prefix).toFile(); 44 | } catch (IOException e) { 45 | throw new UncheckedIOException(e); 46 | } 47 | } 48 | 49 | public static String base64EncodeFile(File inputFile) { 50 | try (BufferedInputStream in = 51 | new BufferedInputStream(new FileInputStream(inputFile), BUFFER_SIZE)) { 52 | BaseEncoding encoding = BaseEncoding.base64(); 53 | StringBuilder result = new StringBuilder(); 54 | byte[] chunk = new byte[BUFFER_SIZE]; 55 | int len; 56 | while ((len = in.read(chunk)) == BUFFER_SIZE) { 57 | result.append(encoding.encode(chunk)); 58 | } 59 | if (len > 0) { 60 | chunk = Arrays.copyOf(chunk, len); 61 | result.append(encoding.encode(chunk)); 62 | } 63 | return result.toString(); 64 | } catch (IOException e) { 65 | throw new UncheckedIOException(e); 66 | } 67 | } 68 | 69 | public static String readToString(File file) { 70 | try (BufferedReader br = 71 | new BufferedReader( 72 | new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { 73 | StringBuilder content = new StringBuilder(); 74 | String line; 75 | while (true) { 76 | line = br.readLine(); 77 | if (line == null) { 78 | break; 79 | } else { 80 | content.append(line).append('\n'); 81 | } 82 | } 83 | return content.toString(); 84 | } catch (IOException e) { 85 | throw new UncheckedIOException(e); 86 | } 87 | } 88 | 89 | public static void ensureDir(File file) { 90 | if (file.exists()) { 91 | checkState(file.isDirectory(), "%s is not directory", file); 92 | } else { 93 | if (file.mkdirs()) { 94 | log.debug("mkdir {}", file); 95 | } 96 | } 97 | } 98 | 99 | @SuppressWarnings("UnstableApiUsage") 100 | public static void copyFile(File src, File dst) { 101 | try { 102 | com.google.common.io.Files.copy(src, dst); 103 | } catch (IOException e) { 104 | throw new UncheckedIOException(e); 105 | } 106 | } 107 | 108 | public static void deleteFileIfExists(@Nullable File file) { 109 | if (file == null || !file.exists()) { 110 | return; 111 | } 112 | 113 | checkArgument(file.isFile(), "not a file: %s", file); 114 | if (file.exists() && file.delete()) { 115 | log.debug("Removed file {}", file); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/bean/Response.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.bean; 2 | 3 | import cn.msjc.utils.lang.ExceptionUtils; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | @Data 12 | @JsonInclude(Include.NON_NULL) 13 | public class Response { 14 | 15 | private ResponseStatus status; 16 | private ResponseMeta meta; 17 | private ErrorMessage err; 18 | private T data; 19 | 20 | public static Response ok() { 21 | return new Response().setStatus(ResponseStatus.ok); 22 | } 23 | 24 | public static Response ok(T data) { 25 | return new Response().setStatus(ResponseStatus.ok).setData(data); 26 | } 27 | 28 | public static Response ok(T data, Pagination pagination) { 29 | return ok(data).setMeta(new ResponseMeta().setPagination(pagination)); 30 | } 31 | 32 | public static Response ok( 33 | T data, Integer pageNum, Long totalElements, Integer totalPages) { 34 | return ok(data) 35 | .setMeta( 36 | new ResponseMeta().setPagination(new Pagination(pageNum, totalElements, totalPages))); 37 | } 38 | 39 | public static Response err(ErrorMessage err) { 40 | return new Response().setStatus(ResponseStatus.error).setErr(err); 41 | } 42 | 43 | public static Response err(String code, String reason) { 44 | return new Response().setStatus(ResponseStatus.error).setErr(new ErrorMessage(code, reason)); 45 | } 46 | 47 | public enum ResponseStatus { 48 | ok, 49 | error 50 | } 51 | 52 | @Data 53 | @JsonInclude(Include.NON_NULL) 54 | public static class ResponseMeta { 55 | 56 | private Pagination pagination; 57 | } 58 | 59 | @Data 60 | @NoArgsConstructor 61 | @JsonInclude(Include.NON_NULL) 62 | public static class Pagination { 63 | 64 | private static final Pagination EMPTY = new Pagination(0, 0L, 0); 65 | public static String PAGE_NUMBER = "pageNum"; 66 | public static String PAGE_SIZE = "pageSize"; 67 | public static String ID_OFFSET = "idOffset"; 68 | public static String ASC = "asc"; 69 | public static String ORDER_BY = "orderBy"; 70 | private Integer pageNum; 71 | private Long totalElements; 72 | private Integer totalPages; 73 | private String pageStart; 74 | private String nextPageStart; 75 | 76 | public Pagination(Integer pageNum, Long totalElements, Integer totalPages) { 77 | this.pageNum = pageNum; 78 | this.totalElements = totalElements; 79 | this.totalPages = totalPages; 80 | } 81 | 82 | public Pagination(String pageStart, String nextPageStart) { 83 | this.pageStart = pageStart; 84 | this.nextPageStart = nextPageStart; 85 | } 86 | 87 | public static Pagination empty() { 88 | return EMPTY; 89 | } 90 | 91 | public static Pagination singlePage(long totalElements) { 92 | return new Pagination(0, totalElements, 0); 93 | } 94 | 95 | public static Pagination singlePage(int totalElements) { 96 | return new Pagination(0, (long) totalElements, 0); 97 | } 98 | } 99 | 100 | @Data 101 | @AllArgsConstructor 102 | public static class ErrorMessage { 103 | 104 | private String code; 105 | 106 | private String reason; 107 | 108 | @Nullable private String exception; 109 | 110 | public ErrorMessage(String code, String reason) { 111 | this.code = code; 112 | this.reason = reason; 113 | this.exception = null; 114 | } 115 | 116 | public ErrorMessage(String code, String reason, @Nullable Throwable t) { 117 | this.code = code; 118 | this.reason = reason; 119 | if (t != null) { 120 | this.exception = ExceptionUtils.getStackTrace(t); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Contributors][contributors-shield]][contributors-url] 2 | [![Forks][forks-shield]][forks-url] 3 | [![Stargazers][stars-shield]][stars-url] 4 | [![Issues][issues-shield]][issues-url] 5 | [![MIT License][license-shield]][license-url] 6 | 7 | 8 |
9 |

10 | 11 | Logo 12 | 13 | 14 |

15 | Demo 16 | · 17 | 更多项目 18 | · 19 | 参考资料 20 |

21 |

22 | 23 | 24 | 25 | # ms-java-common-tools 26 | 27 | # Usage 28 | 29 | ## IDE 30 | 31 | 安装 google-java-format 和 lombok 插件;在配置中 Editor | Code Style | scheme 中导入 `conf/intellij-java-google-style.xml` 风格配置。 32 | 33 | ## Publish 34 | 35 | 此发布指将 tools/ 下公共库发布到 Maven Central 等仓库,参考 [Bintray](https://reflectoring.io/guide-publishing-to-bintray-with-gradle/)。 36 | 37 | ```sh 38 | $ ./gradlew bintrayUpload -Dbintray.user= -Dbintray.key= 39 | ``` 40 | 41 | # About 42 | 43 | 44 | 45 | ## Roadmap 46 | 47 | See the [open issues](https://github.com/wx-chevalier/ms-java-commons/issues) for a list of proposed features (and known issues). 48 | 49 | 50 | 51 | ## Contributing 52 | 53 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 54 | 55 | 1. Fork the Project 56 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 57 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 58 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 59 | 5. Open a Pull Request 60 | 61 | 62 | 63 | ## License 64 | 65 | Distributed under the MIT License. See `LICENSE` for more information. 66 | 67 | 68 | 69 | ## Acknowledgements 70 | 71 | - [Awesome-Lists](https://github.com/wx-chevalier/Awesome-Lists): 📚 Guide to Galaxy, curated, worthy and up-to-date links/reading list for ITCS-Coding/Algorithm/SoftwareArchitecture/AI. 💫 ITCS-编程/算法/软件架构/人工智能等领域的文章/书籍/资料/项目链接精选。 72 | 73 | - [Awesome-CS-Books](https://github.com/wx-chevalier/Awesome-CS-Books): :books: Awesome CS Books/Series(.pdf by git lfs) Warehouse for Geeks, ProgrammingLanguage, SoftwareEngineering, Web, AI, ServerSideApplication, Infrastructure, FE etc. :dizzy: 优秀计算机科学与技术领域相关的书籍归档。 74 | 75 | - [Hutool #Project#](https://www.hutool.cn/): A set of tools that keep Java sweet. 76 | 77 | - [freedom](https://github.com/8treenet/freedom): freedom 是一个基于六边形架构的框架,可以支撑充血的领域模型范式。 78 | 79 | ## Copyright & More | 延伸阅读 80 | 81 | 笔者所有文章遵循[知识共享 署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh),欢迎转载,尊重版权。您还可以前往 [NGTE Books](https://ng-tech.icu/books/) 主页浏览包含知识体系、编程语言、软件工程、模式与架构、Web 与大前端、服务端开发实践与工程架构、分布式基础架构、人工智能与深度学习、产品运营与创业等多类目的书籍列表: 82 | 83 | [![NGTE Books](https://s2.ax1x.com/2020/01/18/19uXtI.png)](https://ng-tech.icu/books/) 84 | 85 | 86 | 87 | 88 | [contributors-shield]: https://img.shields.io/github/contributors/wx-chevalier/ms-java-commons.svg?style=flat-square 89 | [contributors-url]: https://github.com/wx-chevalier/ms-java-commons/graphs/contributors 90 | [forks-shield]: https://img.shields.io/github/forks/wx-chevalier/ms-java-commons.svg?style=flat-square 91 | [forks-url]: https://github.com/wx-chevalier/ms-java-commons/network/members 92 | [stars-shield]: https://img.shields.io/github/stars/wx-chevalier/ms-java-commons.svg?style=flat-square 93 | [stars-url]: https://github.com/wx-chevalier/ms-java-commons/stargazers 94 | [issues-shield]: https://img.shields.io/github/issues/wx-chevalier/ms-java-commons.svg?style=flat-square 95 | [issues-url]: https://github.com/wx-chevalier/ms-java-commons/issues 96 | [license-shield]: https://img.shields.io/github/license/wx-chevalier/ms-java-commons.svg?style=flat-square 97 | [license-url]: https://github.com/wx-chevalier/ms-java-commons/blob/master/LICENSE.txt 98 | -------------------------------------------------------------------------------- /tools/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.jfrog.artifactory" version "4.17.1" 3 | id "com.jfrog.bintray" version "1.8.5" 4 | id "maven-publish" 5 | } 6 | 7 | dependencies { 8 | api "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}" 9 | api "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}" 10 | api "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}" 11 | api "com.fasterxml.jackson.module:jackson-module-parameter-names:${jacksonVersion}" 12 | api "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${jacksonVersion}" 13 | api "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jacksonVersion}" 14 | api "com.googlecode.libphonenumber:libphonenumber:${libphonenumberVersion}" 15 | 16 | api "org.modelmapper:modelmapper:${modelmapperVersion}" 17 | api "com.github.spullara.mustache.java:compiler:${mustacheJavaCompilerVersion}" 18 | 19 | api "com.google.guava:guava:${guavaVersion}" 20 | api "org.slf4j:slf4j-api:${slf4jVersion}" 21 | } 22 | 23 | test { 24 | useJUnitPlatform() 25 | } 26 | 27 | // run gradle with "-Dsnapshot=true" to automatically append "-SNAPSHOT" to the version 28 | version = '0.0.1' + (Boolean.valueOf(System.getProperty("snapshot")) ? "-SNAPSHOT" : "") 29 | sourceCompatibility = 1.8 30 | 31 | ext{ 32 | bintrayUser = System.getProperty("bintray.user") 33 | bintrayKey = System.getProperty("bintray.key") 34 | buildNumber = System.getProperty("build.number") 35 | } 36 | 37 | 38 | task sourcesJar(type: Jar, dependsOn: classes) { 39 | classifier = 'sources' 40 | from sourceSets.main.allSource 41 | } 42 | 43 | javadoc.failOnError = false 44 | task javadocJar(type: Jar, dependsOn: javadoc) { 45 | classifier = 'javadoc' 46 | from javadoc.destinationDir 47 | } 48 | 49 | artifacts { 50 | archives sourcesJar 51 | archives javadocJar 52 | } 53 | 54 | def pomConfig = { 55 | licenses { 56 | license { 57 | name "The Apache Software License, Version 2.0" 58 | url "http://www.apache.org/licenses/LICENSE-2.0.txt" 59 | distribution "repo" 60 | } 61 | } 62 | developers { 63 | developer { 64 | id "wx-chevalier" 65 | name "Chevalier" 66 | email "384924552@qq.com" 67 | } 68 | } 69 | 70 | scm { 71 | url "https://github.com/wx-chevalier/msjc-utils" 72 | } 73 | } 74 | 75 | publishing { 76 | publications { 77 | mavenPublication(MavenPublication) { 78 | from components.java 79 | artifact sourcesJar { 80 | classifier "sources" 81 | } 82 | artifact javadocJar { 83 | classifier "javadoc" 84 | } 85 | groupId 'cn.msjc.utils' 86 | artifactId 'msjc-utils' 87 | version project.version 88 | pom.withXml { 89 | def root = asNode() 90 | root.appendNode('description', 'Java Commons & Boilerplates') 91 | root.appendNode('name', 'ms-java-common') 92 | root.appendNode('url', 'https://github.com/wx-chevalier/msjc-utils') 93 | root.children().last() + pomConfig 94 | } 95 | } 96 | } 97 | } 98 | 99 | artifactory { 100 | contextUrl = 'http://oss.jfrog.org' 101 | publish { 102 | repository { 103 | repoKey = 'oss-snapshot-local' 104 | username = bintrayUser 105 | password = bintrayKey 106 | } 107 | defaults { 108 | publications('mavenPublication') 109 | publishArtifacts = true 110 | publishPom = true 111 | properties = [ 112 | 'build.number': buildNumber, 113 | 'build.name': 'msjc-utils' 114 | ] 115 | } 116 | } 117 | resolve { 118 | repoKey = 'jcenter' 119 | } 120 | clientConfig.info.setBuildNumber(buildNumber) 121 | clientConfig.info.setBuildName('msjc-utils') 122 | } 123 | 124 | bintray { 125 | user = bintrayUser 126 | key = bintrayKey 127 | publications = ['mavenPublication'] 128 | 129 | pkg { 130 | userOrg = 'ms-java-commons' 131 | repo = 'msjc-utils' 132 | name = 'msjc-utils' 133 | licenses = ['Apache-2.0'] 134 | vcsUrl = 'https://github.com/wx-chevalier/msjc-utils' 135 | version { 136 | name = project.version 137 | desc = "build ${buildNumber}" 138 | released = new Date() 139 | gpg { 140 | sign = true 141 | } 142 | } 143 | } 144 | 145 | publish = true 146 | } 147 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/vim,macos,emacs,gradle,intellij+all 2 | # Edit at https://www.gitignore.io/?templates=vim,macos,emacs,gradle,intellij+all 3 | 4 | ### Emacs ### 5 | # -*- mode: gitignore; -*- 6 | *~ 7 | \#*\# 8 | /.emacs.desktop 9 | /.emacs.desktop.lock 10 | *.elc 11 | auto-save-list 12 | tramp 13 | .\#* 14 | 15 | # Org-mode 16 | .org-id-locations 17 | *_archive 18 | 19 | # flymake-mode 20 | *_flymake.* 21 | 22 | # eshell files 23 | /eshell/history 24 | /eshell/lastdir 25 | 26 | # elpa packages 27 | /elpa/ 28 | 29 | # reftex files 30 | *.rel 31 | 32 | # AUCTeX auto folder 33 | /auto/ 34 | 35 | # cask packages 36 | .cask/ 37 | dist/ 38 | 39 | # Flycheck 40 | flycheck_*.el 41 | 42 | # server auth directory 43 | /server/ 44 | 45 | # projectiles files 46 | .projectile 47 | 48 | # directory configuration 49 | .dir-locals.el 50 | 51 | # network security 52 | /network-security.data 53 | 54 | 55 | ### Intellij+all ### 56 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 57 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 58 | 59 | # User-specific stuff 60 | .idea/**/workspace.xml 61 | .idea/**/tasks.xml 62 | .idea/**/usage.statistics.xml 63 | .idea/**/dictionaries 64 | .idea/**/shelf 65 | 66 | # Generated files 67 | .idea/**/contentModel.xml 68 | 69 | # Sensitive or high-churn files 70 | .idea/**/dataSources/ 71 | .idea/**/dataSources.ids 72 | .idea/**/dataSources.local.xml 73 | .idea/**/sqlDataSources.xml 74 | .idea/**/dynamic.xml 75 | .idea/**/uiDesigner.xml 76 | .idea/**/dbnavigator.xml 77 | 78 | # Gradle 79 | .idea/**/gradle.xml 80 | .idea/**/libraries 81 | 82 | # Gradle and Maven with auto-import 83 | # When using Gradle or Maven with auto-import, you should exclude module files, 84 | # since they will be recreated, and may cause churn. Uncomment if using 85 | # auto-import. 86 | # .idea/modules.xml 87 | # .idea/*.iml 88 | # .idea/modules 89 | # *.iml 90 | # *.ipr 91 | 92 | # CMake 93 | cmake-build-*/ 94 | 95 | # Mongo Explorer plugin 96 | .idea/**/mongoSettings.xml 97 | 98 | # File-based project format 99 | *.iws 100 | 101 | # IntelliJ 102 | out/ 103 | 104 | # mpeltonen/sbt-idea plugin 105 | .idea_modules/ 106 | 107 | # JIRA plugin 108 | atlassian-ide-plugin.xml 109 | 110 | # Cursive Clojure plugin 111 | .idea/replstate.xml 112 | 113 | # Crashlytics plugin (for Android Studio and IntelliJ) 114 | com_crashlytics_export_strings.xml 115 | crashlytics.properties 116 | crashlytics-build.properties 117 | fabric.properties 118 | 119 | # Editor-based Rest Client 120 | .idea/httpRequests 121 | 122 | # Android studio 3.1+ serialized cache file 123 | .idea/caches/build_file_checksums.ser 124 | 125 | ### Intellij+all Patch ### 126 | # Ignores the whole .idea folder and all .iml files 127 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 128 | 129 | .idea/ 130 | 131 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 132 | 133 | *.iml 134 | modules.xml 135 | .idea/misc.xml 136 | *.ipr 137 | 138 | # Sonarlint plugin 139 | .idea/sonarlint 140 | 141 | ### macOS ### 142 | # General 143 | .DS_Store 144 | .AppleDouble 145 | .LSOverride 146 | 147 | # Icon must end with two \r 148 | Icon 149 | 150 | # Thumbnails 151 | ._* 152 | 153 | # Files that might appear in the root of a volume 154 | .DocumentRevisions-V100 155 | .fseventsd 156 | .Spotlight-V100 157 | .TemporaryItems 158 | .Trashes 159 | .VolumeIcon.icns 160 | .com.apple.timemachine.donotpresent 161 | 162 | # Directories potentially created on remote AFP share 163 | .AppleDB 164 | .AppleDesktop 165 | Network Trash Folder 166 | Temporary Items 167 | .apdisk 168 | 169 | ### Vim ### 170 | # Swap 171 | [._]*.s[a-v][a-z] 172 | [._]*.sw[a-p] 173 | [._]s[a-rt-v][a-z] 174 | [._]ss[a-gi-z] 175 | [._]sw[a-p] 176 | 177 | # Session 178 | Session.vim 179 | Sessionx.vim 180 | 181 | # Temporary 182 | .netrwhist 183 | # Auto-generated tag files 184 | tags 185 | # Persistent undo 186 | [._]*.un~ 187 | 188 | ### Gradle ### 189 | .gradle 190 | build/ 191 | 192 | # Ignore Gradle GUI config 193 | gradle-app.setting 194 | 195 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 196 | !gradle-wrapper.jar 197 | 198 | # Cache of project 199 | .gradletasknamecache 200 | 201 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 202 | # gradle/wrapper/gradle-wrapper.properties 203 | 204 | ### Gradle Patch ### 205 | **/build/ 206 | 207 | # End of https://www.gitignore.io/api/vim,macos,emacs,gradle,intellij+all 208 | 209 | LOG_FILE_IS_UNDEFINED* 210 | 211 | .settings 212 | bin 213 | .project 214 | .classpath 215 | *.env 216 | 217 | application-*.yml 218 | !application.yml 219 | !application-dev.yml 220 | *.tar.gz -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /tools/src/test/java/cn/msjc/utils/ds/json/JsonUtilsTest.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds.json; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertNull; 7 | import static org.junit.jupiter.api.Assertions.assertThrows; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | import com.fasterxml.jackson.core.type.TypeReference; 11 | import com.fasterxml.jackson.databind.JsonNode; 12 | import com.fasterxml.jackson.databind.node.ArrayNode; 13 | import com.fasterxml.jackson.databind.node.ObjectNode; 14 | import java.io.UncheckedIOException; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import lombok.Data; 19 | import org.junit.jupiter.api.Test; 20 | 21 | class JsonUtilsTest { 22 | 23 | private final JsonUtils JSON = JsonUtils.JSON; 24 | 25 | @Test 26 | void testNewNodeWorks() { 27 | ObjectNode o1 = JSON.newObjectNode(); 28 | assertEquals("{}", JSON.toString(o1)); 29 | 30 | ArrayNode a1 = JSON.newArrayNode(); 31 | assertEquals("[]", JSON.toString(a1)); 32 | 33 | a1.add(o1); 34 | assertEquals("[{}]", JSON.toString(a1)); 35 | 36 | a1.add(JSON.newObjectNode(n -> n.put("hello", "world"))); 37 | assertEquals("[{},{\"hello\":\"world\"}]", JSON.toString(a1)); 38 | 39 | a1.add(JSON.newArrayNode(n -> n.add(42))); 40 | assertEquals("[{},{\"hello\":\"world\"},[42]]", JSON.toString(a1)); 41 | } 42 | 43 | @Test 44 | void testNewJsonString() { 45 | assertEquals("{}", JSON.newJsonObjectString(n -> {})); 46 | assertEquals("{\"hello\":\"world\"}", JSON.newJsonObjectString(n -> n.put("hello", "world"))); 47 | 48 | assertEquals("[]", JSON.newJsonArrayString(n -> {})); 49 | assertEquals("[42,\"hello\"]", JSON.newJsonArrayString(n -> n.add(42).add("hello"))); 50 | } 51 | 52 | @Test 53 | void testNullCheck() { 54 | assertTrue(JSON.isNull(null)); 55 | assertTrue(JSON.isNull(JSON.fromString("null"))); 56 | } 57 | 58 | @Test 59 | void testToJsonNode() { 60 | M m = new M().setM("hello"); 61 | JsonNode mNode = JSON.toJsonNode(m); 62 | M m1 = JSON.fromJsonNode(mNode, M.class); 63 | assertEquals(m, m1); 64 | } 65 | 66 | @SuppressWarnings("ConstantConditions") 67 | @Test 68 | void testFromString() { 69 | M m1 = JSON.fromString("{\"m\":\"hello\"}", M.class); 70 | assertEquals(new M().setM("hello"), m1); 71 | 72 | M m2 = JSON.fromString("null", M.class); 73 | assertNull(m2); 74 | 75 | M m3 = JSON.fromString(null, M.class); 76 | assertNull(m3); 77 | 78 | List ms1 = JSON.fromString("[{\"m\":\"hello\"}]", new TypeReference>() {}); 79 | assertEquals(Collections.singletonList(new M().setM("hello")), ms1); 80 | 81 | List ms2 = JSON.fromString("null", new TypeReference>() {}); 82 | assertNull(ms2); 83 | 84 | List ms3 = JSON.fromString(null, new TypeReference>() {}); 85 | assertNull(ms3); 86 | } 87 | 88 | @Test 89 | void testFromNullReturnsNull() { 90 | assertNull(JSON.fromBytes(null)); 91 | assertNull(JSON.fromBytesOrGet(null, JSON::newObjectNode)); 92 | assertNull(JSON.fromBytes(null, JSON.newObjectNode())); 93 | 94 | assertNull(JSON.fromString(null)); 95 | assertNull(JSON.fromStringOrGet(null, JSON::newObjectNode)); 96 | assertNull(JSON.fromString(null, JSON.newObjectNode())); 97 | 98 | assertNull(JSON.fromJsonNode(null, String.class)); 99 | assertNull(JSON.fromJsonNodeOrGet(null, String.class, () -> "42")); 100 | assertNull(JSON.fromJsonNode(null, String.class, "42")); 101 | 102 | assertNull(JSON.fromJsonNode(null, new TypeReference>() {})); 103 | assertNull( 104 | JSON.fromJsonNodeOrGet(null, new TypeReference>() {}, Collections::emptyList)); 105 | assertNull( 106 | JSON.fromJsonNode(null, new TypeReference>() {}, Collections.emptyList())); 107 | } 108 | 109 | @Test 110 | void testFromXXXThrows() { 111 | String errJsonString = "this is not a json string"; 112 | byte[] errJsonBytes = errJsonString.getBytes(StandardCharsets.UTF_8); 113 | 114 | ArrayNode notMNode = JSON.newArrayNode(); 115 | notMNode.add("this is not M class content"); 116 | 117 | assertThrows(UncheckedIOException.class, () -> JSON.fromString(errJsonString, JsonNode.class)); 118 | assertThrows(IllegalArgumentException.class, () -> JSON.fromJsonNode(notMNode, M.class)); 119 | assertThrows(UncheckedIOException.class, () -> JSON.fromBytes(errJsonBytes)); 120 | } 121 | 122 | @Test 123 | void testWithDefaultIgnoresError() { 124 | String errJsonString = "this is not a json string"; 125 | byte[] errJsonBytes = errJsonString.getBytes(StandardCharsets.UTF_8); 126 | 127 | ObjectNode defaultNode = JSON.newObjectNode(); 128 | 129 | ArrayNode notMNode = JSON.newArrayNode(); 130 | notMNode.add("this is not M class content"); 131 | M defaultM = new M(); 132 | 133 | assertEquals(defaultNode, JSON.fromBytesOrGet(errJsonBytes, () -> defaultNode)); 134 | assertEquals(defaultNode, JSON.fromBytes(errJsonBytes, defaultNode)); 135 | 136 | assertEquals(defaultNode, JSON.fromStringOrGet(errJsonString, () -> defaultNode)); 137 | assertEquals(defaultNode, JSON.fromString(errJsonString, defaultNode)); 138 | 139 | assertEquals(defaultM, JSON.fromJsonNodeOrGet(notMNode, M.class, () -> defaultM)); 140 | assertEquals(defaultM, JSON.fromJsonNode(notMNode, M.class, defaultM)); 141 | 142 | assertEquals( 143 | Collections.emptyList(), 144 | JSON.fromJsonNodeOrGet(notMNode, new TypeReference>() {}, Collections::emptyList)); 145 | assertEquals( 146 | Collections.emptyList(), 147 | JSON.fromJsonNode(notMNode, new TypeReference>() {}, Collections.emptyList())); 148 | } 149 | 150 | @Test 151 | void test_accessors() { 152 | ObjectNode obj = JSON.newObjectNode(n -> n.putPOJO("hello", new M().setM("world"))); 153 | assertTrue(JSON.isNull(obj, "a")); 154 | assertTrue(JSON.isNull(obj, "hello", "a")); 155 | assertFalse(JSON.isNull(obj, "hello", "m")); 156 | assertEquals("world", JSON.getString(obj, "hello", "m").getOrNull()); 157 | } 158 | 159 | @Test 160 | void testVavrModule() { 161 | io.vavr.collection.List s = 162 | JSON.fromString("[\"a\"]", new TypeReference>() {}); 163 | assertNotNull(s); 164 | assertEquals(1, s.size()); 165 | assertEquals("a", s.head()); 166 | } 167 | 168 | @Data 169 | static class M { 170 | private String m; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/system/CmdExecutor.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.system; 2 | 3 | import static com.google.common.base.Preconditions.checkArgument; 4 | 5 | import com.google.common.util.concurrent.FutureCallback; 6 | import com.google.common.util.concurrent.Futures; 7 | import com.google.common.util.concurrent.ListenableFuture; 8 | import com.google.common.util.concurrent.ListeningExecutorService; 9 | import com.google.common.util.concurrent.MoreExecutors; 10 | import com.google.common.util.concurrent.SettableFuture; 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | import java.io.UncheckedIOException; 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.List; 20 | import java.util.Optional; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.concurrent.Executors; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.function.Consumer; 25 | import java.util.function.Function; 26 | import lombok.Getter; 27 | import lombok.extern.slf4j.Slf4j; 28 | import org.jetbrains.annotations.NotNull; 29 | import org.jetbrains.annotations.Nullable; 30 | 31 | @Slf4j 32 | public class CmdExecutor { 33 | 34 | private static final ConcurrentHashMap> processListened = 35 | new ConcurrentHashMap<>(); 36 | 37 | @Getter 38 | private static final ListeningExecutorService executorService = 39 | MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); 40 | 41 | public static Process executeCmd(String... cmd) { 42 | 43 | log.debug("Executing:\n {}", toCmdString(cmd)); 44 | 45 | try { 46 | return Runtime.getRuntime().exec(cmd); 47 | } catch (IOException e) { 48 | throw new UncheckedIOException("Error executing: " + toCmdString(cmd), e); 49 | } 50 | } 51 | 52 | public static boolean checkCommand(Collection cmd, int timeoutSeconds) { 53 | return checkCommand(cmd, timeoutSeconds, false); 54 | } 55 | 56 | public static boolean checkCommand(String[] cmd, int timeoutSeconds) { 57 | return checkCommand(cmd, timeoutSeconds, false); 58 | } 59 | 60 | public static boolean checkCommand( 61 | Collection cmd, int timeoutSeconds, boolean printLogOnError) { 62 | return checkCommand(cmd.toArray(new String[] {}), timeoutSeconds, printLogOnError); 63 | } 64 | 65 | public static boolean checkCommand(String[] cmd, int timeoutSeconds, boolean printLogOnError) { 66 | StringBuilder sb = new StringBuilder(); 67 | 68 | try { 69 | Process process = executeCmd(cmd); 70 | if (printLogOnError) { 71 | listenOnLines( 72 | process, l -> sb.append(" ").append(l), l -> sb.append(" ").append(l), null); 73 | } 74 | 75 | if (!process.waitFor(timeoutSeconds, TimeUnit.SECONDS)) { 76 | process.destroyForcibly(); 77 | } 78 | if (process.exitValue() != 0) { 79 | if (printLogOnError) { 80 | log.warn( 81 | "Check command failed[exitValue={}]: {}\n{}", 82 | process.exitValue(), 83 | toCmdString(cmd), 84 | sb); 85 | } 86 | return false; 87 | } else { 88 | return true; 89 | } 90 | } catch (Throwable t) { 91 | log.warn( 92 | "Check command failed: {} - [{}] {}\n{}", 93 | toCmdString(cmd), 94 | t.getClass(), 95 | t.getMessage(), 96 | sb); 97 | return false; 98 | } 99 | } 100 | 101 | public static ListenableFuture listenOnLines( 102 | Process process, 103 | @Nullable Consumer stdOutLineConsumer, 104 | @Nullable Consumer stdErrLineConsumer, 105 | @Nullable Runnable onExit) { 106 | 107 | Consumer stdOutStreamConsumer = null; 108 | Consumer stdErrStreamConsumer = null; 109 | 110 | Function, Consumer> lineConsumerToStreamConsumer = 111 | (lineConsumer) -> 112 | inputStream -> { 113 | try (BufferedReader reader = 114 | new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { 115 | String line; 116 | while ((line = reader.readLine()) != null) { 117 | lineConsumer.accept(line); 118 | } 119 | } catch (IOException e) { 120 | throw new UncheckedIOException(e); 121 | } 122 | }; 123 | 124 | if (stdOutLineConsumer != null) { 125 | stdOutStreamConsumer = lineConsumerToStreamConsumer.apply(stdOutLineConsumer); 126 | } 127 | if (stdErrLineConsumer != null) { 128 | stdErrStreamConsumer = lineConsumerToStreamConsumer.apply(stdErrLineConsumer); 129 | } 130 | 131 | return listenOnStream(process, stdOutStreamConsumer, stdErrStreamConsumer, onExit); 132 | } 133 | 134 | // 暂时一个进程只能一次 listen 135 | // onExit 会确保在所有 stream consumer 执行完之后、且进程退出时执行 136 | // @returns 会等待进程结束以及所有的 stream consumer 结束 137 | public static ListenableFuture listenOnStream( 138 | Process process, 139 | @Nullable Consumer stdOutStreamConsumer, 140 | @Nullable Consumer stdErrStreamConsumer, 141 | @Nullable Runnable onExit) { 142 | 143 | return processListened.compute( 144 | process, 145 | (k, prevFuture) -> { 146 | checkArgument(prevFuture == null, "Process stream already listened: %s", process); 147 | 148 | List> streamFutures = new ArrayList<>(); 149 | 150 | if (stdOutStreamConsumer != null) { 151 | streamFutures.add( 152 | executorService.submit( 153 | () -> stdOutStreamConsumer.accept(process.getInputStream()))); 154 | } 155 | 156 | if (stdErrStreamConsumer != null) { 157 | streamFutures.add( 158 | executorService.submit( 159 | () -> stdErrStreamConsumer.accept(process.getErrorStream()))); 160 | } 161 | 162 | SettableFuture future = SettableFuture.create(); 163 | 164 | executorService.submit( 165 | () -> { 166 | try { 167 | process.waitFor(); 168 | } catch (InterruptedException e) { 169 | log.info("interrupted", e); 170 | } finally { 171 | processListened.remove(process); 172 | future.set(process); 173 | Futures.allAsList(streamFutures) 174 | .addListener( 175 | () -> Optional.ofNullable(onExit).ifPresent(Runnable::run), 176 | executorService); 177 | } 178 | }); 179 | 180 | List> futures = new ArrayList<>(streamFutures); 181 | futures.add(future); 182 | return Futures.allAsList(futures); 183 | }); 184 | } 185 | 186 | public static void logExecutionError( 187 | ListenableFuture future, String[] cmd, StringBuilder stdErrOutBuilder) { 188 | Futures.addCallback( 189 | future, 190 | new FutureCallback() { 191 | @Override 192 | public void onSuccess(@Nullable Object result) {} 193 | 194 | @Override 195 | public void onFailure(@NotNull Throwable t) { 196 | log.warn( 197 | "Error executing\n {}:\n{}", 198 | toCmdString(cmd), 199 | stdErrOutBuilder.toString().replaceAll("\n", "\n "), 200 | t); 201 | } 202 | }, 203 | executorService); 204 | } 205 | 206 | public static String toCmdString(String[] cmd) { 207 | if (cmd.length == 0) { 208 | return ""; 209 | } 210 | StringBuilder sb = new StringBuilder(); 211 | for (int i = 0; i < cmd.length; i++) { 212 | if (i == 0) { 213 | sb.append(cmd[i]); 214 | } else { 215 | sb.append(" '").append(cmd[i]).append("'"); 216 | } 217 | } 218 | return sb.toString(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/lang/concurrent/FutureUtils.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.lang.concurrent; 2 | 3 | import com.google.common.util.concurrent.FutureCallback; 4 | import com.google.common.util.concurrent.Futures; 5 | import com.google.common.util.concurrent.ListenableFuture; 6 | import com.google.common.util.concurrent.ListenableScheduledFuture; 7 | import com.google.common.util.concurrent.ListeningScheduledExecutorService; 8 | import com.google.common.util.concurrent.SettableFuture; 9 | import io.vavr.concurrent.Future; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.concurrent.Executor; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.TimeoutException; 15 | import java.util.concurrent.atomic.AtomicReference; 16 | import java.util.function.BiConsumer; 17 | import java.util.function.Consumer; 18 | import java.util.function.Supplier; 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.jetbrains.annotations.NotNull; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | @Slf4j 24 | public class FutureUtils { 25 | 26 | public static ListenableFuture sequenceRun( 27 | List>> runners, 28 | Supplier> pause, 29 | Executor executor) { 30 | ListenableFuture future = pause.get(); 31 | for (Supplier> runner : runners) { 32 | future = Futures.transformAsync(future, r -> runner.get(), executor); 33 | future = Futures.transformAsync(future, r -> pause.get(), executor); 34 | } 35 | return future; 36 | } 37 | 38 | // returns: cancel created timeout action & reschedule a new timeout 39 | public static BiConsumer createTimeoutScheduler( 40 | SettableFuture future, ListeningScheduledExecutorService scheduledExecutor) { 41 | 42 | AtomicReference> timeoutFuture = new AtomicReference<>(); 43 | 44 | return (newTimeoutSeconds, onTimeout) -> { 45 | ListenableScheduledFuture prevSchedule = timeoutFuture.get(); 46 | 47 | if (prevSchedule != null) { 48 | prevSchedule.cancel(true); 49 | } 50 | 51 | if (!future.isDone() && newTimeoutSeconds > 0) { 52 | ListenableScheduledFuture newSchedule = 53 | scheduledExecutor.schedule(onTimeout, newTimeoutSeconds, TimeUnit.SECONDS); 54 | 55 | timeoutFuture.set(newSchedule); 56 | 57 | future.addListener(() -> newSchedule.cancel(true), scheduledExecutor); 58 | } 59 | }; 60 | } 61 | 62 | public static BiConsumer createTimeoutReschedulerAcceptsTimeoutMessage( 63 | SettableFuture future, ListeningScheduledExecutorService scheduledExecutor) { 64 | BiConsumer timeoutRescheduler = 65 | FutureUtils.createTimeoutScheduler(future, scheduledExecutor); 66 | 67 | return (timeoutSeconds, timeoutMessage) -> { 68 | timeoutRescheduler.accept( 69 | timeoutSeconds, () -> future.setException(new TimeoutException(timeoutMessage))); 70 | }; 71 | } 72 | 73 | public static void listenOnFuture( 74 | ListenableFuture future, 75 | @Nullable Consumer onSuccess, 76 | @Nullable Consumer onError, 77 | Executor executor) { 78 | Futures.addCallback( 79 | future, 80 | new FutureCallback() { 81 | @Override 82 | public void onSuccess(@Nullable T result) { 83 | Optional.ofNullable(onSuccess).ifPresent(v -> v.accept(result)); 84 | } 85 | 86 | @Override 87 | public void onFailure(@NotNull Throwable t) { 88 | Optional.ofNullable(onError).ifPresent(v -> v.accept(t)); 89 | } 90 | }, 91 | executor); 92 | } 93 | 94 | public static void listenOnAllFutures( 95 | List> futures, 96 | @Nullable Runnable onSuccess, 97 | @Nullable Consumer onError, 98 | Executor executor) { 99 | Futures.addCallback( 100 | Futures.allAsList(futures), 101 | new FutureCallback() { 102 | @Override 103 | public void onSuccess(@Nullable Object result) { 104 | Optional.ofNullable(onSuccess).ifPresent(Runnable::run); 105 | } 106 | 107 | @Override 108 | public void onFailure(@NotNull Throwable t) { 109 | Optional.ofNullable(onError).ifPresent(v -> v.accept(t)); 110 | } 111 | }, 112 | executor); 113 | } 114 | 115 | /** @param command returns isFinished */ 116 | public static ListenableScheduledFuture scheduleAtFixedRateUntilFinished( 117 | Supplier command, 118 | long initialDelay, 119 | long period, 120 | TimeUnit unit, 121 | ListeningScheduledExecutorService executor) { 122 | AtomicReference> future = new AtomicReference<>(); 123 | future.set( 124 | executor.scheduleAtFixedRate( 125 | () -> { 126 | if (command.get()) { 127 | Optional.ofNullable(future.get()) 128 | .filter(v -> !v.isDone()) 129 | .ifPresent(v -> v.cancel(true)); 130 | } 131 | }, 132 | initialDelay, 133 | period, 134 | unit)); 135 | return future.get(); 136 | } 137 | 138 | public static void abortConsequentOnCause( 139 | ListenableFuture cause, ListenableFuture consequent, Executor executor) { 140 | Runnable noAction = () -> {}; 141 | abortConsequentOnCause(cause, consequent, noAction, noAction, executor); 142 | } 143 | 144 | public static void abortConsequentOnCause( 145 | ListenableFuture cause, Future consequent, Executor executor) { 146 | Runnable noAction = () -> {}; 147 | abortConsequentOnCause(cause, consequent, noAction, noAction, executor); 148 | } 149 | 150 | public static void abortConsequentOnCause( 151 | ListenableFuture cause, 152 | ListenableFuture consequent, 153 | Runnable onSuccessCancel, 154 | Runnable onFailureCancel, 155 | Executor executor) { 156 | Futures.addCallback( 157 | cause, 158 | new FutureCallback() { 159 | @Override 160 | public void onSuccess(@Nullable Object result) { 161 | if (!consequent.isDone()) { 162 | consequent.cancel(true); 163 | onSuccessCancel.run(); 164 | } 165 | } 166 | 167 | @Override 168 | public void onFailure(@NotNull Throwable t) { 169 | if (!consequent.isDone()) { 170 | consequent.cancel(true); 171 | onFailureCancel.run(); 172 | } 173 | } 174 | }, 175 | executor); 176 | } 177 | 178 | public static void abortConsequentOnCause( 179 | ListenableFuture cause, 180 | Future consequent, 181 | Runnable onSuccessCancel, 182 | Runnable onFailureCancel, 183 | Executor executor) { 184 | Futures.addCallback( 185 | cause, 186 | new FutureCallback() { 187 | @Override 188 | public void onSuccess(@Nullable Object result) { 189 | if (!consequent.isCompleted()) { 190 | consequent.cancel(true); 191 | onSuccessCancel.run(); 192 | } 193 | } 194 | 195 | @Override 196 | public void onFailure(@NotNull Throwable t) { 197 | if (!consequent.isCompleted()) { 198 | consequent.cancel(true); 199 | onFailureCancel.run(); 200 | } 201 | } 202 | }, 203 | executor); 204 | } 205 | 206 | public static void fastCancelOnResultDone( 207 | ListenableFuture res, ListenableFuture execution, Executor executor) { 208 | res.addListener( 209 | () -> { 210 | if (!execution.isDone()) { 211 | execution.cancel(true); 212 | } 213 | }, 214 | executor); 215 | } 216 | 217 | public static void connectAnyResultAndExecutionFuture( 218 | SettableFuture res, ListenableFuture execution, Executor executor) { 219 | Futures.addCallback( 220 | execution, 221 | new FutureCallback() { 222 | @Override 223 | public void onSuccess(@Nullable Object result) { 224 | if (!res.isDone()) { 225 | res.set(null); 226 | } 227 | } 228 | 229 | @Override 230 | public void onFailure(@NotNull Throwable t) { 231 | if (!res.isDone()) { 232 | res.setException(t); 233 | } 234 | } 235 | }, 236 | executor); 237 | 238 | res.addListener( 239 | () -> { 240 | if (!execution.isDone()) { 241 | execution.cancel(true); 242 | } 243 | }, 244 | executor); 245 | } 246 | 247 | public static void connectResultAndExecutionFuture( 248 | SettableFuture res, ListenableFuture execution, Executor executor) { 249 | 250 | Futures.addCallback( 251 | execution, 252 | new FutureCallback() { 253 | @Override 254 | public void onSuccess(@Nullable T result) { 255 | if (!res.isDone()) { 256 | res.set(result); 257 | } 258 | } 259 | 260 | @Override 261 | public void onFailure(@NotNull Throwable t) { 262 | if (!res.isDone()) { 263 | res.setException(t); 264 | } 265 | } 266 | }, 267 | executor); 268 | 269 | res.addListener( 270 | () -> { 271 | if (!execution.isDone()) { 272 | execution.cancel(true); 273 | } 274 | }, 275 | executor); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 101 | 366 |
367 |

368 | ms-java-commons 369 |

370 |

371 | 更简单地构建云原生微服务应用 372 |

373 |
374 | 375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 | 391 | 392 |
393 |
394 |
-------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/ds/json/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds.json; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.core.type.TypeReference; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.node.ArrayNode; 8 | import com.fasterxml.jackson.databind.node.ObjectNode; 9 | import io.vavr.API; 10 | import io.vavr.collection.Iterator; 11 | import io.vavr.control.Option; 12 | import io.vavr.jackson.datatype.VavrModule; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.io.UncheckedIOException; 16 | import java.util.function.Consumer; 17 | import java.util.function.Function; 18 | import java.util.function.Supplier; 19 | import lombok.AccessLevel; 20 | import lombok.AllArgsConstructor; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | /** 24 | * JSON 工具,对 Jackson ObjectMapper 的浅封装 25 | * 26 | *
    27 | *
  • toXXX 转换为 JSON 对象/JSON 字符串;可以定制 null 对象返回的字符串,默认返回 null 值序列化的结果,也即 "null" 28 | *
  • fromXXX 从 JSON 字符串/JSON 对象转换回指定类型,转换异常抛出 29 | *
  • fromXXX(..., defaultValue) 同 fromXXX,如果异常,则返回 defaultValue 30 | *
  • fromXXXOrGet(..., defaultValueGetter) 同 fromXXX,如果异常,计算 defaultValueGetter 返回 31 | *
  • - from 系列函数,输入 null 返回 null 32 | *
33 | */ 34 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 35 | public final class JsonUtils { 36 | 37 | private final ObjectMapper OM; 38 | 39 | public static final JsonUtils JSON = newJsonTools(new ObjectMapper()); 40 | 41 | public static JsonUtils newJsonTools(ObjectMapper mapper) { 42 | mapper.registerModule(new VavrModule()); 43 | return new JsonUtils(mapper); 44 | } 45 | 46 | public static JsonUtils newJsonTools(Consumer mapperCustomizer) { 47 | ObjectMapper mapper = new ObjectMapper(); 48 | mapperCustomizer.accept(mapper); 49 | return new JsonUtils(mapper); 50 | } 51 | 52 | public ObjectNode newObjectNode() { 53 | return OM.createObjectNode(); 54 | } 55 | 56 | public ObjectNode newObjectNode(Consumer nodeConsumer) { 57 | ObjectNode objectNode = OM.createObjectNode(); 58 | nodeConsumer.accept(objectNode); 59 | return objectNode; 60 | } 61 | 62 | public ArrayNode newArrayNode() { 63 | return OM.createArrayNode(); 64 | } 65 | 66 | public ArrayNode newArrayNode(Consumer nodeConsumer) { 67 | ArrayNode arrayNode = OM.createArrayNode(); 68 | nodeConsumer.accept(arrayNode); 69 | return arrayNode; 70 | } 71 | 72 | public String newJsonObjectString(Consumer nodeConsumer) { 73 | ObjectNode node = newObjectNode(); 74 | nodeConsumer.accept(node); 75 | return toString(node); 76 | } 77 | 78 | public String newJsonArrayString(Consumer nodeConsumer) { 79 | ArrayNode node = newArrayNode(); 80 | nodeConsumer.accept(node); 81 | return toString(node); 82 | } 83 | 84 | // accessors 85 | 86 | public Option get(JsonNode node, Function converter, String... paths) { 87 | for (String path : paths) { 88 | if (isNull(node)) { 89 | break; 90 | } else { 91 | if (node.isPojo()) { 92 | // handing POJO 93 | node = toJsonNode(node); 94 | node = node.get(path); 95 | } else { 96 | node = node.get(path); 97 | } 98 | } 99 | } 100 | if (isNull(node)) { 101 | return API.None(); 102 | } else { 103 | return API.Option(converter.apply(node)); 104 | } 105 | } 106 | 107 | public boolean isNull(JsonNode node) { 108 | return node == null || node.isNull(); 109 | } 110 | 111 | public boolean isNull(JsonNode node, String... paths) { 112 | return get(node, this::isNull, paths).getOrElse(true); 113 | } 114 | 115 | public Option getLong(JsonNode node, String... paths) { 116 | return get(node, JsonNode::asLong, paths); 117 | } 118 | 119 | public Option getInt(JsonNode node, String... paths) { 120 | return get(node, JsonNode::asInt, paths); 121 | } 122 | 123 | public Option getDouble(JsonNode node, String... paths) { 124 | return get(node, JsonNode::asDouble, paths); 125 | } 126 | 127 | public Option getString(JsonNode node, String... paths) { 128 | return get(node, JsonNode::asText, paths); 129 | } 130 | 131 | public JsonNode removeNull(JsonNode node) { 132 | if (node instanceof ObjectNode) { 133 | for (String f : Iterator.ofAll(node.fieldNames()).toList()) { 134 | JsonNode subNode = node.get(f); 135 | if (subNode.isNull()) { 136 | ((ObjectNode) node).remove(f); 137 | } 138 | } 139 | } 140 | return node; 141 | } 142 | 143 | // converters 144 | 145 | public JsonNode toJsonNode(Object object) { 146 | return OM.valueToTree(object); 147 | } 148 | 149 | // node == null -> null 150 | @Nullable 151 | public T fromJsonNode(@Nullable JsonNode node, Class clazz) { 152 | return OM.convertValue(node, clazz); 153 | } 154 | 155 | // ignore error and calculate default value 156 | @Nullable 157 | public T fromJsonNodeOrGet( 158 | @Nullable JsonNode node, Class clazz, Supplier defaultValGetter) { 159 | try { 160 | return fromJsonNode(node, clazz); 161 | } catch (Throwable t) { 162 | return defaultValGetter.get(); 163 | } 164 | } 165 | 166 | // ignore error and return default value 167 | @Nullable 168 | public T fromJsonNode(@Nullable JsonNode node, Class clazz, T defaultValue) { 169 | return fromJsonNodeOrGet(node, clazz, () -> defaultValue); 170 | } 171 | 172 | // node == null -> null 173 | @Nullable 174 | public T fromJsonNode(@Nullable JsonNode node, TypeReference typeReference) { 175 | return OM.convertValue(node, typeReference); 176 | } 177 | 178 | // ignore error and calculate default value 179 | @Nullable 180 | public T fromJsonNodeOrGet( 181 | @Nullable JsonNode node, TypeReference typeReference, Supplier defaultValGetter) { 182 | try { 183 | return fromJsonNode(node, typeReference); 184 | } catch (Throwable t) { 185 | return defaultValGetter.get(); 186 | } 187 | } 188 | 189 | // ignore error and return default value 190 | @Nullable 191 | public T fromJsonNode( 192 | @Nullable JsonNode node, TypeReference typeReference, T defaultValue) { 193 | return fromJsonNodeOrGet(node, typeReference, () -> defaultValue); 194 | } 195 | 196 | @Nullable 197 | public JsonNode fromBytes(@Nullable byte[] bytes) { 198 | if (bytes == null) { 199 | return null; 200 | } 201 | try { 202 | return OM.readTree(bytes); 203 | } catch (IOException e) { 204 | throw new UncheckedIOException(e); 205 | } 206 | } 207 | 208 | public JsonNode fromBytesOrGet(byte[] bytes, Supplier defaultValueGetter) { 209 | try { 210 | return fromBytes(bytes); 211 | } catch (Throwable t) { 212 | return defaultValueGetter.get(); 213 | } 214 | } 215 | 216 | public JsonNode fromBytes(byte[] bytes, JsonNode defaultValue) { 217 | return fromBytesOrGet(bytes, () -> defaultValue); 218 | } 219 | 220 | public JsonNode fromString(String data) { 221 | if (data == null) { 222 | return null; 223 | } 224 | try { 225 | return OM.readTree(data); 226 | } catch (IOException e) { 227 | throw new UncheckedIOException(e); 228 | } 229 | } 230 | 231 | public JsonNode fromStringOrGet(String data, Supplier defaultValueGetter) { 232 | try { 233 | return fromString(data); 234 | } catch (Throwable t) { 235 | return defaultValueGetter.get(); 236 | } 237 | } 238 | 239 | public JsonNode fromString(String data, JsonNode defaultValue) { 240 | return fromStringOrGet(data, () -> defaultValue); 241 | } 242 | 243 | @Nullable 244 | public V fromString(String json, TypeReference typeReference) { 245 | if (json == null) { 246 | return null; 247 | } 248 | try { 249 | return OM.readValue(json, typeReference); 250 | } catch (IOException e) { 251 | throw new UncheckedIOException(e); 252 | } 253 | } 254 | 255 | @Nullable 256 | public V fromString(String json, Class clazz) { 257 | if (json == null) { 258 | return null; 259 | } 260 | try { 261 | return OM.readValue(json, clazz); 262 | } catch (IOException e) { 263 | throw new UncheckedIOException(e); 264 | } 265 | } 266 | 267 | public V fromString(String json, TypeReference typeReference, V defaultValue) { 268 | return fromStringOrGet(json, typeReference, () -> defaultValue); 269 | } 270 | 271 | public V fromString(String json, Class clazz, V defaultValue) { 272 | return fromStringOrGet(json, clazz, () -> defaultValue); 273 | } 274 | 275 | public V fromStringOrGet( 276 | String json, TypeReference typeReference, Supplier defaultValueGetter) { 277 | try { 278 | return OM.readValue(json, typeReference); 279 | } catch (Exception e) { 280 | return defaultValueGetter.get(); 281 | } 282 | } 283 | 284 | public V fromStringOrGet(String json, Class clazz, Supplier defaultValueGetter) { 285 | try { 286 | return OM.readValue(json, clazz); 287 | } catch (Exception e) { 288 | return defaultValueGetter.get(); 289 | } 290 | } 291 | 292 | @Nullable 293 | public V fromInputStream(InputStream inputStream, TypeReference typeReference) { 294 | if (inputStream == null) { 295 | return null; 296 | } 297 | try { 298 | return OM.readValue(inputStream, typeReference); 299 | } catch (IOException e) { 300 | throw new UncheckedIOException(e); 301 | } 302 | } 303 | 304 | public String toStringOrGetNullString(Object object, Supplier nullStringGetter) { 305 | if (object == null) { 306 | return nullStringGetter.get(); 307 | } 308 | return toString(object); 309 | } 310 | 311 | public String toString(Object object, String nullString) { 312 | return toStringOrGetNullString(object, () -> nullString); 313 | } 314 | 315 | public String toString(Object object) { 316 | try { 317 | return OM.writeValueAsString(object); 318 | } catch (JsonProcessingException e) { 319 | throw new UncheckedIOException(e); 320 | } 321 | } 322 | 323 | public String toString(JsonNode node) { 324 | try { 325 | return OM.writeValueAsString(node); 326 | } catch (JsonProcessingException e) { 327 | throw new UncheckedIOException(e); 328 | } 329 | } 330 | 331 | public byte[] toBytes(JsonNode node) { 332 | try { 333 | return OM.writeValueAsBytes(node); 334 | } catch (JsonProcessingException e) { 335 | throw new UncheckedIOException(e); 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /config/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 58 | 61 | 62 | 63 | 64 | 65 | 68 | 69 | 70 | 72 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 125 | 126 | 127 | 129 | 130 | 131 | 132 | 134 | 135 | 136 | 137 | 139 | 140 | 141 | 142 | 144 | 145 | 146 | 147 | 149 | 150 | 151 | 152 | 153 | 155 | 156 | 157 | 158 | 160 | 161 | 162 | 163 | 165 | 166 | 167 | 168 | 169 | 171 | 173 | 175 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 201 | 202 | 203 | 204 | 205 | 206 | 209 | 210 | 211 | 212 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 226 | 227 | 228 | 229 | 230 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /tools/src/main/java/cn/msjc/utils/ds/date/LocalDateTimeUtil.java: -------------------------------------------------------------------------------- 1 | package cn.msjc.utils.ds.date; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import cn.hutool.core.date.DateTime; 5 | import cn.hutool.core.date.TemporalAccessorUtil; 6 | import cn.hutool.core.date.TemporalUtil; 7 | import cn.hutool.core.util.ObjectUtil; 8 | import cn.hutool.core.util.ReUtil; 9 | import cn.hutool.core.util.StrUtil; 10 | import java.time.Duration; 11 | import java.time.Instant; 12 | import java.time.LocalDate; 13 | import java.time.LocalDateTime; 14 | import java.time.LocalTime; 15 | import java.time.Period; 16 | import java.time.ZoneId; 17 | import java.time.ZonedDateTime; 18 | import java.time.format.DateTimeFormatter; 19 | import java.time.format.DateTimeFormatterBuilder; 20 | import java.time.temporal.ChronoField; 21 | import java.time.temporal.ChronoUnit; 22 | import java.time.temporal.Temporal; 23 | import java.time.temporal.TemporalAccessor; 24 | import java.time.temporal.TemporalUnit; 25 | import java.util.Date; 26 | import java.util.TimeZone; 27 | 28 | /** JDK8+中的{@link LocalDateTime} 工具类封装 */ 29 | public class LocalDateTimeUtil { 30 | 31 | /** 32 | * 当前时间,默认时区 33 | * 34 | * @return {@link LocalDateTime} 35 | */ 36 | public static LocalDateTime now() { 37 | return LocalDateTime.now(); 38 | } 39 | 40 | /** 41 | * {@link Instant}转{@link LocalDateTime},使用默认时区 42 | * 43 | * @param instant {@link Instant} 44 | * @return {@link LocalDateTime} 45 | */ 46 | public static LocalDateTime of(Instant instant) { 47 | return of(instant, ZoneId.systemDefault()); 48 | } 49 | 50 | /** 51 | * {@link Instant}转{@link LocalDateTime},使用UTC时区 52 | * 53 | * @param instant {@link Instant} 54 | * @return {@link LocalDateTime} 55 | */ 56 | public static LocalDateTime ofUTC(Instant instant) { 57 | return of(instant, ZoneId.of("UTC")); 58 | } 59 | 60 | /** 61 | * {@link ZonedDateTime}转{@link LocalDateTime} 62 | * 63 | * @param zonedDateTime {@link ZonedDateTime} 64 | * @return {@link LocalDateTime} 65 | */ 66 | public static LocalDateTime of(ZonedDateTime zonedDateTime) { 67 | if (null == zonedDateTime) { 68 | return null; 69 | } 70 | return zonedDateTime.toLocalDateTime(); 71 | } 72 | 73 | /** 74 | * {@link Instant}转{@link LocalDateTime} 75 | * 76 | * @param instant {@link Instant} 77 | * @param zoneId 时区 78 | * @return {@link LocalDateTime} 79 | */ 80 | public static LocalDateTime of(Instant instant, ZoneId zoneId) { 81 | if (null == instant) { 82 | return null; 83 | } 84 | 85 | return LocalDateTime.ofInstant( 86 | instant, ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault())); 87 | } 88 | 89 | /** 90 | * {@link Instant}转{@link LocalDateTime} 91 | * 92 | * @param instant {@link Instant} 93 | * @param timeZone 时区 94 | * @return {@link LocalDateTime} 95 | */ 96 | public static LocalDateTime of(Instant instant, TimeZone timeZone) { 97 | if (null == instant) { 98 | return null; 99 | } 100 | 101 | return of(instant, ObjectUtil.defaultIfNull(timeZone, TimeZone.getDefault()).toZoneId()); 102 | } 103 | 104 | /** 105 | * 毫秒转{@link LocalDateTime},使用默认时区 106 | * 107 | *

注意:此方法使用默认时区,如果非UTC,会产生时间偏移 108 | * 109 | * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 110 | * @return {@link LocalDateTime} 111 | */ 112 | public static LocalDateTime of(long epochMilli) { 113 | return of(Instant.ofEpochMilli(epochMilli)); 114 | } 115 | 116 | /** 117 | * 毫秒转{@link LocalDateTime},使用UTC时区 118 | * 119 | * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 120 | * @return {@link LocalDateTime} 121 | */ 122 | public static LocalDateTime ofUTC(long epochMilli) { 123 | return ofUTC(Instant.ofEpochMilli(epochMilli)); 124 | } 125 | 126 | /** 127 | * 毫秒转{@link LocalDateTime},根据时区不同,结果会产生时间偏移 128 | * 129 | * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 130 | * @param zoneId 时区 131 | * @return {@link LocalDateTime} 132 | */ 133 | public static LocalDateTime of(long epochMilli, ZoneId zoneId) { 134 | return of(Instant.ofEpochMilli(epochMilli), zoneId); 135 | } 136 | 137 | /** 138 | * 毫秒转{@link LocalDateTime},结果会产生时间偏移 139 | * 140 | * @param epochMilli 从1970-01-01T00:00:00Z开始计数的毫秒数 141 | * @param timeZone 时区 142 | * @return {@link LocalDateTime} 143 | */ 144 | public static LocalDateTime of(long epochMilli, TimeZone timeZone) { 145 | return of(Instant.ofEpochMilli(epochMilli), timeZone); 146 | } 147 | 148 | /** 149 | * {@link Date}转{@link LocalDateTime},使用默认时区 150 | * 151 | * @param date Date对象 152 | * @return {@link LocalDateTime} 153 | */ 154 | public static LocalDateTime of(Date date) { 155 | if (null == date) { 156 | return null; 157 | } 158 | 159 | if (date instanceof DateTime) { 160 | return of(date.toInstant(), ((DateTime) date).getZoneId()); 161 | } 162 | return of(date.toInstant()); 163 | } 164 | 165 | /** 166 | * {@link TemporalAccessor}转{@link LocalDateTime},使用默认时区 167 | * 168 | * @param temporalAccessor {@link TemporalAccessor} 169 | * @return {@link LocalDateTime} 170 | */ 171 | public static LocalDateTime of(TemporalAccessor temporalAccessor) { 172 | if (null == temporalAccessor) { 173 | return null; 174 | } 175 | 176 | if (temporalAccessor instanceof LocalDate) { 177 | return ((LocalDate) temporalAccessor).atStartOfDay(); 178 | } 179 | 180 | return LocalDateTime.of( 181 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR), 182 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR), 183 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH), 184 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.HOUR_OF_DAY), 185 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.MINUTE_OF_HOUR), 186 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.SECOND_OF_MINUTE), 187 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.NANO_OF_SECOND)); 188 | } 189 | 190 | /** 191 | * {@link TemporalAccessor}转{@link LocalDate},使用默认时区 192 | * 193 | * @param temporalAccessor {@link TemporalAccessor} 194 | * @return {@link LocalDate} 195 | * @since 5.3.10 196 | */ 197 | public static LocalDate ofDate(TemporalAccessor temporalAccessor) { 198 | if (null == temporalAccessor) { 199 | return null; 200 | } 201 | 202 | if (temporalAccessor instanceof LocalDateTime) { 203 | return ((LocalDateTime) temporalAccessor).toLocalDate(); 204 | } 205 | 206 | return LocalDate.of( 207 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.YEAR), 208 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.MONTH_OF_YEAR), 209 | TemporalAccessorUtil.get(temporalAccessor, ChronoField.DAY_OF_MONTH)); 210 | } 211 | 212 | /** 213 | * 解析日期时间字符串为{@link LocalDateTime},仅支持yyyy-MM-dd'T'HH:mm:ss格式,例如:2007-12-03T10:15:30 214 | * 215 | * @param text 日期时间字符串 216 | * @return {@link LocalDateTime} 217 | */ 218 | public static LocalDateTime parse(CharSequence text) { 219 | return parse(text, (DateTimeFormatter) null); 220 | } 221 | 222 | /** 223 | * 解析日期时间字符串为{@link LocalDateTime},格式支持日期时间、日期、时间 224 | * 225 | * @param text 日期时间字符串 226 | * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter} 227 | * @return {@link LocalDateTime} 228 | */ 229 | public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) { 230 | if (null == text) { 231 | return null; 232 | } 233 | if (null == formatter) { 234 | return LocalDateTime.parse(text); 235 | } 236 | 237 | return of(formatter.parse(text)); 238 | } 239 | 240 | /** 241 | * 解析日期时间字符串为{@link LocalDateTime} 242 | * 243 | * @param text 日期时间字符串 244 | * @param format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS 245 | * @return {@link LocalDateTime} 246 | */ 247 | public static LocalDateTime parse(CharSequence text, String format) { 248 | if (null == text) { 249 | return null; 250 | } 251 | 252 | DateTimeFormatter formatter = null; 253 | if (StrUtil.isNotBlank(format)) { 254 | // 修复yyyyMMddHHmmssSSS格式不能解析的问题 255 | // fix issue#1082 256 | // see 257 | // https://stackoverflow.com/questions/22588051/is-java-time-failing-to-parse-fraction-of-second 258 | // jdk8 bug at: https://bugs.openjdk.java.net/browse/JDK-8031085 259 | if (StrUtil.startWithIgnoreEquals(format, DatePattern.PURE_DATETIME_PATTERN)) { 260 | final String fraction = StrUtil.removePrefix(format, DatePattern.PURE_DATETIME_PATTERN); 261 | if (ReUtil.isMatch("[S]{1,2}", fraction)) { 262 | // 将yyyyMMddHHmmssS、yyyyMMddHHmmssSS的日期统一替换为yyyyMMddHHmmssSSS格式,用0补 263 | text += StrUtil.repeat('0', 3 - fraction.length()); 264 | } 265 | formatter = 266 | new DateTimeFormatterBuilder() 267 | .appendPattern(DatePattern.PURE_DATETIME_PATTERN) 268 | .appendValue(ChronoField.MILLI_OF_SECOND, 3) 269 | .toFormatter(); 270 | } else { 271 | formatter = DateTimeFormatter.ofPattern(format); 272 | } 273 | } 274 | 275 | return parse(text, formatter); 276 | } 277 | 278 | /** 279 | * 解析日期时间字符串为{@link LocalDate},仅支持yyyy-MM-dd'T'HH:mm:ss格式,例如:2007-12-03T10:15:30 280 | * 281 | * @param text 日期时间字符串 282 | * @return {@link LocalDate} 283 | * @since 5.3.10 284 | */ 285 | public static LocalDate parseDate(CharSequence text) { 286 | return parseDate(text, (DateTimeFormatter) null); 287 | } 288 | 289 | /** 290 | * 解析日期时间字符串为{@link LocalDate},格式支持日期 291 | * 292 | * @param text 日期时间字符串 293 | * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter} 294 | * @return {@link LocalDate} 295 | * @since 5.3.10 296 | */ 297 | public static LocalDate parseDate(CharSequence text, DateTimeFormatter formatter) { 298 | if (null == text) { 299 | return null; 300 | } 301 | if (null == formatter) { 302 | return LocalDate.parse(text); 303 | } 304 | 305 | return ofDate(formatter.parse(text)); 306 | } 307 | 308 | /** 309 | * 解析日期字符串为{@link LocalDate} 310 | * 311 | * @param text 日期字符串 312 | * @param format 日期格式,类似于yyyy-MM-dd 313 | * @return {@link LocalDateTime} 314 | */ 315 | public static LocalDate parseDate(CharSequence text, String format) { 316 | if (null == text) { 317 | return null; 318 | } 319 | return parseDate(text, DateTimeFormatter.ofPattern(format)); 320 | } 321 | 322 | /** 323 | * 格式化日期时间为yyyy-MM-dd HH:mm:ss格式 324 | * 325 | * @param time {@link LocalDateTime} 326 | * @return 格式化后的字符串 327 | * @since 5.3.11 328 | */ 329 | public static String formatNormal(LocalDateTime time) { 330 | return format(time, DatePattern.NORM_DATETIME_FORMATTER); 331 | } 332 | 333 | /** 334 | * 格式化日期时间为指定格式 335 | * 336 | * @param time {@link LocalDateTime} 337 | * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter} 338 | * @return 格式化后的字符串 339 | */ 340 | public static String format(LocalDateTime time, DateTimeFormatter formatter) { 341 | return TemporalAccessorUtil.format(time, formatter); 342 | } 343 | 344 | /** 345 | * 格式化日期时间为指定格式 346 | * 347 | * @param time {@link LocalDateTime} 348 | * @param format 日期格式,类似于yyyy-MM-dd HH:mm:ss,SSS 349 | * @return 格式化后的字符串 350 | */ 351 | public static String format(LocalDateTime time, String format) { 352 | if (null == time) { 353 | return null; 354 | } 355 | return format(time, DateTimeFormatter.ofPattern(format)); 356 | } 357 | 358 | /** 359 | * 格式化日期时间为yyyy-MM-dd格式 360 | * 361 | * @param date {@link LocalDate} 362 | * @return 格式化后的字符串 363 | * @since 5.3.11 364 | */ 365 | public static String formatNormal(LocalDate date) { 366 | return format(date, DatePattern.NORM_DATE_FORMATTER); 367 | } 368 | 369 | /** 370 | * 格式化日期时间为指定格式 371 | * 372 | * @param date {@link LocalDate} 373 | * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter} 374 | * @return 格式化后的字符串 375 | * @since 5.3.10 376 | */ 377 | public static String format(LocalDate date, DateTimeFormatter formatter) { 378 | return TemporalAccessorUtil.format(date, formatter); 379 | } 380 | 381 | /** 382 | * 格式化日期时间为指定格式 383 | * 384 | * @param date {@link LocalDate} 385 | * @param format 日期格式,类似于yyyy-MM-dd 386 | * @return 格式化后的字符串 387 | * @since 5.3.10 388 | */ 389 | public static String format(LocalDate date, String format) { 390 | if (null == date) { 391 | return null; 392 | } 393 | return format(date, DateTimeFormatter.ofPattern(format)); 394 | } 395 | 396 | /** 397 | * 日期偏移,根据field不同加不同值(偏移会修改传入的对象) 398 | * 399 | * @param time {@link LocalDateTime} 400 | * @param number 偏移量,正数为向后偏移,负数为向前偏移 401 | * @param field 偏移单位,见{@link ChronoField},不能为null 402 | * @return 偏移后的日期时间 403 | */ 404 | public static LocalDateTime offset(LocalDateTime time, long number, TemporalUnit field) { 405 | if (null == time) { 406 | return null; 407 | } 408 | 409 | return time.plus(number, field); 410 | } 411 | 412 | /** 413 | * 获取两个日期的差,如果结束时间早于开始时间,获取结果为负。 414 | * 415 | *

返回结果为{@link Duration}对象,通过调用toXXX方法返回相差单位 416 | * 417 | * @param startTimeInclude 开始时间(包含) 418 | * @param endTimeExclude 结束时间(不包含) 419 | * @return 时间差 {@link Duration}对象 420 | * @see TemporalUtil#between(Temporal, Temporal) 421 | */ 422 | public static Duration between(LocalDateTime startTimeInclude, LocalDateTime endTimeExclude) { 423 | return TemporalUtil.between(startTimeInclude, endTimeExclude); 424 | } 425 | 426 | /** 427 | * 获取两个日期的差,如果结束时间早于开始时间,获取结果为负。 428 | * 429 | *

返回结果为时间差的long值 430 | * 431 | * @param startTimeInclude 开始时间(包括) 432 | * @param endTimeExclude 结束时间(不包括) 433 | * @param unit 时间差单位 434 | * @return 时间差 435 | * @since 5.4.5 436 | */ 437 | public static long between( 438 | LocalDateTime startTimeInclude, LocalDateTime endTimeExclude, ChronoUnit unit) { 439 | return TemporalUtil.between(startTimeInclude, endTimeExclude, unit); 440 | } 441 | 442 | /** 443 | * 获取两个日期的表象时间差,如果结束时间早于开始时间,获取结果为负。 444 | * 445 | *

比如2011年2月1日,和2021年8月11日,日相差了10天,月相差6月 446 | * 447 | * @param startTimeInclude 开始时间(包括) 448 | * @param endTimeExclude 结束时间(不包括) 449 | * @return 时间差 450 | * @since 5.4.5 451 | */ 452 | public static Period betweenPeriod(LocalDate startTimeInclude, LocalDate endTimeExclude) { 453 | return Period.between(startTimeInclude, endTimeExclude); 454 | } 455 | 456 | /** 457 | * 修改为一天的开始时间,例如:2020-02-02 00:00:00,000 458 | * 459 | * @param time 日期时间 460 | * @return 一天的开始时间 461 | */ 462 | public static LocalDateTime beginOfDay(LocalDateTime time) { 463 | return time.with(LocalTime.MIN); 464 | } 465 | 466 | /** 467 | * 修改为一天的结束时间,例如:2020-02-02 23:59:59,999 468 | * 469 | * @param time 日期时间 470 | * @return 一天的结束时间 471 | */ 472 | public static LocalDateTime endOfDay(LocalDateTime time) { 473 | return time.with(LocalTime.MAX); 474 | } 475 | 476 | /** 477 | * {@link TemporalAccessor}转换为 时间戳(从1970-01-01T00:00:00Z开始的毫秒数) 478 | * 479 | * @param temporalAccessor Date对象 480 | * @return {@link Instant}对象 481 | * @since 5.4.1 482 | * @see TemporalAccessorUtil#toEpochMilli(TemporalAccessor) 483 | */ 484 | public static long toEpochMilli(TemporalAccessor temporalAccessor) { 485 | return TemporalAccessorUtil.toEpochMilli(temporalAccessor); 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /config/intellij-java-google-style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 14 | 15 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 56 | 57 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | 72 | 73 | 74 | 92 | 93 | 94 | 98 | 99 | 100 | 101 | 105 | 129 | 130 | 131 | 134 | 135 | 136 | 137 | 140 | 158 | 159 | 160 | 164 | 166 | 167 | 168 | 172 | 174 | 175 | 176 | 180 | 185 | 186 | 187 | 190 | 191 | 192 | 193 | 196 | 197 | 198 | 199 | 202 | 203 | 204 | 205 | 206 |

207 | 208 | 209 | 210 | xmlns:android 211 | 212 | ^$ 213 | 214 | 215 | 216 |
217 |
218 | 219 | 220 | 221 | xmlns:.* 222 | 223 | ^$ 224 | 225 | 226 | BY_NAME 227 | 228 |
229 |
230 | 231 | 232 | 233 | .*:id 234 | 235 | http://schemas.android.com/apk/res/android 236 | 237 | 238 | 239 |
240 |
241 | 242 | 243 | 244 | style 245 | 246 | ^$ 247 | 248 | 249 | 250 |
251 |
252 | 253 | 254 | 255 | .* 256 | 257 | ^$ 258 | 259 | 260 | BY_NAME 261 | 262 |
263 |
264 | 265 | 266 | 267 | .*:.*Style 268 | 269 | http://schemas.android.com/apk/res/android 270 | 271 | 272 | BY_NAME 273 | 274 |
275 |
276 | 277 | 278 | 279 | .*:layout_width 280 | 281 | http://schemas.android.com/apk/res/android 282 | 283 | 284 | 285 |
286 |
287 | 288 | 289 | 290 | .*:layout_height 291 | 292 | http://schemas.android.com/apk/res/android 293 | 294 | 295 | 296 |
297 |
298 | 299 | 300 | 301 | .*:layout_weight 302 | 303 | http://schemas.android.com/apk/res/android 304 | 305 | 306 | 307 |
308 |
309 | 310 | 311 | 312 | .*:layout_margin 313 | 314 | http://schemas.android.com/apk/res/android 315 | 316 | 317 | 318 |
319 |
320 | 321 | 322 | 323 | .*:layout_marginTop 324 | 325 | http://schemas.android.com/apk/res/android 326 | 327 | 328 | 329 |
330 |
331 | 332 | 333 | 334 | .*:layout_marginBottom 335 | 336 | http://schemas.android.com/apk/res/android 337 | 338 | 339 | 340 |
341 |
342 | 343 | 344 | 345 | .*:layout_marginStart 346 | 347 | http://schemas.android.com/apk/res/android 348 | 349 | 350 | 351 |
352 |
353 | 354 | 355 | 356 | .*:layout_marginEnd 357 | 358 | http://schemas.android.com/apk/res/android 359 | 360 | 361 | 362 |
363 |
364 | 365 | 366 | 367 | .*:layout_marginLeft 368 | 369 | http://schemas.android.com/apk/res/android 370 | 371 | 372 | 373 |
374 |
375 | 376 | 377 | 378 | .*:layout_marginRight 379 | 380 | http://schemas.android.com/apk/res/android 381 | 382 | 383 | 384 |
385 |
386 | 387 | 388 | 389 | .*:layout_.* 390 | 391 | http://schemas.android.com/apk/res/android 392 | 393 | 394 | BY_NAME 395 | 396 |
397 |
398 | 399 | 400 | 401 | .*:padding 402 | 403 | http://schemas.android.com/apk/res/android 404 | 405 | 406 | 407 |
408 |
409 | 410 | 411 | 412 | .*:paddingTop 413 | 414 | http://schemas.android.com/apk/res/android 415 | 416 | 417 | 418 |
419 |
420 | 421 | 422 | 423 | .*:paddingBottom 424 | 425 | http://schemas.android.com/apk/res/android 426 | 427 | 428 | 429 |
430 |
431 | 432 | 433 | 434 | .*:paddingStart 435 | 436 | http://schemas.android.com/apk/res/android 437 | 438 | 439 | 440 |
441 |
442 | 443 | 444 | 445 | .*:paddingEnd 446 | 447 | http://schemas.android.com/apk/res/android 448 | 449 | 450 | 451 |
452 |
453 | 454 | 455 | 456 | .*:paddingLeft 457 | 458 | http://schemas.android.com/apk/res/android 459 | 460 | 461 | 462 |
463 |
464 | 465 | 466 | 467 | .*:paddingRight 468 | 469 | http://schemas.android.com/apk/res/android 470 | 471 | 472 | 473 |
474 |
475 | 476 | 477 | 478 | .* 479 | http://schemas.android.com/apk/res/android 480 | 481 | 482 | BY_NAME 483 | 484 |
485 |
486 | 487 | 488 | 489 | .* 490 | http://schemas.android.com/apk/res-auto 491 | 492 | 493 | BY_NAME 494 | 495 |
496 |
497 | 498 | 499 | 500 | .* 501 | http://schemas.android.com/tools 502 | 503 | 504 | BY_NAME 505 | 506 |
507 |
508 | 509 | 510 | 511 | .* 512 | .* 513 | 514 | 515 | BY_NAME 516 | 517 |
518 | 519 | 520 | 521 | 525 | 526 | 527 | 528 | 531 | 543 | 555 | 561 | 568 |