From 4bb729b9d01f01f6bd41f87073f68092f4811066 Mon Sep 17 00:00:00 2001 From: xfe1235 Date: Thu, 30 Jul 2020 23:09:06 +0800 Subject: [PATCH] init --- .gitignore | 31 ++ LICENSE | 201 ++++++++ README.md | 2 + pom.xml | 198 ++++++++ .../club/lemos/common/api/ApiException.java | 37 ++ .../club/lemos/common/api/IErrorCode.java | 18 + .../club/lemos/common/api/IResultCode.java | 24 + src/main/java/club/lemos/common/api/R.java | 205 ++++++++ .../club/lemos/common/api/RException.java | 22 + .../club/lemos/common/api/ResultCode.java | 90 ++++ .../lemos/common/constant/CommonConstant.java | 58 +++ .../lemos/common/constant/RegexConstant.java | 34 ++ .../club/lemos/common/enums/AdminState.java | 18 + .../club/lemos/common/jackson/JsonUtil.java | 149 ++++++ .../lemos/common/mp/base/AdminBaseEntity.java | 41 ++ .../club/lemos/common/mp/base/BaseEntity.java | 20 + .../club/lemos/common/mp/base/FindOne.java | 25 + .../lemos/common/mp/base/TenantEntity.java | 25 + .../lemos/common/mp/base/UpdateFullById.java | 68 +++ .../lemos/common/mp/base/XxBaseMapper.java | 37 ++ .../lemos/common/mp/base/XxServiceImpl.java | 73 +++ .../lemos/common/mp/base/XxSqlInjector.java | 27 ++ .../common/mp/base/XxTenantSqlParser.java | 293 +++++++++++ .../mp/handler/BaseMetaObjectHandler.java | 29 ++ .../JacksonJsonNodeTypeHandler.java | 39 ++ .../java/club/lemos/common/node/BaseNode.java | 31 ++ .../club/lemos/common/node/ForestNode.java | 25 + .../lemos/common/node/ForestNodeManager.java | 64 +++ .../lemos/common/node/ForestNodeMerger.java | 33 ++ .../java/club/lemos/common/node/INode.java | 29 ++ .../java/club/lemos/common/node/NodeTest.java | 27 ++ .../java/club/lemos/common/node/TreeNode.java | 22 + .../lemos/common/support/BeanProperty.java | 11 + .../club/lemos/common/support/SpringUtil.java | 66 +++ .../lemos/common/support/ThreadContext.java | 200 ++++++++ .../support/XssHttpServletRequestWrapper.java | 74 +++ .../club/lemos/common/utils/Base64Util.java | 96 ++++ .../club/lemos/common/utils/BeanUtil.java | 120 +++++ .../lemos/common/utils/CollectionUtil.java | 62 +++ .../club/lemos/common/utils/DateUtil.java | 73 +++ .../club/lemos/common/utils/FuncUtil.java | 29 ++ .../club/lemos/common/utils/MicroUUID.java | 278 +++++++++++ .../club/lemos/common/utils/ObjectUtil.java | 53 ++ .../club/lemos/common/utils/RandomType.java | 11 + .../club/lemos/common/utils/Snowflake.java | 206 ++++++++ .../club/lemos/common/utils/StreamUtil.java | 6 + .../club/lemos/common/utils/StringPool.java | 75 +++ .../club/lemos/common/utils/StringUtil.java | 454 ++++++++++++++++++ .../java/club/lemos/common/utils/WebUtil.java | 97 ++++ .../club/lemos/common/validate/GroupA.java | 4 + .../club/lemos/common/validate/GroupB.java | 4 + .../club/lemos/common/validate/GroupC.java | 4 + .../club/lemos/common/validate/GroupD.java | 4 + 53 files changed, 3922 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/club/lemos/common/api/ApiException.java create mode 100644 src/main/java/club/lemos/common/api/IErrorCode.java create mode 100644 src/main/java/club/lemos/common/api/IResultCode.java create mode 100644 src/main/java/club/lemos/common/api/R.java create mode 100644 src/main/java/club/lemos/common/api/RException.java create mode 100644 src/main/java/club/lemos/common/api/ResultCode.java create mode 100644 src/main/java/club/lemos/common/constant/CommonConstant.java create mode 100644 src/main/java/club/lemos/common/constant/RegexConstant.java create mode 100644 src/main/java/club/lemos/common/enums/AdminState.java create mode 100644 src/main/java/club/lemos/common/jackson/JsonUtil.java create mode 100644 src/main/java/club/lemos/common/mp/base/AdminBaseEntity.java create mode 100644 src/main/java/club/lemos/common/mp/base/BaseEntity.java create mode 100644 src/main/java/club/lemos/common/mp/base/FindOne.java create mode 100644 src/main/java/club/lemos/common/mp/base/TenantEntity.java create mode 100644 src/main/java/club/lemos/common/mp/base/UpdateFullById.java create mode 100644 src/main/java/club/lemos/common/mp/base/XxBaseMapper.java create mode 100644 src/main/java/club/lemos/common/mp/base/XxServiceImpl.java create mode 100644 src/main/java/club/lemos/common/mp/base/XxSqlInjector.java create mode 100644 src/main/java/club/lemos/common/mp/base/XxTenantSqlParser.java create mode 100644 src/main/java/club/lemos/common/mp/handler/BaseMetaObjectHandler.java create mode 100644 src/main/java/club/lemos/common/mp/typehandler/JacksonJsonNodeTypeHandler.java create mode 100644 src/main/java/club/lemos/common/node/BaseNode.java create mode 100644 src/main/java/club/lemos/common/node/ForestNode.java create mode 100644 src/main/java/club/lemos/common/node/ForestNodeManager.java create mode 100644 src/main/java/club/lemos/common/node/ForestNodeMerger.java create mode 100644 src/main/java/club/lemos/common/node/INode.java create mode 100644 src/main/java/club/lemos/common/node/NodeTest.java create mode 100644 src/main/java/club/lemos/common/node/TreeNode.java create mode 100644 src/main/java/club/lemos/common/support/BeanProperty.java create mode 100644 src/main/java/club/lemos/common/support/SpringUtil.java create mode 100644 src/main/java/club/lemos/common/support/ThreadContext.java create mode 100644 src/main/java/club/lemos/common/support/XssHttpServletRequestWrapper.java create mode 100644 src/main/java/club/lemos/common/utils/Base64Util.java create mode 100644 src/main/java/club/lemos/common/utils/BeanUtil.java create mode 100644 src/main/java/club/lemos/common/utils/CollectionUtil.java create mode 100644 src/main/java/club/lemos/common/utils/DateUtil.java create mode 100644 src/main/java/club/lemos/common/utils/FuncUtil.java create mode 100644 src/main/java/club/lemos/common/utils/MicroUUID.java create mode 100644 src/main/java/club/lemos/common/utils/ObjectUtil.java create mode 100644 src/main/java/club/lemos/common/utils/RandomType.java create mode 100644 src/main/java/club/lemos/common/utils/Snowflake.java create mode 100644 src/main/java/club/lemos/common/utils/StreamUtil.java create mode 100644 src/main/java/club/lemos/common/utils/StringPool.java create mode 100644 src/main/java/club/lemos/common/utils/StringUtil.java create mode 100644 src/main/java/club/lemos/common/utils/WebUtil.java create mode 100644 src/main/java/club/lemos/common/validate/GroupA.java create mode 100644 src/main/java/club/lemos/common/validate/GroupB.java create mode 100644 src/main/java/club/lemos/common/validate/GroupC.java create mode 100644 src/main/java/club/lemos/common/validate/GroupD.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a2a3040 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9401f5f --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# jtool +基于 Spring Boot 二次方法封装的 Java工具类库。 \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..579bf30 --- /dev/null +++ b/pom.xml @@ -0,0 +1,198 @@ + + + 4.0.0 + + club.lemos + jtool + 1.0.0-RC1 + jar + jtool + + A set of tools for spring boot + https://github.com/lemos1235/jtool + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + https://github.com/lemos1235/jtool + scm:git:https://github.com/lemos1235/jtool.git + scm:git:https://github.com/lemos1235/jtool.git + + + + The jtool Project Contributors + xfe1235@gmail.com + + + + + 1.8 + ${java.version} + ${java.version} + UTF-8 + 2.3.2.RELEASE + 1.18.12 + 1.9 + 3.3.2 + 3.0.0 + 3.8.0 + 2.22.1 + 3.0.1 + 3.0.1 + 2.8.2 + 1.6 + 0.8.3 + 3.1.0 + 3.8 + 1.6.8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler.version} + + ${java.version} + ${java.version} + ${encoding} + + + + + + + + org.apache.commons + commons-text + ${commons-text.version} + + + org.springframework.boot + spring-boot-starter + ${spring.boot.version} + provided + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + provided + + + org.projectlombok + lombok + ${lombok.version} + provided + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + provided + + + + + + github + + + github + GitHub OWNER Apache Maven Packages + https://maven.pkg.github.com/lemos1235/jtool + + + + + oss + + true + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven.source.version} + + + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven.javadoc.version} + + private + false + + + + package + + jar + + + en_US + UTF-8 + UTF-8 + none + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven.gpg.version} + + + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${maven.staging.version} + true + + oss + https://oss.sonatype.org/ + true + + + + + + + oss + https://oss.sonatype.org/content/repositories/snapshots/ + + + oss + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + \ No newline at end of file diff --git a/src/main/java/club/lemos/common/api/ApiException.java b/src/main/java/club/lemos/common/api/ApiException.java new file mode 100644 index 0000000..7f66be4 --- /dev/null +++ b/src/main/java/club/lemos/common/api/ApiException.java @@ -0,0 +1,37 @@ +package club.lemos.common.api; + + +public class ApiException extends RuntimeException { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -5885155226898287919L; + + /** + * 错误码 + */ + private IErrorCode errorCode; + + public ApiException(IErrorCode errorCode) { + super(errorCode.getMsg()); + this.errorCode = errorCode; + } + + public ApiException(String message) { + super(message); + } + + public ApiException(Throwable cause) { + super(cause); + } + + public ApiException(String message, Throwable cause) { + super(message, cause); + } + + public IErrorCode getErrorCode() { + return errorCode; + } +} + diff --git a/src/main/java/club/lemos/common/api/IErrorCode.java b/src/main/java/club/lemos/common/api/IErrorCode.java new file mode 100644 index 0000000..3b3daa4 --- /dev/null +++ b/src/main/java/club/lemos/common/api/IErrorCode.java @@ -0,0 +1,18 @@ +package club.lemos.common.api; + +/** + * REST API 错误码接口 + */ +public interface IErrorCode { + + /** + * 错误编码 -1、失败 0、成功 + */ + long getCode(); + + /** + * 错误描述 + */ + String getMsg(); +} + diff --git a/src/main/java/club/lemos/common/api/IResultCode.java b/src/main/java/club/lemos/common/api/IResultCode.java new file mode 100644 index 0000000..2b02ca7 --- /dev/null +++ b/src/main/java/club/lemos/common/api/IResultCode.java @@ -0,0 +1,24 @@ +package club.lemos.common.api; + +import java.io.Serializable; + +/** + * 业务代码接口 + */ +public interface IResultCode extends Serializable { + + /** + * 消息 + * + * @return String + */ + String getMsg(); + + /** + * 状态码 + * + * @return int + */ + int getCode(); + +} diff --git a/src/main/java/club/lemos/common/api/R.java b/src/main/java/club/lemos/common/api/R.java new file mode 100644 index 0000000..c97cdde --- /dev/null +++ b/src/main/java/club/lemos/common/api/R.java @@ -0,0 +1,205 @@ +package club.lemos.common.api; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +import java.io.Serializable; +import java.util.Optional; + +/** + * 统一API响应结果封装 + */ +@Getter +@Setter +@ToString +@NoArgsConstructor +public class R implements Serializable { + + private static final long serialVersionUID = 1L; + + private static final String DEFAULT_SUCCESS_MESSAGE = "操作成功"; + private static final String DEFAULT_FAILURE_MESSAGE = "操作失败"; + private static final String DEFAULT_NULL_MESSAGE = "暂无承载数据!"; + + private int code; + private boolean success; + private T data; + private String msg; + + private R(IResultCode resultCode) { + this(resultCode, null, resultCode.getMsg()); + } + + private R(IResultCode resultCode, String msg) { + this(resultCode, null, msg); + } + + private R(IResultCode resultCode, T data) { + this(resultCode, data, resultCode.getMsg()); + } + + private R(IResultCode resultCode, T data, String msg) { + this(resultCode.getCode(), data, msg); + } + + private R(int code, T data, String msg) { + this.code = code; + this.data = data; + this.msg = msg; + this.success = ResultCode.SUCCESS.code == code; + } + + /** + * 判断返回是否为成功 + * + * @param result Result + * @return 是否成功 + */ + public static boolean isSuccess(@Nullable R result) { + return Optional.ofNullable(result) + .map(x -> ObjectUtils.nullSafeEquals(ResultCode.SUCCESS.code, x.code)) + .orElse(Boolean.FALSE); + } + + /** + * 判断返回是否为成功 + * + * @param result Result + * @return 是否成功 + */ + public static boolean isNotSuccess(@Nullable R result) { + return !R.isSuccess(result); + } + + /** + * 返回R + * + * @param data 数据 + * @param T 泛型标记 + * @return R + */ + public static R data(T data) { + return data(data, DEFAULT_SUCCESS_MESSAGE); + } + + /** + * 返回R + * + * @param data 数据 + * @param msg 消息 + * @param T 泛型标记 + * @return R + */ + public static R data(T data, String msg) { + return data(200, data, msg); + } + + /** + * 返回R + * + * @param code 状态码 + * @param data 数据 + * @param msg 消息 + * @param T 泛型标记 + * @return R + */ + public static R data(int code, T data, String msg) { + return new R<>(code, data, data == null ? DEFAULT_NULL_MESSAGE : msg); + } + + /** + * 返回R + * + * @param msg 消息 + * @param T 泛型标记 + * @return R + */ + public static R success(String msg) { + return new R<>(ResultCode.SUCCESS, msg); + } + + /** + * 返回R + * + * @param resultCode 业务代码 + * @param T 泛型标记 + * @return R + */ + public static R success(IResultCode resultCode) { + return new R<>(resultCode); + } + + /** + * 返回R + * + * @param resultCode 业务代码 + * @param msg 消息 + * @param T 泛型标记 + * @return R + */ + public static R success(IResultCode resultCode, String msg) { + return new R<>(resultCode, msg); + } + + /** + * 返回R + * + * @param msg 消息 + * @param T 泛型标记 + * @return R + */ + public static R fail(String msg) { + return new R<>(ResultCode.FAILURE, msg); + } + + + /** + * 返回R + * + * @param code 状态码 + * @param msg 消息 + * @param T 泛型标记 + * @return R + */ + public static R fail(int code, String msg) { + return new R<>(code, null, msg); + } + + /** + * 返回R + * + * @param resultCode 业务代码 + * @param T 泛型标记 + * @return R + */ + public static R fail(IResultCode resultCode) { + return new R<>(resultCode); + } + + /** + * 返回R + * + * @param resultCode 业务代码 + * @param msg 消息 + * @param T 泛型标记 + * @return R + */ + public static R fail(IResultCode resultCode, String msg) { + return new R<>(resultCode, msg); + } + + /** + * 返回R + * + * @param flag 成功状态 + * @return R + */ + public static R status(boolean flag) { + return flag ? success(DEFAULT_SUCCESS_MESSAGE) : fail(DEFAULT_FAILURE_MESSAGE); + } + +} diff --git a/src/main/java/club/lemos/common/api/RException.java b/src/main/java/club/lemos/common/api/RException.java new file mode 100644 index 0000000..032438e --- /dev/null +++ b/src/main/java/club/lemos/common/api/RException.java @@ -0,0 +1,22 @@ +package club.lemos.common.api; + +public class RException extends ApiException { + + /** + * 错误码 + */ + private IResultCode resultCode; + + public RException(String message) { + super(message); + } + + public RException(IResultCode resultCode) { + super(resultCode.getMsg()); + this.resultCode = resultCode; + } + + public IResultCode getResultCode() { + return resultCode; + } +} diff --git a/src/main/java/club/lemos/common/api/ResultCode.java b/src/main/java/club/lemos/common/api/ResultCode.java new file mode 100644 index 0000000..c8156ee --- /dev/null +++ b/src/main/java/club/lemos/common/api/ResultCode.java @@ -0,0 +1,90 @@ +package club.lemos.common.api; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import javax.servlet.http.HttpServletResponse; + +/** + * 业务代码枚举 + */ +@Getter +@AllArgsConstructor +public enum ResultCode implements IResultCode { + + /** + * 操作成功 + */ + SUCCESS(HttpServletResponse.SC_OK, "操作成功"), + + /** + * 业务异常 + */ + FAILURE(HttpServletResponse.SC_BAD_REQUEST, "业务异常"), + + /** + * 请求未授权 + */ + UN_AUTHORIZED(HttpServletResponse.SC_UNAUTHORIZED, "请求未授权"), + + /** + * 404 没找到请求 + */ + NOT_FOUND(HttpServletResponse.SC_NOT_FOUND, "404 没找到请求"), + + /** + * 消息不能读取 + */ + MSG_NOT_READABLE(HttpServletResponse.SC_BAD_REQUEST, "消息不能读取"), + + /** + * 不支持当前请求方法 + */ + METHOD_NOT_SUPPORTED(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "不支持当前请求方法"), + + /** + * 不支持当前媒体类型 + */ + MEDIA_TYPE_NOT_SUPPORTED(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "不支持当前媒体类型"), + + /** + * 请求被拒绝 + */ + REQ_REJECT(HttpServletResponse.SC_FORBIDDEN, "请求被拒绝"), + + /** + * 服务器异常 + */ + INTERNAL_SERVER_ERROR(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务器异常"), + + /** + * 缺少必要的请求参数 + */ + PARAM_MISS(HttpServletResponse.SC_BAD_REQUEST, "缺少必要的请求参数"), + + /** + * 请求参数类型错误 + */ + PARAM_TYPE_ERROR(HttpServletResponse.SC_BAD_REQUEST, "请求参数类型错误"), + + /** + * 请求参数绑定错误 + */ + PARAM_BIND_ERROR(HttpServletResponse.SC_BAD_REQUEST, "请求参数绑定错误"), + + /** + * 参数校验失败 + */ + PARAM_VALID_ERROR(HttpServletResponse.SC_BAD_REQUEST, "参数校验失败"), + ; + + /** + * code编码 + */ + final int code; + /** + * 中文信息描述 + */ + final String msg; + +} diff --git a/src/main/java/club/lemos/common/constant/CommonConstant.java b/src/main/java/club/lemos/common/constant/CommonConstant.java new file mode 100644 index 0000000..7d9140f --- /dev/null +++ b/src/main/java/club/lemos/common/constant/CommonConstant.java @@ -0,0 +1,58 @@ +package club.lemos.common.constant; + + +/** + * 通用常量 + */ +public interface CommonConstant { + + /** + * 状态相关 + */ + int DB_NOT_DELETED = 0; + int DB_IS_DELETED = 1; + int DB_ADMIN_NON_LOCKED = 0; + int DB_ADMIN_LOCKED = 1; + int DB_DISABlED = 0; + int DB_ENABlED = 1; + + /** + * 开发环境相关 + */ + String DEV_CODE = "dev"; + String HOME_CODE = "home"; + String PROD_CODE = "prod"; + String TEST_CODE = "test"; + + /** + * api 相关 + */ + String DEFAULT_NULL_MESSAGE = "暂无承载数据"; + String DEFAULT_SUCCESS_MESSAGE = "操作成功"; + String DEFAULT_FAILURE_MESSAGE = "操作失败"; + String DEFAULT_UNAUTHORIZED_MESSAGE = "签名认证失败"; + + /** + * 时间相关 + */ + String PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss"; + String PATTERN_DATE = "yyyy-MM-dd"; + String PATTERN_TIME = "HH:mm:ss"; + + /** + * 数据库相关 + */ + String DB_CREATE_TIME_FIELD_NAME ="createTime"; + String DB_UPDATE_TIME_FIELD_NAME ="updateTime"; + String DB_IS_DELETED_FIELD_NAME ="isDeleted"; + String DATETIME_MIN ="1000-01-01T00:00:00"; + String DATETIME_MAX ="9999-12-31T23:59:59"; + + String DB_CREATE_USER_COLUMN_NAME ="create_user"; + String DB_CREATE_TIME_COLUMN_NAME ="create_time"; + String DB_UPDATE_USER_COLUMN_NAME ="update_user"; + String DB_UPDATE_TIME_COLUMN_NAME ="update_time"; + String DB_ENABLED_COLUMN_NAME ="enabled"; + + String DB_TENANT_ID = "tenant_id"; +} diff --git a/src/main/java/club/lemos/common/constant/RegexConstant.java b/src/main/java/club/lemos/common/constant/RegexConstant.java new file mode 100644 index 0000000..ecf4b0c --- /dev/null +++ b/src/main/java/club/lemos/common/constant/RegexConstant.java @@ -0,0 +1,34 @@ +package club.lemos.common.constant; + +public interface RegexConstant { + + /** + * 手机号 + */ + String PHONE = "1[3-9][0-9]{9}"; + + /** + * 用户名 + */ + String USERNAME = "[a-zA-Z][a-zA-Z0-9_]{1,11}"; + + /** + * 用户名或手机号 + */ + String USERNAME_OR_PHONE = String.format("(%s|%s)", USERNAME, PHONE); + + /** + * 密码 + */ + String PASSWORD = ".{6,32}"; + + /** + * 邮箱 + */ + String EMAIL = "\\w+([-+.]*\\w+)*@([\\da-z](-[\\da-z])?)+(\\.{1,2}[a-z]+)+"; + + /** + * 网址 + */ + String URL = "^https?://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; +} diff --git a/src/main/java/club/lemos/common/enums/AdminState.java b/src/main/java/club/lemos/common/enums/AdminState.java new file mode 100644 index 0000000..fc0bd5d --- /dev/null +++ b/src/main/java/club/lemos/common/enums/AdminState.java @@ -0,0 +1,18 @@ +package club.lemos.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum AdminState { + + SAVED("已保存", 1), + COMMITTED("已提交", 2), + UN_PASS("未通过", 3), + PASS("已通过", 4), + ; + + private final String name; + private final int value; +} diff --git a/src/main/java/club/lemos/common/jackson/JsonUtil.java b/src/main/java/club/lemos/common/jackson/JsonUtil.java new file mode 100644 index 0000000..3bbb67e --- /dev/null +++ b/src/main/java/club/lemos/common/jackson/JsonUtil.java @@ -0,0 +1,149 @@ +package club.lemos.common.jackson; + +import club.lemos.common.utils.StringPool; +import club.lemos.common.utils.StringUtil; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class JsonUtil { + + public static String toJson(T value) { + try { + return getInstance().writeValueAsString(value); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + @SneakyThrows + public static byte[] toJsonAsBytes(Object object) { + return getInstance().writeValueAsBytes(object); + } + + public static T parse(String content, Class valueType) { + try { + return getInstance().readValue(content, valueType); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + return null; + } + + @SneakyThrows + public static T parse(String content, TypeReference typeReference) { + return getInstance().readValue(content, typeReference); + } + + @SneakyThrows + public static T parse(byte[] bytes, Class valueType) { + return getInstance().readValue(bytes, valueType); + } + + @SneakyThrows + public static T parse(byte[] bytes, TypeReference typeReference) { + return getInstance().readValue(bytes, typeReference); + } + + @SneakyThrows + public static T parse(InputStream in, Class valueType) { + return getInstance().readValue(in, valueType); + } + + @SneakyThrows + public static T parse(InputStream in, TypeReference typeReference) { + return getInstance().readValue(in, typeReference); + } + + public static List parseArray(String content, Class valueTypeRef) { + try { + + if (!StringUtil.startsWithIgnoreCase(content, StringPool.LEFT_SQ_BRACKET)) { + content = StringPool.LEFT_SQ_BRACKET + content + StringPool.RIGHT_SQ_BRACKET; + } + + List> list = getInstance().readValue(content, new TypeReference>>() { + }); + List result = new ArrayList<>(); + for (Map map : list) { + result.add(toPojo(map, valueTypeRef)); + } + return result; + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return null; + } + + public static Map toMap(String content) { + try { + return getInstance().readValue(content, Map.class); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return null; + } + + public static Map toMap(String content, Class valueTypeRef) { + try { + Map> map = getInstance().readValue(content, new TypeReference>>() { + }); + Map result = new HashMap<>(16); + for (Map.Entry> entry : map.entrySet()) { + result.put(entry.getKey(), toPojo(entry.getValue(), valueTypeRef)); + } + return result; + } catch (IOException e) { + log.error(e.getMessage(), e); + } + return null; + } + + public static T toPojo(Map fromValue, Class toValueType) { + return getInstance().convertValue(fromValue, toValueType); + } + + @SneakyThrows + public static JsonNode readTree(String jsonString) { + return getInstance().readTree(jsonString); + } + + @SneakyThrows + public static JsonNode readTree(InputStream in) { + return getInstance().readTree(in); + } + + @SneakyThrows + public static JsonNode readTree(byte[] content) { + return getInstance().readTree(content); + } + + @SneakyThrows + public static JsonNode readTree(JsonParser jsonParser) { + return getInstance().readTree(jsonParser); + } + + public static ObjectMapper getInstance() { + ObjectMapper instance = JacksonHolder.INSTANCE; + instance.registerModule(new JavaTimeModule()); + return instance; + } + + private static class JacksonHolder { + private static final ObjectMapper INSTANCE = new ObjectMapper(); + } + +} \ No newline at end of file diff --git a/src/main/java/club/lemos/common/mp/base/AdminBaseEntity.java b/src/main/java/club/lemos/common/mp/base/AdminBaseEntity.java new file mode 100644 index 0000000..2a00c2f --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/AdminBaseEntity.java @@ -0,0 +1,41 @@ +package club.lemos.common.mp.base; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +@EqualsAndHashCode(callSuper = true) +@Data +public class AdminBaseEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long updateUser; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 删除标记[0:未删除,1:删除] + */ + @TableField(fill = FieldFill.INSERT) + @TableLogic + private Integer isDeleted; +} \ No newline at end of file diff --git a/src/main/java/club/lemos/common/mp/base/BaseEntity.java b/src/main/java/club/lemos/common/mp/base/BaseEntity.java new file mode 100644 index 0000000..edd069f --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/BaseEntity.java @@ -0,0 +1,20 @@ +package club.lemos.common.mp.base; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + +} \ No newline at end of file diff --git a/src/main/java/club/lemos/common/mp/base/FindOne.java b/src/main/java/club/lemos/common/mp/base/FindOne.java new file mode 100644 index 0000000..79c6cf8 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/FindOne.java @@ -0,0 +1,25 @@ +package club.lemos.common.mp.base; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlSource; + +/** + * 根据 id 查询,包含逻辑删除 + */ +public class FindOne extends AbstractMethod { + + @Override + public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { + /* 执行 SQL ,动态 SQL 参考类 SqlMethod */ + String sql = "select * from " + tableInfo.getTableName() + + " where " + tableInfo.getKeyColumn() + "=#{" + tableInfo.getKeyProperty() + "}"; + /* mapper 接口方法名一致 */ + String method = "findOne"; + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); + return addSelectMappedStatementForTable(mapperClass, method, sqlSource, tableInfo); + } + +} + diff --git a/src/main/java/club/lemos/common/mp/base/TenantEntity.java b/src/main/java/club/lemos/common/mp/base/TenantEntity.java new file mode 100644 index 0000000..80cc695 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/TenantEntity.java @@ -0,0 +1,25 @@ +package club.lemos.common.mp.base; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class TenantEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 租户id + */ + private String tenantId; + +} \ No newline at end of file diff --git a/src/main/java/club/lemos/common/mp/base/UpdateFullById.java b/src/main/java/club/lemos/common/mp/base/UpdateFullById.java new file mode 100644 index 0000000..9e425f7 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/UpdateFullById.java @@ -0,0 +1,68 @@ +package club.lemos.common.mp.base; + +import club.lemos.common.constant.CommonConstant; +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlSource; + +import java.util.List; +import java.util.Objects; + +import static java.util.stream.Collectors.joining; + +/** + * 全量更新 + */ +public class UpdateFullById extends AbstractMethod { + + @Override + public MappedStatement injectMappedStatement(Class mapperClass, Class modelClass, TableInfo tableInfo) { + String script = ""; + String method = "updateFullById"; + final String additional = optlockVersion(tableInfo); + String sql = String.format(script, tableInfo.getTableName(), + sqlSet(false, tableInfo, false, ENTITY, ENTITY_DOT), + tableInfo.getKeyColumn(), ENTITY_DOT + tableInfo.getKeyProperty(), additional); + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); + return addUpdateMappedStatement(mapperClass, modelClass, method, sqlSource); + } + + public String sqlSet(boolean ew, TableInfo table, boolean judgeAliasNull, String alias, String prefix) { + String sqlScript = getCustomAllSqlSet(table, prefix); + if (judgeAliasNull) { + sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", alias), true); + } + if (ew) { + sqlScript += NEWLINE; + sqlScript += SqlScriptUtils.convertIf(SqlScriptUtils.unSafeParam(U_WRAPPER_SQL_SET), + String.format("%s != null and %s != null", WRAPPER, U_WRAPPER_SQL_SET), false); + } + sqlScript = SqlScriptUtils.convertSet(sqlScript); + return sqlScript; + } + + /** + * 全量更新 + *

+ * create_user create_time 不参与更新 + * enabled 依然默认策略增量更新 + */ + public String getCustomAllSqlSet(TableInfo table, final String prefix) { + List fieldList = table.getFieldList(); + final String newPrefix = prefix == null ? EMPTY : prefix; + return fieldList.stream() + .filter(i -> !(i.getColumn().equals(CommonConstant.DB_CREATE_USER_COLUMN_NAME) + | i.getColumn().equals(CommonConstant.DB_CREATE_TIME_COLUMN_NAME))) + .map(i -> { + if (i.getColumn().equals(CommonConstant.DB_ENABLED_COLUMN_NAME)) { + return i.getSqlSet(false, newPrefix); + } else { + return i.getSqlSet(true, newPrefix); + } + }).filter(Objects::nonNull).collect(joining(NEWLINE)); + } + +} diff --git a/src/main/java/club/lemos/common/mp/base/XxBaseMapper.java b/src/main/java/club/lemos/common/mp/base/XxBaseMapper.java new file mode 100644 index 0000000..80052b4 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/XxBaseMapper.java @@ -0,0 +1,37 @@ +package club.lemos.common.mp.base; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; +import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper; +import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; +import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper; +import org.apache.ibatis.annotations.Param; + +import java.io.Serializable; + +public interface XxBaseMapper extends BaseMapper { + + default QueryChainWrapper queryChain() { + return new QueryChainWrapper<>(this); + } + + default LambdaQueryChainWrapper lambdaQueryChain() { + return new LambdaQueryChainWrapper<>(this); + } + + default UpdateChainWrapper updateChain() { + return new UpdateChainWrapper<>(this); + } + + default LambdaUpdateChainWrapper lambdaUpdateChain() { + return new LambdaUpdateChainWrapper<>(this); + } + + int deleteByIdWithFill(T entity); + + T findOne(Serializable id); + + int updateFullById(@Param(Constants.ENTITY) T entity); +} + diff --git a/src/main/java/club/lemos/common/mp/base/XxServiceImpl.java b/src/main/java/club/lemos/common/mp/base/XxServiceImpl.java new file mode 100644 index 0000000..7d6a8c6 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/XxServiceImpl.java @@ -0,0 +1,73 @@ +package club.lemos.common.mp.base; + +import com.baomidou.mybatisplus.core.enums.SqlMethod; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; +import org.apache.ibatis.binding.MapperMethod; +import org.springframework.transaction.annotation.Transactional; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Objects; + +public class XxServiceImpl, T> extends ServiceImpl + implements IService { + + @Transactional(rollbackFor = Exception.class) + public boolean saveOrUpdateFull(T entity) { + if (null != entity) { + Class cls = entity.getClass(); + TableInfo tableInfo = TableInfoHelper.getTableInfo(cls); + Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!"); + String keyProperty = tableInfo.getKeyProperty(); + Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!"); + Object idVal = ReflectionKit.getFieldValue(entity, tableInfo.getKeyProperty()); + return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateFullById(entity); + } + return false; + } + + public boolean updateFullById(T entity) { + return retBool(baseMapper.updateFullById(entity)); + } + + public boolean deleteByIdWithFull(T entity) { + return retBool(baseMapper.deleteByIdWithFill(entity)); + } + + @Transactional(rollbackFor = Exception.class) + public boolean saveOrUpdateFullBatch(Collection entityList) { + return saveOrUpdateFullBatch(entityList, 1000); + } + + @Transactional(rollbackFor = Exception.class) + public boolean saveOrUpdateFullBatch(Collection entityList, int batchSize) { + Assert.notEmpty(entityList, "error: entityList must not be empty"); + Class cls = currentModelClass(); + TableInfo tableInfo = TableInfoHelper.getTableInfo(cls); + Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!"); + String keyProperty = tableInfo.getKeyProperty(); + Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!"); + executeBatch(entityList, batchSize, (sqlSession, entity) -> { + Object idVal = ReflectionKit.getFieldValue(entity, keyProperty); + if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) { + sqlSession.insert(sqlStatement(SqlMethod.INSERT_ONE), entity); + } else { + MapperMethod.ParamMap param = new MapperMethod.ParamMap<>(); + param.put(Constants.ENTITY, entity); + String method = "updateFullById"; + String sqlStatement = SqlHelper.table(currentModelClass()).getSqlStatement(method); + sqlSession.update(sqlStatement, param); + } + } ); + return true; + } + +} diff --git a/src/main/java/club/lemos/common/mp/base/XxSqlInjector.java b/src/main/java/club/lemos/common/mp/base/XxSqlInjector.java new file mode 100644 index 0000000..45b8068 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/XxSqlInjector.java @@ -0,0 +1,27 @@ +package club.lemos.common.mp.base; + +import com.baomidou.mybatisplus.core.injector.AbstractMethod; +import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; +import com.baomidou.mybatisplus.extension.injector.methods.LogicDeleteByIdWithFill; + +import java.util.List; + +/** + * 自定义Sql注入 + * + * @author nieqiurong 2018/8/11 20:23. + */ +public class XxSqlInjector extends DefaultSqlInjector { + + @Override + public List getMethodList(Class mapperClass) { + List methodList = super.getMethodList(mapperClass); + //添加自定义方法 + methodList.add(new FindOne()); + methodList.add(new UpdateFullById()); + //内置选装件 + methodList.add(new LogicDeleteByIdWithFill()); + return methodList; + } + +} diff --git a/src/main/java/club/lemos/common/mp/base/XxTenantSqlParser.java b/src/main/java/club/lemos/common/mp/base/XxTenantSqlParser.java new file mode 100644 index 0000000..2f99f10 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/base/XxTenantSqlParser.java @@ -0,0 +1,293 @@ +package club.lemos.common.mp.base; + +import club.lemos.common.constant.CommonConstant; +import com.baomidou.mybatisplus.core.parser.AbstractJsqlParser; +import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.BinaryExpression; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.*; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.select.*; +import net.sf.jsqlparser.statement.update.Update; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@Slf4j +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class XxTenantSqlParser extends AbstractJsqlParser { + + private TenantHandler tenantHandler; + + /** + * select 语句处理 + */ + @Override + public void processSelectBody(SelectBody selectBody) { + if (selectBody instanceof PlainSelect) { + processPlainSelect((PlainSelect) selectBody); + } else if (selectBody instanceof WithItem) { + WithItem withItem = (WithItem) selectBody; + if (withItem.getSelectBody() != null) { + processSelectBody(withItem.getSelectBody()); + } + } else { + SetOperationList operationList = (SetOperationList) selectBody; + if (operationList.getSelects() != null && operationList.getSelects().size() > 0) { + operationList.getSelects().forEach(this::processSelectBody); + } + } + } + + /** + * insert 语句处理 + */ + @Override + public void processInsert(Insert insert) { + Optional any = insert.getColumns() + .stream().filter(i -> i.toString().equals(CommonConstant.DB_TENANT_ID)).findAny(); + if (!any.isPresent()) { + if (tenantHandler.doTableFilter(insert.getTable().getName())) { + // 过滤退出执行 + return; + } + insert.getColumns().add(new Column(tenantHandler.getTenantIdColumn())); + if (insert.getSelect() != null) { + processPlainSelect((PlainSelect) insert.getSelect().getSelectBody(), true); + } else if (insert.getItemsList() != null) { + // fixed github pull/295 + ItemsList itemsList = insert.getItemsList(); + if (itemsList instanceof MultiExpressionList) { + ((MultiExpressionList) itemsList).getExprList().forEach(el -> el.getExpressions().add(tenantHandler.getTenantId(false))); + } else { + ((ExpressionList) insert.getItemsList()).getExpressions().add(tenantHandler.getTenantId(false)); + } + } else { + throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId"); + } + } + } + + /** + * update 语句处理 + */ + @Override + public void processUpdate(Update update) { + final Table table = update.getTable(); + if (tenantHandler.doTableFilter(table.getName())) { + // 过滤退出执行 + return; + } + update.setWhere(this.andExpression(table, update.getWhere())); + } + + /** + * delete 语句处理 + */ + @Override + public void processDelete(Delete delete) { + if (tenantHandler.doTableFilter(delete.getTable().getName())) { + // 过滤退出执行 + return; + } + delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere())); + } + + /** + * delete update 语句 where 处理 + */ + protected BinaryExpression andExpression(Table table, Expression where) { + //获得where条件表达式 + EqualsTo equalsTo = new EqualsTo(); + equalsTo.setLeftExpression(this.getAliasColumn(table)); + equalsTo.setRightExpression(tenantHandler.getTenantId(true)); + if (null != where) { + if (where instanceof OrExpression) { + return new AndExpression(equalsTo, new Parenthesis(where)); + } else { + return new AndExpression(equalsTo, where); + } + } + return equalsTo; + } + + /** + * 处理 PlainSelect + */ + protected void processPlainSelect(PlainSelect plainSelect) { + processPlainSelect(plainSelect, false); + } + + /** + * 处理 PlainSelect + * + * @param plainSelect ignore + * @param addColumn 是否添加租户列,insert into select语句中需要 + */ + protected void processPlainSelect(PlainSelect plainSelect, boolean addColumn) { + FromItem fromItem = plainSelect.getFromItem(); + if (fromItem instanceof Table) { + Table fromTable = (Table) fromItem; + if (!tenantHandler.doTableFilter(fromTable.getName())) { + //#1186 github + plainSelect.setWhere(builderExpression(plainSelect.getWhere(), fromTable)); + if (addColumn) { + plainSelect.getSelectItems().add(new SelectExpressionItem(new Column(tenantHandler.getTenantIdColumn()))); + } + } + } else { + processFromItem(fromItem); + } + List joins = plainSelect.getJoins(); + if (joins != null && joins.size() > 0) { + joins.forEach(j -> { + processJoin(j); + processFromItem(j.getRightItem()); + }); + } + } + + /** + * 处理子查询等 + */ + protected void processFromItem(FromItem fromItem) { + if (fromItem instanceof SubJoin) { + SubJoin subJoin = (SubJoin) fromItem; + if (subJoin.getJoinList() != null) { + subJoin.getJoinList().forEach(this::processJoin); + } + if (subJoin.getLeft() != null) { + processFromItem(subJoin.getLeft()); + } + } else if (fromItem instanceof SubSelect) { + SubSelect subSelect = (SubSelect) fromItem; + if (subSelect.getSelectBody() != null) { + processSelectBody(subSelect.getSelectBody()); + } + } else if (fromItem instanceof ValuesList) { + logger.debug("Perform a subquery, if you do not give us feedback"); + } else if (fromItem instanceof LateralSubSelect) { + LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem; + if (lateralSubSelect.getSubSelect() != null) { + SubSelect subSelect = lateralSubSelect.getSubSelect(); + if (subSelect.getSelectBody() != null) { + processSelectBody(subSelect.getSelectBody()); + } + } + } + } + + /** + * 处理联接语句 + */ + protected void processJoin(Join join) { + if (join.getRightItem() instanceof Table) { + Table fromTable = (Table) join.getRightItem(); + if (this.tenantHandler.doTableFilter(fromTable.getName())) { + // 过滤退出执行 + return; + } + join.setOnExpression(builderExpression(join.getOnExpression(), fromTable)); + } + } + + /** + * 处理条件: + * 支持 getTenantHandler().getTenantId()是一个完整的表达式:tenant in (1,2) + * 默认tenantId的表达式: LongValue(1)这种依旧支持 + */ + protected Expression builderExpression(Expression currentExpression, Table table) { + final Expression tenantExpression = getTenantHandler().getTenantId( Objects.nonNull(currentExpression) ); + Expression appendExpression; + if (!(tenantExpression instanceof SupportsOldOracleJoinSyntax)) { + appendExpression = new EqualsTo(); + ((EqualsTo) appendExpression).setLeftExpression(this.getAliasColumn(table)); + ((EqualsTo) appendExpression).setRightExpression(tenantExpression); + } else { + appendExpression = processTableAlias4CustomizedTenantIdExpression(tenantExpression, table); + } + if (currentExpression == null) { + return appendExpression; + } + if (currentExpression instanceof BinaryExpression) { + BinaryExpression binaryExpression = (BinaryExpression) currentExpression; + doExpression(binaryExpression.getLeftExpression()); + doExpression(binaryExpression.getRightExpression()); + } else if (currentExpression instanceof InExpression) { + InExpression inExp = (InExpression) currentExpression; + ItemsList rightItems = inExp.getRightItemsList(); + if (rightItems instanceof SubSelect) { + processSelectBody(((SubSelect) rightItems).getSelectBody()); + } + } + if (currentExpression instanceof OrExpression) { + return new AndExpression(new Parenthesis(currentExpression), appendExpression); + } else { + return new AndExpression(currentExpression, appendExpression); + } + } + + protected void doExpression(Expression expression) { + if (expression instanceof FromItem) { + processFromItem((FromItem) expression); + } else if (expression instanceof InExpression) { + InExpression inExp = (InExpression) expression; + ItemsList rightItems = inExp.getRightItemsList(); + if (rightItems instanceof SubSelect) { + processSelectBody(((SubSelect) rightItems).getSelectBody()); + } + } + } + + /** + * 目前: 针对自定义的tenantId的条件表达式[tenant_id in (1,2,3)],无法处理多租户的字段加上表别名 + * select a.id, b.name + * from a + * join b on b.aid = a.id and [b.]tenant_id in (1,2) --别名[b.]无法加上 TODO + * + * @param expression + * @param table + * @return 加上别名的多租户字段表达式 + */ + protected Expression processTableAlias4CustomizedTenantIdExpression(Expression expression, Table table) { + //cannot add table alias for customized tenantId expression, + // when tables including tenantId at the join table poistion + return expression; + } + + /** + * 租户字段别名设置 + *

tableName.tenantId 或 tableAlias.tenantId

+ * + * @param table 表对象 + * @return 字段 + */ + protected Column getAliasColumn(Table table) { + StringBuilder column = new StringBuilder(); + if (null == table.getAlias()) { + column.append(table.getName()); + } else { + column.append(table.getAlias().getName()); + } + column.append(StringPool.DOT); + column.append(tenantHandler.getTenantIdColumn()); + return new Column(column.toString()); + } +} diff --git a/src/main/java/club/lemos/common/mp/handler/BaseMetaObjectHandler.java b/src/main/java/club/lemos/common/mp/handler/BaseMetaObjectHandler.java new file mode 100644 index 0000000..13ba6cd --- /dev/null +++ b/src/main/java/club/lemos/common/mp/handler/BaseMetaObjectHandler.java @@ -0,0 +1,29 @@ +package club.lemos.common.mp.handler; + + +import club.lemos.common.constant.CommonConstant; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; + +import java.time.LocalDateTime; + +@Slf4j +public class BaseMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + log.debug("start insert fill ...."); + LocalDateTime now = LocalDateTime.now(); + this.setFieldValByName(CommonConstant.DB_CREATE_TIME_FIELD_NAME, now, metaObject); + this.setFieldValByName(CommonConstant.DB_UPDATE_TIME_FIELD_NAME, now, metaObject); + this.setFieldValByName(CommonConstant.DB_IS_DELETED_FIELD_NAME, CommonConstant.DB_NOT_DELETED, metaObject); + } + + @Override + public void updateFill(MetaObject metaObject) { + log.debug("start update fill ...."); + this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject); + } + +} \ No newline at end of file diff --git a/src/main/java/club/lemos/common/mp/typehandler/JacksonJsonNodeTypeHandler.java b/src/main/java/club/lemos/common/mp/typehandler/JacksonJsonNodeTypeHandler.java new file mode 100644 index 0000000..59e37a9 --- /dev/null +++ b/src/main/java/club/lemos/common/mp/typehandler/JacksonJsonNodeTypeHandler.java @@ -0,0 +1,39 @@ +package club.lemos.common.mp.typehandler; + +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; + +import java.io.IOException; + +@Slf4j +@MappedTypes({JsonNode.class}) +@MappedJdbcTypes(value=JdbcType.VARCHAR) +public class JacksonJsonNodeTypeHandler extends AbstractJsonTypeHandler { + private static final ObjectMapper objectMapper = new ObjectMapper(); + private final Class type = JsonNode.class; + + @Override + protected JsonNode parse(String json) { + try { + return objectMapper.readValue(json, type); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + protected String toJson(JsonNode obj) { + try { + return objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/club/lemos/common/node/BaseNode.java b/src/main/java/club/lemos/common/node/BaseNode.java new file mode 100644 index 0000000..8194725 --- /dev/null +++ b/src/main/java/club/lemos/common/node/BaseNode.java @@ -0,0 +1,31 @@ +package club.lemos.common.node; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 节点基类 + */ +@Data +public class BaseNode implements INode { + + /** + * 主键ID + */ + protected Integer id; + + /** + * 父节点ID + */ + protected Integer parentId; + + /** + * 子孙节点 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + protected List children = new ArrayList<>(); + +} diff --git a/src/main/java/club/lemos/common/node/ForestNode.java b/src/main/java/club/lemos/common/node/ForestNode.java new file mode 100644 index 0000000..a403d47 --- /dev/null +++ b/src/main/java/club/lemos/common/node/ForestNode.java @@ -0,0 +1,25 @@ +package club.lemos.common.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; + + +/** + * 森林节点类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class ForestNode extends BaseNode { + + /** + * 节点内容 + */ + private Object content; + + public ForestNode(Integer id, Integer parentId, Object content) { + this.id = id; + this.parentId = parentId; + this.content = content; + } + +} diff --git a/src/main/java/club/lemos/common/node/ForestNodeManager.java b/src/main/java/club/lemos/common/node/ForestNodeManager.java new file mode 100644 index 0000000..65c1b06 --- /dev/null +++ b/src/main/java/club/lemos/common/node/ForestNodeManager.java @@ -0,0 +1,64 @@ +package club.lemos.common.node; + +import java.util.ArrayList; +import java.util.List; + +/** + * 森林管理类 + */ +public class ForestNodeManager { + + /** + * 森林的所有节点 + */ + private final List list; + + /** + * 森林的父节点ID + */ + private final List parentIds = new ArrayList<>(); + + public ForestNodeManager(List items) { + list = items; + } + + /** + * 根据节点ID获取一个节点 + * + * @param id 节点ID + * @return 对应的节点对象 + */ + public INode getTreeNodeAT(int id) { + for (INode forestNode : list) { + if (forestNode.getId() == id) { + return forestNode; + } + } + return null; + } + + /** + * 增加父节点ID + * + * @param parentId 父节点ID + */ + public void addParentId(Integer parentId) { + parentIds.add(parentId); + } + + /** + * 获取树的根节点(一个森林对应多颗树) + * + * @return 树的根节点集合 + */ + public List getRoot() { + List roots = new ArrayList<>(); + for (T forestNode : list) { + if (forestNode.getParentId() == 0 || parentIds.contains(forestNode.getId())) { + roots.add(forestNode); + } + } + return roots; + } + +} diff --git a/src/main/java/club/lemos/common/node/ForestNodeMerger.java b/src/main/java/club/lemos/common/node/ForestNodeMerger.java new file mode 100644 index 0000000..61f3435 --- /dev/null +++ b/src/main/java/club/lemos/common/node/ForestNodeMerger.java @@ -0,0 +1,33 @@ +package club.lemos.common.node; + +import java.util.List; + +/** + * 森林节点归并类 + */ +public class ForestNodeMerger { + + /** + * 将节点数组归并为一个森林(多棵树)(填充节点的children域) + * 时间复杂度为O(n^2) + * + * @param items 节点域 + * @param T 泛型标记 + * @return 多棵树的根节点集合 + */ + public static List merge(List items) { + ForestNodeManager forestNodeManager = new ForestNodeManager<>(items); + items.forEach(forestNode -> { + if (forestNode.getParentId() != 0) { + INode node = forestNodeManager.getTreeNodeAT(forestNode.getParentId()); + if (node != null) { + node.getChildren().add(forestNode); + } else { + forestNodeManager.addParentId(forestNode.getId()); + } + } + }); + return forestNodeManager.getRoot(); + } + +} diff --git a/src/main/java/club/lemos/common/node/INode.java b/src/main/java/club/lemos/common/node/INode.java new file mode 100644 index 0000000..bd54f82 --- /dev/null +++ b/src/main/java/club/lemos/common/node/INode.java @@ -0,0 +1,29 @@ +package club.lemos.common.node; + +import java.util.List; + + +public interface INode { + + /** + * 主键 + * + * @return Integer + */ + Integer getId(); + + /** + * 父主键 + * + * @return Integer + */ + Integer getParentId(); + + /** + * 子孙节点 + * + * @return List + */ + List getChildren(); + +} diff --git a/src/main/java/club/lemos/common/node/NodeTest.java b/src/main/java/club/lemos/common/node/NodeTest.java new file mode 100644 index 0000000..37a0379 --- /dev/null +++ b/src/main/java/club/lemos/common/node/NodeTest.java @@ -0,0 +1,27 @@ +package club.lemos.common.node; + +import club.lemos.common.jackson.JsonUtil; + +import java.util.ArrayList; +import java.util.List; + + +public class NodeTest { + + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new ForestNode(1, 0, "1")); + list.add(new ForestNode(2, 0, "2")); + list.add(new ForestNode(3, 1, "3")); + list.add(new ForestNode(4, 2, "4")); + list.add(new ForestNode(5, 3, "5")); + list.add(new ForestNode(6, 4, "6")); + list.add(new ForestNode(7, 3, "7")); + list.add(new ForestNode(8, 5, "8")); + list.add(new ForestNode(9, 6, "9")); + list.add(new ForestNode(10, 9, "10")); + List tns = ForestNodeMerger.merge(list); + System.out.println(JsonUtil.toJson(tns)); + } + +} diff --git a/src/main/java/club/lemos/common/node/TreeNode.java b/src/main/java/club/lemos/common/node/TreeNode.java new file mode 100644 index 0000000..fa8befa --- /dev/null +++ b/src/main/java/club/lemos/common/node/TreeNode.java @@ -0,0 +1,22 @@ +package club.lemos.common.node; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * 树型节点类 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class TreeNode extends BaseNode implements Serializable { + + private String title; + + private Integer key; + + private Integer value; + + private Integer type; +} diff --git a/src/main/java/club/lemos/common/support/BeanProperty.java b/src/main/java/club/lemos/common/support/BeanProperty.java new file mode 100644 index 0000000..29a47d1 --- /dev/null +++ b/src/main/java/club/lemos/common/support/BeanProperty.java @@ -0,0 +1,11 @@ +package club.lemos.common.support; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class BeanProperty { + private final String name; + private final Class type; +} diff --git a/src/main/java/club/lemos/common/support/SpringUtil.java b/src/main/java/club/lemos/common/support/SpringUtil.java new file mode 100644 index 0000000..c5f61ba --- /dev/null +++ b/src/main/java/club/lemos/common/support/SpringUtil.java @@ -0,0 +1,66 @@ +package club.lemos.common.support; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.lang.NonNull; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +public class SpringUtil implements ApplicationContextAware, DisposableBean { + + private static ApplicationContext context; + + @Override + public void destroy() throws Exception { + context = null; + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext context) throws BeansException { + SpringUtil.context = context; + } + + public static T getBean(Class clazz) { + if (clazz == null) { + return null; + } + return context.getBean(clazz); + } + + public static T getBean(String beanName, Class clazz) { + if (null == beanName || "".equals(beanName.trim())) { + return null; + } + if (clazz == null) { + return null; + } + return context.getBean(beanName, clazz); + } + + public static ApplicationContext getContext() { + if (context == null) { + return null; + } + return context; + } + + public static void publishEvent(ApplicationEvent event) { + if (context == null) { + return; + } + context.publishEvent(event); + } + + public static HttpServletRequest getCurrentRequest() throws IllegalStateException { + ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attrs == null) { + throw new IllegalStateException("当前线程中不存在 Request 上下文"); + } + return attrs.getRequest(); + } +} \ No newline at end of file diff --git a/src/main/java/club/lemos/common/support/ThreadContext.java b/src/main/java/club/lemos/common/support/ThreadContext.java new file mode 100644 index 0000000..1465e59 --- /dev/null +++ b/src/main/java/club/lemos/common/support/ThreadContext.java @@ -0,0 +1,200 @@ +package club.lemos.common.support; + + +import club.lemos.common.utils.CollectionUtil; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * A ThreadContext provides a means of binding and unbinding objects to the + * current thread based on key/value pairs. + *

+ *

An internal {@link HashMap} is used to maintain the key/value pairs + * for each thread.

+ *

+ *

If the desired behavior is to ensure that bound data is not shared across + * threads in a pooled or reusable threaded environment, the application (or more likely a framework) must + * bind and remove any necessary values at the beginning and end of stack + * execution, respectively (i.e. individually explicitly or all via the clear method).

+ * + * @see #remove() + * @since 0.1 + */ +@Slf4j +public class ThreadContext { + + private static final ThreadLocal> resources = new InheritableThreadLocalMap>(); + + /** + * Default no-argument constructor. + */ + protected ThreadContext() { + } + + /** + * Returns the ThreadLocal Map. This Map is used internally to bind objects + * to the current thread by storing each object under a unique key. + * + * @return the map of bound resources + */ + public static Map getResources() { + if (resources.get() == null){ + return Collections.emptyMap(); + } else { + return new HashMap(resources.get()); + } + } + + /** + * Allows a caller to explicitly set the entire resource map. This operation overwrites everything that existed + * previously in the ThreadContext - if you need to retain what was on the thread prior to calling this method, + * call the {@link #getResources()} method, which will give you the existing state. + * + * @param newResources the resources to replace the existing {@link #getResources() resources}. + * @since 1.0 + */ + public static void setResources(Map newResources) { + if (CollectionUtil.isEmpty(newResources)) { + return; + } + ensureResourcesInitialized(); + resources.get().clear(); + resources.get().putAll(newResources); + } + + /** + * Returns the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there + * is no value for that {@code key}. + * + * @param key the map key to use to lookup the value + * @return the value bound in the {@code ThreadContext} under the specified {@code key}, or {@code null} if there + * is no value for that {@code key}. + * @since 1.0 + */ + private static Object getValue(Object key) { + Map perThreadResources = resources.get(); + return perThreadResources != null ? perThreadResources.get(key) : null; + } + + private static void ensureResourcesInitialized(){ + if (resources.get() == null){ + resources.set(new HashMap()); + } + } + + /** + * Returns the object for the specified key that is bound to + * the current thread. + * + * @param key the key that identifies the value to return + * @return the object keyed by key or null if + * no value exists for the specified key + */ + public static Object get(Object key) { + if (log.isTraceEnabled()) { + String msg = "get() - in thread [" + Thread.currentThread().getName() + "]"; + log.trace(msg); + } + + Object value = getValue(key); + if ((value != null) && log.isTraceEnabled()) { + String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" + + key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]"; + log.trace(msg); + } + return value; + } + + /** + * Binds value for the given key to the current thread. + *

+ *

A null value has the same effect as if remove was called for the given + * key, i.e.: + *

+ *

+     * if ( value == null ) {
+     *     remove( key );
+     * }
+ * + * @param key The key with which to identify the value. + * @param value The value to bind to the thread. + * @throws IllegalArgumentException if the key argument is null. + */ + public static void put(Object key, Object value) { + if (key == null) { + throw new IllegalArgumentException("key cannot be null"); + } + + if (value == null) { + remove(key); + return; + } + + ensureResourcesInitialized(); + resources.get().put(key, value); + + if (log.isTraceEnabled()) { + String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" + + key + "] to thread " + "[" + Thread.currentThread().getName() + "]"; + log.trace(msg); + } + } + + /** + * Unbinds the value for the given key from the current + * thread. + * + * @param key The key identifying the value bound to the current thread. + * @return the object unbound or null if there was nothing bound + * under the specified key name. + */ + public static Object remove(Object key) { + Map perThreadResources = resources.get(); + Object value = perThreadResources != null ? perThreadResources.remove(key) : null; + + if ((value != null) && log.isTraceEnabled()) { + String msg = "Removed value of type [" + value.getClass().getName() + "] for key [" + + key + "]" + "from thread [" + Thread.currentThread().getName() + "]"; + log.trace(msg); + } + + return value; + } + + /** + * {@link ThreadLocal#remove Remove}s the underlying {@link ThreadLocal ThreadLocal} from the thread. + *

+ * This method is meant to be the final 'clean up' operation that is called at the end of thread execution to + * prevent thread corruption in pooled thread environments. + * + * @since 1.0 + */ + public static void remove() { + resources.remove(); + } + + private static final class InheritableThreadLocalMap> extends InheritableThreadLocal> { + + /** + * This implementation was added to address a + * + * user-reported issue. + * @param parentValue the parent value, a HashMap as defined in the {@link #initialValue()} method. + * @return the HashMap to be used by any parent-spawned child threads (a clone of the parent HashMap). + */ + @SuppressWarnings({"unchecked"}) + protected Map childValue(Map parentValue) { + if (parentValue != null) { + return (Map) ((HashMap) parentValue).clone(); + } else { + return null; + } + } + } +} + + diff --git a/src/main/java/club/lemos/common/support/XssHttpServletRequestWrapper.java b/src/main/java/club/lemos/common/support/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..53a397b --- /dev/null +++ b/src/main/java/club/lemos/common/support/XssHttpServletRequestWrapper.java @@ -0,0 +1,74 @@ +package club.lemos.common.support; + +import club.lemos.common.utils.StringUtil; +import org.apache.commons.text.StringEscapeUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import java.util.Map; + +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { + + public XssHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public String getParameter(String name) { + String value = super.getParameter(xssEncode(name)); + if (StringUtil.isNotBlank(value)) { + value = xssEncode(value); + } + return value; + } + + @Override + public String[] getParameterValues(String name) { + String[] parameters = super.getParameterValues(name); + if (parameters == null || parameters.length == 0) { + return null; + } + + for (int i = 0; i < parameters.length; i++) { + parameters[i] = xssEncode(parameters[i]); + } + return parameters; + } + + @Override + public Map getParameterMap() { + Map map = new LinkedHashMap<>(); + Map parameters = super.getParameterMap(); + for (String key : parameters.keySet()) { + String[] values = parameters.get(key); + for (int i = 0; i < values.length; i++) { + values[i] = xssEncode(values[i]); + } + map.put(key, values); + } + return map; + } + + @Override + public String getHeader(String name) { + String value = super.getHeader(xssEncode(name)); + if (StringUtil.isNotBlank(value)) { + value = xssEncode(value); + } + return value; + } + + private String xssEncode(String input) { + return StringEscapeUtils.unescapeHtml4(input); + } + +} diff --git a/src/main/java/club/lemos/common/utils/Base64Util.java b/src/main/java/club/lemos/common/utils/Base64Util.java new file mode 100644 index 0000000..cdfef63 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/Base64Util.java @@ -0,0 +1,96 @@ +package club.lemos.common.utils; + +import java.nio.charset.StandardCharsets; + +public class Base64Util extends org.springframework.util.Base64Utils { + + /** + * 编码 + * + * @param value 字符串 + * @return {String} + */ + public static String encode(String value) { + return Base64Util.encode(value, StandardCharsets.UTF_8); + } + + /** + * 编码 + * + * @param value 字符串 + * @param charset 字符集 + * @return {String} + */ + public static String encode(String value, java.nio.charset.Charset charset) { + byte[] val = value.getBytes(charset); + return new String(Base64Util.encode(val), charset); + } + + /** + * 编码URL安全 + * + * @param value 字符串 + * @return {String} + */ + public static String encodeUrlSafe(String value) { + return Base64Util.encodeUrlSafe(value, StandardCharsets.UTF_8); + } + + /** + * 编码URL安全 + * + * @param value 字符串 + * @param charset 字符集 + * @return {String} + */ + public static String encodeUrlSafe(String value, java.nio.charset.Charset charset) { + byte[] val = value.getBytes(charset); + return new String(Base64Util.encodeUrlSafe(val), charset); + } + + /** + * 解码 + * + * @param value 字符串 + * @return {String} + */ + public static String decode(String value) { + return Base64Util.decode(value, StandardCharsets.UTF_8); + } + + /** + * 解码 + * + * @param value 字符串 + * @param charset 字符集 + * @return {String} + */ + public static String decode(String value, java.nio.charset.Charset charset) { + byte[] val = value.getBytes(charset); + byte[] decodedValue = Base64Util.decode(val); + return new String(decodedValue, charset); + } + + /** + * 解码URL安全 + * + * @param value 字符串 + * @return {String} + */ + public static String decodeUrlSafe(String value) { + return Base64Util.decodeUrlSafe(value, StandardCharsets.UTF_8); + } + + /** + * 解码URL安全 + * + * @param value 字符串 + * @param charset 字符集 + * @return {String} + */ + public static String decodeUrlSafe(String value, java.nio.charset.Charset charset) { + byte[] val = value.getBytes(charset); + byte[] decodedValue = Base64Util.decodeUrlSafe(val); + return new String(decodedValue, charset); + } +} diff --git a/src/main/java/club/lemos/common/utils/BeanUtil.java b/src/main/java/club/lemos/common/utils/BeanUtil.java new file mode 100644 index 0000000..53e1f2d --- /dev/null +++ b/src/main/java/club/lemos/common/utils/BeanUtil.java @@ -0,0 +1,120 @@ +package club.lemos.common.utils; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.beans.BeansException; +import org.springframework.cglib.core.CodeGenerationException; +import org.springframework.util.StringUtils; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +public class BeanUtil extends BeanUtils { + + public static T copy(Object source, Class clazz) { + T to = instantiateClass(clazz); + copyProperties(source, to); + return to; + } + + /** + * 实例化对象 + * + * @param clazz 类 + * @param 泛型标记 + * @return 对象 + */ + @SuppressWarnings("unchecked") + public static T newInstance(Class clazz) { + return (T) instantiateClass(clazz); + } + + /** + * 实例化对象 + * + * @param clazzStr 类名 + * @param 泛型标记 + * @return 对象 + */ + public static T newInstance(String clazzStr) { + try { + Class clazz = Class.forName(clazzStr); + return newInstance(clazz); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * exclude null property + * + * @param source source + * @return ignoreProperties + */ + public static String[] getNullPropertyNames(Object source) { + final BeanWrapper src = new BeanWrapperImpl(source); + PropertyDescriptor[] pds = src.getPropertyDescriptors(); + + Set emptyNames = new HashSet<>(); + for (PropertyDescriptor pd : pds) { + Object srcValue = src.getPropertyValue(pd.getName()); + + if (srcValue == null) { + emptyNames.add(pd.getName()); + } else if (srcValue instanceof String) { + if (!StringUtils.hasText((String) srcValue)) { + emptyNames.add(pd.getName()); + } + } + } + String[] result = new String[emptyNames.size()]; + return emptyNames.toArray(result); + } + + /** + * 获取 Bean 的所有 get方法 + * + * @param type 类 + * @return PropertyDescriptor数组 + */ + public static PropertyDescriptor[] getBeanGetters(Class type) { + return getPropertiesHelper(type, true, false); + } + + /** + * 获取 Bean 的所有 set方法 + * + * @param type 类 + * @return PropertyDescriptor数组 + */ + public static PropertyDescriptor[] getBeanSetters(Class type) { + return getPropertiesHelper(type, false, true); + } + + private static PropertyDescriptor[] getPropertiesHelper(Class type, boolean read, boolean write) { + try { + PropertyDescriptor[] all = BeanUtil.getPropertyDescriptors(type); + if (read && write) { + return all; + } else { + List properties = new ArrayList<>(all.length); + for (PropertyDescriptor pd : all) { + if (read && pd.getReadMethod() != null) { + properties.add(pd); + } else if (write && pd.getWriteMethod() != null) { + properties.add(pd); + } + } + return properties.toArray(new PropertyDescriptor[0]); + } + } catch (BeansException ex) { + throw new CodeGenerationException(ex); + } + } + +} diff --git a/src/main/java/club/lemos/common/utils/CollectionUtil.java b/src/main/java/club/lemos/common/utils/CollectionUtil.java new file mode 100644 index 0000000..cb440b6 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/CollectionUtil.java @@ -0,0 +1,62 @@ +package club.lemos.common.utils; + +import org.springframework.lang.Nullable; +import org.springframework.util.CollectionUtils; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +public class CollectionUtil extends CollectionUtils { + + /** + * Check whether the given Array contains the given element. + * + * @param array the Array to check + * @param element the element to look for + * @param The generic tag + * @return {@code true} if found, {@code false} else + */ + public static boolean contains(@Nullable T[] array, final T element) { + if (array == null) { + return false; + } + return Arrays.stream(array).anyMatch(x -> ObjectUtil.nullSafeEquals(x, element)); + } + + /** + * 对象是否为数组对象 + * + * @param obj 对象 + * @return 是否为数组对象,如果为{@code null} 返回false + */ + public static boolean isArray(Object obj) { + if (null == obj) { + return false; + } + return obj.getClass().isArray(); + } + + /** + * Determine whether the given Collection is not empty: + * i.e. {@code null} or of zero length. + * + * @param coll the Collection to check + * @return boolean + */ + public static boolean isNotEmpty(@Nullable Collection coll) { + return !CollectionUtils.isEmpty(coll); + } + + /** + * Determine whether the given Map is not empty: + * i.e. {@code null} or of zero length. + * + * @param map the Map to check + * @return boolean + */ + public static boolean isNotEmpty(@Nullable Map map) { + return !CollectionUtils.isEmpty(map); + } + +} \ No newline at end of file diff --git a/src/main/java/club/lemos/common/utils/DateUtil.java b/src/main/java/club/lemos/common/utils/DateUtil.java new file mode 100644 index 0000000..4255399 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/DateUtil.java @@ -0,0 +1,73 @@ +package club.lemos.common.utils; + +import club.lemos.common.constant.CommonConstant; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.Objects; + +public class DateUtil { + + public static LocalDateTime millisToLocalDateTime(Long epochMilli) { + return LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault()); + } + + public static Long localDateTimeToMills(LocalDateTime ldt) { + return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + public static Long localDateTimeToSeconds(LocalDateTime ldt) { + return ldt.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond(); + } + + public static Long safeLocalDateTimeToSeconds(LocalDateTime ldt) { + if (Objects.isNull(ldt)) { + return null; + } + return ldt.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond(); + } + + public static String millisToString(Long epochMilli) { + return localDateTimeToString(millisToLocalDateTime(epochMilli)); + } + + public static String localDateTimeToString(LocalDateTime ldt) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CommonConstant.PATTERN_DATETIME); + return ldt.format(formatter); + } + + public static String localDateToString(LocalDate ld) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CommonConstant.PATTERN_DATE); + return ld.format(formatter); + } + + public static LocalDate toLocalDate(String date) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CommonConstant.PATTERN_DATE); + return LocalDate.parse(date, formatter); + } + + public static long localDateToMills(LocalDate ld) { + return ld.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + public static long localDateToSeconds(LocalDate ld) { + return ld.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli() / 1000; + } + + public static long getCurrentSeconds() { + return dateToSeconds(new Date()); + } + + public static long dateToSeconds(Date date) { + return (date.getTime()) / 1000; + } + + public static Date secondsToDate(long seconds) { + return new Date(seconds * 1000L); + } + +} diff --git a/src/main/java/club/lemos/common/utils/FuncUtil.java b/src/main/java/club/lemos/common/utils/FuncUtil.java new file mode 100644 index 0000000..ec30ca7 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/FuncUtil.java @@ -0,0 +1,29 @@ +package club.lemos.common.utils; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class FuncUtil { + + public static Predicate distinctByKey(Function keyExtractor) { + Set seen = ConcurrentHashMap.newKeySet(); + return t -> seen.add(keyExtractor.apply(t)); + } + + @SafeVarargs + public static Predicate distinctByKeys(Function... keyExtractors) { + final Set seen = ConcurrentHashMap.newKeySet(); + return t -> { + List keys = Arrays.stream(keyExtractors) + .map(ke -> ke.apply(t)) + .collect(Collectors.toList()); + return seen.add(keys); + }; + } + +} diff --git a/src/main/java/club/lemos/common/utils/MicroUUID.java b/src/main/java/club/lemos/common/utils/MicroUUID.java new file mode 100644 index 0000000..9d70ad2 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/MicroUUID.java @@ -0,0 +1,278 @@ +package club.lemos.common.utils; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public enum MicroUUID { + + /** + * The Generating 15 bit UUID + */ + UUID15 { + @Override + public Object getId() { + return MicroUUID.randomUUID15(); + } + }, + + /** + * The Generating 15 bit Long UUID + */ + UUID15_LONG { + @Override + public Object getId() { + return MicroUUID.randomUUID15Long(); + } + }, + + /** + * The Generating 19 bit UUID + */ + UUID19 { + @Override + public Object getId() { + return MicroUUID.randomUUID19(); + } + }, + + /** + * The Generating 32 bit UUID + */ + UUID32 { + @Override + public Object getId() { + return MicroUUID.randomUUID32(); + } + }, + + /** + * The Generating 36 bit UUID + */ + UUID { + @Override + public Object getId() { + return MicroUUID.randomUUID(); + } + }; + + private final static String STR_BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private final static char[] DIGITS = STR_BASE.toCharArray(); + private final static Map DIGIT_MAP = new HashMap<>(); + + static { + for (int i = 0; i < DIGITS.length; i++) { + DIGIT_MAP.put(DIGITS[i], i); + } + } + + /** + * 支持的最小进制数 + */ + private static final int MIN_RADIX = 2; + /** + * 支持的最大进制数 + */ + private static final int MAX_RADIX = DIGITS.length; + + public abstract Object getId(); + + /** + * 获取36位UUID(原生UUID) + */ + public static String randomUUID() { + return java.util.UUID.randomUUID().toString(); + } + + /** + * 获取32位UUID + */ + public static String randomUUID32() { + return java.util.UUID.randomUUID().toString().replace("-", ""); + } + + /** + * 获取19位的UUID + */ + public static String randomUUID19() { + // 产生UUID + UUID uuid = java.util.UUID.randomUUID(); + // 分区转换 + return digits(uuid.getMostSignificantBits() >> 32, 8) + + digits(uuid.getMostSignificantBits() >> 16, 4) + + digits(uuid.getMostSignificantBits(), 4) + + digits(uuid.getLeastSignificantBits() >> 48, 4) + + digits(uuid.getLeastSignificantBits(), 12); + } + + /** + * 获取15位的UUID(精度有所损失) + */ + public static String randomUUID15() { + return UUIDMaker.generate(); + } + + /** + * 获取15位的Long型UUID(精度有所损失) + */ + public static long randomUUID15Long() { + return toNumber(randomUUID15(), 10); + } + + public static String randomUUIDBase64() { + UUID uuid = java.util.UUID.randomUUID(); + byte[] byUuid = new byte[16]; + long least = uuid.getLeastSignificantBits(); + long most = uuid.getMostSignificantBits(); + long2bytes(most, byUuid, 0); + long2bytes(least, byUuid, 8); + + return Base64.getEncoder().encodeToString(byUuid); + } + + private static void long2bytes(long value, byte[] bytes, int offset) { + for (int i = 7; i > -1; i--) { + bytes[offset++] = (byte) ((value >> 8 * i) & 0xFF); + } + } + + /** + * 将字符串转换为长整型数字 + * + * @param s 数字字符串 + * @param radix 进制数 + */ + private static long toNumber(String s, int radix) { + if (s == null) { + throw new NumberFormatException("null"); + } + if (radix < MIN_RADIX) { + throw new NumberFormatException("radix " + radix + " less than Numbers.MIN_RADIX"); + } + if (radix > MAX_RADIX) { + throw new NumberFormatException("radix " + radix + " greater than Numbers.MAX_RADIX"); + } + + boolean negative = false; + Integer digit, i = 0, len = s.length(); + long result = 0, limit = -Long.MAX_VALUE, multmin; + if (len <= 0) { + throw forInputString(s); + } + + char firstChar = s.charAt(0); + if (firstChar < '0') { + if (firstChar == '-') { + negative = true; + limit = Long.MIN_VALUE; + } else if (firstChar != '+') { + throw forInputString(s); + } + if (len == 1) { + throw forInputString(s); + } + i++; + } + + multmin = limit / radix; + while (i < len) { + digit = DIGIT_MAP.get(s.charAt(i++)); + if (digit == null || digit < 0 || result < multmin) { + throw forInputString(s); + } + result *= radix; + if (result < limit + digit) { + throw forInputString(s); + } + result -= digit; + } + + return negative ? result : -result; + } + + /** + * 将长整型数值转换为指定的进制数(最大支持62进制,字母数字已经用尽) + * + * @param num num + * @param radix radix + * @return string value + */ + private static String toString(long num, int radix) { + if (radix < MIN_RADIX || radix > MAX_RADIX) { + radix = 10; + } + if (radix == 10) { + return Long.toString(num); + } + + final int size = 65; + int charPos = 64; + char[] buf = new char[size]; + boolean negative = (num < 0); + if (!negative) { + num = -num; + } + while (num <= -radix) { + buf[charPos--] = DIGITS[(int) (-(num % radix))]; + num = num / radix; + } + buf[charPos] = DIGITS[(int) (-num)]; + if (negative) { + buf[--charPos] = '-'; + } + + return new String(buf, charPos, (size - charPos)); + } + + private static String digits(long val, int digits) { + long hi = 1L << (digits * 4); + return toString(hi | (val & (hi - 1)), MAX_RADIX).substring(1); + } + + private static NumberFormatException forInputString(String s) { + return new NumberFormatException("For input string: " + s); + } + + private static class UUIDMaker { + + private final static String STR = "0123456789abcdefghijklmnopqrstuvwxyz"; + private final static int PIX_LEN = STR.length(); + private static volatile int pixOne = 0; + private static volatile int pixTwo = 0; + private static volatile int pixThree = 0; + private static volatile int pixFour = 0; + + /** + * 生成短时间内不会重复的长度为15位的字符串,主要用于模块数据库主键生成使用。
+ * 生成策略为获取自1970年1月1日零时零分零秒至当前时间的毫秒数的16进制字符串值,该字符串值为11位
+ * 并追加四位"0-z"的自增字符串.
+ * 如果系统时间设置为大于2304-6-27 7:00:26的时间,将会报错!
+ * 由于系统返回的毫秒数与操作系统关系很大,所以本方法并不准确。
+ * 本方法可以保证在系统返回的一个毫秒数内生成36的4次方个(1679616)ID不重复。
+ */ + private synchronized static String generate() { + String hexString = Long.toHexString(System.currentTimeMillis()); + pixFour++; + if (pixFour == PIX_LEN) { + pixFour = 0; + pixThree++; + if (pixThree == PIX_LEN) { + pixThree = 0; + pixTwo++; + if (pixTwo == PIX_LEN) { + pixTwo = 0; + pixOne++; + if (pixOne == PIX_LEN) { + pixOne = 0; + } + } + } + } + + return hexString + STR.charAt(pixOne) + STR.charAt(pixTwo) + + STR.charAt(pixThree) + STR.charAt(pixFour); + } + } + +} diff --git a/src/main/java/club/lemos/common/utils/ObjectUtil.java b/src/main/java/club/lemos/common/utils/ObjectUtil.java new file mode 100644 index 0000000..92c2936 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/ObjectUtil.java @@ -0,0 +1,53 @@ +package club.lemos.common.utils; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.lang.Nullable; + +import java.util.HashSet; +import java.util.Set; + +/** + * 对象工具类 + */ +public class ObjectUtil extends org.springframework.util.ObjectUtils { + + /** + * 判断元素不为空 + * + * @param obj object + * @return boolean + */ + public static boolean isNotEmpty(@Nullable Object obj) { + return !ObjectUtil.isEmpty(obj); + } + + /** + * 获取非空的属性名称 + * @param source Object + * @return String[] + */ + public static String[] getNullPropertyNames(Object source) { + final BeanWrapper src = new BeanWrapperImpl(source); + java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors(); + + Set emptyNames = new HashSet<>(); + for (java.beans.PropertyDescriptor pd : pds) { + Object srcValue = src.getPropertyValue(pd.getName()); + + if (srcValue == null) { + emptyNames.add(pd.getName()); + } else if (srcValue instanceof String) { + if (StringUtil.isBlank((String) srcValue)) { + emptyNames.add(pd.getName()); + } + } + } + String[] result = new String[emptyNames.size()]; + return emptyNames.toArray(result); + } + + public static boolean isArray(Object obj) { + return obj!=null && obj.getClass().isArray(); + } +} diff --git a/src/main/java/club/lemos/common/utils/RandomType.java b/src/main/java/club/lemos/common/utils/RandomType.java new file mode 100644 index 0000000..857dc2c --- /dev/null +++ b/src/main/java/club/lemos/common/utils/RandomType.java @@ -0,0 +1,11 @@ +package club.lemos.common.utils; + +/** + * 生成的随机数类型 + */ +public enum RandomType { + /** + * INT STRING ALL + */ + INT, STRING, ALL +} diff --git a/src/main/java/club/lemos/common/utils/Snowflake.java b/src/main/java/club/lemos/common/utils/Snowflake.java new file mode 100644 index 0000000..c2f07fb --- /dev/null +++ b/src/main/java/club/lemos/common/utils/Snowflake.java @@ -0,0 +1,206 @@ +package club.lemos.common.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.net.InetAddress; +import java.util.concurrent.ThreadLocalRandom; + +@Slf4j +public final class Snowflake { + + /** + * 起始时间戳 + **/ + private final static long START_TIME = 1519740777809L; + /** + * dataCenterId占用的位数:2 + **/ + private final static long DATA_CENTER_ID_BITS = 2L; + /** + * workerId占用的位数:8 + **/ + private final static long WORKER_ID_BITS = 8L; + /** + * 序列号占用的位数:12(表示只允许workId的范围为:0-4095) + **/ + private final static long SEQUENCE_BITS = 12L; + + /** + * workerId可以使用范围:0-255 + **/ + private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); + /** + * dataCenterId可以使用范围:0-3 + **/ + private final static long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS); + + private final static long WORKER_ID_SHIFT = SEQUENCE_BITS; + private final static long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; + private final static long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS; + + /** + * 用mask防止溢出:位与运算保证计算的结果范围始终是 0-4095 + **/ + private final static long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS); + + private final long workerId; + private final long dataCenterId; + private long sequence = 0L; + private long lastTimestamp = -1L; + + private static byte LAST_IP = 0; + private final boolean clock; + private final long timeOffset; + private final boolean randomSequence; + private final ThreadLocalRandom tlr = ThreadLocalRandom.current(); + + public Snowflake(long dataCenterId) { + this(dataCenterId, 0x000000FF & getLastIPAddress(), false, 5L, false); + log.debug("Snowflake Current Machine WorkerId {}", 0x000000FF & getLastIPAddress()); + } + + public Snowflake(long dataCenterId, boolean clock, boolean randomSequence) { + this(dataCenterId, 0x000000FF & getLastIPAddress(), clock, 5L, randomSequence); + } + + /** + * 基于Snowflake创建分布式ID生成器 + * + * @param dataCenterId 数据中心ID,数据范围为0~255 + * @param workerId 工作机器ID,数据范围为0~3 + * @param clock true表示解决高并发下获取时间戳的性能问题 + * @param timeOffset 允许时间回拨的毫秒量,建议5ms + * @param randomSequence true表示使用毫秒内的随机序列(超过范围则取余) + */ + public Snowflake(long dataCenterId, long workerId, boolean clock, long timeOffset, boolean randomSequence) { + if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) { + throw new IllegalArgumentException("Data Center Id can't be greater than " + + MAX_DATA_CENTER_ID + " or less than 0"); + } + if (workerId > MAX_WORKER_ID || workerId < 0) { + throw new IllegalArgumentException("Worker Id can't be greater than " + + MAX_WORKER_ID + " or less than 0"); + } + + this.workerId = workerId; + this.dataCenterId = dataCenterId; + this.clock = clock; + this.timeOffset = timeOffset; + this.randomSequence = randomSequence; + } + + /** + * 获取ID + * + * @return long + */ + public synchronized Long nextId() { + long currentTimestamp = this.timeGen(); + + // 闰秒:如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常 + if (currentTimestamp < lastTimestamp) { + // 校验时间偏移回拨量 + long offset = lastTimestamp - currentTimestamp; + if (offset > timeOffset) { + throw new RuntimeException("Clock moved backwards, refusing to generate id for [" + offset + "ms]"); + } + + try { + // 时间回退timeOffset毫秒内,则允许等待2倍的偏移量后重新获取,解决小范围的时间回拨问题 + this.wait(offset << 1); + } catch (Exception e) { + throw new RuntimeException(e); + } + // 再次获取 + currentTimestamp = this.timeGen(); + // 再次校验 + if (currentTimestamp < lastTimestamp) { + throw new RuntimeException("Clock moved backwards, refusing to generate id for [" + offset + "ms]"); + } + } + + // 同一毫秒内序列直接自增 + if (lastTimestamp == currentTimestamp) { + // randomSequence为true表示随机生成允许范围内的序列起始值并取余数,否则毫秒内起始值为0L开始自增 + long tempSequence = sequence + 1; + if (randomSequence && tempSequence > SEQUENCE_MASK) { + tempSequence = tempSequence % SEQUENCE_MASK; + } + + // 通过位与运算保证计算的结果范围始终是 0-4095 + sequence = tempSequence & SEQUENCE_MASK; + if (sequence == 0) { + currentTimestamp = this.tilNextMillis(lastTimestamp); + } + } else { + // randomSequence为true表示随机生成允许范围内的序列起始值,否则毫秒内起始值为0L开始自增 + sequence = randomSequence ? tlr.nextLong(SEQUENCE_MASK + 1) : 0L; + } + + lastTimestamp = currentTimestamp; + long currentOffsetTime = currentTimestamp - START_TIME; + + /* + * 1.左移运算是为了将数值移动到对应的段(41、5、5,12那段因为本来就在最右,因此不用左移) + * 2.然后对每个左移后的值(la、lb、lc、sequence)做位或运算,是为了把各个短的数据合并起来,合并成一个二进制数 + * 3.最后转换成10进制,就是最终生成的id + */ + return (currentOffsetTime << TIMESTAMP_LEFT_SHIFT) | + // 数据中心位 + (dataCenterId << DATA_CENTER_ID_SHIFT) | + // 工作ID位 + (workerId << WORKER_ID_SHIFT) | + // 毫秒序列化位 + sequence; + } + + /** + * 保证返回的毫秒数在参数之后(阻塞到下一个毫秒,直到获得新的时间戳)——CAS + * + * @param lastTimestamp last timestamp + * @return next millis + */ + private long tilNextMillis(long lastTimestamp) { + long timestamp = this.timeGen(); + while (timestamp <= lastTimestamp) { + // 如果发现时间回拨,则自动重新获取(可能会处于无限循环中) + timestamp = this.timeGen(); + } + + return timestamp; + } + + /** + * 获得系统当前毫秒时间戳 + * + * @return timestamp 毫秒时间戳 + */ + private long timeGen() { + return System.currentTimeMillis(); + } + + /** + * 用IP地址最后几个字节标示 + *

+ * eg:192.168.1.30->30 + * + * @return last IP + */ + private static byte getLastIPAddress() { + if (LAST_IP != 0) { + return LAST_IP; + } + + try { + InetAddress inetAddress = InetAddress.getLocalHost(); + byte[] addressByte = inetAddress.getAddress(); + LAST_IP = addressByte[addressByte.length - 1]; + } catch (Exception e) { + throw new RuntimeException("Unknown Host Exception", e); + } + + return LAST_IP; + } + +} + diff --git a/src/main/java/club/lemos/common/utils/StreamUtil.java b/src/main/java/club/lemos/common/utils/StreamUtil.java new file mode 100644 index 0000000..236132c --- /dev/null +++ b/src/main/java/club/lemos/common/utils/StreamUtil.java @@ -0,0 +1,6 @@ +package club.lemos.common.utils; + +import org.springframework.util.StreamUtils; + +public class StreamUtil extends StreamUtils { +} diff --git a/src/main/java/club/lemos/common/utils/StringPool.java b/src/main/java/club/lemos/common/utils/StringPool.java new file mode 100644 index 0000000..a96ac11 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/StringPool.java @@ -0,0 +1,75 @@ +package club.lemos.common.utils; + +public interface StringPool { + + String AMPERSAND = "&"; + String AND = "and"; + String AT = "@"; + String ASTERISK = "*"; + String STAR = ASTERISK; + String BACK_SLASH = "\\"; + String COLON = ":"; + String COMMA = ","; + String DASH = "-"; + String DOLLAR = "$"; + String DOT = "."; + String DOTDOT = ".."; + String DOT_CLASS = ".class"; + String DOT_JAVA = ".java"; + String EMPTY = ""; + String EQUALS = "="; + String FALSE = "false"; + String SLASH = "/"; + String HASH = "#"; + String HAT = "^"; + String LEFT_BRACE = "{"; + String LEFT_BRACKET = "("; + String LEFT_CHEV = "<"; + String NEWLINE = "\n"; + String N = "n"; + String NO = "no"; + String NULL = "null"; + String OFF = "off"; + String ON = "on"; + String PERCENT = "%"; + String PIPE = "|"; + String PLUS = "+"; + String QUESTION_MARK = "?"; + String EXCLAMATION_MARK = "!"; + String QUOTE = "\""; + String RETURN = "\r"; + String TAB = "\t"; + String RIGHT_BRACE = "}"; + String RIGHT_BRACKET = ")"; + String RIGHT_CHEV = ">"; + String SEMICOLON = ";"; + String SINGLE_QUOTE = "'"; + String BACKTICK = "`"; + String SPACE = " "; + String TILDA = "~"; + String LEFT_SQ_BRACKET = "["; + String RIGHT_SQ_BRACKET = "]"; + String TRUE = "true"; + String UNDERSCORE = "_"; + String UTF_8 = "UTF-8"; + String US_ASCII = "US-ASCII"; + String ISO_8859_1 = "ISO-8859-1"; + String Y = "y"; + String YES = "yes"; + String ONE = "1"; + String ZERO = "0"; + String DOLLAR_LEFT_BRACE = "${"; + String CRLF = "\r\n"; + + String HTML_NBSP = " "; + String HTML_AMP = "&"; + String HTML_QUOTE = """; + String HTML_LT = "<"; + String HTML_GT = ">"; + + // ---------------------------------------------------------------- array + + String[] EMPTY_ARRAY = new String[0]; + + byte[] BYTES_NEW_LINE = "\n".getBytes(); +} diff --git a/src/main/java/club/lemos/common/utils/StringUtil.java b/src/main/java/club/lemos/common/utils/StringUtil.java new file mode 100644 index 0000000..b7ea379 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/StringUtil.java @@ -0,0 +1,454 @@ +package club.lemos.common.utils; + +import org.springframework.util.Assert; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class StringUtil extends org.springframework.util.StringUtils { + + /** + * Check whether the given {@code CharSequence} contains actual text. + *

More specifically, this method returns {@code true} if the + * {@code CharSequence} is not {@code null}, its length is greater than + * 0, and it contains at least one non-whitespace character. + *

+     * StringUtil.isBlank(null) = true
+     * StringUtil.isBlank("") = true
+     * StringUtil.isBlank(" ") = true
+     * StringUtil.isBlank("12345") = false
+     * StringUtil.isBlank(" 12345 ") = false
+     * 
+ * + * @param cs the {@code CharSequence} to check (may be {@code null}) + * @return {@code true} if the {@code CharSequence} is not {@code null}, + * its length is greater than 0, and it does not contain whitespace only + * @see Character#isWhitespace + */ + public static boolean isBlank(final CharSequence cs) { + return !StringUtil.hasText(cs); + } + + /** + *

Checks if a CharSequence is not empty (""), not null and not whitespace only.

+ *
+     * StringUtil.isNotBlank(null)	  = false
+     * StringUtil.isNotBlank("")		= false
+     * StringUtil.isNotBlank(" ")	   = false
+     * StringUtil.isNotBlank("bob")	 = true
+     * StringUtil.isNotBlank("  bob  ") = true
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is + * not empty and not null and not whitespace + * @see Character#isWhitespace + */ + public static boolean isNotBlank(final CharSequence cs) { + return StringUtil.hasText(cs); + } + + /** + * 有 任意 一个 Blank + * + * @param css CharSequence + * @return boolean + */ + public static boolean isAnyBlank(final CharSequence... css) { + if (ObjectUtil.isEmpty(css)) { + return true; + } + return Stream.of(css).anyMatch(StringUtil::isBlank); + } + + /** + * 是否全非 Blank + * + * @param css CharSequence + * @return boolean + */ + public static boolean isNoneBlank(final CharSequence... css) { + if (ObjectUtil.isEmpty(css)) { + return false; + } + return Stream.of(css).allMatch(StringUtil::isNotBlank); + } + + /** + * 判断一个字符串是否是数字 + * + * @param cs the CharSequence to check, may be null + * @return {boolean} + */ + public static boolean isNumeric(final CharSequence cs) { + if (isBlank(cs)) { + return false; + } + for (int i = cs.length(); --i >= 0; ) { + int chr = cs.charAt(i); + if (chr < 48 || chr > 57) { + return false; + } + } + return true; + } + + /** + * Convert a {@code Collection} into a delimited {@code String} (e.g., CSV). + *

Useful for {@code toString()} implementations. + * + * @param coll the {@code Collection} to convert + * @return the delimited {@code String} + */ + public static String join(Collection coll) { + return StringUtil.collectionToCommaDelimitedString(coll); + } + + /** + * Convert a {@code Collection} into a delimited {@code String} (e.g. CSV). + *

Useful for {@code toString()} implementations. + * + * @param coll the {@code Collection} to convert + * @param delim the delimiter to use (typically a ",") + * @return the delimited {@code String} + */ + public static String join(Collection coll, String delim) { + return StringUtil.collectionToDelimitedString(coll, delim); + } + + /** + * Convert a {@code String} array into a comma delimited {@code String} + * (i.e., CSV). + *

Useful for {@code toString()} implementations. + * + * @param arr the array to display + * @return the delimited {@code String} + */ + public static String join(Object[] arr) { + return StringUtil.arrayToCommaDelimitedString(arr); + } + + /** + * Convert a {@code String} array into a delimited {@code String} (e.g. CSV). + *

Useful for {@code toString()} implementations. + * + * @param arr the array to display + * @param delim the delimiter to use (typically a ",") + * @return the delimited {@code String} + */ + public static String join(Object[] arr, String delim) { + return StringUtil.arrayToDelimitedString(arr, delim); + } + + /** + * 清理字符串,清理出某些不可见字符 + * + * @param txt 字符串 + * @return {String} + */ + public static String cleanChars(String txt) { + return txt.replaceAll("[  `·•�\\f\\t\\v\\s]", ""); + } + + + private static final String S_INT = "0123456789"; + private static final String S_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final String S_ALL = S_INT + S_STR; + + /** + * 随机数生成 + * + * @param count 字符长度 + * @return 随机数 + */ + public static String random(int count) { + return StringUtil.random(count, RandomType.ALL); + } + + /** + * 随机数生成 + * + * @param count 字符长度 + * @param randomType 随机数类别 + * @return 随机数 + */ + public static String random(int count, RandomType randomType) { + if (count == 0) { + return ""; + } + Assert.isTrue(count > 0, "Requested random string length " + count + " is less than 0."); + final ThreadLocalRandom random = ThreadLocalRandom.current(); + char[] buffer = new char[count]; + for (int i = 0; i < count; i++) { + if (RandomType.INT == randomType) { + buffer[i] = S_INT.charAt(random.nextInt(S_INT.length())); + } else if (RandomType.STRING == randomType) { + buffer[i] = S_STR.charAt(random.nextInt(S_STR.length())); + } else { + buffer[i] = S_ALL.charAt(random.nextInt(S_ALL.length())); + } + } + return new String(buffer); + } + + /** + * 创建StringBuilder对象 + * + * @param sb 初始StringBuilder + * @param strs 初始字符串列表 + * @return StringBuilder对象 + */ + public static StringBuilder appendBuilder(StringBuilder sb, CharSequence... strs) { + for (CharSequence str : strs) { + sb.append(str); + } + return sb; + } + + /** + * 去掉指定前缀 + * + * @param str 字符串 + * @param prefix 前缀 + * @return 切掉后的字符串,若前缀不是 preffix, 返回原字符串 + */ + public static String removePrefix(CharSequence str, CharSequence prefix) { + if (isEmpty(str) || isEmpty(prefix)) { + return StringPool.EMPTY; + } + final String str2 = str.toString(); + if (str2.startsWith(prefix.toString())) { + return subSuf(str2, prefix.length()); + } + return str2; + } + + /** + * 忽略大小写去掉指定前缀 + * + * @param str 字符串 + * @param prefix 前缀 + * @return 切掉后的字符串,若前缀不是 prefix, 返回原字符串 + */ + public static String removePrefixIgnoreCase(CharSequence str, CharSequence prefix) { + if (isEmpty(str) || isEmpty(prefix)) { + return StringPool.EMPTY; + } + final String str2 = str.toString(); + if (str2.toLowerCase().startsWith(prefix.toString().toLowerCase())) { + return subSuf(str2, prefix.length()); + } + return str2; + } + + /** + * 去掉指定后缀 + * + * @param str 字符串 + * @param suffix 后缀 + * @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串 + */ + public static String removeSuffix(CharSequence str, CharSequence suffix) { + if (isEmpty(str) || isEmpty(suffix)) { + return ""; + } + final String str2 = str.toString(); + if (str2.endsWith(suffix.toString())) { + return subPre(str2, str2.length() - suffix.length()); + } + return str2; + } + + /** + * 切割指定位置之后部分的字符串 + * + * @param string 字符串 + * @param fromIndex 切割开始的位置(包括) + * @return 切割后后剩余的后半部分字符串 + */ + public static String subSuf(CharSequence string, int fromIndex) { + if (isEmpty(string)) { + return null; + } + return sub(string, fromIndex, string.length()); + } + + /** + * 切割指定位置之前部分的字符串 + * + * @param string 字符串 + * @param toIndex 切割到的位置(不包括) + * @return 切割后的剩余的前半部分字符串 + */ + public static String subPre(CharSequence string, int toIndex) { + return sub(string, 0, toIndex); + } + + /** + * 切割指定位置之前部分的字符串且保留后缀 + * + * @param string 字符串 + * @param toIndex 切割到的位置(不包括) + * @return 切割后的剩余的前半部分字符串且保留后缀 + */ + public static String subPreAndKeepSuffix(CharSequence string, int toIndex) { + final String str2 = string.toString(); + String suffix = sub(str2, str2.lastIndexOf("."), str2.length()); + String pre = removeSuffix(string, suffix); + return subPre(pre, toIndex) + suffix; + } + + /** + * 改进JDK subString
+ * index从0开始计算,最后一个字符为-1
+ * 如果from和to位置一样,返回 ""
+ * 如果from或to为负数,则按照length从后向前数位置,如果绝对值大于字符串长度,则from归到0,to归到length
+ * 如果经过修正的index中from大于to,则互换from和to example:
+ * abcdefgh 2 3 =》 c
+ * abcdefgh 2 -3 =》 cde
+ * + * @param str String + * @param fromIndex 开始的index(包括) + * @param toIndex 结束的index(不包括) + * @return 字串 + */ + public static String sub(CharSequence str, int fromIndex, int toIndex) { + if (isEmpty(str)) { + return ""; + } + int len = str.length(); + + if (fromIndex < 0) { + fromIndex = len + fromIndex; + if (fromIndex < 0) { + fromIndex = 0; + } + } else if (fromIndex > len) { + fromIndex = len; + } + + if (toIndex < 0) { + toIndex = len + toIndex; + if (toIndex < 0) { + toIndex = len; + } + } else if (toIndex > len) { + toIndex = len; + } + + if (toIndex < fromIndex) { + int tmp = fromIndex; + fromIndex = toIndex; + toIndex = tmp; + } + + if (fromIndex == toIndex) { + return ""; + } + + return str.toString().substring(fromIndex, toIndex); + } + + public static List idsToList(String ids) { + return idsToList(ids, ","); + } + + public static List idsToList(String ids, String separator) { + return Arrays.stream(ids.split(separator)) + .map(Long::parseLong).collect(Collectors.toList()); + } + +// ---------------------------------------------------------------- starts and ends + + /** + * Tests if this string starts with the specified prefix with ignored case. + * + * @param src source string to test + * @param subS starting substring + * @return true if the character sequence represented by the argument is + * a prefix of the character sequence represented by this string; + * false otherwise. + */ + public static boolean startsWithIgnoreCase(final String src, final String subS) { + return startsWithIgnoreCase(src, subS, 0); + } + + /** + * Tests if this string starts with the specified prefix with ignored case + * and with the specified prefix beginning a specified index. + * + * @param src source string to test + * @param subS starting substring + * @param startIndex index from where to test + * @return true if the character sequence represented by the argument is + * a prefix of the character sequence represented by this string; + * false otherwise. + */ + public static boolean startsWithIgnoreCase(final String src, final String subS, final int startIndex) { + String sub = subS.toLowerCase(); + int sublen = sub.length(); + if (startIndex + sublen > src.length()) { + return false; + } + int j = 0; + int i = startIndex; + return equalsIgnoreCase(src, sub, sublen, j, i); + } + + /** + * Tests if this string ends with the specified suffix. + * + * @param src String to test + * @param subS suffix + * @return true if the character sequence represented by the argument is + * a suffix of the character sequence represented by this object; + * false otherwise. + */ + public static boolean endsWithIgnoreCase(final String src, final String subS) { + String sub = subS.toLowerCase(); + int sublen = sub.length(); + int j = 0; + int i = src.length() - sublen; + if (i < 0) { + return false; + } + return equalsIgnoreCase(src, sub, sublen, j, i); + } + + private static boolean equalsIgnoreCase(String src, String sub, int sublen, int j, int i) { + while (j < sublen) { + char source = Character.toLowerCase(src.charAt(i)); + if (sub.charAt(j) != source) { + return false; + } + j++; + i++; + } + return true; + } + + /** + * Returns if string starts with given character. + */ + public static boolean startsWithChar(final String s, final char c) { + if (s.length() == 0) { + return false; + } + return s.charAt(0) == c; + } + + /** + * Returns if string ends with provided character. + */ + public static boolean endsWithChar(final String s, final char c) { + if (s.length() == 0) { + return false; + } + return s.charAt(s.length() - 1) == c; + } +} + diff --git a/src/main/java/club/lemos/common/utils/WebUtil.java b/src/main/java/club/lemos/common/utils/WebUtil.java new file mode 100644 index 0000000..c541b06 --- /dev/null +++ b/src/main/java/club/lemos/common/utils/WebUtil.java @@ -0,0 +1,97 @@ +package club.lemos.common.utils; + +import club.lemos.common.jackson.JsonUtil; +import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +public class WebUtil extends org.springframework.web.util.WebUtils { + + public static final String UN_KNOWN = "unknown"; + + /** + * 获取 HttpServletRequest + * + * @return {HttpServletRequest} + */ + public static HttpServletRequest getRequest() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + return (requestAttributes == null) ? null : ((ServletRequestAttributes) requestAttributes).getRequest(); + } + + /** + * 返回json + * + * @param response HttpServletResponse + * @param result 结果对象 + */ + public static void renderJson(HttpServletResponse response, Object result) { + renderJson(response, result, MediaType.APPLICATION_JSON_VALUE); + } + + /** + * 返回json + * + * @param response HttpServletResponse + * @param result 结果对象 + * @param contentType contentType + */ + public static void renderJson(HttpServletResponse response, Object result, String contentType) { + response.setCharacterEncoding("UTF-8"); + response.setContentType(contentType); + try (PrintWriter out = response.getWriter()) { + out.append(JsonUtil.toJson(result)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取ip + * + * @return {String} + */ + public static String getIP() { + return getIP(WebUtil.getRequest()); + } + + /** + * 获取ip + * + * @param request HttpServletRequest + * @return {String} + */ + @Nullable + public static String getIP(HttpServletRequest request) { + Assert.notNull(request, "HttpServletRequest is null"); + String ip = request.getHeader("X-Requested-For"); + if (StringUtil.isBlank(ip) || UN_KNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + if (StringUtil.isBlank(ip) || UN_KNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (StringUtil.isBlank(ip) || UN_KNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (StringUtil.isBlank(ip) || UN_KNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (StringUtil.isBlank(ip) || UN_KNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (StringUtil.isBlank(ip) || UN_KNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return StringUtil.isBlank(ip) ? null : ip.split(",")[0]; + } + +} diff --git a/src/main/java/club/lemos/common/validate/GroupA.java b/src/main/java/club/lemos/common/validate/GroupA.java new file mode 100644 index 0000000..65554de --- /dev/null +++ b/src/main/java/club/lemos/common/validate/GroupA.java @@ -0,0 +1,4 @@ +package club.lemos.common.validate; + +public interface GroupA { +} diff --git a/src/main/java/club/lemos/common/validate/GroupB.java b/src/main/java/club/lemos/common/validate/GroupB.java new file mode 100644 index 0000000..df20c62 --- /dev/null +++ b/src/main/java/club/lemos/common/validate/GroupB.java @@ -0,0 +1,4 @@ +package club.lemos.common.validate; + +public interface GroupB { +} diff --git a/src/main/java/club/lemos/common/validate/GroupC.java b/src/main/java/club/lemos/common/validate/GroupC.java new file mode 100644 index 0000000..4595b30 --- /dev/null +++ b/src/main/java/club/lemos/common/validate/GroupC.java @@ -0,0 +1,4 @@ +package club.lemos.common.validate; + +public interface GroupC { +} diff --git a/src/main/java/club/lemos/common/validate/GroupD.java b/src/main/java/club/lemos/common/validate/GroupD.java new file mode 100644 index 0000000..3677344 --- /dev/null +++ b/src/main/java/club/lemos/common/validate/GroupD.java @@ -0,0 +1,4 @@ +package club.lemos.common.validate; + +public interface GroupD { +}