feat: 项目初始化
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
### IDEA ###
|
||||||
|
.DS_Store
|
||||||
|
~/*
|
||||||
|
.idea/*
|
||||||
|
*.iml
|
||||||
|
*/target/*
|
||||||
|
*/*.iml
|
||||||
|
/.gradle/
|
||||||
|
/application.pid
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
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:
|
||||||
|
|
||||||
|
You must give any other recipients of the Work or Derivative Works a copy of
|
||||||
|
this License; and
|
||||||
|
You must cause any modified files to carry prominent notices stating that You
|
||||||
|
changed the files; and
|
||||||
|
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
|
||||||
|
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 2019 Zheng Jie
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>cutejava</artifactId>
|
||||||
|
<groupId>cn.odboy</groupId>
|
||||||
|
<version>1.4.1</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>cutejava-framework</artifactId>
|
||||||
|
<name>公共模块</name>
|
||||||
|
</project>
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package cn.odboy.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于标记匿名访问方法
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface AnonymousAccess {
|
||||||
|
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.annotation;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation for mapping HTTP {@code DELETE} requests onto specific handler methods. 支持匿名访问 DeleteMapping
|
||||||
|
*
|
||||||
|
* @see AnonymousGetMapping
|
||||||
|
* @see AnonymousPostMapping
|
||||||
|
* @see AnonymousPutMapping
|
||||||
|
* @see AnonymousPatchMapping
|
||||||
|
* @see RequestMapping
|
||||||
|
*/
|
||||||
|
@AnonymousAccess
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@RequestMapping(method = RequestMethod.DELETE)
|
||||||
|
public @interface AnonymousDeleteMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#name}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#value}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#path}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] path() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#params}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] params() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#headers}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] headers() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#consumes}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] consumes() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#produces}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] produces() default {};
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.annotation;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation for mapping HTTP {@code GET} requests onto specific handler methods.
|
||||||
|
* <p>
|
||||||
|
* 支持匿名访问 GetMapping
|
||||||
|
*
|
||||||
|
* @see RequestMapping
|
||||||
|
*/
|
||||||
|
@AnonymousAccess
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@RequestMapping(method = RequestMethod.GET)
|
||||||
|
public @interface AnonymousGetMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#name}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#value}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#path}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] path() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#params}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] params() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#headers}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] headers() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#consumes}.
|
||||||
|
*
|
||||||
|
* @since 4.3.5
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] consumes() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#produces}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] produces() default {};
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.annotation;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation for mapping HTTP {@code PATCH} requests onto specific handler methods. 支持匿名访问 PatchMapping
|
||||||
|
*
|
||||||
|
* @see AnonymousGetMapping
|
||||||
|
* @see AnonymousPostMapping
|
||||||
|
* @see AnonymousPutMapping
|
||||||
|
* @see AnonymousDeleteMapping
|
||||||
|
* @see RequestMapping
|
||||||
|
*/
|
||||||
|
@AnonymousAccess
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@RequestMapping(method = RequestMethod.PATCH)
|
||||||
|
public @interface AnonymousPatchMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#name}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#value}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#path}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] path() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#params}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] params() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#headers}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] headers() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#consumes}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] consumes() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#produces}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] produces() default {};
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.annotation;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation for mapping HTTP {@code POST} requests onto specific handler methods. 支持匿名访问 PostMapping
|
||||||
|
*
|
||||||
|
* @see AnonymousGetMapping
|
||||||
|
* @see AnonymousPostMapping
|
||||||
|
* @see AnonymousPutMapping
|
||||||
|
* @see AnonymousDeleteMapping
|
||||||
|
* @see RequestMapping
|
||||||
|
*/
|
||||||
|
@AnonymousAccess
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@RequestMapping(method = RequestMethod.POST)
|
||||||
|
public @interface AnonymousPostMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#name}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#value}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#path}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] path() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#params}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] params() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#headers}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] headers() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#consumes}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] consumes() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#produces}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] produces() default {};
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.annotation;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation for mapping HTTP {@code PUT} requests onto specific handler methods. 支持匿名访问 PutMapping
|
||||||
|
*
|
||||||
|
* @see AnonymousGetMapping
|
||||||
|
* @see AnonymousPostMapping
|
||||||
|
* @see AnonymousPutMapping
|
||||||
|
* @see AnonymousDeleteMapping
|
||||||
|
* @see RequestMapping
|
||||||
|
*/
|
||||||
|
@AnonymousAccess
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@RequestMapping(method = RequestMethod.PUT)
|
||||||
|
public @interface AnonymousPutMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#name}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#value}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#path}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] path() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#params}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] params() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#headers}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] headers() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#consumes}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] consumes() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for {@link RequestMapping#produces}.
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = RequestMapping.class) String[] produces() default {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用参数
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-07-23
|
||||||
|
*/
|
||||||
|
public class CsArgs {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class FindByLongId extends CsObject {
|
||||||
|
@NotNull(message = "id必填")
|
||||||
|
private Long id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class DeleteById extends CsObject {
|
||||||
|
@NotNull(message = "id必填")
|
||||||
|
private Long id;
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class CsBaseCreateTimeTb extends CsObject {
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class CsBaseTimeTb extends CsObject {
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true)
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class CsBaseUpdateTimeTb extends CsObject {
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true)
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class CsBaseUserCreateTimeTb extends CsObject {
|
||||||
|
|
||||||
|
@CreatedBy
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建人", hidden = true)
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/* 分组校验 */
|
||||||
|
public @interface Create {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分组校验 */
|
||||||
|
public @interface Update {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
ToStringBuilder builder = new ToStringBuilder(this);
|
||||||
|
Field[] fields = this.getClass().getDeclaredFields();
|
||||||
|
try {
|
||||||
|
for (Field f : fields) {
|
||||||
|
f.setAccessible(true);
|
||||||
|
builder.append(f.getName(), f.get(this)).append("\n");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
builder.append("toString builder encounter an error");
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑删除通用模型
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CsBaseUserTimeLogicTb extends CsObject {
|
||||||
|
@CreatedBy
|
||||||
|
@TableField(value = "create_by", fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建人", hidden = true)
|
||||||
|
private String createBy;
|
||||||
|
@LastModifiedBy
|
||||||
|
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@ApiModelProperty(value = "更新人", hidden = true)
|
||||||
|
private String updateBy;
|
||||||
|
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建时间", hidden = true)
|
||||||
|
private Date createTime;
|
||||||
|
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@ApiModelProperty(value = "更新时间", hidden = true)
|
||||||
|
private Date updateTime;
|
||||||
|
@ApiModelProperty(value = "数据有效性", hidden = true)
|
||||||
|
@TableLogic
|
||||||
|
@TableField("available")
|
||||||
|
private Integer available;
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.springframework.data.annotation.CreatedBy;
|
||||||
|
import org.springframework.data.annotation.LastModifiedBy;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class CsBaseUserTimeTb extends CsObject {
|
||||||
|
|
||||||
|
@CreatedBy
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建人", hidden = true)
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
@LastModifiedBy
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@ApiModelProperty(value = "更新人", hidden = true)
|
||||||
|
private String updateBy;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@ApiModelProperty(value = "创建时间: yyyy-MM-dd HH:mm:ss", hidden = true)
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
@ApiModelProperty(value = "更新时间: yyyy-MM-dd HH:mm:ss", hidden = true)
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/* 分组校验 */
|
||||||
|
public @interface Create {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分组校验 */
|
||||||
|
public @interface Update {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
ToStringBuilder builder = new ToStringBuilder(this);
|
||||||
|
Field[] fields = this.getClass().getDeclaredFields();
|
||||||
|
try {
|
||||||
|
for (Field f : fields) {
|
||||||
|
f.setAccessible(true);
|
||||||
|
builder.append(f.getName(), f.get(this)).append("\n");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
builder.append("toString builder encounter an error");
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础对象
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2022-01-12
|
||||||
|
*/
|
||||||
|
public class CsObject implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据分页参数
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CsPageArgs<T> implements Serializable {
|
||||||
|
@NotNull(message = "参数page不能为空")
|
||||||
|
@Min(value = 1, message = "参数page最小值为1")
|
||||||
|
@ApiModelProperty(value = "页码", example = "1")
|
||||||
|
private Integer page;
|
||||||
|
@NotNull(message = "参数size不能为空")
|
||||||
|
@Min(value = 1, message = "参数size最小值为1")
|
||||||
|
@ApiModelProperty(value = "每页数据量", example = "10")
|
||||||
|
private Integer size;
|
||||||
|
private T args;
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结果封装类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class CsPageResult<T> extends CsObject {
|
||||||
|
private List<T> content;
|
||||||
|
private long totalElements;
|
||||||
|
|
||||||
|
public static <T> CsPageResult<T> emptyListData() {
|
||||||
|
CsPageResult<T> result = new CsPageResult<>();
|
||||||
|
result.setTotalElements(0);
|
||||||
|
result.setContent(new ArrayList<>());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> CsPageResult<T> listData(IPage<T> page) {
|
||||||
|
CsPageResult<T> result = new CsPageResult<>();
|
||||||
|
result.setTotalElements(page.getTotal());
|
||||||
|
result.setContent(page.getRecords());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.base;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元数据选项
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class CsSelectOptionVo extends CsObject {
|
||||||
|
private String label;
|
||||||
|
private String value;
|
||||||
|
private Map<String, Object> ext;
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码配置枚举
|
||||||
|
*/
|
||||||
|
public enum CaptchaCodeEnum {
|
||||||
|
/**
|
||||||
|
* 算数
|
||||||
|
*/
|
||||||
|
ARITHMETIC,
|
||||||
|
/**
|
||||||
|
* 中文
|
||||||
|
*/
|
||||||
|
CHINESE,
|
||||||
|
/**
|
||||||
|
* 中文闪图
|
||||||
|
*/
|
||||||
|
CHINESE_GIF,
|
||||||
|
/**
|
||||||
|
* 闪图
|
||||||
|
*/
|
||||||
|
GIF,
|
||||||
|
/**
|
||||||
|
* 静态
|
||||||
|
*/
|
||||||
|
SPEC
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum FileTypeEnum {
|
||||||
|
/* 文件类型 */
|
||||||
|
IMAGE("image", "图片"),
|
||||||
|
DOC("doc", "文档"),
|
||||||
|
VOICE("voice", "音频"),
|
||||||
|
VIDEO("video", "视频"),
|
||||||
|
OTHER("other", "其他");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
}
|
||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.constant;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum RequestMethodEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜寻 @AnonymousGetMapping
|
||||||
|
*/
|
||||||
|
GET("GET"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜寻 @AnonymousPostMapping
|
||||||
|
*/
|
||||||
|
POST("POST"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜寻 @AnonymousPutMapping
|
||||||
|
*/
|
||||||
|
PUT("PUT"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜寻 @AnonymousPatchMapping
|
||||||
|
*/
|
||||||
|
PATCH("PATCH"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜寻 @AnonymousDeleteMapping
|
||||||
|
*/
|
||||||
|
DELETE("DELETE"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 否则就是所有 Request 接口都放行
|
||||||
|
*/
|
||||||
|
ALL("All");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request 类型
|
||||||
|
*/
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
public static RequestMethodEnum find(String type) {
|
||||||
|
for (RequestMethodEnum value : RequestMethodEnum.values()) {
|
||||||
|
if (value.getType().equals(type)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常用静态常量
|
||||||
|
*/
|
||||||
|
public interface SystemConst {
|
||||||
|
/**
|
||||||
|
* win 系统
|
||||||
|
*/
|
||||||
|
String WIN = "win";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mac 系统
|
||||||
|
*/
|
||||||
|
String MAC = "mac";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求头名称
|
||||||
|
*/
|
||||||
|
String HEADER_NAME = "Authorization";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token前缀
|
||||||
|
*/
|
||||||
|
String TOKEN_PREFIX = "Bearer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小数点
|
||||||
|
*/
|
||||||
|
String SYMBOL_DOT = ".";
|
||||||
|
String SYMBOL_EQUAL = "=";
|
||||||
|
String SYMBOL_ADD = "+";
|
||||||
|
String SYMBOL_SUBTRACT = "-";
|
||||||
|
String SYMBOL_AT = "@";
|
||||||
|
String PROPERTY_OS_NAME = "os.name";
|
||||||
|
String OS_NAME_WINDOWS = "Windows";
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.context;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bean加载完毕 监听
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-16
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class BeanLoadFinishListener implements ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||||
|
log.info("所有的Bean都加载完毕了");
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.context;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要目的是输出应用的服务地址
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2024-04-20
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class BootApplication {
|
||||||
|
@Bean
|
||||||
|
public CsSpringBeanHolder springBeanHolder() {
|
||||||
|
return new CsSpringBeanHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void inited(ConfigurableApplicationContext application) throws UnknownHostException {
|
||||||
|
Environment env = application.getEnvironment();
|
||||||
|
String ip = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
String port = env.getProperty("server.port");
|
||||||
|
String path = StrUtil.isEmpty(env.getProperty("config.servlet.context-path")) ?
|
||||||
|
"" :
|
||||||
|
env.getProperty("config.servlet.context-path");
|
||||||
|
log.info(
|
||||||
|
"\n----------------------------------------------------------\n\t" +
|
||||||
|
"Application is running! Access URLs:\n\t" + "Local: \t\thttp://localhost:" + port + path + "/\n\t" +
|
||||||
|
"External: \thttp://" + ip + ":" + port + path + "/\n\t" + "Swagger文档: \thttp://" + ip + ":" + port +
|
||||||
|
path + "/doc.html\n" + "----------------------------------------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.context;
|
||||||
|
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 HttpServletRequest
|
||||||
|
*/
|
||||||
|
public class CsRequestHolder {
|
||||||
|
|
||||||
|
public static HttpServletRequest getHttpServletRequest() {
|
||||||
|
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.context;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.mozilla.javascript.Context;
|
||||||
|
import org.mozilla.javascript.Scriptable;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脚本工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-09-23
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class CsScriptHelper {
|
||||||
|
public Object evalObj(String script, Map<String, Object> args) {
|
||||||
|
try (Context rhino = Context.enter()) {
|
||||||
|
Scriptable scope = rhino.initStandardObjects();
|
||||||
|
bindArgumentsToScope(scope, args);
|
||||||
|
Object result = rhino.evaluateString(scope, script, "JavaScript", 1, null);
|
||||||
|
log.info("执行结果: {}", Context.toString(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindArgumentsToScope(Scriptable scope, Map<?, ?> args) {
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<?, ?> entry : args.entrySet()) {
|
||||||
|
Object jsValue = Context.javaToJS(entry.getValue(), scope);
|
||||||
|
scope.put(entry.getKey().toString(), scope, jsValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindArgumentsToScope(Scriptable scope, Object args) {
|
||||||
|
if (args == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 单个对象参数
|
||||||
|
Object jsValue = Context.javaToJS(args, scope);
|
||||||
|
scope.put("args", scope, jsValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.context;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
public class CsSpringBeanHolder implements ApplicationContextAware, DisposableBean {
|
||||||
|
|
||||||
|
private static ApplicationContext applicationContext = null;
|
||||||
|
private static final List<CallBack> CALL_BACKS = new ArrayList<>();
|
||||||
|
private static boolean addCallback = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对 某些初始化方法, 在SpringContextHolder 未初始化时 提交回调方法。 在SpringContextHolder 初始化后, 进行回调使用
|
||||||
|
*
|
||||||
|
* @param callBack 回调函数
|
||||||
|
*/
|
||||||
|
public synchronized static void addCallBacks(CallBack callBack) {
|
||||||
|
if (addCallback) {
|
||||||
|
CsSpringBeanHolder.CALL_BACKS.add(callBack);
|
||||||
|
} else {
|
||||||
|
log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
|
||||||
|
callBack.executor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||||
|
*/
|
||||||
|
public static <T> T getBean(String name) {
|
||||||
|
assertContextInjected();
|
||||||
|
return (T) applicationContext.getBean(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
|
||||||
|
*/
|
||||||
|
public static <T> T getBean(Class<T> requiredType) {
|
||||||
|
assertContextInjected();
|
||||||
|
return applicationContext.getBean(requiredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取SpringBoot 配置信息
|
||||||
|
*
|
||||||
|
* @param property 属性key
|
||||||
|
* @param defaultValue 默认值
|
||||||
|
* @param requiredType 返回类型
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
|
||||||
|
T result = defaultValue;
|
||||||
|
try {
|
||||||
|
result = getBean(Environment.class).getProperty(property, requiredType);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取SpringBoot 配置信息
|
||||||
|
*
|
||||||
|
* @param property 属性key
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static String getProperties(String property) {
|
||||||
|
return getProperties(property, null, String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取SpringBoot 配置信息
|
||||||
|
*
|
||||||
|
* @param property 属性key
|
||||||
|
* @param requiredType 返回类型
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static <T> T getProperties(String property, Class<T> requiredType) {
|
||||||
|
return getProperties(property, null, requiredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查ApplicationContext不为空.
|
||||||
|
*/
|
||||||
|
private static void assertContextInjected() {
|
||||||
|
if (applicationContext == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"applicaitonContext属性未注入, 请在applicationContext" +
|
||||||
|
".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除SpringContextHolder中的ApplicationContext为Null.
|
||||||
|
*/
|
||||||
|
private static void clearHolder() {
|
||||||
|
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
|
||||||
|
applicationContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
CsSpringBeanHolder.clearHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
if (CsSpringBeanHolder.applicationContext != null) {
|
||||||
|
log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" +
|
||||||
|
CsSpringBeanHolder.applicationContext);
|
||||||
|
}
|
||||||
|
CsSpringBeanHolder.applicationContext = applicationContext;
|
||||||
|
if (addCallback) {
|
||||||
|
for (CallBack callBack : CsSpringBeanHolder.CALL_BACKS) {
|
||||||
|
callBack.executor();
|
||||||
|
}
|
||||||
|
CALL_BACKS.clear();
|
||||||
|
}
|
||||||
|
CsSpringBeanHolder.addCallback = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 @Service 的所有 bean 名称
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static List<String> getAllServiceBeanName() {
|
||||||
|
return new ArrayList<>(Arrays.asList(applicationContext.getBeanNamesForAnnotation(Service.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CallBack {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调执行方法
|
||||||
|
*/
|
||||||
|
void executor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本回调任务名称
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
default String getCallBackName() {
|
||||||
|
return Thread.currentThread().getId() + ":" + this.getClass().getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.doc;
|
||||||
|
|
||||||
|
import cn.odboy.constant.SystemConst;
|
||||||
|
import cn.odboy.framework.properties.AppProperties;
|
||||||
|
import cn.odboy.util.CsAnonTagUtil;
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||||
|
import springfox.documentation.builders.ApiInfoBuilder;
|
||||||
|
import springfox.documentation.builders.PathSelectors;
|
||||||
|
import springfox.documentation.service.*;
|
||||||
|
import springfox.documentation.spi.DocumentationType;
|
||||||
|
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||||
|
import springfox.documentation.spring.web.plugins.Docket;
|
||||||
|
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
|
||||||
|
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
|
||||||
|
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* api页面 /doc.html
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableSwagger2
|
||||||
|
public class SwaggerConfig {
|
||||||
|
@Value("${server.servlet.context-path:}")
|
||||||
|
private String apiPath;
|
||||||
|
@Autowired
|
||||||
|
private AppProperties properties;
|
||||||
|
@Autowired
|
||||||
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Docket createRestApi() {
|
||||||
|
return new Docket(DocumentationType.SWAGGER_2).enable(properties.getSwagger().getEnabled())
|
||||||
|
.pathMapping("/")
|
||||||
|
.apiInfo(apiInfo())
|
||||||
|
.select()
|
||||||
|
.paths(PathSelectors.regex("^(?!/error).*"))
|
||||||
|
.paths(PathSelectors.any())
|
||||||
|
.build()
|
||||||
|
//添加登陆认证
|
||||||
|
.securitySchemes(securitySchemes())
|
||||||
|
.securityContexts(securityContexts());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApiInfo apiInfo() {
|
||||||
|
return new ApiInfoBuilder().description("一个简单且易上手的自动化运维平台")
|
||||||
|
.title("CuteJava 接口文档")
|
||||||
|
.version("1.4.1")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SecurityScheme> securitySchemes() {
|
||||||
|
//设置请求头信息
|
||||||
|
List<SecurityScheme> securitySchemes = new ArrayList<>();
|
||||||
|
ApiKey apiKey = new ApiKey(SystemConst.HEADER_NAME, SystemConst.HEADER_NAME, "header");
|
||||||
|
securitySchemes.add(apiKey);
|
||||||
|
return securitySchemes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SecurityContext> securityContexts() {
|
||||||
|
//设置需要登录认证的路径
|
||||||
|
List<SecurityContext> securityContexts = new ArrayList<>();
|
||||||
|
securityContexts.add(getContextByPath());
|
||||||
|
return securityContexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecurityContext getContextByPath() {
|
||||||
|
Set<String> urls = CsAnonTagUtil.getAllAnonymousUrl(applicationContext);
|
||||||
|
urls = urls.stream().filter(url -> !"/".equals(url)).collect(Collectors.toSet());
|
||||||
|
String regExp = "^(?!" + apiPath + String.join("|" + apiPath, urls) + ").*$";
|
||||||
|
return SecurityContext.builder().securityReferences(defaultAuth()).operationSelector(o -> o.requestMappingPattern()
|
||||||
|
// 排除不需要认证的接口
|
||||||
|
.matches(regExp)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SecurityReference> defaultAuth() {
|
||||||
|
List<SecurityReference> securityReferences = new ArrayList<>();
|
||||||
|
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||||
|
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||||
|
authorizationScopes[0] = authorizationScope;
|
||||||
|
securityReferences.add(new SecurityReference(SystemConst.HEADER_NAME, authorizationScopes));
|
||||||
|
return securityReferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解决Springfox与SpringBoot集成后, WebMvcRequestHandlerProvider和WebFluxRequestHandlerProvider冲突问题
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
|
||||||
|
return new BeanPostProcessor() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
|
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
|
||||||
|
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
|
||||||
|
List<T> filteredMappings =
|
||||||
|
mappings.stream().filter(mapping -> mapping.getPatternParser() == null).collect(Collectors.toList());
|
||||||
|
mappings.clear();
|
||||||
|
mappings.addAll(filteredMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
|
||||||
|
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
|
||||||
|
if (field != null) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new IllegalStateException("Failed to access handlerMappings field", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.exception;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一异常处理
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class BadRequestException extends RuntimeException {
|
||||||
|
|
||||||
|
private Integer status = BAD_REQUEST.value();
|
||||||
|
|
||||||
|
public BadRequestException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadRequestException(HttpStatus status, String msg) {
|
||||||
|
super(msg);
|
||||||
|
this.status = status.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadRequestException(Throwable cause) {
|
||||||
|
super(cause.getMessage());
|
||||||
|
this.status = BAD_REQUEST.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.exception;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class ServerException extends RuntimeException {
|
||||||
|
|
||||||
|
private Integer status = INTERNAL_SERVER_ERROR.value();
|
||||||
|
|
||||||
|
public ServerException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerException(HttpStatus status, String msg) {
|
||||||
|
super(msg);
|
||||||
|
this.status = status.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerException(Throwable cause) {
|
||||||
|
super(cause.getMessage());
|
||||||
|
this.status = INTERNAL_SERVER_ERROR.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.exception.handler;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public final class ApiError {
|
||||||
|
|
||||||
|
private Integer status = 400;
|
||||||
|
private Long timestamp;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
private ApiError() {
|
||||||
|
timestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiError error(final String message) {
|
||||||
|
ApiError apiError = new ApiError();
|
||||||
|
apiError.setMessage(message);
|
||||||
|
return apiError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiError error(final Integer status, final String message) {
|
||||||
|
ApiError apiError = new ApiError();
|
||||||
|
apiError.setStatus(status);
|
||||||
|
apiError.setMessage(message);
|
||||||
|
return apiError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.exception.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.validation.FieldError;
|
||||||
|
import org.springframework.validation.ObjectError;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
/**
|
||||||
|
* 全局异常处理
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有不可知的异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(Throwable.class)
|
||||||
|
public ResponseEntity<ApiError> handleException(Throwable e) {
|
||||||
|
// 打印堆栈信息
|
||||||
|
log.error(ExceptionUtil.stacktraceToString(e));
|
||||||
|
return buildResponseEntity(ApiError.error(e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BadCredentialsException
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BadCredentialsException.class)
|
||||||
|
public ResponseEntity<ApiError> badCredentialsException(BadCredentialsException e) {
|
||||||
|
// 打印堆栈信息
|
||||||
|
String message = "坏的凭证".equals(e.getMessage()) ? "用户名或密码不正确" : e.getMessage();
|
||||||
|
log.error(message);
|
||||||
|
return buildResponseEntity(ApiError.error(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理自定义异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(value = BadRequestException.class)
|
||||||
|
public ResponseEntity<ApiError> badRequestException(BadRequestException e) {
|
||||||
|
// 打印堆栈信息
|
||||||
|
log.error(ExceptionUtil.stacktraceToString(e));
|
||||||
|
return buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有接口数据验证异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||||
|
// 打印堆栈信息
|
||||||
|
log.error(ExceptionUtil.stacktraceToString(e));
|
||||||
|
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
|
||||||
|
String message = objectError.getDefaultMessage();
|
||||||
|
if (objectError instanceof FieldError) {
|
||||||
|
message = ((FieldError) objectError).getField() + ": " + message;
|
||||||
|
}
|
||||||
|
return buildResponseEntity(ApiError.error(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一返回
|
||||||
|
*/
|
||||||
|
private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
|
||||||
|
return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.logging;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计执行时长
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ExecutionTimeStatisticsFace {
|
||||||
|
/**
|
||||||
|
* 接口描述
|
||||||
|
*/
|
||||||
|
String description();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示异常日志
|
||||||
|
*/
|
||||||
|
boolean showErrLog() default true;
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.logging;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.TimeInterval;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行时间统计
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-05-07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class ExecutionTimeStatisticsFaceAspect {
|
||||||
|
@Around("@annotation(executionTimeStatisticsFace)")
|
||||||
|
public Object aroundFace(ProceedingJoinPoint joinPoint, ExecutionTimeStatisticsFace executionTimeStatisticsFace) throws Throwable {
|
||||||
|
TimeInterval timeInterval = new TimeInterval();
|
||||||
|
try {
|
||||||
|
Object proceed = joinPoint.proceed();
|
||||||
|
log.info("[{}] 接口执行成功,耗时: {} ms", executionTimeStatisticsFace.description(), timeInterval.intervalMs());
|
||||||
|
return proceed;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (executionTimeStatisticsFace.showErrLog()) {
|
||||||
|
log.error("[{}] 接口执行失败,耗时: {} ms", executionTimeStatisticsFace.description(), timeInterval.intervalMs(), e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.monitor.controller;
|
||||||
|
|
||||||
|
import cn.odboy.annotation.AnonymousGetMapping;
|
||||||
|
import cn.odboy.framework.monitor.service.CsHealthCheckPointService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 健康检查接口
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/healthCheck")
|
||||||
|
@Api(tags = "系统:健康检查接口")
|
||||||
|
public class HealthCheckController {
|
||||||
|
@Autowired
|
||||||
|
private CsHealthCheckPointService healthCheckPointService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 就绪检查
|
||||||
|
*/
|
||||||
|
@AnonymousGetMapping(value = "/readiness")
|
||||||
|
public ResponseEntity<?> doReadiness() {
|
||||||
|
return healthCheckPointService.doReadiness();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存活检查
|
||||||
|
*/
|
||||||
|
@AnonymousGetMapping(value = "/liveness")
|
||||||
|
public ResponseEntity<?> doLiveness() {
|
||||||
|
return healthCheckPointService.doLiveness();
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 访问首页提示
|
||||||
|
// */
|
||||||
|
// @AnonymousGetMapping("/")
|
||||||
|
// public String index() {
|
||||||
|
// return "success";
|
||||||
|
// }
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.monitor.service;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
public interface CsHealthCheckPointService {
|
||||||
|
/**
|
||||||
|
* 就绪检查
|
||||||
|
*/
|
||||||
|
ResponseEntity<?> doReadiness();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存活检查
|
||||||
|
*/
|
||||||
|
ResponseEntity<?> doLiveness();
|
||||||
|
}
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态查询 枚举
|
||||||
|
*/
|
||||||
|
public enum CsMpQTypeEnum {
|
||||||
|
/**
|
||||||
|
* 相等
|
||||||
|
*/
|
||||||
|
EQUAL
|
||||||
|
/**
|
||||||
|
* 大于等于
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
GREATER_THAN
|
||||||
|
/**
|
||||||
|
* 小于等于
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
LESS_THAN
|
||||||
|
/**
|
||||||
|
* 中模糊查询
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
INNER_LIKE
|
||||||
|
/**
|
||||||
|
* 左模糊查询
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
LEFT_LIKE
|
||||||
|
/**
|
||||||
|
* 右模糊查询
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
RIGHT_LIKE
|
||||||
|
/**
|
||||||
|
* 小于
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
LESS_THAN_NQ
|
||||||
|
/**
|
||||||
|
* 包含
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
IN
|
||||||
|
/**
|
||||||
|
* 不包含
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
NOT_IN
|
||||||
|
/**
|
||||||
|
* 不等于
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
NOT_EQUAL
|
||||||
|
/**
|
||||||
|
* between(注:被注解的属性一定得是数组或者集合, 长度大于等于2)
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
BETWEEN
|
||||||
|
/**
|
||||||
|
* 不为空
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
NOT_NULL
|
||||||
|
/**
|
||||||
|
* 为空
|
||||||
|
*/
|
||||||
|
,
|
||||||
|
IS_NULL
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.config;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询描述
|
||||||
|
*
|
||||||
|
* @date 2023-11-25
|
||||||
|
*/
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface CsMpQuery {
|
||||||
|
/**
|
||||||
|
* 基本对象的属性名
|
||||||
|
*/
|
||||||
|
String propName() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询方式
|
||||||
|
*/
|
||||||
|
CsMpQTypeEnum type() default CsMpQTypeEnum.EQUAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多字段模糊搜索, 仅支持String类型字段, 多个用逗号隔开, 如@MpQuery(blurry = "email,username")
|
||||||
|
*/
|
||||||
|
String blurry() default "";
|
||||||
|
}
|
||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.config;
|
||||||
|
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author /
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
public enum DataTypeEnum {
|
||||||
|
/**
|
||||||
|
* mysql
|
||||||
|
*/
|
||||||
|
MYSQL("mysql", "mysql", "com.mysql.jdbc.Driver", "`", "`", "'", "'"),
|
||||||
|
/**
|
||||||
|
* oracle
|
||||||
|
*/
|
||||||
|
ORACLE("oracle", "oracle", "oracle.jdbc.driver.OracleDriver", "\"", "\"", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* sql config
|
||||||
|
*/
|
||||||
|
SQLSERVER("sqlserver", "sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "\"", "\"", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* h2
|
||||||
|
*/
|
||||||
|
H2("h2", "h2", "org.h2.Driver", "`", "`", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* phoenix
|
||||||
|
*/
|
||||||
|
PHOENIX("phoenix", "hbase phoenix", "org.apache.phoenix.jdbc.PhoenixDriver", "", "", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* mongo
|
||||||
|
*/
|
||||||
|
MONGODB("mongo", "mongodb", "mongodb.jdbc.MongoDriver", "`", "`", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* sql4es
|
||||||
|
*/
|
||||||
|
ELASTICSEARCH("sql4es", "elasticsearch", "nl.anchormen.sql4es.jdbc.ESDriver", "", "", "'", "'"),
|
||||||
|
/**
|
||||||
|
* presto
|
||||||
|
*/
|
||||||
|
PRESTO("presto", "presto", "com.facebook.presto.jdbc.PrestoDriver", "", "", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* moonbox
|
||||||
|
*/
|
||||||
|
MOONBOX("moonbox", "moonbox", "moonbox.jdbc.MbDriver", "`", "`", "`", "`"),
|
||||||
|
/**
|
||||||
|
* cassandra
|
||||||
|
*/
|
||||||
|
CASSANDRA("cassandra", "cassandra", "com.github.adejanovski.cassandra.jdbc.CassandraDriver", "", "", "'", "'"),
|
||||||
|
/**
|
||||||
|
* click house
|
||||||
|
*/
|
||||||
|
CLICKHOUSE("clickhouse", "clickhouse", "ru.yandex.clickhouse.ClickHouseDriver", "", "", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* kylin
|
||||||
|
*/
|
||||||
|
KYLIN("kylin", "kylin", "org.apache.kylin.jdbc.Driver", "\"", "\"", "\"", "\""),
|
||||||
|
/**
|
||||||
|
* vertica
|
||||||
|
*/
|
||||||
|
VERTICA("vertica", "vertica", "com.vertica.jdbc.Driver", "", "", "'", "'"),
|
||||||
|
/**
|
||||||
|
* sap
|
||||||
|
*/
|
||||||
|
HANA("sap", "sap hana", "com.sap.db.jdbc.Driver", "", "", "'", "'"),
|
||||||
|
/**
|
||||||
|
* impala
|
||||||
|
*/
|
||||||
|
IMPALA("impala", "impala", "com.cloudera.impala.jdbc41.Driver", "", "", "'", "'");
|
||||||
|
private String feature;
|
||||||
|
private String desc;
|
||||||
|
private String driver;
|
||||||
|
private String keywordPrefix;
|
||||||
|
private String keywordSuffix;
|
||||||
|
private String aliasPrefix;
|
||||||
|
private String aliasSuffix;
|
||||||
|
private static final String JDBC_URL_PREFIX = "jdbc:";
|
||||||
|
|
||||||
|
DataTypeEnum(
|
||||||
|
String feature,
|
||||||
|
String desc,
|
||||||
|
String driver,
|
||||||
|
String keywordPrefix,
|
||||||
|
String keywordSuffix,
|
||||||
|
String aliasPrefix,
|
||||||
|
String aliasSuffix) {
|
||||||
|
this.feature = feature;
|
||||||
|
this.desc = desc;
|
||||||
|
this.driver = driver;
|
||||||
|
this.keywordPrefix = keywordPrefix;
|
||||||
|
this.keywordSuffix = keywordSuffix;
|
||||||
|
this.aliasPrefix = aliasPrefix;
|
||||||
|
this.aliasSuffix = aliasSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DataTypeEnum urlOf(String jdbcUrl) {
|
||||||
|
String url = jdbcUrl.toLowerCase().trim();
|
||||||
|
for (DataTypeEnum dataTypeEnum : values()) {
|
||||||
|
if (url.startsWith(JDBC_URL_PREFIX + dataTypeEnum.feature)) {
|
||||||
|
try {
|
||||||
|
Class<?> aClass = Class.forName(dataTypeEnum.getDriver());
|
||||||
|
if (null == aClass) {
|
||||||
|
throw new BadRequestException("Unable to get driver instance for jdbcUrl: " + jdbcUrl);
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new BadRequestException("Unable to get driver instance: " + jdbcUrl);
|
||||||
|
}
|
||||||
|
return dataTypeEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFeature() {
|
||||||
|
return feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDriver() {
|
||||||
|
return driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeywordPrefix() {
|
||||||
|
return keywordPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeywordSuffix() {
|
||||||
|
return keywordSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAliasPrefix() {
|
||||||
|
return aliasPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAliasSuffix() {
|
||||||
|
return aliasSuffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.config;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 p6spy sql输出格式
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MpCustomP6SpyLogger implements MessageFormattingStrategy {
|
||||||
|
/**
|
||||||
|
* 重置颜色
|
||||||
|
*/
|
||||||
|
private static final String RESET = "\u001B[0m";
|
||||||
|
/**
|
||||||
|
* 红色
|
||||||
|
*/
|
||||||
|
private static final String RED = "\u001B[31m";
|
||||||
|
/**
|
||||||
|
* 绿色
|
||||||
|
*/
|
||||||
|
private static final String GREEN = "\u001B[32m";
|
||||||
|
/**
|
||||||
|
* 蓝色
|
||||||
|
*/
|
||||||
|
private static final String BLUE = "\u001B[34m";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化 sql
|
||||||
|
*
|
||||||
|
* @param connectionId 连接id
|
||||||
|
* @param now 当前时间
|
||||||
|
* @param elapsed 执行时长
|
||||||
|
* @param category sql分类
|
||||||
|
* @param prepared 预编译sql
|
||||||
|
* @param sql 执行sql
|
||||||
|
* @param url 数据库连接url
|
||||||
|
* @return 格式化后的sql
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
|
||||||
|
// 去掉换行和多余空格
|
||||||
|
if (StrUtil.isNotBlank(sql)) {
|
||||||
|
sql = sql.replaceAll("\\s+", " ").trim();
|
||||||
|
}
|
||||||
|
// 格式化并加上颜色
|
||||||
|
return String.format("CuteJava - %s%s%s %s[%s] %sREVIEW_SQL [Interval: %dms]%s -> %s;", RED, now, RESET, GREEN, Thread.currentThread().getName(), BLUE, elapsed, RESET, sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.config;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||||
|
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||||
|
import com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById;
|
||||||
|
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
|
||||||
|
import org.apache.ibatis.session.Configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入SQL注入器
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-11
|
||||||
|
*/
|
||||||
|
public class MpInsertBatchSqlInjector extends DefaultSqlInjector {
|
||||||
|
@Override
|
||||||
|
public List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass, TableInfo tableInfo) {
|
||||||
|
List<AbstractMethod> methodList = super.getMethodList(configuration, mapperClass, tableInfo);
|
||||||
|
// 新增自定义方法, InsertBatchSomeColumn这个方法Mp没有开放出来
|
||||||
|
methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
|
||||||
|
// 全量更新, 包括null字段
|
||||||
|
methodList.add(new AlwaysUpdateSomeColumnById(i -> i.getFieldFill() != FieldFill.INSERT));
|
||||||
|
return methodList;
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.config;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.DbType;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableTransactionManagement
|
||||||
|
public class MybatisPlusConfig {
|
||||||
|
@Bean
|
||||||
|
public MybatisPlusInterceptor paginationInterceptor() {
|
||||||
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||||
|
// MySQL的分页拦截器
|
||||||
|
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
|
||||||
|
paginationInnerInterceptor.setMaxLimit(500L);
|
||||||
|
/// 这个配置会导致:当分页没数据时, 游标从0开始,会导致无论page为什么值,就算分页超出最大页数,依然能够查询到
|
||||||
|
/// paginationInnerInterceptor.setOverflow(true);
|
||||||
|
paginationInnerInterceptor.setOptimizeJoin(true);
|
||||||
|
interceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||||
|
// 乐观锁
|
||||||
|
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||||
|
// 防全表更新与删除
|
||||||
|
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||||
|
return interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapper支持批量插入
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public MpInsertBatchSqlInjector insertBatchSqlInjector() {
|
||||||
|
return new MpInsertBatchSqlInjector();
|
||||||
|
}
|
||||||
|
}
|
||||||
+92
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.config;
|
||||||
|
|
||||||
|
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
|
||||||
|
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
|
||||||
|
import com.alibaba.druid.util.Utils;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
@ConditionalOnWebApplication
|
||||||
|
@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
|
||||||
|
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
|
public class RemoveDruidAdConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法名: removeDruidAdFilterRegistrationBean 方法描述 除去页面底部的广告
|
||||||
|
*
|
||||||
|
* @param properties com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties
|
||||||
|
* @return org.springframework.boot.web.servlet.FilterRegistrationBean
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {
|
||||||
|
|
||||||
|
// 获取web监控页面的参数
|
||||||
|
DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
|
||||||
|
// 提取common.js的配置路径
|
||||||
|
String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
|
||||||
|
String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
|
||||||
|
|
||||||
|
final String filePath = "support/http/resources/js/common.js";
|
||||||
|
|
||||||
|
//创建filter进行过滤
|
||||||
|
Filter filter = new Filter() {
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig filterConfig) throws ServletException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
|
if (httpRequest.getRequestURI().endsWith("js/common.js")) {
|
||||||
|
// 获取common.js
|
||||||
|
String text = Utils.readFromResource(filePath);
|
||||||
|
// 正则替换banner, 除去底部的广告信息
|
||||||
|
text = text.replaceAll("<a.*?druid_banner\"></a><br/>", "");
|
||||||
|
text = text.replaceAll("powered by.*?shrek.wang</a>", "");
|
||||||
|
httpResponse.setContentType("application/javascript");
|
||||||
|
httpResponse.setCharacterEncoding("UTF-8");
|
||||||
|
httpResponse.getWriter().write(text);
|
||||||
|
} else {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
|
||||||
|
registrationBean.setFilter(filter);
|
||||||
|
registrationBean.addUrlPatterns(commonJsPattern);
|
||||||
|
return registrationBean;
|
||||||
|
}
|
||||||
|
}
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义任意对象查询工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-05-15
|
||||||
|
*/
|
||||||
|
public class CsMpAnyQUtil {
|
||||||
|
public static <T> T selectOne(BaseMapper<T> baseMapper, Object queryParams) {
|
||||||
|
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
|
||||||
|
return baseMapper.selectOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, M> M selectOne(BaseMapper<T> baseMapper, Object queryParams, Class<M> mapperClazz) {
|
||||||
|
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
|
||||||
|
T mapperTarget = baseMapper.selectOne(queryWrapper);
|
||||||
|
if (mapperTarget == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return BeanUtil.copyProperties(mapperTarget, mapperClazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> selectList(BaseMapper<T> baseMapper, Object queryParams) {
|
||||||
|
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
|
||||||
|
return baseMapper.selectList(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, M> List<M> selectList(BaseMapper<T> baseMapper, Object queryParams, Class<M> mapperClazz) {
|
||||||
|
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
|
||||||
|
List<T> mapperTarget = baseMapper.selectList(queryWrapper);
|
||||||
|
if (mapperTarget == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return BeanUtil.copyToList(mapperTarget, mapperClazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Long selectCount(BaseMapper<T> baseMapper, Object queryParams) {
|
||||||
|
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
|
||||||
|
return baseMapper.selectCount(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Page<T> selectPage(BaseMapper<T> baseMapper, Page<T> page, Object queryParams) {
|
||||||
|
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
|
||||||
|
return baseMapper.selectPage(page, queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, M> Page<M> selectPage(
|
||||||
|
BaseMapper<T> baseMapper,
|
||||||
|
Page<T> page,
|
||||||
|
Object queryParams,
|
||||||
|
Class<M> mapperClazz) {
|
||||||
|
QueryWrapper<T> queryWrapper = CsMpQUtil.build(queryParams);
|
||||||
|
Page<T> tPage = baseMapper.selectPage(page, queryWrapper);
|
||||||
|
Page<M> mapperTarget = new Page<>();
|
||||||
|
mapperTarget.setRecords(BeanUtil.copyToList(tPage.getRecords(), mapperClazz));
|
||||||
|
mapperTarget.setTotal(tPage.getTotal());
|
||||||
|
mapperTarget.setSize(tPage.getSize());
|
||||||
|
mapperTarget.setCurrent(tPage.getCurrent());
|
||||||
|
return mapperTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.odboy.base.CsBaseUserTimeTb;
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
|
||||||
|
import com.baomidou.mybatisplus.generator.config.*;
|
||||||
|
import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery;
|
||||||
|
import com.baomidou.mybatisplus.generator.config.rules.DateType;
|
||||||
|
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
|
||||||
|
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
|
||||||
|
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
|
||||||
|
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
|
||||||
|
import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.sql.Types;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成器入口
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-11
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CsMpCmdGenUtil {
|
||||||
|
// 项目根路径。生成结果 C:\Users\Administrator\Documents\GitHub\cutejava\cutejava
|
||||||
|
private static final String projectRootPath = System.getProperty("user.dir") + "/";
|
||||||
|
/**
|
||||||
|
* 数据库类型 MySQL
|
||||||
|
*/
|
||||||
|
private static final IDbQuery DB_QUERY = new MySqlQuery();
|
||||||
|
/**
|
||||||
|
* 类型转换
|
||||||
|
*/
|
||||||
|
private static final String TYPE_TINY_INT = "tinyint";
|
||||||
|
private static final String TYPE_DATETIME = "datetime";
|
||||||
|
private static final String TYPE_DATE = "date";
|
||||||
|
private String databaseUrl;
|
||||||
|
private String databaseUsername;
|
||||||
|
private String databasePassword;
|
||||||
|
private String driverClassName;
|
||||||
|
private String parentPackageName = "cn.odboy";
|
||||||
|
private String parentModuleName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码生成函数
|
||||||
|
*
|
||||||
|
* @param moduleName 模块名称
|
||||||
|
* @param tablePrefix 表前缀(可空)
|
||||||
|
* @param tableNames 表名称, 多个用逗号分隔
|
||||||
|
*/
|
||||||
|
public void gen(String moduleName, String tablePrefix, List<String> tableNames) {
|
||||||
|
this.parentModuleName = moduleName;
|
||||||
|
// 1.数据库配置
|
||||||
|
DataSourceConfig.Builder dataSourceConfigBuilder =
|
||||||
|
new DataSourceConfig.Builder(this.databaseUrl, this.databaseUsername, this.databasePassword).driverClassName(
|
||||||
|
this.driverClassName);
|
||||||
|
dataSourceConfigBuilder.dbQuery(DB_QUERY);
|
||||||
|
dataSourceConfigBuilder.keyWordsHandler(new MySqlKeyWordsHandler());
|
||||||
|
// 1.1.快速生成器
|
||||||
|
FastAutoGenerator fastAutoGenerator = FastAutoGenerator.create(dataSourceConfigBuilder);
|
||||||
|
// 2.全局配置
|
||||||
|
// 自定义类型转换
|
||||||
|
fastAutoGenerator.dataSourceConfig(getDataSourceConfigConsumer());
|
||||||
|
// 覆盖已生成文件
|
||||||
|
// 添加swagger注解
|
||||||
|
// 设置注释的作者
|
||||||
|
// 设置注释的日期格式
|
||||||
|
// 使用java8新的时间类型
|
||||||
|
fastAutoGenerator.globalConfig(getGlobalConfigConsumer());
|
||||||
|
// 3.包配置
|
||||||
|
// 设置父包名
|
||||||
|
// 设置父包模块名
|
||||||
|
// 设置MVC下各个模块的包名
|
||||||
|
// 设置XML资源文件的目录
|
||||||
|
fastAutoGenerator.packageConfig(getPackageConfigConsumer());
|
||||||
|
// 4.模板配置
|
||||||
|
// 使用Freemarker引擎模板, 默认的是Velocity引擎模板
|
||||||
|
AbstractTemplateEngine templateEngine = new FreemarkerTemplateEngine();
|
||||||
|
fastAutoGenerator.templateEngine(templateEngine);
|
||||||
|
// 5.策略配置
|
||||||
|
// 设置需要生成的表名
|
||||||
|
// 设置过滤表前缀
|
||||||
|
fastAutoGenerator.strategyConfig(getStrategyConfigConsumer(tableNames));
|
||||||
|
if (tablePrefix != null) {
|
||||||
|
// 表前缀, 可多个
|
||||||
|
fastAutoGenerator.strategyConfig(strategyConfigBuilder -> strategyConfigBuilder.addTablePrefix(tablePrefix));
|
||||||
|
}
|
||||||
|
// 5.1.Entity策略配置
|
||||||
|
// 生成实体时生成字段的注解, 包括@TableId注解等
|
||||||
|
// 数据库表和字段映射到实体的命名策略, 为下划线转驼峰
|
||||||
|
// 全局主键类型为None
|
||||||
|
// 实体名称格式化为XXXEntity
|
||||||
|
fastAutoGenerator.strategyConfig(getEntityConfigConsumer());
|
||||||
|
// 5.2.Controller策略配置
|
||||||
|
// 开启生成@RestController控制器
|
||||||
|
fastAutoGenerator.strategyConfig(strategyConfigBuilder -> strategyConfigBuilder.controllerBuilder()
|
||||||
|
.enableRestStyle());
|
||||||
|
// 如果不需要生成Controller, fastAutoGenerator.templateConfig(templateConfig -> templateConfig.controller(""))。5.3.Service策略配置:格式化service接口和实现类的文件名称, 去掉默认的ServiceName前面的I
|
||||||
|
fastAutoGenerator.strategyConfig(
|
||||||
|
strategyConfigBuilder -> strategyConfigBuilder.serviceBuilder()
|
||||||
|
.formatServiceFileName("%sService")
|
||||||
|
.formatServiceImplFileName("%sServiceImpl"));
|
||||||
|
// 5.4.Mapper策略配置
|
||||||
|
// 格式化 mapper文件名,格式化xml实现类文件名称
|
||||||
|
fastAutoGenerator.strategyConfig(getStrategyConfigConsumer());
|
||||||
|
// 6.生成代码
|
||||||
|
fastAutoGenerator.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Consumer<StrategyConfig.Builder> getEntityConfigConsumer() {
|
||||||
|
return strategyConfigBuilder -> strategyConfigBuilder.entityBuilder()
|
||||||
|
.enableLombok()
|
||||||
|
// 如果不需要生成注解, 去掉.enableTableFieldAnnotation()
|
||||||
|
.enableTableFieldAnnotation()
|
||||||
|
.enableFileOverride()
|
||||||
|
.naming(NamingStrategy.underline_to_camel)
|
||||||
|
.columnNaming(NamingStrategy.underline_to_camel)
|
||||||
|
.idType(IdType.AUTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Consumer<StrategyConfig.Builder> getStrategyConfigConsumer(List<String> tableNames) {
|
||||||
|
return strategyConfigBuilder -> strategyConfigBuilder.enableCapitalMode()
|
||||||
|
.enableSkipView()
|
||||||
|
.disableSqlFilter()
|
||||||
|
.addInclude(tableNames)
|
||||||
|
// 实体类生成策略
|
||||||
|
.entityBuilder()
|
||||||
|
.superClass(CsBaseUserTimeTb.class)
|
||||||
|
.addIgnoreColumns("create_time", "update_time", "create_by", "update_by")
|
||||||
|
// 开启生成实体时生成字段注解。
|
||||||
|
// 会在实体类的属性前,添加[@TableField("nickname")]
|
||||||
|
.enableTableFieldAnnotation()
|
||||||
|
.disableSerialVersionUID()
|
||||||
|
// 阶段2:Mapper策略配置
|
||||||
|
.mapperBuilder()
|
||||||
|
// 开启 @Mapper 注解。
|
||||||
|
// 会在mapper接口上添加注解[@Mapper]
|
||||||
|
// .enableMapperAnnotation()
|
||||||
|
// 启用 BaseResultMap 生成。
|
||||||
|
// 会在mapper.xml文件生成[通用查询映射结果]配置。
|
||||||
|
// .enableBaseResultMap()
|
||||||
|
// 启用 BaseColumnList。
|
||||||
|
// 会在mapper.xml文件生成[通用查询结果列 ]配置
|
||||||
|
// .enableBaseColumnList()
|
||||||
|
// 阶段4:Controller策略配置
|
||||||
|
.controllerBuilder()
|
||||||
|
// 会在控制类中加[@RestController]注解。
|
||||||
|
.enableRestStyle()
|
||||||
|
// 开启驼峰转连字符
|
||||||
|
.enableHyphenStyle()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<PackageConfig.Builder> getPackageConfigConsumer() {
|
||||||
|
String mapperPath = projectRootPath + "CodeGen/resources/mapper";
|
||||||
|
if (StrUtil.isBlank(this.parentModuleName)) {
|
||||||
|
throw new BadRequestException("模块名称必填");
|
||||||
|
}
|
||||||
|
return packageConfigBuilder -> packageConfigBuilder.parent(this.parentPackageName)
|
||||||
|
.moduleName(this.parentModuleName)
|
||||||
|
.entity("dal.dataobject")
|
||||||
|
.mapper("dal.mysql")
|
||||||
|
.service("service")
|
||||||
|
.serviceImpl("service.impl")
|
||||||
|
.controller("controller")
|
||||||
|
.pathInfo(Collections.singletonMap(OutputFile.xml, mapperPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Consumer<GlobalConfig.Builder> getGlobalConfigConsumer() {
|
||||||
|
String outputDir = projectRootPath + "CodeGen";
|
||||||
|
return globalConfigBuilder -> globalConfigBuilder.enableSwagger()
|
||||||
|
.outputDir(outputDir)
|
||||||
|
.author("codegen")
|
||||||
|
.commentDate("yyyy-MM-dd")
|
||||||
|
.dateType(DateType.TIME_PACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Consumer<StrategyConfig.Builder> getStrategyConfigConsumer() {
|
||||||
|
return strategyConfigBuilder -> strategyConfigBuilder.mapperBuilder()
|
||||||
|
.mapperAnnotation(Mapper.class)
|
||||||
|
.formatMapperFileName("%sMapper")
|
||||||
|
.formatXmlFileName("%sMapper")
|
||||||
|
.entityBuilder()
|
||||||
|
.formatFileName("%sTb");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Consumer<DataSourceConfig.Builder> getDataSourceConfigConsumer() {
|
||||||
|
return builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
|
||||||
|
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
|
||||||
|
if (typeCode == Types.SMALLINT) {
|
||||||
|
return DbColumnType.INTEGER;
|
||||||
|
}
|
||||||
|
if (typeCode == Types.DATE) {
|
||||||
|
return DbColumnType.DATE;
|
||||||
|
}
|
||||||
|
if (typeCode == Types.TIME) {
|
||||||
|
return DbColumnType.DATE;
|
||||||
|
}
|
||||||
|
if (typeCode == Types.TIMESTAMP) {
|
||||||
|
return DbColumnType.DATE;
|
||||||
|
}
|
||||||
|
return typeRegistry.getColumnType(metaInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+231
@@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.core;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.odboy.base.CsBaseUserTimeTb;
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import cn.odboy.framework.mybatisplus.config.CsMpQuery;
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询条件创建工具
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CsMpQUtil {
|
||||||
|
public static <R, Q> QueryWrapper<R> build(Q query) {
|
||||||
|
QueryWrapper<R> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (query == null) {
|
||||||
|
return queryWrapper;
|
||||||
|
}
|
||||||
|
Class<?> clazz = query.getClass();
|
||||||
|
try {
|
||||||
|
List<Field> fields = getAllFields(clazz, new ArrayList<>());
|
||||||
|
for (Field field : fields) {
|
||||||
|
if (!field.canAccess(query)) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
CsMpQuery q = field.getAnnotation(CsMpQuery.class);
|
||||||
|
if (q != null) {
|
||||||
|
String attributeName = getAttributeName(q, field);
|
||||||
|
Object fieldVal = field.get(query);
|
||||||
|
if (ObjectUtil.isNull(fieldVal) || "".equals(fieldVal)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(q.blurry())) {
|
||||||
|
handleBlurryQuery(queryWrapper, q.blurry(), fieldVal);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handleWrapper(q, queryWrapper, attributeName, fieldVal);
|
||||||
|
}
|
||||||
|
field.setAccessible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to build query wrapper for class: {}", clazz.getName(), e);
|
||||||
|
}
|
||||||
|
return queryWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAttributeName(CsMpQuery q, Field field) {
|
||||||
|
String attributeName = StrUtil.isBlank(q.propName()) ? field.getName() : q.propName();
|
||||||
|
return StrUtil.toUnderlineCase(attributeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R> void handleWrapper(
|
||||||
|
CsMpQuery q,
|
||||||
|
QueryWrapper<R> queryWrapper,
|
||||||
|
String attributeName,
|
||||||
|
Object fieldVal) {
|
||||||
|
switch (q.type()) {
|
||||||
|
case EQUAL:
|
||||||
|
queryWrapper.eq(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case GREATER_THAN:
|
||||||
|
queryWrapper.ge(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case LESS_THAN:
|
||||||
|
queryWrapper.le(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case LESS_THAN_NQ:
|
||||||
|
queryWrapper.lt(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case INNER_LIKE:
|
||||||
|
queryWrapper.like(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case LEFT_LIKE:
|
||||||
|
queryWrapper.likeLeft(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case RIGHT_LIKE:
|
||||||
|
queryWrapper.likeRight(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case IN:
|
||||||
|
handleInOrNotQuery(true, queryWrapper, attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case NOT_IN:
|
||||||
|
handleInOrNotQuery(false, queryWrapper, attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case NOT_EQUAL:
|
||||||
|
queryWrapper.ne(attributeName, fieldVal);
|
||||||
|
break;
|
||||||
|
case NOT_NULL:
|
||||||
|
queryWrapper.isNotNull(attributeName);
|
||||||
|
break;
|
||||||
|
case IS_NULL:
|
||||||
|
queryWrapper.isNull(attributeName);
|
||||||
|
break;
|
||||||
|
case BETWEEN:
|
||||||
|
handleBetweenQuery(queryWrapper, fieldVal, attributeName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R> void handleInOrNotQuery(
|
||||||
|
boolean b,
|
||||||
|
QueryWrapper<R> queryWrapper,
|
||||||
|
String attributeName,
|
||||||
|
Object fieldVal) {
|
||||||
|
Collection<?> wrapNotInVal = (Collection<?>) fieldVal;
|
||||||
|
if (CollectionUtil.isNotEmpty(wrapNotInVal)) {
|
||||||
|
Optional<?> anyValOptional = wrapNotInVal.stream().findAny();
|
||||||
|
if (anyValOptional.isPresent()) {
|
||||||
|
Object o = anyValOptional.get();
|
||||||
|
if (o instanceof Long) {
|
||||||
|
if (b) {
|
||||||
|
queryWrapper.in(attributeName, fieldVal);
|
||||||
|
} else {
|
||||||
|
queryWrapper.notIn(attributeName, fieldVal);
|
||||||
|
}
|
||||||
|
} else if (o instanceof Integer) {
|
||||||
|
if (b) {
|
||||||
|
queryWrapper.in(attributeName, fieldVal);
|
||||||
|
} else {
|
||||||
|
queryWrapper.notIn(attributeName, fieldVal);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException("InOrNotIn查询参数类型的对象属性值必须是Long/Integer类型集合");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <R> void handleBetweenQuery(QueryWrapper<R> queryWrapper, Object fieldVal, String finalAttributeName) {
|
||||||
|
if (fieldVal instanceof List) {
|
||||||
|
List<Object> between = new ArrayList<>((List<?>) fieldVal);
|
||||||
|
int minLength = 2;
|
||||||
|
if (CollectionUtil.isNotEmpty(between) && between.size() >= minLength) {
|
||||||
|
queryWrapper.between(finalAttributeName, between.get(0), between.get(1));
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException("BETWEEN类型的对象列表长度必须 >= 2");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException("BETWEEN类型的对象必须是一个List子集合");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模糊多字段
|
||||||
|
*
|
||||||
|
* @param queryWrapper /
|
||||||
|
* @param blurry /
|
||||||
|
* @param fieldVal /
|
||||||
|
* @param <R> /
|
||||||
|
*/
|
||||||
|
private static <R> void handleBlurryQuery(QueryWrapper<R> queryWrapper, String blurry, Object fieldVal) {
|
||||||
|
List<String> blurryList =
|
||||||
|
Arrays.stream(blurry.split(",")).filter(StrUtil::isNotBlank).distinct().collect(Collectors.toList());
|
||||||
|
queryWrapper.and(wrapper -> {
|
||||||
|
for (String blurryItem : blurryList) {
|
||||||
|
String column = StrUtil.toUnderlineCase(blurryItem);
|
||||||
|
wrapper.or();
|
||||||
|
wrapper.like(column, fieldVal.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Field> getAllFields(Class<?> clazz, List<Field> fields) {
|
||||||
|
if (clazz != null) {
|
||||||
|
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||||
|
getAllFields(clazz.getSuperclass(), fields);
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TableName("test_domain")
|
||||||
|
private static class TestDomain {
|
||||||
|
@NotNull(groups = CsBaseUserTimeTb.Update.class)
|
||||||
|
@TableId(value = "user_id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
@TableField(value = "dept_id")
|
||||||
|
private Long deptId;
|
||||||
|
@NotBlank
|
||||||
|
private String username;
|
||||||
|
@NotBlank
|
||||||
|
private String nickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
QueryWrapper<TestDomain> query = new QueryWrapper<TestDomain>();
|
||||||
|
query.or(wrapper -> wrapper.eq("username", 1).or().eq("nickname", 2));
|
||||||
|
query.eq("id", 1);
|
||||||
|
query.orderByDesc("id");
|
||||||
|
System.err.println("getSqlSelect=================================");
|
||||||
|
System.err.println(query.getSqlSelect());
|
||||||
|
System.err.println("getSqlSegment=================================");
|
||||||
|
System.err.println(query.getSqlSegment());
|
||||||
|
System.err.println("getSqlComment=================================");
|
||||||
|
System.err.println(query.getSqlComment());
|
||||||
|
System.err.println("getSqlSet=================================");
|
||||||
|
System.err.println(query.getSqlSet());
|
||||||
|
System.err.println("getTargetSql=================================");
|
||||||
|
System.err.println(query.getTargetSql());
|
||||||
|
}
|
||||||
|
}
|
||||||
+189
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.core.interfaces;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
|
import cn.odboy.base.CsPageResult;
|
||||||
|
import cn.odboy.util.CsPageUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共抽象Mapper接口类
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2022-04-03
|
||||||
|
*/
|
||||||
|
public interface CsMpMapper<E> extends BaseMapper<E> {
|
||||||
|
/**
|
||||||
|
* 批量插入
|
||||||
|
*
|
||||||
|
* @param entityList List<E>
|
||||||
|
* @return 影响的行数
|
||||||
|
*/
|
||||||
|
int insertBatchSomeColumn(List<E> entityList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id全量更新
|
||||||
|
*
|
||||||
|
* @param entity E
|
||||||
|
* @return 影响的行数
|
||||||
|
*/
|
||||||
|
int alwaysUpdateSomeColumnById(@Param("et") E entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典
|
||||||
|
*
|
||||||
|
* @param wrapper LambdaQueryWrapper<E>
|
||||||
|
* @param keyName 键对应的列名称
|
||||||
|
* @param valueName 值对应的列名称
|
||||||
|
* @return Map<String, Object>
|
||||||
|
*/
|
||||||
|
default Map<String, Object> getMapWithKv(LambdaQueryWrapper<E> wrapper, String keyName, String valueName) {
|
||||||
|
Map<String, Object> result = new ConcurrentHashMap<>(1);
|
||||||
|
List<E> list = this.selectList(wrapper);
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return new HashMap<>(3);
|
||||||
|
}
|
||||||
|
E tempObj = list.stream().findFirst().orElse(null);
|
||||||
|
if (tempObj == null) {
|
||||||
|
return new HashMap<>(3);
|
||||||
|
}
|
||||||
|
Field[] fields = ReflectUtil.getFields(tempObj.getClass());
|
||||||
|
for (E e : list) {
|
||||||
|
Field keyField = null;
|
||||||
|
Field valueField = null;
|
||||||
|
for (Field field : fields) {
|
||||||
|
String fieldName = field.getName();
|
||||||
|
if (fieldName.equals(keyName)) {
|
||||||
|
keyField = field;
|
||||||
|
} else if (fieldName.equals(valueName)) {
|
||||||
|
valueField = field;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String fieldKey = String.valueOf(ReflectUtil.getFieldValue(e, keyField));
|
||||||
|
Object fieldValue = ReflectUtil.getFieldValue(e, valueField);
|
||||||
|
if (fieldKey != null) {
|
||||||
|
result.put(fieldKey, fieldValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件查询列表, 并返回期望的对象
|
||||||
|
*
|
||||||
|
* @param wrapper LambdaQueryWrapper<E>
|
||||||
|
* @param clazz 期望的对象类型
|
||||||
|
* @return List<T>
|
||||||
|
*/
|
||||||
|
default <T> List<T> queryFeatureClazzList(LambdaQueryWrapper<E> wrapper, Class<T> clazz) {
|
||||||
|
List<E> list = this.selectList(wrapper);
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return BeanUtil.copyToList(list, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件查询列表, 并返回期望的对象
|
||||||
|
*
|
||||||
|
* @param wrapper LambdaQueryWrapper<E>
|
||||||
|
* @param clazz 期望的对象类型
|
||||||
|
* @return List<T>
|
||||||
|
*/
|
||||||
|
default <T> List<T> queryFeatureClazzList(LambdaQueryChainWrapper<E> wrapper, Class<T> clazz) {
|
||||||
|
List<E> list = wrapper.list();
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
return BeanUtil.copyToList(list, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件查询分页列表, 并返回期望的对象
|
||||||
|
*
|
||||||
|
* @param pageable 分页参数
|
||||||
|
* @param wrapper LambdaQueryWrapper<E> wrapper
|
||||||
|
* @param clazz 期望的对象类型
|
||||||
|
* @return IPage<T>
|
||||||
|
*/
|
||||||
|
default <T> CsPageResult<T> queryFeatureClazzPage(Pageable pageable, LambdaQueryWrapper<E> wrapper, Class<T> clazz) {
|
||||||
|
int pageNumber = pageable.getPageNumber();
|
||||||
|
int pageSize = pageable.getPageSize();
|
||||||
|
pageNumber = pageNumber <= 0 ? 1 : pageNumber;
|
||||||
|
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||||
|
IPage<E> pageInfo = this.selectPage(new Page<>(pageNumber, pageSize), wrapper);
|
||||||
|
if (CollUtil.isEmpty(pageInfo.getRecords())) {
|
||||||
|
return new CsPageResult<>(new ArrayList<>(), pageInfo.getTotal());
|
||||||
|
}
|
||||||
|
return new CsPageResult<>(BeanUtil.copyToList(pageInfo.getRecords(), clazz), pageInfo.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件查询分页列表, 并返回期望的对象
|
||||||
|
*
|
||||||
|
* @param pageable 分页参数
|
||||||
|
* @param wrapper LambdaQueryWrapper<E> wrapper
|
||||||
|
* @param clazz 期望的对象类型
|
||||||
|
* @return IPage<T>
|
||||||
|
*/
|
||||||
|
default <T> CsPageResult<T> queryFeatureClazzPage(
|
||||||
|
Pageable pageable,
|
||||||
|
LambdaQueryChainWrapper<E> wrapper,
|
||||||
|
Class<T> clazz) {
|
||||||
|
int pageNumber = pageable.getPageNumber();
|
||||||
|
int pageSize = pageable.getPageSize();
|
||||||
|
pageNumber = pageNumber <= 0 ? 1 : pageNumber;
|
||||||
|
pageSize = pageSize <= 0 ? 10 : pageSize;
|
||||||
|
IPage<E> pageInfo = wrapper.page(new Page<>(pageNumber, pageSize));
|
||||||
|
if (CollUtil.isEmpty(pageInfo.getRecords())) {
|
||||||
|
return CsPageUtil.emptyListData();
|
||||||
|
}
|
||||||
|
return new CsPageResult<>(BeanUtil.copyToList(pageInfo.getRecords(), clazz), pageInfo.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标类
|
||||||
|
*
|
||||||
|
* @param wrapper LambdaQueryChainWrapper<E> wrapper
|
||||||
|
* @param clazz 期望的对象类型
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
default <T> T getFeatureClazz(LambdaQueryChainWrapper<E> wrapper, Class<T> clazz) {
|
||||||
|
E e = this.selectOne(wrapper);
|
||||||
|
if (e == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return BeanUtil.copyProperties(e, clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.core.interfaces;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsPageResult;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共抽象Service接口类
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2022-04-03
|
||||||
|
*/
|
||||||
|
public interface CsMpService<T> extends IService<T> {
|
||||||
|
<G> int saveFeatureClazz(G resources);
|
||||||
|
|
||||||
|
<G> int saveFeatureClazzList(List<G> resources);
|
||||||
|
|
||||||
|
<G> int modifyFeatureClazzById(G resources);
|
||||||
|
|
||||||
|
<G> boolean modifyFeatureClazzListById(Collection<G> resources, int batchSize);
|
||||||
|
|
||||||
|
<G> G queryFeatureClazzById(Serializable id, Class<G> targetClazz);
|
||||||
|
|
||||||
|
T queryClazzByArgs(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> orderColumn);
|
||||||
|
|
||||||
|
<G> G queryFeatureClazzByArgs(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> orderColumn, Class<G> clazz);
|
||||||
|
|
||||||
|
<G> List<G> queryFeatureClazzListByIds(List<Serializable> ids, Class<G> targetClazz);
|
||||||
|
|
||||||
|
<Q> List<T> queryClazzListByArgs(Q criteria);
|
||||||
|
|
||||||
|
List<T> queryClazzListByArgs(LambdaQueryWrapper<T> wrapper);
|
||||||
|
|
||||||
|
<G, Q> List<G> queryFeatureClazzListByArgs(Q criteria, Class<G> targetClazz);
|
||||||
|
|
||||||
|
<G> List<G> queryFeatureClazzListByArgs(LambdaQueryWrapper<T> wrapper, Class<G> targetClazz);
|
||||||
|
|
||||||
|
CsPageResult<T> queryClazzPageByArgs(LambdaQueryWrapper<T> wrapper, IPage<T> pageable);
|
||||||
|
|
||||||
|
<G> CsPageResult<G> queryFeatureClazzPageByArgs(
|
||||||
|
LambdaQueryWrapper<T> wrapper,
|
||||||
|
IPage<T> pageable,
|
||||||
|
Class<G> targetClazz);
|
||||||
|
|
||||||
|
<G, Q> CsPageResult<G> queryFeatureClazzPageByArgs(Q criteria, IPage<T> pageable, Class<G> targetClazz);
|
||||||
|
|
||||||
|
<Q> CsPageResult<T> queryClazzPageByArgs(Q criteria, IPage<T> pageable);
|
||||||
|
|
||||||
|
int modifyClazzByArgs(LambdaQueryWrapper<T> wrapper, T entity);
|
||||||
|
|
||||||
|
int removeClazzByArgs(LambdaQueryWrapper<T> wrapper);
|
||||||
|
}
|
||||||
+259
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.mybatisplus.core.interfaces;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.odboy.base.CsPageResult;
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import cn.odboy.framework.mybatisplus.core.CsMpQUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import org.apache.ibatis.binding.MapperMethod;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公共抽象Mapper接口类
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2022-04-03
|
||||||
|
*/
|
||||||
|
public class CsMpServiceImpl<M extends CsMpMapper<T>, T> extends ServiceImpl<M, T> {
|
||||||
|
/**
|
||||||
|
* 快速插入
|
||||||
|
*
|
||||||
|
* @param resources /
|
||||||
|
*/
|
||||||
|
public <G> int saveFeatureClazz(G resources) {
|
||||||
|
return getBaseMapper().insert(BeanUtil.copyProperties(resources, this.getEntityClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速批量插入
|
||||||
|
*
|
||||||
|
* @param resources /
|
||||||
|
*/
|
||||||
|
public <G> int saveFeatureClazzList(List<G> resources) {
|
||||||
|
return getBaseMapper().insertBatchSomeColumn(BeanUtil.copyToList(resources, this.getEntityClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速更新
|
||||||
|
*
|
||||||
|
* @param resources /
|
||||||
|
*/
|
||||||
|
public <G> int modifyFeatureClazzById(G resources) {
|
||||||
|
return getBaseMapper().updateById(BeanUtil.copyProperties(resources, this.getEntityClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快速批量更新
|
||||||
|
*
|
||||||
|
* @param resources /
|
||||||
|
* @param batchSize 每批数量
|
||||||
|
*/
|
||||||
|
public <G> boolean modifyFeatureClazzListById(Collection<G> resources, int batchSize) {
|
||||||
|
String sqlStatement = this.getSqlStatement(SqlMethod.UPDATE_BY_ID);
|
||||||
|
return this.executeBatch(resources, batchSize, (sqlSession, entity) -> {
|
||||||
|
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
|
||||||
|
param.put("et", BeanUtil.copyProperties(entity, this.getEntityClass()));
|
||||||
|
sqlSession.update(sqlStatement, param);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过id查询
|
||||||
|
*
|
||||||
|
* @param id /
|
||||||
|
* @param targetClazz 目标类型
|
||||||
|
*/
|
||||||
|
public <G> G queryFeatureClazzById(Serializable id, Class<G> targetClazz) {
|
||||||
|
T entity = this.getById(id);
|
||||||
|
return entity == null ? null : BeanUtil.copyProperties(entity, targetClazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查一条数据
|
||||||
|
*
|
||||||
|
* @param wrapper 制造条件
|
||||||
|
*/
|
||||||
|
public T queryClazzByArgs(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> orderColumn) {
|
||||||
|
if (orderColumn == null) {
|
||||||
|
throw new BadRequestException("参数orderColumn(排序的列)必填");
|
||||||
|
}
|
||||||
|
if (wrapper == null) {
|
||||||
|
wrapper = new LambdaQueryWrapper<>();
|
||||||
|
}
|
||||||
|
wrapper.last("limit 1");
|
||||||
|
wrapper.orderByDesc(orderColumn);
|
||||||
|
List<T> ts = baseMapper.selectList(wrapper);
|
||||||
|
if (ts.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ts.stream().findFirst().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <G> G queryFeatureClazzByArgs(LambdaQueryWrapper<T> wrapper, SFunction<T, ?> orderColumn, Class<G> clazz) {
|
||||||
|
if (orderColumn == null) {
|
||||||
|
throw new BadRequestException("参数orderColumn(排序的列)必填");
|
||||||
|
}
|
||||||
|
if (wrapper == null) {
|
||||||
|
wrapper = new LambdaQueryWrapper<>();
|
||||||
|
}
|
||||||
|
wrapper.last("limit 1");
|
||||||
|
wrapper.orderByDesc(orderColumn);
|
||||||
|
List<T> ts = baseMapper.selectList(wrapper);
|
||||||
|
if (ts.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return BeanUtil.copyProperties(ts.stream().findFirst().get(), clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过id集合查询
|
||||||
|
*
|
||||||
|
* @param ids /
|
||||||
|
* @param targetClazz 目标类型
|
||||||
|
*/
|
||||||
|
public <G> List<G> queryFeatureClazzListByIds(List<Serializable> ids, Class<G> targetClazz) {
|
||||||
|
List<T> entitys = this.listByIds(ids);
|
||||||
|
return entitys == null || entitys.isEmpty() ? CollUtil.newArrayList() : BeanUtil.copyToList(entitys, targetClazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查有关的数据
|
||||||
|
*
|
||||||
|
* @param criteria 动态条件
|
||||||
|
*/
|
||||||
|
public <Q> List<T> queryClazzListByArgs(Q criteria) {
|
||||||
|
QueryWrapper<T> predicate = CsMpQUtil.build(criteria);
|
||||||
|
return baseMapper.selectList(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查有关的数据
|
||||||
|
*
|
||||||
|
* @param wrapper 制造条件
|
||||||
|
*/
|
||||||
|
public List<T> queryClazzListByArgs(LambdaQueryWrapper<T> wrapper) {
|
||||||
|
if (wrapper == null) {
|
||||||
|
wrapper = new LambdaQueryWrapper<>();
|
||||||
|
}
|
||||||
|
return baseMapper.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查有关的数据并转成目标类型的数据
|
||||||
|
*
|
||||||
|
* @param criteria 动态条件
|
||||||
|
* @param targetClazz 目标类型
|
||||||
|
*/
|
||||||
|
public <G, Q> List<G> queryFeatureClazzListByArgs(Q criteria, Class<G> targetClazz) {
|
||||||
|
QueryWrapper<T> predicate = CsMpQUtil.build(criteria);
|
||||||
|
List<T> data = baseMapper.selectList(predicate);
|
||||||
|
return BeanUtil.copyToList(data, targetClazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查有关的数据并转换为目标类型
|
||||||
|
*
|
||||||
|
* @param wrapper 制造条件
|
||||||
|
* @param targetClazz 目标类型
|
||||||
|
*/
|
||||||
|
public <G> List<G> queryFeatureClazzListByArgs(LambdaQueryWrapper<T> wrapper, Class<G> targetClazz) {
|
||||||
|
if (wrapper == null) {
|
||||||
|
wrapper = new LambdaQueryWrapper<>();
|
||||||
|
}
|
||||||
|
return BeanUtil.copyToList(baseMapper.selectList(wrapper), targetClazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查有关的分页数据
|
||||||
|
*
|
||||||
|
* @param wrapper 制造条件
|
||||||
|
* @param pageable 分页参数
|
||||||
|
*/
|
||||||
|
public CsPageResult<T> queryClazzPageByArgs(LambdaQueryWrapper<T> wrapper, IPage<T> pageable) {
|
||||||
|
if (wrapper == null) {
|
||||||
|
wrapper = new LambdaQueryWrapper<>();
|
||||||
|
}
|
||||||
|
IPage<T> originPageData = baseMapper.selectPage(pageable, wrapper);
|
||||||
|
return new CsPageResult<>(originPageData.getRecords(), originPageData.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查有关的分页数据
|
||||||
|
*
|
||||||
|
* @param wrapper 制造条件
|
||||||
|
* @param pageable 分页参数
|
||||||
|
* @param targetClazz 目标类型
|
||||||
|
*/
|
||||||
|
public <G> CsPageResult<G> queryFeatureClazzPageByArgs(
|
||||||
|
LambdaQueryWrapper<T> wrapper,
|
||||||
|
IPage<T> pageable,
|
||||||
|
Class<G> targetClazz) {
|
||||||
|
if (wrapper == null) {
|
||||||
|
wrapper = new LambdaQueryWrapper<>();
|
||||||
|
}
|
||||||
|
IPage<T> originPageData = baseMapper.selectPage(pageable, wrapper);
|
||||||
|
return new CsPageResult<>(BeanUtil.copyToList(originPageData.getRecords(), targetClazz), originPageData.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <G, Q> CsPageResult<G> queryFeatureClazzPageByArgs(Q criteria, IPage<T> pageable, Class<G> targetClazz) {
|
||||||
|
QueryWrapper<T> wrapper = CsMpQUtil.build(criteria);
|
||||||
|
IPage<T> originPageData = baseMapper.selectPage(pageable, wrapper);
|
||||||
|
return new CsPageResult<>(BeanUtil.copyToList(originPageData.getRecords(), targetClazz), originPageData.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
public <Q> CsPageResult<T> queryClazzPageByArgs(Q criteria, IPage<T> pageable) {
|
||||||
|
QueryWrapper<T> wrapper = CsMpQUtil.build(criteria);
|
||||||
|
IPage<T> originPageData = baseMapper.selectPage(pageable, wrapper);
|
||||||
|
return new CsPageResult<>(originPageData.getRecords(), originPageData.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件更新
|
||||||
|
*
|
||||||
|
* @param wrapper 制造条件
|
||||||
|
* @param entity 更新内容
|
||||||
|
*/
|
||||||
|
public int modifyClazzByArgs(LambdaQueryWrapper<T> wrapper, T entity) {
|
||||||
|
if (wrapper == null) {
|
||||||
|
throw new BadRequestException("参数wrapper必填");
|
||||||
|
}
|
||||||
|
return baseMapper.update(entity, wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件删除
|
||||||
|
*
|
||||||
|
* @param wrapper 制造条件
|
||||||
|
*/
|
||||||
|
public int removeClazzByArgs(LambdaQueryWrapper<T> wrapper) {
|
||||||
|
if (wrapper == null) {
|
||||||
|
throw new BadRequestException("参数wrapper必填");
|
||||||
|
}
|
||||||
|
return baseMapper.delete(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties;
|
||||||
|
|
||||||
|
import cn.odboy.framework.properties.model.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用配置
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-04-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "app")
|
||||||
|
public class AppProperties {
|
||||||
|
private ContentRsaEncodeSettingModel rsa;
|
||||||
|
private JwtAuthSettingModel jwt;
|
||||||
|
private UserLoginSettingModel login;
|
||||||
|
private SwaggerApiDocSettingModel swagger;
|
||||||
|
private ThreadPoolSettingModel asyncTaskPool;
|
||||||
|
private CaptchaModel captcha;
|
||||||
|
private StorageOSSModel oss;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CaptchaModel extends CsObject {
|
||||||
|
/**
|
||||||
|
* 验证码有效时间, 单位: 秒
|
||||||
|
*/
|
||||||
|
private Long expireTime;
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码加密传输, 前端公钥加密,后端私钥解密
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ContentRsaEncodeSettingModel extends CsObject {
|
||||||
|
private String privateKey;
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jwt参数配置
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class JwtAuthSettingModel extends CsObject {
|
||||||
|
/**
|
||||||
|
* 必须使用最少88位的Base64对该令牌进行编码
|
||||||
|
*/
|
||||||
|
private String base64Secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌过期时间 此处单位/毫秒
|
||||||
|
*/
|
||||||
|
private Long tokenValidityInSeconds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token 续期检查
|
||||||
|
*/
|
||||||
|
private Long detect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 续期时间
|
||||||
|
*/
|
||||||
|
private Long renew;
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class OSSConfigModel extends CsObject {
|
||||||
|
/**
|
||||||
|
* 服务地址
|
||||||
|
*/
|
||||||
|
private String endpoint;
|
||||||
|
/**
|
||||||
|
* 存储桶名称
|
||||||
|
*/
|
||||||
|
private String bucketName;
|
||||||
|
/**
|
||||||
|
* Access Key
|
||||||
|
*/
|
||||||
|
private String accessKey;
|
||||||
|
/**
|
||||||
|
* Secret Key
|
||||||
|
*/
|
||||||
|
private String secretKey;
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class StorageOSSModel {
|
||||||
|
/**
|
||||||
|
* 上传大小, 单位: M
|
||||||
|
*/
|
||||||
|
private Long maxSize;
|
||||||
|
private OSSConfigModel minio;
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-04-13
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SwaggerApiDocSettingModel extends CsObject {
|
||||||
|
private Boolean enabled;
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程池 配置
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-04-13
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ThreadPoolSettingModel extends CsObject {
|
||||||
|
/**
|
||||||
|
* 核心线程池大小
|
||||||
|
*/
|
||||||
|
private int corePoolSize;
|
||||||
|
/**
|
||||||
|
* 最大线程数
|
||||||
|
*/
|
||||||
|
private int maxPoolSize;
|
||||||
|
/**
|
||||||
|
* 活跃时间
|
||||||
|
*/
|
||||||
|
private int keepAliveSeconds;
|
||||||
|
/**
|
||||||
|
* 队列容量
|
||||||
|
*/
|
||||||
|
private int queueCapacity;
|
||||||
|
}
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import cn.odboy.constant.CaptchaCodeEnum;
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import cn.odboy.util.CsStringUtil;
|
||||||
|
import com.wf.captcha.*;
|
||||||
|
import com.wf.captcha.base.Captcha;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码配置
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class UserLoginCaptchaSettingModel extends CsObject {
|
||||||
|
private CaptchaCodeEnum codeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码有效期 分钟
|
||||||
|
*/
|
||||||
|
private Long expiration = 5L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码内容长度
|
||||||
|
*/
|
||||||
|
private int length = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码宽度
|
||||||
|
*/
|
||||||
|
private int width = 111;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码高度
|
||||||
|
*/
|
||||||
|
private int height = 36;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码字体
|
||||||
|
*/
|
||||||
|
private String fontName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字体大小
|
||||||
|
*/
|
||||||
|
private int fontSize = 25;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 依据配置信息生产验证码
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public Captcha getCaptcha() {
|
||||||
|
Captcha captcha;
|
||||||
|
switch (codeType) {
|
||||||
|
case ARITHMETIC:
|
||||||
|
// 算术类型 https://gitee.com/whvse/EasyCaptcha
|
||||||
|
captcha = new FixedArithmeticCaptcha(width, height);
|
||||||
|
// 几位数运算, 默认是两位
|
||||||
|
captcha.setLen(length);
|
||||||
|
break;
|
||||||
|
case CHINESE:
|
||||||
|
captcha = new ChineseCaptcha(width, height);
|
||||||
|
captcha.setLen(length);
|
||||||
|
break;
|
||||||
|
case CHINESE_GIF:
|
||||||
|
captcha = new ChineseGifCaptcha(width, height);
|
||||||
|
captcha.setLen(length);
|
||||||
|
break;
|
||||||
|
case GIF:
|
||||||
|
captcha = new GifCaptcha(width, height);
|
||||||
|
captcha.setLen(length);
|
||||||
|
break;
|
||||||
|
case SPEC:
|
||||||
|
captcha = new SpecCaptcha(width, height);
|
||||||
|
captcha.setLen(length);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new BadRequestException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
|
||||||
|
}
|
||||||
|
if (StrUtil.isNotBlank(fontName)) {
|
||||||
|
captcha.setFont(new Font(fontName, Font.PLAIN, fontSize));
|
||||||
|
}
|
||||||
|
return captcha;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
|
||||||
|
public FixedArithmeticCaptcha(int width, int height) {
|
||||||
|
super(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected char[] alphas() {
|
||||||
|
// 生成随机数字和运算符
|
||||||
|
int n1 = num(1, 10), n2 = num(1, 10);
|
||||||
|
int opt = num(3);
|
||||||
|
|
||||||
|
// 计算结果
|
||||||
|
int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
|
||||||
|
// 转换为字符运算符
|
||||||
|
char optChar = "+-x".charAt(opt);
|
||||||
|
|
||||||
|
this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
|
||||||
|
this.chars = String.valueOf(res);
|
||||||
|
|
||||||
|
return chars.toCharArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.properties.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录配置
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class UserLoginSettingModel extends CsObject {
|
||||||
|
/**
|
||||||
|
* 账号单用户 登录
|
||||||
|
*/
|
||||||
|
private boolean single = false;
|
||||||
|
private UserLoginCaptchaSettingModel captchaSetting;
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.quartz.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.quartz.spi.TriggerFiredBundle;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.scheduling.quartz.AdaptableJobFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时任务配置
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@Scope("singleton")
|
||||||
|
public class QuartzConfig {
|
||||||
|
/**
|
||||||
|
* 解决Job中注入Spring Bean为null的问题
|
||||||
|
*/
|
||||||
|
@Component("quartzJobFactory")
|
||||||
|
public static class QuartzJobFactory extends AdaptableJobFactory {
|
||||||
|
|
||||||
|
private final AutowireCapableBeanFactory autowireCapableBeanFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public QuartzJobFactory(AutowireCapableBeanFactory autowireCapableBeanFactory) {
|
||||||
|
this.autowireCapableBeanFactory = autowireCapableBeanFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Object createJobInstance(@NonNull TriggerFiredBundle triggerFiredBundle) throws Exception {
|
||||||
|
try {
|
||||||
|
// 调用父类的方法, 把Job注入Spring中
|
||||||
|
Object jobInstance = super.createJobInstance(triggerFiredBundle);
|
||||||
|
autowireCapableBeanFactory.autowireBean(jobInstance);
|
||||||
|
return jobInstance;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Job注入Spring失败, {}", triggerFiredBundle, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.quartz.core;
|
||||||
|
|
||||||
|
import cn.odboy.framework.context.CsSpringBeanHolder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行定时任务
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class QuartzRunnable implements Callable<Object> {
|
||||||
|
private final Object target;
|
||||||
|
private final Method method;
|
||||||
|
private final String params;
|
||||||
|
|
||||||
|
public QuartzRunnable(String beanName, String methodName, String params)
|
||||||
|
throws NoSuchMethodException, SecurityException {
|
||||||
|
this.target = CsSpringBeanHolder.getBean(beanName);
|
||||||
|
this.params = params;
|
||||||
|
if (StringUtils.isNotBlank(params)) {
|
||||||
|
this.method = target.getClass().getDeclaredMethod(methodName, String.class);
|
||||||
|
} else {
|
||||||
|
this.method = target.getClass().getDeclaredMethod(methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
public Object call() throws Exception {
|
||||||
|
ReflectionUtils.makeAccessible(method);
|
||||||
|
if (StringUtils.isNotBlank(params)) {
|
||||||
|
return method.invoke(target, params);
|
||||||
|
} else {
|
||||||
|
return method.invoke(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+843
@@ -0,0 +1,843 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.redis;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnection;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.*;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
public class CsRedisHelper {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CsRedisHelper.class);
|
||||||
|
|
||||||
|
private RedisTemplate<Object, Object> redisTemplate;
|
||||||
|
|
||||||
|
public CsRedisHelper(RedisTemplate<Object, Object> redisTemplate) {
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
this.redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||||
|
this.redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定缓存失效时间
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param time 时间(秒) 注意:这里将会替换原有的时间
|
||||||
|
*/
|
||||||
|
public boolean expire(String key, long time) {
|
||||||
|
try {
|
||||||
|
if (time > 0) {
|
||||||
|
redisTemplate.expire(key, time, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定缓存失效时间
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param time 时间(秒) 注意:这里将会替换原有的时间
|
||||||
|
* @param timeUnit 单位
|
||||||
|
*/
|
||||||
|
public boolean expire(String key, long time, TimeUnit timeUnit) {
|
||||||
|
try {
|
||||||
|
if (time > 0) {
|
||||||
|
redisTemplate.expire(key, time, timeUnit);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 key 获取过期时间
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @return 时间(秒) 返回0代表为永久有效
|
||||||
|
*/
|
||||||
|
public long getExpire(Object key) {
|
||||||
|
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找匹配key
|
||||||
|
*
|
||||||
|
* @param pattern key
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public List<String> scan(String pattern) {
|
||||||
|
ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
|
||||||
|
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
|
||||||
|
RedisConnection rc = Objects.requireNonNull(factory).getConnection();
|
||||||
|
Cursor<byte[]> cursor = rc.scan(options);
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
while (cursor.hasNext()) {
|
||||||
|
result.add(new String(cursor.next()));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
RedisConnectionUtils.releaseConnection(rc, factory);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询 key
|
||||||
|
*
|
||||||
|
* @param patternKey key
|
||||||
|
* @param page 页码
|
||||||
|
* @param size 每页数目
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public List<String> findKeysForPage(String patternKey, int page, int size) {
|
||||||
|
ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
|
||||||
|
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
|
||||||
|
RedisConnection rc = Objects.requireNonNull(factory).getConnection();
|
||||||
|
Cursor<byte[]> cursor = rc.scan(options);
|
||||||
|
List<String> result = new ArrayList<>(size);
|
||||||
|
int tmpIndex = 0;
|
||||||
|
int fromIndex = page * size;
|
||||||
|
int toIndex = page * size + size;
|
||||||
|
while (cursor.hasNext()) {
|
||||||
|
if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
|
||||||
|
result.add(new String(cursor.next()));
|
||||||
|
tmpIndex++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 获取到满足条件的数据后,就可以退出了
|
||||||
|
if (tmpIndex >= toIndex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tmpIndex++;
|
||||||
|
cursor.next();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
RedisConnectionUtils.releaseConnection(rc, factory);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断key是否存在
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return true 存在 false不存在
|
||||||
|
*/
|
||||||
|
public boolean hasKey(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.hasKey(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除缓存
|
||||||
|
*
|
||||||
|
* @param key 可以传一个值 或多个
|
||||||
|
*/
|
||||||
|
public void del(String... keys) {
|
||||||
|
if (keys != null && keys.length > 0) {
|
||||||
|
if (keys.length == 1) {
|
||||||
|
boolean result = redisTemplate.delete(keys[0]);
|
||||||
|
log.debug("--------------------------------------------");
|
||||||
|
log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(", 结果:").append(result).toString());
|
||||||
|
log.debug("--------------------------------------------");
|
||||||
|
} else {
|
||||||
|
Set<Object> keySet = new HashSet<>();
|
||||||
|
for (String key : keys) {
|
||||||
|
if (redisTemplate.hasKey(key)) {
|
||||||
|
keySet.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long count = redisTemplate.delete(keySet);
|
||||||
|
log.debug("--------------------------------------------");
|
||||||
|
log.debug("成功删除缓存:" + keySet.toString());
|
||||||
|
log.debug("缓存删除数量:" + count + "个");
|
||||||
|
log.debug("--------------------------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量模糊删除key
|
||||||
|
*
|
||||||
|
* @param pattern
|
||||||
|
*/
|
||||||
|
public void scanDel(String pattern) {
|
||||||
|
ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
|
||||||
|
try (Cursor<byte[]> cursor = redisTemplate.executeWithStickyConnection(
|
||||||
|
(RedisCallback<Cursor<byte[]>>) connection -> (Cursor<byte[]>) new ConvertingCursor<>(
|
||||||
|
connection.scan(options),
|
||||||
|
redisTemplate.getKeySerializer()::deserialize))) {
|
||||||
|
while (cursor.hasNext()) {
|
||||||
|
redisTemplate.delete(cursor.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================String=============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存获取
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public Object get(String key) {
|
||||||
|
return key == null ? null : redisTemplate.opsForValue().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存获取
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public <T> T get(String key, Class<T> clazz) {
|
||||||
|
Object value = key == null ? null : redisTemplate.opsForValue().get(key);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 如果 value 不是目标类型, 则尝试将其反序列化为 clazz 类型 by
|
||||||
|
if (!clazz.isInstance(value)) {
|
||||||
|
return JSON.parseObject(value.toString(), clazz);
|
||||||
|
} else if (clazz.isInstance(value)) {
|
||||||
|
return clazz.cast(value);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存获取
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param clazz 列表中元素的类型
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public <T> List<T> getList(String key, Class<T> clazz) {
|
||||||
|
Object value = key == null ? null : redisTemplate.opsForValue().get(key);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof List<?>) {
|
||||||
|
List<?> list = (List<?>) value;
|
||||||
|
// 检查每个元素是否为指定类型
|
||||||
|
if (list.stream().allMatch(clazz::isInstance)) {
|
||||||
|
return list.stream().map(clazz::cast).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存获取
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public String getStr(String key) {
|
||||||
|
if (StrUtil.isBlank(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Object value = redisTemplate.opsForValue().get(key);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取
|
||||||
|
*
|
||||||
|
* @param keys
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Object> multiGet(List<String> keys) {
|
||||||
|
List list = redisTemplate.opsForValue().multiGet(Sets.newHashSet(keys));
|
||||||
|
List resultList = Lists.newArrayList();
|
||||||
|
Optional.ofNullable(list).ifPresent(e -> list.forEach(ele -> Optional.ofNullable(ele).ifPresent(resultList::add)));
|
||||||
|
return resultList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存放入
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @return true成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean set(String key, Object value) {
|
||||||
|
int attempt = 0;
|
||||||
|
while (attempt < 3) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForValue().set(key, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
attempt++;
|
||||||
|
log.error("Attempt {} failed: {}", attempt, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存放入并设置时间
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期, 注意:这里将会替换原有的时间
|
||||||
|
* @return true成功 false 失败
|
||||||
|
*/
|
||||||
|
public boolean set(String key, Object value, long time) {
|
||||||
|
try {
|
||||||
|
if (time > 0) {
|
||||||
|
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
|
||||||
|
} else {
|
||||||
|
set(key, value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存放入并设置时间
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间,注意:这里将会替换原有的时间
|
||||||
|
* @param timeUnit 类型
|
||||||
|
* @return true成功 false 失败
|
||||||
|
*/
|
||||||
|
public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
|
||||||
|
try {
|
||||||
|
if (time > 0) {
|
||||||
|
redisTemplate.opsForValue().set(key, value, time, timeUnit);
|
||||||
|
} else {
|
||||||
|
set(key, value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================Map=================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashGet
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @param item 项 不能为null
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public Object hget(String key, String item) {
|
||||||
|
return redisTemplate.opsForHash().get(key, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取hashKey对应的所有键值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return 对应的多个键值
|
||||||
|
*/
|
||||||
|
public Map<Object, Object> hmget(String key) {
|
||||||
|
return redisTemplate.opsForHash().entries(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashSet
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param map 对应多个键值
|
||||||
|
* @return true 成功 false 失败
|
||||||
|
*/
|
||||||
|
public boolean hmset(String key, Map<String, Object> map) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().putAll(key, map);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashSet
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param map 对应多个键值
|
||||||
|
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
|
||||||
|
* @return true成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean hmset(String key, Map<String, Object> map, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().putAll(key, map);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向一张hash表中放入数据,如果不存在将创建
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param value 值
|
||||||
|
* @return true 成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean hset(String key, String item, Object value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().put(key, item, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向一张hash表中放入数据,如果不存在将创建
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
|
||||||
|
* @return true 成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean hset(String key, String item, Object value, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().put(key, item, value);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除hash表中的值
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @param item 项 可以使多个 不能为null
|
||||||
|
*/
|
||||||
|
public void hdel(String key, Object... item) {
|
||||||
|
redisTemplate.opsForHash().delete(key, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断hash表中是否有该项的值
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @param item 项 不能为null
|
||||||
|
* @return true 存在 false不存在
|
||||||
|
*/
|
||||||
|
public boolean hHasKey(String key, String item) {
|
||||||
|
return redisTemplate.opsForHash().hasKey(key, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param by 要增加几(大于0)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public double hincr(String key, String item, double by) {
|
||||||
|
return redisTemplate.opsForHash().increment(key, item, by);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hash递减
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param by 要减少记(小于0)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public double hdecr(String key, String item, double by) {
|
||||||
|
return redisTemplate.opsForHash().increment(key, item, -by);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================set=============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据key获取Set中的所有值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Set<Object> sGet(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().members(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据value从一个set中查询,是否存在
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @return true 存在 false不存在
|
||||||
|
*/
|
||||||
|
public boolean sHasKey(String key, Object value) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().isMember(key, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数据放入set缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param values 值 可以是多个
|
||||||
|
* @return 成功个数
|
||||||
|
*/
|
||||||
|
public long sSet(String key, Object... values) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().add(key, values);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将set数据放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param time 时间(秒) 注意:这里将会替换原有的时间
|
||||||
|
* @param values 值 可以是多个
|
||||||
|
* @return 成功个数
|
||||||
|
*/
|
||||||
|
public long sSetAndTime(String key, long time, Object... values) {
|
||||||
|
try {
|
||||||
|
Long count = redisTemplate.opsForSet().add(key, values);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取set缓存的长度
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long sGetSetSize(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().size(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除值为value的
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param values 值 可以是多个
|
||||||
|
* @return 移除的个数
|
||||||
|
*/
|
||||||
|
public long setRemove(String key, Object... values) {
|
||||||
|
try {
|
||||||
|
Long count = redisTemplate.opsForSet().remove(key, values);
|
||||||
|
return count;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===============================list=================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取list缓存的内容
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param start 开始
|
||||||
|
* @param end 结束 0 到 -1代表所有值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Object> lGet(String key, long start, long end) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForList().range(key, start, end);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取list缓存的长度
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long lGetListSize(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForList().size(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过索引 获取list中的值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Object lGetIndex(String key, long index) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForList().index(key, index);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, Object value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPush(key, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒) 注意:这里将会替换原有的时间
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, Object value, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPush(key, value);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, List<Object> value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPushAll(key, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒) 注意:这里将会替换原有的时间
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, List<Object> value, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPushAll(key, value);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据索引修改list中的某条数据
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param index 索引
|
||||||
|
* @param value 值
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public boolean lUpdateIndex(String key, long index, Object value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().set(key, index, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除N个值为value
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param count 移除多少个
|
||||||
|
* @param value 值
|
||||||
|
* @return 移除的个数
|
||||||
|
*/
|
||||||
|
public long lRemove(String key, long count, Object value) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForList().remove(key, count, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param prefix 前缀
|
||||||
|
* @param ids id
|
||||||
|
*/
|
||||||
|
public void delByKeys(String prefix, Set<Long> ids) {
|
||||||
|
Set<Object> keys = new HashSet<>();
|
||||||
|
for (Long id : ids) {
|
||||||
|
keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));
|
||||||
|
}
|
||||||
|
long count = redisTemplate.delete(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================incr=============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递增
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Long increment(String key) {
|
||||||
|
return redisTemplate.opsForValue().increment(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递减
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Long decrement(String key) {
|
||||||
|
return redisTemplate.opsForValue().decrement(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================校验=============================
|
||||||
|
public boolean isHealth() {
|
||||||
|
try {
|
||||||
|
redisTemplate.getConnectionFactory().getConnection().ping();
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Redis health check failed: {}", e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅当键不存在时设置值(原子操作)
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param timeout 过期时间
|
||||||
|
* @param unit 时间单位
|
||||||
|
* @return 如果设置成功返回true,否则返回false
|
||||||
|
*/
|
||||||
|
public Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
|
||||||
|
try {
|
||||||
|
String stringValue = ObjUtil.toString(value);
|
||||||
|
// 使用Redis的SETNX命令实现原子性操作
|
||||||
|
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, stringValue, timeout, unit);
|
||||||
|
return success != null && success;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Redis setIfAbsent操作失败, key: {}", key, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.redis;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONFactory;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.codec.digest.MurmurHash3;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||||
|
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||||
|
import org.springframework.cache.Cache;
|
||||||
|
import org.springframework.cache.annotation.CachingConfigurerSupport;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.cache.interceptor.CacheErrorHandler;
|
||||||
|
import org.springframework.cache.interceptor.KeyGenerator;
|
||||||
|
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||||
|
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||||
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||||
|
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||||
|
import org.springframework.data.redis.serializer.SerializationException;
|
||||||
|
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@EnableCaching
|
||||||
|
@AutoConfigureBefore(RedisAutoConfiguration.class)
|
||||||
|
public class RedisConfiguration extends CachingConfigurerSupport {
|
||||||
|
/**
|
||||||
|
* 自动识别json对象白名单配置(仅允许解析的包名, 范围越小越安全)<br/> 未配置可能导致, 登录失败, 反复登录等问题
|
||||||
|
*/
|
||||||
|
private static final String[] WHITELIST_STR =
|
||||||
|
{
|
||||||
|
"org.springframework",
|
||||||
|
"cn.odboy.system.dal.dataobject",
|
||||||
|
"cn.odboy.system.dal.model",
|
||||||
|
"cn.odboy.task.dal.dataobject",
|
||||||
|
"cn.odboy.task.dal.model",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 redis 数据默认过期时间,默认2小时 设置@cacheable 序列化方式
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public RedisCacheConfiguration redisCacheConfiguration() {
|
||||||
|
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
|
||||||
|
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
|
||||||
|
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2));
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "redisTemplate")
|
||||||
|
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||||
|
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||||
|
// 指定 key 和 value 的序列化方案
|
||||||
|
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
|
||||||
|
// value值的序列化采用fastJsonRedisSerializer
|
||||||
|
template.setValueSerializer(fastJsonRedisSerializer);
|
||||||
|
template.setHashValueSerializer(fastJsonRedisSerializer);
|
||||||
|
// 设置fastJson的序列化白名单
|
||||||
|
for (String pack : WHITELIST_STR) {
|
||||||
|
JSONFactory.getDefaultObjectReaderProvider().addAutoTypeAccept(pack);
|
||||||
|
}
|
||||||
|
// key的序列化采用StringRedisSerializer
|
||||||
|
template.setKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setHashKeySerializer(new StringRedisSerializer());
|
||||||
|
template.setConnectionFactory(redisConnectionFactory);
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存管理器
|
||||||
|
*
|
||||||
|
* @param redisConnectionFactory /
|
||||||
|
* @return 缓存管理器
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
|
||||||
|
RedisCacheConfiguration config = redisCacheConfiguration();
|
||||||
|
return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义缓存key生成策略
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Override
|
||||||
|
public KeyGenerator keyGenerator() {
|
||||||
|
return (target, method, params) -> {
|
||||||
|
Map<String, Object> container = new HashMap<>(8);
|
||||||
|
Class<?> targetClassClass = target.getClass();
|
||||||
|
// 类地址
|
||||||
|
container.put("class", targetClassClass.toGenericString());
|
||||||
|
// 方法名称
|
||||||
|
container.put("methodName", method.getName());
|
||||||
|
// 包名称
|
||||||
|
container.put("package", targetClassClass.getPackage());
|
||||||
|
// 参数列表
|
||||||
|
for (int i = 0; i < params.length; i++) {
|
||||||
|
container.put(String.valueOf(i), params[i]);
|
||||||
|
}
|
||||||
|
// 转为JSON字符串
|
||||||
|
String jsonString = JSON.toJSONString(container);
|
||||||
|
// 使用 MurmurHash 生成 hash
|
||||||
|
return Integer.toHexString(MurmurHash3.hash32x86(jsonString.getBytes()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
public CacheErrorHandler errorHandler() {
|
||||||
|
return new SimpleCacheErrorHandler() {
|
||||||
|
@Override
|
||||||
|
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
|
||||||
|
// 处理缓存读取错误
|
||||||
|
log.error("Cache Get Error: {}", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
|
||||||
|
// 处理缓存写入错误
|
||||||
|
log.error("Cache Put Error: {}", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
|
||||||
|
// 处理缓存删除错误
|
||||||
|
log.error("Cache Evict Error: {}", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCacheClearError(RuntimeException exception, Cache cache) {
|
||||||
|
// 处理缓存清除错误
|
||||||
|
log.error("Cache Clear Error: {}", exception.getMessage());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value 序列化
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
static class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||||
|
|
||||||
|
private final Class<T> clazz;
|
||||||
|
|
||||||
|
FastJsonRedisSerializer(Class<T> clazz) {
|
||||||
|
super();
|
||||||
|
this.clazz = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 序列化
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public byte[] serialize(T t) throws SerializationException {
|
||||||
|
if (t == null) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反序列化
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T deserialize(byte[] bytes) throws SerializationException {
|
||||||
|
if (bytes == null || bytes.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String str = new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
// 移除对 SupportAutoType 的使用
|
||||||
|
return JSON.parseObject(str, clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.server.config;
|
||||||
|
|
||||||
|
import cn.odboy.framework.properties.AppProperties;
|
||||||
|
import cn.odboy.framework.properties.model.ThreadPoolSettingModel;
|
||||||
|
import com.alibaba.ttl.TtlRunnable;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建自定义的线程池
|
||||||
|
*/
|
||||||
|
@EnableAsync
|
||||||
|
@Configuration
|
||||||
|
public class AsyncTheadPoolConfig implements AsyncConfigurer {
|
||||||
|
@Autowired
|
||||||
|
private AppProperties properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义线程池, 用法 @Async
|
||||||
|
*
|
||||||
|
* @return Executor
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Executor getAsyncExecutor() {
|
||||||
|
ThreadPoolSettingModel asyncTaskPool = properties.getAsyncTaskPool();
|
||||||
|
// 自定义工厂
|
||||||
|
ThreadFactory factory = r -> new Thread(r, "DefaultAsync-" + new AtomicInteger(1).getAndIncrement());
|
||||||
|
// 自定义线程池
|
||||||
|
return new ThreadPoolExecutor(
|
||||||
|
asyncTaskPool.getCorePoolSize(),
|
||||||
|
asyncTaskPool.getMaxPoolSize(),
|
||||||
|
asyncTaskPool.getKeepAliveSeconds(),
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
new ArrayBlockingQueue<>(asyncTaskPool.getQueueCapacity()),
|
||||||
|
factory,
|
||||||
|
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义线程池, 用法, 注入到类中使用
|
||||||
|
*/
|
||||||
|
@Bean("taskAsync")
|
||||||
|
public ThreadPoolTaskExecutor taskAsync() {
|
||||||
|
ThreadPoolSettingModel asyncTaskPool = properties.getAsyncTaskPool();
|
||||||
|
// 用法 private ThreadPoolTaskExecutor taskExecutor
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(asyncTaskPool.getCorePoolSize());
|
||||||
|
executor.setMaxPoolSize(asyncTaskPool.getMaxPoolSize());
|
||||||
|
executor.setQueueCapacity(asyncTaskPool.getQueueCapacity());
|
||||||
|
executor.setThreadNamePrefix("TaskAsync-");
|
||||||
|
executor.setKeepAliveSeconds(asyncTaskPool.getKeepAliveSeconds());
|
||||||
|
// DiscardOldestPolicy,抛弃最早的任务,将新任务加入队列。
|
||||||
|
// AbortPolicy,拒绝执行新任务,并抛出异常。
|
||||||
|
// CallerRunsPolicy,交由调用者线程执行新任务,如果调用者线程已关闭,则抛弃任务。
|
||||||
|
// DiscardPolicy,直接抛弃新任务。
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
|
||||||
|
// 设置包装器
|
||||||
|
executor.setTaskDecorator(TtlRunnable::get);
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean("longTimeTaskAsync")
|
||||||
|
public ThreadPoolTaskExecutor longTimeTaskAsync() {
|
||||||
|
ThreadPoolSettingModel asyncTaskPool = properties.getAsyncTaskPool();
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(asyncTaskPool.getCorePoolSize());
|
||||||
|
executor.setMaxPoolSize(asyncTaskPool.getMaxPoolSize());
|
||||||
|
executor.setQueueCapacity(asyncTaskPool.getQueueCapacity());
|
||||||
|
executor.setThreadNamePrefix("LongTimeTaskAsync-");
|
||||||
|
executor.setKeepAliveSeconds(asyncTaskPool.getKeepAliveSeconds());
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
|
||||||
|
// 设置包装器
|
||||||
|
executor.setTaskDecorator(TtlRunnable::get);
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.server.converters;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DateStringArraysToListDateConverter implements Converter<String[], List<Date>> {
|
||||||
|
private static final ThreadLocal<SimpleDateFormat> SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Date> convert(String[] source) {
|
||||||
|
if (source.length == 0) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return Arrays.stream(source).map(s -> {
|
||||||
|
try {
|
||||||
|
return SDF.get().parse(s);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid date string: " + s);
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.server.converters;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DateStringToDateConverter implements Converter<String, Date> {
|
||||||
|
private static final ThreadLocal<SimpleDateFormat> SDF = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date convert(String source) {
|
||||||
|
if (StrUtil.isBlank(source)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return SDF.get().parse(source);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid date string: " + source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.server.core;
|
||||||
|
|
||||||
|
import cn.odboy.framework.server.converters.DateStringArraysToListDateConverter;
|
||||||
|
import cn.odboy.framework.server.converters.DateStringToDateConverter;
|
||||||
|
import com.alibaba.fastjson2.JSONWriter;
|
||||||
|
import com.alibaba.fastjson2.support.config.FastJsonConfig;
|
||||||
|
import com.alibaba.fastjson2.support.spring.http.converter.FastJsonHttpMessageConverter;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.format.FormatterRegistry;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨越、静态资源映射、Http消息转换
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-03-21
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebMvc
|
||||||
|
public class ConfigurerAdapter implements WebMvcConfigurer {
|
||||||
|
@Autowired
|
||||||
|
private DateStringArraysToListDateConverter dateStringArraysToListDateConverter;
|
||||||
|
@Autowired
|
||||||
|
private DateStringToDateConverter dateStringToDateConverter;
|
||||||
|
@Autowired
|
||||||
|
private CsFileLocalUploadHelper fileUploadPathHelper;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.setAllowCredentials(true);
|
||||||
|
config.addAllowedOriginPattern("*");
|
||||||
|
config.addAllowedHeader("*");
|
||||||
|
config.addAllowedMethod("*");
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
return new CorsFilter(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
String path = fileUploadPathHelper.getPath();
|
||||||
|
String pathUtl = "file:" + path.replace("\\", "/");
|
||||||
|
registry.addResourceHandler("/avatar/**").addResourceLocations(pathUtl).setCachePeriod(0);
|
||||||
|
registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0);
|
||||||
|
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http消息转换器
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||||
|
// 添加默认的 StringHttpMessageConverter
|
||||||
|
converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
|
||||||
|
// 配置 FastJsonHttpMessageConverter
|
||||||
|
FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
|
||||||
|
List<MediaType> supportMediaTypeList = new ArrayList<>();
|
||||||
|
supportMediaTypeList.add(MediaType.APPLICATION_JSON);
|
||||||
|
FastJsonConfig config = new FastJsonConfig();
|
||||||
|
config.setDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
// 开启引用检测, 枚举支持
|
||||||
|
/// 是否输出值为null的字段
|
||||||
|
// config.setWriterFeatures(JSONWriter.Feature.WriteMapNullValue);
|
||||||
|
/// 字段如果为null,输出为false,而非null
|
||||||
|
// config.setWriterFeatures(JSONWriter.Feature.WriteNullBooleanAsFalse);
|
||||||
|
/// 字段如果为null,输出为[],而非null
|
||||||
|
// config.setWriterFeatures(JSONWriter.Feature.WriteNullListAsEmpty);
|
||||||
|
/// 字符类型字段如果为null,输出为"",而非null
|
||||||
|
// config.setWriterFeatures(JSONWriter.Feature.WriteNullStringAsEmpty);
|
||||||
|
/// 太具体的数值会直接影响逻辑本身, 所以不要这个
|
||||||
|
// config.setWriterFeatures(JSONWriter.Feature.WriteNullNumberAsZero);
|
||||||
|
/// 全局Long转字符串,会导致基础框架本身异常, 所以不要这个
|
||||||
|
// config.setWriterFeatures(JSONWriter.Feature.WriteLongAsString);
|
||||||
|
config.setWriterFeatures(JSONWriter.Feature.WriteEnumUsingToString);
|
||||||
|
config.setWriterFeatures(JSONWriter.Feature.ReferenceDetection);
|
||||||
|
fastJsonConverter.setFastJsonConfig(config);
|
||||||
|
fastJsonConverter.setSupportedMediaTypes(supportMediaTypeList);
|
||||||
|
fastJsonConverter.setDefaultCharset(StandardCharsets.UTF_8);
|
||||||
|
// 将 FastJsonHttpMessageConverter 添加到列表末尾
|
||||||
|
converters.add(fastJsonConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义时间转换器
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addFormatters(FormatterRegistry registry) {
|
||||||
|
registry.addConverter(dateStringArraysToListDateConverter);
|
||||||
|
registry.addConverter(dateStringToDateConverter);
|
||||||
|
}
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.server.core;
|
||||||
|
|
||||||
|
import cn.odboy.constant.SystemConst;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简化文件上传路径配置
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class CsFileLocalUploadHelper {
|
||||||
|
/**
|
||||||
|
* 单位MB
|
||||||
|
*/
|
||||||
|
private static final int AVATAR_MAX_SIZE = 5;
|
||||||
|
private static final String PATH_WINDOWS = "C:\\${appName}\\file\\";
|
||||||
|
private static final String PATH_MAC = "/Users/odboy/${appName}/file/";
|
||||||
|
private static final String PATH_LINUX = "/home/${appName}/file/";
|
||||||
|
@Value("${spring.application.name}")
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
String os = System.getProperty("os.name");
|
||||||
|
if (os.toLowerCase().startsWith(SystemConst.WIN)) {
|
||||||
|
return PATH_WINDOWS.replace("${appName}", appName);
|
||||||
|
} else if (os.toLowerCase().startsWith(SystemConst.MAC)) {
|
||||||
|
return PATH_MAC.replace("${appName}", appName);
|
||||||
|
}
|
||||||
|
return PATH_LINUX.replace("${appName}", appName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAvatarMaxSize() {
|
||||||
|
return AVATAR_MAX_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.websocket.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ServerEndpointExporter serverEndpointExporter() {
|
||||||
|
return new ServerEndpointExporter();
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.websocket.config;
|
||||||
|
|
||||||
|
import io.undertow.server.DefaultByteBufferPool;
|
||||||
|
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||||
|
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||||
|
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解决启动io.undertow.websockets.jsr UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used的警告
|
||||||
|
*
|
||||||
|
* @date 2024-06-07
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class WsFactory implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(UndertowServletWebServerFactory factory) {
|
||||||
|
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||||
|
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||||
|
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 1024));
|
||||||
|
deploymentInfo.addServletContextAttribute(
|
||||||
|
"io.undertow.websockets.jsr.WebSocketDeploymentInfo",
|
||||||
|
webSocketDeploymentInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.websocket.context;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket客户端管理
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-07-24
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CsWsClientManager {
|
||||||
|
/**
|
||||||
|
* concurrent包的线程安全Map, 用来存放每个客户端对应的MyWebSocket对象。(分布式必出问题)
|
||||||
|
*/
|
||||||
|
private static final Map<String, CsWsServer> clientMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static void addClient(String sid, CsWsServer csWsServer) {
|
||||||
|
// 如果存在就先删除一个, 防止重复推送消息
|
||||||
|
removeClient(sid);
|
||||||
|
clientMap.put(sid, csWsServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeClient(String sid) {
|
||||||
|
CsWsServer wsServer = clientMap.get(sid);
|
||||||
|
if (wsServer != null) {
|
||||||
|
try {
|
||||||
|
// log.info("关闭session, sid={}", sid);
|
||||||
|
wsServer.getSession().close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Close session failed, sid={}", sid, e);
|
||||||
|
}
|
||||||
|
// log.info("停止任务, sid={}", sid);
|
||||||
|
wsServer.stopTask(sid);
|
||||||
|
// log.info("移除客户端, sid={}", sid);
|
||||||
|
clientMap.remove(sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<CsWsServer> getAllClient() {
|
||||||
|
return clientMap.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CsWsServer getClientBySid(String sid) {
|
||||||
|
return clientMap.get(sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.websocket.context;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CsWsMessage extends CsObject {
|
||||||
|
private String bizCode;
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
public CsWsMessage(String bizCode, String data) {
|
||||||
|
this.bizCode = bizCode;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.websocket.context;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.websocket.*;
|
||||||
|
import javax.websocket.server.PathParam;
|
||||||
|
import javax.websocket.server.ServerEndpoint;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@ServerEndpoint("/websocket/{sid}")
|
||||||
|
@Getter
|
||||||
|
public class CsWsServer {
|
||||||
|
private static final Map<String, Thread> taskThreadMap = new ConcurrentHashMap<>();
|
||||||
|
/**
|
||||||
|
* 与某个客户端的连接会话, 需要通过它来给客户端发送数据
|
||||||
|
*/
|
||||||
|
private Session session;
|
||||||
|
/**
|
||||||
|
* 接收sid
|
||||||
|
*/
|
||||||
|
private String sid = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接建立成功调用的方法
|
||||||
|
*/
|
||||||
|
@OnOpen
|
||||||
|
public void onOpen(Session session, @PathParam("sid") String sid) {
|
||||||
|
this.session = session;
|
||||||
|
// {username}#{bizCode}#{contextParams}
|
||||||
|
this.sid = sid;
|
||||||
|
CsWsClientManager.addClient(sid, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收到客户端消息后调用的方法
|
||||||
|
*
|
||||||
|
* @param message 客户端发送过来的消息
|
||||||
|
*/
|
||||||
|
@OnMessage
|
||||||
|
public void onMessage(String message, Session session) {
|
||||||
|
CsWsMessage wsMessage = JSON.parseObject(message, CsWsMessage.class);
|
||||||
|
String bizCode = wsMessage.getBizCode();
|
||||||
|
Object data = wsMessage.getData();
|
||||||
|
log.info("收到来 sid={} 的信息: message={}, bizCode={}, data={}", sid, message, bizCode, JSON.toJSONString(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接关闭调用的方法
|
||||||
|
*/
|
||||||
|
@OnClose
|
||||||
|
public void onClose() {
|
||||||
|
try {
|
||||||
|
CsWsClientManager.removeClient(this.sid);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("WebSocket onClose error, sid={}", this.sid, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnError
|
||||||
|
public void onError(Session session, Throwable error) {
|
||||||
|
// log.error("WebSocket sid={} 发生错误", this.sid, error);
|
||||||
|
try {
|
||||||
|
CsWsClientManager.removeClient(this.sid);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("WebSocket onClose error, sid={}", this.sid, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实现服务器主动推送
|
||||||
|
*/
|
||||||
|
private void innerSendMessage(String message) throws IOException {
|
||||||
|
this.session.getBasicRemote().sendText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精准推送消息
|
||||||
|
*/
|
||||||
|
public void sendMessage(CsWsMessage message, @PathParam("sid") String sid) throws IOException {
|
||||||
|
String body = JSON.toJSONString(message);
|
||||||
|
// log.info("推送消息到{}, 推送内容:{}", sid, message);
|
||||||
|
try {
|
||||||
|
if (sid == null) {
|
||||||
|
sendToAll(body);
|
||||||
|
} else {
|
||||||
|
CsWsServer wsClient = CsWsClientManager.getClientBySid(sid);
|
||||||
|
if (wsClient != null) {
|
||||||
|
wsClient.innerSendMessage(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群发消息
|
||||||
|
*/
|
||||||
|
public void sendToAll(String message) {
|
||||||
|
for (CsWsServer item : CsWsClientManager.getAllClient()) {
|
||||||
|
try {
|
||||||
|
item.innerSendMessage(message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("发送消息给 sid={} 失败", item.sid, e);
|
||||||
|
CsWsClientManager.removeClient(item.sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CsWsServer that = (CsWsServer) o;
|
||||||
|
return Objects.equals(session, that.session) && Objects.equals(sid, that.sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(session, sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Thread getTaskThread(String sid) {
|
||||||
|
Thread thread = taskThreadMap.get(sid);
|
||||||
|
if (thread == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (thread.isInterrupted()) {
|
||||||
|
taskThreadMap.remove(sid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartTask(Runnable runnable) {
|
||||||
|
stopTask(this.sid);
|
||||||
|
Thread thread = new Thread(runnable);
|
||||||
|
thread.start();
|
||||||
|
taskThreadMap.put(this.sid, thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopTask(String sid) {
|
||||||
|
Thread thread = getTaskThread(this.sid);
|
||||||
|
if (thread != null) {
|
||||||
|
thread.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.websocket.model;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsObject;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class WsSidVo extends CsObject {
|
||||||
|
private String username;
|
||||||
|
private String bizCode;
|
||||||
|
private String param;
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.framework.websocket.util;
|
||||||
|
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import cn.odboy.framework.websocket.model.WsSidVo;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket消息解析工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-07-25
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public class WsMessageUtil {
|
||||||
|
public static WsSidVo parseSid(String sid) {
|
||||||
|
// sid {username}_{bizCode}_{param}
|
||||||
|
String[] sids = sid.split("_");
|
||||||
|
if (sids.length != 3) {
|
||||||
|
throw new BadRequestException("sid格式异常");
|
||||||
|
}
|
||||||
|
WsSidVo sidVo = new WsSidVo();
|
||||||
|
sidVo.setUsername(sids[0]);
|
||||||
|
sidVo.setBizCode(sids[1]);
|
||||||
|
sidVo.setParam(sids[2]);
|
||||||
|
return sidVo;
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.odboy.annotation.AnonymousAccess;
|
||||||
|
import cn.odboy.constant.RequestMethodEnum;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
|
||||||
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匿名标记工具
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public class CsAnonTagUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取匿名标记的URL
|
||||||
|
*
|
||||||
|
* @param applicationContext /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static Map<String, Set<String>> getAnonymousUrl(final ApplicationContext applicationContext) {
|
||||||
|
RequestMappingHandlerMapping requestMappingHandlerMapping =
|
||||||
|
(RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
|
||||||
|
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
|
||||||
|
Map<String, java.util.Set<String>> anonymousUrls = new HashMap<>(8);
|
||||||
|
// 获取匿名标记
|
||||||
|
Set<String> get = new HashSet<>();
|
||||||
|
Set<String> post = new HashSet<>();
|
||||||
|
Set<String> put = new HashSet<>();
|
||||||
|
Set<String> patch = new HashSet<>();
|
||||||
|
Set<String> delete = new HashSet<>();
|
||||||
|
Set<String> all = new HashSet<>();
|
||||||
|
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
|
||||||
|
HandlerMethod handlerMethod = infoEntry.getValue();
|
||||||
|
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
|
||||||
|
if (null != anonymousAccess && infoEntry.getKey() != null) {
|
||||||
|
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
|
||||||
|
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.isEmpty() ?
|
||||||
|
RequestMethodEnum.ALL.getType() :
|
||||||
|
requestMethods.get(0).name());
|
||||||
|
PatternsRequestCondition patternsCondition = infoEntry.getKey().getPatternsCondition();
|
||||||
|
if (patternsCondition != null) {
|
||||||
|
switch (Objects.requireNonNull(request)) {
|
||||||
|
case GET:
|
||||||
|
get.addAll(patternsCondition.getPatterns());
|
||||||
|
break;
|
||||||
|
case POST:
|
||||||
|
post.addAll(patternsCondition.getPatterns());
|
||||||
|
break;
|
||||||
|
case PUT:
|
||||||
|
put.addAll(patternsCondition.getPatterns());
|
||||||
|
break;
|
||||||
|
case PATCH:
|
||||||
|
patch.addAll(patternsCondition.getPatterns());
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
delete.addAll(patternsCondition.getPatterns());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
all.addAll(patternsCondition.getPatterns());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
|
||||||
|
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
|
||||||
|
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
|
||||||
|
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
|
||||||
|
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
|
||||||
|
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
|
||||||
|
return anonymousUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有匿名标记的URL
|
||||||
|
*
|
||||||
|
* @param applicationContext /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static Set<String> getAllAnonymousUrl(final ApplicationContext applicationContext) {
|
||||||
|
Set<String> allUrl = new HashSet<>();
|
||||||
|
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(applicationContext);
|
||||||
|
for (Map.Entry<String, Set<String>> entry : anonymousUrls.entrySet()) {
|
||||||
|
allUrl.addAll(anonymousUrls.get(entry.getKey()));
|
||||||
|
}
|
||||||
|
return allUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.codec.Base64Encoder;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密令牌生成工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2022-11-16
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsBase64SecretUtil {
|
||||||
|
|
||||||
|
public static String build() {
|
||||||
|
// 128位uuid
|
||||||
|
return Base64Encoder.encode(getLongUuid(4).getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLongUuid(int bit) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (int i = 0; i < bit; i++) {
|
||||||
|
result.append(IdUtil.simpleUUID());
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(build());
|
||||||
|
}
|
||||||
|
}
|
||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算类
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsBigDecimalUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将对象转换为 BigDecimal
|
||||||
|
*
|
||||||
|
* @param obj 输入对象
|
||||||
|
* @return 转换后的 BigDecimal
|
||||||
|
*/
|
||||||
|
private static BigDecimal toBigDecimal(Object obj) {
|
||||||
|
if (obj instanceof BigDecimal) {
|
||||||
|
return (BigDecimal) obj;
|
||||||
|
} else if (obj instanceof Long) {
|
||||||
|
return BigDecimal.valueOf((Long) obj);
|
||||||
|
} else if (obj instanceof Integer) {
|
||||||
|
return BigDecimal.valueOf((Integer) obj);
|
||||||
|
} else if (obj instanceof Double) {
|
||||||
|
return new BigDecimal(String.valueOf(obj));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加法
|
||||||
|
*
|
||||||
|
* @param a 加数
|
||||||
|
* @param b 加数
|
||||||
|
* @return 两个加数的和, 保留两位小数
|
||||||
|
*/
|
||||||
|
public static BigDecimal add(Object a, Object b) {
|
||||||
|
BigDecimal bdA = toBigDecimal(a);
|
||||||
|
BigDecimal bdB = toBigDecimal(b);
|
||||||
|
return bdA.add(bdB).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 减法
|
||||||
|
*
|
||||||
|
* @param a 被减数
|
||||||
|
* @param b 减数
|
||||||
|
* @return 两数的差, 保留两位小数
|
||||||
|
*/
|
||||||
|
public static BigDecimal subtract(Object a, Object b) {
|
||||||
|
BigDecimal bdA = toBigDecimal(a);
|
||||||
|
BigDecimal bdB = toBigDecimal(b);
|
||||||
|
return bdA.subtract(bdB).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 乘法
|
||||||
|
*
|
||||||
|
* @param a 乘数
|
||||||
|
* @param b 乘数
|
||||||
|
* @return 两个乘数的积, 保留两位小数
|
||||||
|
*/
|
||||||
|
public static BigDecimal multiply(Object a, Object b) {
|
||||||
|
BigDecimal bdA = toBigDecimal(a);
|
||||||
|
BigDecimal bdB = toBigDecimal(b);
|
||||||
|
return bdA.multiply(bdB).setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 除法
|
||||||
|
*
|
||||||
|
* @param a 被除数
|
||||||
|
* @param b 除数
|
||||||
|
* @return 两数的商,保留两位小数
|
||||||
|
*/
|
||||||
|
public static BigDecimal divide(Object a, Object b) {
|
||||||
|
BigDecimal bdA = toBigDecimal(a);
|
||||||
|
BigDecimal bdB = toBigDecimal(b);
|
||||||
|
return bdA.divide(bdB, 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 除法
|
||||||
|
*
|
||||||
|
* @param a 被除数
|
||||||
|
* @param b 除数
|
||||||
|
* @param scale 保留小数位数
|
||||||
|
* @return 两数的商,保留两位小数
|
||||||
|
*/
|
||||||
|
public static BigDecimal divide(Object a, Object b, int scale) {
|
||||||
|
BigDecimal bdA = toBigDecimal(a);
|
||||||
|
BigDecimal bdB = toBigDecimal(b);
|
||||||
|
return bdA.divide(bdB, scale, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分转元
|
||||||
|
*
|
||||||
|
* @param obj 分的金额
|
||||||
|
* @return 转换后的元,保留两位小数
|
||||||
|
*/
|
||||||
|
public static BigDecimal centsToYuan(Object obj) {
|
||||||
|
BigDecimal cents = toBigDecimal(obj);
|
||||||
|
return cents.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元转分
|
||||||
|
*
|
||||||
|
* @param obj 元的金额
|
||||||
|
* @return 转换后的分
|
||||||
|
*/
|
||||||
|
public static Long yuanToCents(Object obj) {
|
||||||
|
BigDecimal yuan = toBigDecimal(obj);
|
||||||
|
return yuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
BigDecimal num1 = new BigDecimal("10.123");
|
||||||
|
BigDecimal num2 = new BigDecimal("2.456");
|
||||||
|
|
||||||
|
System.out.println("加法结果: " + add(num1, num2));
|
||||||
|
System.out.println("减法结果: " + subtract(num1, num2));
|
||||||
|
System.out.println("乘法结果: " + multiply(num1, num2));
|
||||||
|
System.out.println("除法结果: " + divide(num1, num2));
|
||||||
|
|
||||||
|
Long cents = 12345L;
|
||||||
|
System.out.println("分转元结果: " + centsToYuan(cents));
|
||||||
|
|
||||||
|
BigDecimal yuan = new BigDecimal("123.45");
|
||||||
|
System.out.println("元转分结果: " + yuanToCents(yuan));
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.hutool.http.useragent.UserAgent;
|
||||||
|
import cn.hutool.http.useragent.UserAgentUtil;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浏览器 相关
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsBrowserUtil {
|
||||||
|
private static final String UNKNOWN = "unknown";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取ip地址
|
||||||
|
*/
|
||||||
|
public static String getIp(HttpServletRequest request) {
|
||||||
|
String ip = request.getHeader("x-forwarded-for");
|
||||||
|
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||||
|
}
|
||||||
|
if (ip == null || ip.isEmpty() || UNKNOWN.equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
String comma = ",";
|
||||||
|
String localhost = "127.0.0.1";
|
||||||
|
if (ip.contains(comma)) {
|
||||||
|
ip = ip.split(",")[0];
|
||||||
|
}
|
||||||
|
if (localhost.equals(ip)) {
|
||||||
|
// 获取本机真正的ip地址
|
||||||
|
try {
|
||||||
|
ip = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
log.error("获取本机地址失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取浏览器
|
||||||
|
*/
|
||||||
|
public static String getVersion(HttpServletRequest request) {
|
||||||
|
UserAgent ua = UserAgentUtil.parse(request.getHeader("User-Agent"));
|
||||||
|
String browser = ua.getBrowser().toString() + " " + ua.getVersion();
|
||||||
|
return browser.replace(".0.0.0", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类 相关
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsClassUtil {
|
||||||
|
@SuppressWarnings({"unchecked", "all"})
|
||||||
|
public static List<Field> getAllFields(Class clazz, List<Field> fields) {
|
||||||
|
if (clazz != null) {
|
||||||
|
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||||
|
getAllFields(clazz.getSuperclass(), fields);
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于关闭各种连接, 缺啥补啥
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsCloseUtil {
|
||||||
|
|
||||||
|
public static void close(Closeable closeable) {
|
||||||
|
if (null != closeable) {
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 静默关闭
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void close(AutoCloseable closeable) {
|
||||||
|
if (null != closeable) {
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 静默关闭
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsSelectOptionVo;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集合工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2022-11-09
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsCollUtil extends cn.hutool.core.collection.CollUtil {
|
||||||
|
private static <T, K> Predicate<T> distinctPredicate(Function<? super T, ? extends K> keyMapper) {
|
||||||
|
Map<Object, Boolean> map = new HashMap<>(3);
|
||||||
|
return (t) -> null == map.putIfAbsent(keyMapper.apply(t), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流根据对象属性去重
|
||||||
|
* <p>
|
||||||
|
* 使用方式: list = CollUtil.streamDistinct(list, Object::getXXX)
|
||||||
|
*/
|
||||||
|
public static <T, K> List<T> streamDistinct(List<T> data, Function<? super T, ? extends K> keyMapper) {
|
||||||
|
return data.stream().filter(distinctPredicate(keyMapper)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流根据对象属性自然排序
|
||||||
|
* <p>
|
||||||
|
* 使用方式: list = CollUtil.streamAscSort(list, Object::getXXX)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @param keyExtractor /
|
||||||
|
* @param <T> /
|
||||||
|
* @param <U> /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static <T, U extends Comparable<? super U>> List<T> streamAscSort(
|
||||||
|
List<T> data,
|
||||||
|
Function<? super T, ? extends U> keyExtractor) {
|
||||||
|
return data.stream().sorted(Comparator.comparing(keyExtractor)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流根据对象属性倒转排序
|
||||||
|
* <p>
|
||||||
|
* 使用方式: list = CollUtil.streamDescSort(list, Object::getXXX)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @param keyExtractor /
|
||||||
|
* @param <T> /
|
||||||
|
* @param <U> /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static <T, U extends Comparable<? super U>> List<T> streamDescSort(
|
||||||
|
List<T> data,
|
||||||
|
Function<? super T, ? extends U> keyExtractor) {
|
||||||
|
return data.stream().sorted(Comparator.comparing(keyExtractor).reversed()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流拆分对象为键值对
|
||||||
|
* <p>
|
||||||
|
* 使用方式: map = CollUtil.streamToMap(list, Object::getXXKey, Object::getXXValue)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @param keyMapper /
|
||||||
|
* @param valueMapper /
|
||||||
|
* @param <T> /
|
||||||
|
* @param <K> /
|
||||||
|
* @param <U> /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static <T, K, U> Map<K, U> streamToMap(
|
||||||
|
List<T> data,
|
||||||
|
Function<? super T, ? extends K> keyMapper,
|
||||||
|
Function<? super T, ? extends U> valueMapper) {
|
||||||
|
// 根据key去重再并流
|
||||||
|
return streamDistinct(data, keyMapper).stream().collect(Collectors.toMap(keyMapper, valueMapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流对List<Double>求和
|
||||||
|
* <p>
|
||||||
|
* 使用方式: total = CollUtil.streamDoubleAdd(doubleList)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static Double streamDoubleAdd(List<Double> data) {
|
||||||
|
if (CsCollUtil.isNotEmpty(data)) {
|
||||||
|
return data.stream().filter(Objects::nonNull).reduce(Double::sum).orElse(0.0);
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流对List<BigDecimal>求和
|
||||||
|
* <p>
|
||||||
|
* 使用方式: total = CollUtil.streamBigDecimalAdd(doubleList)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static BigDecimal streamBigDecimalAdd(List<BigDecimal> data) {
|
||||||
|
if (CsCollUtil.isNotEmpty(data)) {
|
||||||
|
return data.stream().filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流对List<Object>中的某一BigDecimal属性求和
|
||||||
|
* <p>
|
||||||
|
* 使用方式: total = CollUtil.streamBigDecimalAdd(list, XXX)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static <T> BigDecimal streamBigDecimalAdd(List<T> data, String elementName) {
|
||||||
|
return data.stream().filter(Objects::nonNull).map(m -> {
|
||||||
|
// 反射, 取集合中各元素的某属性
|
||||||
|
BigDecimal that;
|
||||||
|
try {
|
||||||
|
Field field = m.getClass().getDeclaredField(elementName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
that = (BigDecimal) field.get(m);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 忽略异常
|
||||||
|
that = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
return that;
|
||||||
|
}).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流对List<BigDecimal>取最大值
|
||||||
|
* <p>
|
||||||
|
* 使用方式: max = CollUtil.streamBigDecimalMax(list)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static BigDecimal streamBigDecimalMax(List<BigDecimal> data) {
|
||||||
|
return data.stream().filter(Objects::nonNull).reduce(BigDecimal::max).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流对List<BigDecimal>取最小值
|
||||||
|
* <p>
|
||||||
|
* 使用方式: max = CollUtil.streamBigDecimalMin(list)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static BigDecimal streamBigDecimalMin(List<BigDecimal> data) {
|
||||||
|
return data.stream().filter(Objects::nonNull).reduce(BigDecimal::min).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流对List<BigDecimal>取平均值
|
||||||
|
* <p>
|
||||||
|
* 使用方式: avg = CollUtil.streamBigDecimalAvg(list)
|
||||||
|
*
|
||||||
|
* @param data /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static BigDecimal streamBigDecimalAvg(List<BigDecimal> data) {
|
||||||
|
return data.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||||
|
.divide(BigDecimal.valueOf(data.size()), 6, RoundingMode.FLOOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<CsSelectOptionVo> testData = new ArrayList<>();
|
||||||
|
CsSelectOptionVo selectOption1 = CsSelectOptionVo.builder().label("苹果").value("apple").build();
|
||||||
|
CsSelectOptionVo selectOption2 = CsSelectOptionVo.builder().label("苹果").value("apple").build();
|
||||||
|
CsSelectOptionVo selectOption3 = CsSelectOptionVo.builder().label("香蕉").value("banana").build();
|
||||||
|
CsSelectOptionVo selectOption4 = CsSelectOptionVo.builder().label("猫").value("cat").build();
|
||||||
|
testData.add(selectOption1);
|
||||||
|
testData.add(selectOption2);
|
||||||
|
testData.add(selectOption3);
|
||||||
|
testData.add(selectOption4);
|
||||||
|
List<CsSelectOptionVo> selectOptions1 = CsCollUtil.streamAscSort(testData, CsSelectOptionVo::getValue);
|
||||||
|
System.out.println(JSON.toJSONString(selectOptions1));
|
||||||
|
List<CsSelectOptionVo> selectOptions2 = CsCollUtil.streamDescSort(testData, CsSelectOptionVo::getValue);
|
||||||
|
System.out.println(JSON.toJSONString(selectOptions2));
|
||||||
|
List<CsSelectOptionVo> selectOptions3 = CsCollUtil.streamDistinct(testData, CsSelectOptionVo::getValue);
|
||||||
|
System.out.println(JSON.toJSONString(selectOptions3));
|
||||||
|
Map<String, String> selectMap1 =
|
||||||
|
CsCollUtil.streamToMap(testData, CsSelectOptionVo::getValue, CsSelectOptionVo::getLabel);
|
||||||
|
System.out.println(JSON.toJSONString(selectMap1));
|
||||||
|
// 这只是一个示范
|
||||||
|
BigDecimal total1 = CsCollUtil.streamBigDecimalAdd(testData, "value");
|
||||||
|
System.out.println(total1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间相关工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-10-01
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public class CsDateUtil {
|
||||||
|
/**
|
||||||
|
* 获取当前时间毫秒数
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static String getNowDateTimeMsStr() {
|
||||||
|
return DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间,但不包括毫秒数
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static String getNowDateTimeStr() {
|
||||||
|
return DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前时间,但不包括毫秒数
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static String getNowDateStr() {
|
||||||
|
return DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化到毫秒
|
||||||
|
*
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @return 格式化后的时间,X天X时X分X秒X毫秒
|
||||||
|
*/
|
||||||
|
public static String formatMillsDuration(Date startTime, Date endTime) {
|
||||||
|
if (startTime == null || endTime == null) {
|
||||||
|
// 什么都不返回
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
long betweenSeconds = DateUtil.between(startTime, endTime, cn.hutool.core.date.DateUnit.SECOND);
|
||||||
|
|
||||||
|
long days = betweenSeconds / (24 * 60 * 60);
|
||||||
|
long hours = (betweenSeconds % (24 * 60 * 60)) / (60 * 60);
|
||||||
|
long minutes = (betweenSeconds % (60 * 60)) / 60;
|
||||||
|
long seconds = betweenSeconds % 60;
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
if (days > 0) {
|
||||||
|
result.append(days).append("天");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
result.append(hours).append("时");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes > 0) {
|
||||||
|
result.append(minutes).append("分");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds > 0 || result.length() == 0) {
|
||||||
|
result.append(seconds).append("秒");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化到秒
|
||||||
|
*
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @return 格式化后的时间,X天X时X分X秒
|
||||||
|
*/
|
||||||
|
public static String formatSecondsDuration(Date startTime, Date endTime) {
|
||||||
|
if (startTime == null || endTime == null) {
|
||||||
|
// 什么都不返回
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
long betweenMs = DateUtil.betweenMs(startTime, endTime);
|
||||||
|
long days = betweenMs / (24 * 60 * 60 * 1000);
|
||||||
|
long hours = (betweenMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000);
|
||||||
|
long minutes = (betweenMs % (60 * 60 * 1000)) / (60 * 1000);
|
||||||
|
long seconds = (betweenMs % (60 * 1000)) / 1000;
|
||||||
|
long milliseconds = betweenMs % 1000;
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
|
if (days > 0) {
|
||||||
|
result.append(days).append("天");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
result.append(hours).append("时");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes > 0) {
|
||||||
|
result.append(minutes).append("分");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds > 0) {
|
||||||
|
result.append(seconds).append("秒");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (milliseconds > 0) {
|
||||||
|
result.append(milliseconds).append("毫秒");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有单位都是0,则显示0毫秒
|
||||||
|
if (result.length() == 0) {
|
||||||
|
result.append("0毫秒");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.DESKeySpec;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DES加密
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsDesEncryptUtil {
|
||||||
|
|
||||||
|
private static final String STR_PARAM = "Passw0rd";
|
||||||
|
private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
private static DESKeySpec getDesKeySpec(String source) throws Exception {
|
||||||
|
if (source == null || source.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new DESKeySpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对称加密
|
||||||
|
*/
|
||||||
|
public static String desEncrypt(String source) throws Exception {
|
||||||
|
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
|
||||||
|
DESKeySpec desKeySpec = getDesKeySpec(source);
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
|
||||||
|
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
|
||||||
|
return byte2hex(cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对称解密
|
||||||
|
*/
|
||||||
|
public static String desDecrypt(String source) throws Exception {
|
||||||
|
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
|
||||||
|
byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
|
||||||
|
DESKeySpec desKeySpec = getDesKeySpec(source);
|
||||||
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
|
||||||
|
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
|
||||||
|
byte[] retByte = cipher.doFinal(src);
|
||||||
|
return new String(retByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String byte2hex(byte[] inStr) {
|
||||||
|
String stmp;
|
||||||
|
StringBuilder out = new StringBuilder(inStr.length * 2);
|
||||||
|
for (byte b : inStr) {
|
||||||
|
stmp = Integer.toHexString(b & 0xFF);
|
||||||
|
if (stmp.length() == 1) {
|
||||||
|
out.append("0").append(stmp);
|
||||||
|
} else {
|
||||||
|
out.append(stmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] hex2byte(byte[] b) {
|
||||||
|
int size = 2;
|
||||||
|
if ((b.length % size) != 0) {
|
||||||
|
throw new IllegalArgumentException("长度不是偶数");
|
||||||
|
}
|
||||||
|
byte[] b2 = new byte[b.length / 2];
|
||||||
|
for (int n = 0; n < b.length; n += size) {
|
||||||
|
String item = new String(b, n, 2);
|
||||||
|
b2[n / 2] = (byte) Integer.parseInt(item, 16);
|
||||||
|
}
|
||||||
|
return b2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+206
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分布式id生成工具(不建议用,仅供参考)
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-04-05
|
||||||
|
*/
|
||||||
|
public final class CsDistributedIdUtil {
|
||||||
|
/**
|
||||||
|
* 起始毫秒时间戳, 2020-12-30
|
||||||
|
*/
|
||||||
|
private static final long START_TIMESTAMP = 1609314517000L;
|
||||||
|
/**
|
||||||
|
* 数据中心 ID 所占位数
|
||||||
|
*/
|
||||||
|
private final long dataCenterIdBits;
|
||||||
|
/**
|
||||||
|
* 机器 ID 所占位数
|
||||||
|
*/
|
||||||
|
private final long workerIdBits;
|
||||||
|
/**
|
||||||
|
* 序列号所占位数
|
||||||
|
*/
|
||||||
|
private final long sequenceBits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据中心 ID 最大值
|
||||||
|
*/
|
||||||
|
private final long maxDataCenterId;
|
||||||
|
/**
|
||||||
|
* 机器 ID 最大值
|
||||||
|
*/
|
||||||
|
private final long maxWorkerId;
|
||||||
|
/**
|
||||||
|
* 序列号最大值
|
||||||
|
*/
|
||||||
|
private final long sequenceMask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机器 ID 向左移位数
|
||||||
|
*/
|
||||||
|
private final long workerIdShift;
|
||||||
|
/**
|
||||||
|
* 数据中心 ID 向左移位数
|
||||||
|
*/
|
||||||
|
private final long dataCenterIdShift;
|
||||||
|
/**
|
||||||
|
* 时间戳向左移位数
|
||||||
|
*/
|
||||||
|
private final long timestampLeftShift;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据中心 ID
|
||||||
|
*/
|
||||||
|
private final long dataCenterId;
|
||||||
|
/**
|
||||||
|
* 机器 ID
|
||||||
|
*/
|
||||||
|
private final long workerId;
|
||||||
|
/**
|
||||||
|
* 序列号
|
||||||
|
*/
|
||||||
|
private final AtomicLong sequence = new AtomicLong(0L);
|
||||||
|
/**
|
||||||
|
* 上一次生成 ID 的时间戳
|
||||||
|
*/
|
||||||
|
private final AtomicLong lastTimestamp = new AtomicLong(-1L);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单例实例持有者类
|
||||||
|
*/
|
||||||
|
private static class InstanceHolder {
|
||||||
|
private static final CsDistributedIdUtil INSTANCE = new CsDistributedIdUtil(20, 12, 5, 5, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数, 初始化数据中心 ID 和机器 ID
|
||||||
|
*
|
||||||
|
* @param dataCenterId 数据中心 ID
|
||||||
|
* @param workerId 机器 ID
|
||||||
|
* @param dataCenterIdBits 数据中心 ID 所占位数
|
||||||
|
* @param workerIdBits 机器 ID 所占位数
|
||||||
|
* @param sequenceBits 序列号所占位数
|
||||||
|
*/
|
||||||
|
public CsDistributedIdUtil(
|
||||||
|
long dataCenterId,
|
||||||
|
long workerId,
|
||||||
|
long dataCenterIdBits,
|
||||||
|
long workerIdBits,
|
||||||
|
long sequenceBits) {
|
||||||
|
this.dataCenterIdBits = dataCenterIdBits;
|
||||||
|
this.workerIdBits = workerIdBits;
|
||||||
|
this.sequenceBits = sequenceBits;
|
||||||
|
|
||||||
|
this.maxDataCenterId = ~(-1L << dataCenterIdBits);
|
||||||
|
this.maxWorkerId = ~(-1L << workerIdBits);
|
||||||
|
this.sequenceMask = ~(-1L << sequenceBits);
|
||||||
|
|
||||||
|
this.workerIdShift = sequenceBits;
|
||||||
|
this.dataCenterIdShift = sequenceBits + workerIdBits;
|
||||||
|
this.timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
|
||||||
|
|
||||||
|
if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
|
||||||
|
throw new IllegalArgumentException("Data center ID can't be greater than " + maxDataCenterId + " or less than 0");
|
||||||
|
}
|
||||||
|
if (workerId > maxWorkerId || workerId < 0) {
|
||||||
|
throw new IllegalArgumentException("Worker ID can't be greater than " + maxWorkerId + " or less than 0");
|
||||||
|
}
|
||||||
|
this.dataCenterId = dataCenterId;
|
||||||
|
this.workerId = workerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成下一个分布式 ID
|
||||||
|
*
|
||||||
|
* @return 生成的分布式 ID
|
||||||
|
*/
|
||||||
|
public long nextId() {
|
||||||
|
long currentTimestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 如果当前时间戳小于上一次生成 ID 的时间戳, 说明时钟回拨,抛出异常
|
||||||
|
if (currentTimestamp < lastTimestamp.get()) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"Clock moved backwards. Refusing to generate id for " + (lastTimestamp.get() - currentTimestamp) +
|
||||||
|
" milliseconds");
|
||||||
|
}
|
||||||
|
|
||||||
|
long lastTimestampValue = lastTimestamp.get();
|
||||||
|
if (currentTimestamp == lastTimestampValue) {
|
||||||
|
// 同一毫秒内,序列号自增
|
||||||
|
long seq = sequence.incrementAndGet() & sequenceMask;
|
||||||
|
if (seq == 0) {
|
||||||
|
// 序列号溢出,等待下一毫秒
|
||||||
|
currentTimestamp = waitNextMillis(lastTimestampValue);
|
||||||
|
}
|
||||||
|
return ((currentTimestamp - START_TIMESTAMP) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) |
|
||||||
|
(workerId << workerIdShift) | seq;
|
||||||
|
} else {
|
||||||
|
// 时间戳改变,重置序列号
|
||||||
|
sequence.set(0L);
|
||||||
|
lastTimestamp.set(currentTimestamp);
|
||||||
|
return ((currentTimestamp - START_TIMESTAMP) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) |
|
||||||
|
(workerId << workerIdShift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等待下一毫秒
|
||||||
|
*
|
||||||
|
* @param lastTimestamp 上一次生成 ID 的时间戳
|
||||||
|
* @return 当前时间戳
|
||||||
|
*/
|
||||||
|
private long waitNextMillis(long lastTimestamp) {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
while (timestamp <= lastTimestamp) {
|
||||||
|
timestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单例实例的下一个分布式 ID
|
||||||
|
*
|
||||||
|
* @return 生成的分布式 ID
|
||||||
|
*/
|
||||||
|
public static long fastNextId() {
|
||||||
|
return InstanceHolder.INSTANCE.nextId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主方法,用于测试分布式 ID 生成
|
||||||
|
*
|
||||||
|
* @param args 命令行参数
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
new Thread(() -> {
|
||||||
|
long id = Thread.currentThread().getId();
|
||||||
|
for (int j = 0; j < 20; j++) {
|
||||||
|
System.out.println(id + " - " + CsDistributedIdUtil.fastNextId());
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.hutool.poi.excel.BigExcelWriter;
|
||||||
|
import cn.hutool.poi.excel.ExcelUtil;
|
||||||
|
import cn.odboy.constant.FileTypeEnum;
|
||||||
|
import cn.odboy.constant.SystemConst;
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
|
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static cn.odboy.constant.SystemConst.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File工具类, 扩展 hutool 工具包
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsFileUtil extends cn.hutool.core.io.FileUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统临时目录
|
||||||
|
* <br>
|
||||||
|
* windows 包含路径分割符, 但Linux 不包含, 在windows \\==\ 前提下, 为安全起见 同意拼装 路径分割符,
|
||||||
|
* <pre>
|
||||||
|
* java.io.tmpdir
|
||||||
|
* windows : C:\Users/xxx\AppData\Local\Temp\
|
||||||
|
* linux: /temp
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public static final String SYS_TEM_DIR = System.getProperty("java.io.tmpdir") + File.separator;
|
||||||
|
/**
|
||||||
|
* 定义GB的计算常量
|
||||||
|
*/
|
||||||
|
private static final int GB = 1024 * 1024 * 1024;
|
||||||
|
/**
|
||||||
|
* 定义MB的计算常量
|
||||||
|
*/
|
||||||
|
private static final int MB = 1024 * 1024;
|
||||||
|
/**
|
||||||
|
* 定义KB的计算常量
|
||||||
|
*/
|
||||||
|
private static final int KB = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化小数
|
||||||
|
*/
|
||||||
|
private static final DecimalFormat DF = new DecimalFormat("0.00");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MultipartFile转File
|
||||||
|
*/
|
||||||
|
public static File toFile(MultipartFile multipartFile) {
|
||||||
|
// 获取文件名
|
||||||
|
String fileName = multipartFile.getOriginalFilename();
|
||||||
|
// 获取文件后缀
|
||||||
|
String prefix = "." + getSuffix(fileName);
|
||||||
|
File file = null;
|
||||||
|
try {
|
||||||
|
// 用uuid作为文件名, 防止生成的临时文件重复
|
||||||
|
file = new File(SYS_TEM_DIR + IdUtil.simpleUUID() + prefix);
|
||||||
|
// MultipartFile to File
|
||||||
|
multipartFile.transferTo(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("保存临时文件失败", e);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件大小转换
|
||||||
|
*/
|
||||||
|
public static String getSize(long size) {
|
||||||
|
String resultSize;
|
||||||
|
if (size / GB >= 1) {
|
||||||
|
//如果当前Byte的值大于等于1GB
|
||||||
|
resultSize = DF.format(size / (float) GB) + " GB";
|
||||||
|
} else if (size / MB >= 1) {
|
||||||
|
//如果当前Byte的值大于等于1MB
|
||||||
|
resultSize = DF.format(size / (float) MB) + " MB";
|
||||||
|
} else if (size / KB >= 1) {
|
||||||
|
//如果当前Byte的值大于等于1KB
|
||||||
|
resultSize = DF.format(size / (float) KB) + " KB";
|
||||||
|
} else {
|
||||||
|
resultSize = size + " B";
|
||||||
|
}
|
||||||
|
return resultSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* inputStream 转 File
|
||||||
|
*/
|
||||||
|
public static File inputStreamToFile(InputStream ins, String name) {
|
||||||
|
File file = new File(SYS_TEM_DIR + name);
|
||||||
|
if (file.exists()) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
OutputStream os = null;
|
||||||
|
try {
|
||||||
|
os = Files.newOutputStream(file.toPath());
|
||||||
|
int bytesRead;
|
||||||
|
int len = 8192;
|
||||||
|
byte[] buffer = new byte[len];
|
||||||
|
while ((bytesRead = ins.read(buffer, 0, len)) != -1) {
|
||||||
|
os.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
CsCloseUtil.close(os);
|
||||||
|
CsCloseUtil.close(ins);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将文件名解析成文件的上传路径
|
||||||
|
*/
|
||||||
|
public static File upload(MultipartFile file, String filePath) {
|
||||||
|
Date date = new Date();
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssS");
|
||||||
|
// 过滤非法文件名
|
||||||
|
String name = getPrefix(verifyFilename(file.getOriginalFilename()));
|
||||||
|
String suffix = getSuffix(file.getOriginalFilename());
|
||||||
|
String nowStr = "-" + format.format(date);
|
||||||
|
try {
|
||||||
|
String fileName = name + nowStr + "." + suffix;
|
||||||
|
String path = filePath + fileName;
|
||||||
|
// getCanonicalFile 可解析正确各种路径
|
||||||
|
File dest = new File(path).getCanonicalFile();
|
||||||
|
// 检测是否存在目录
|
||||||
|
if (!dest.getParentFile().exists()) {
|
||||||
|
if (!dest.getParentFile().mkdirs()) {
|
||||||
|
log.error("创建目录失败, {}", dest.getParentFile().getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 文件写入
|
||||||
|
file.transferTo(dest);
|
||||||
|
return dest;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("文件上传失败", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出excel
|
||||||
|
*/
|
||||||
|
public static void downloadExcel(List<Map<String, Object>> list, HttpServletResponse response) throws IOException {
|
||||||
|
String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
|
||||||
|
File file = new File(tempPath);
|
||||||
|
BigExcelWriter writer = ExcelUtil.getBigWriter(file);
|
||||||
|
// 处理数据以防止CSV注入
|
||||||
|
List<Map<String, Object>> sanitizedList = list.parallelStream().map(map -> {
|
||||||
|
Map<String, Object> sanitizedMap = new LinkedHashMap<>();
|
||||||
|
map.forEach((key, value) -> {
|
||||||
|
if (value instanceof String) {
|
||||||
|
String strValue = (String) value;
|
||||||
|
// 检查并处理以特殊字符开头的值
|
||||||
|
if (strValue.startsWith(SYMBOL_EQUAL) || strValue.startsWith(SYMBOL_ADD) ||
|
||||||
|
strValue.startsWith(SYMBOL_SUBTRACT) || strValue.startsWith(
|
||||||
|
SYMBOL_AT)) {
|
||||||
|
// 添加单引号前缀
|
||||||
|
strValue = "'" + strValue;
|
||||||
|
}
|
||||||
|
sanitizedMap.put(key, strValue);
|
||||||
|
} else {
|
||||||
|
sanitizedMap.put(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sanitizedMap;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
// 一次性写出内容, 使用默认样式,强制输出标题
|
||||||
|
writer.write(sanitizedList, true);
|
||||||
|
SXSSFSheet sheet = (SXSSFSheet) writer.getSheet();
|
||||||
|
//上面需要强转SXSSFSheet 不然没有trackAllColumnsForAutoSizing方法
|
||||||
|
sheet.trackAllColumnsForAutoSizing();
|
||||||
|
//列宽自适应
|
||||||
|
writer.autoSizeColumnAll();
|
||||||
|
//response为HttpServletResponse对象
|
||||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
|
||||||
|
//test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
|
||||||
|
ServletOutputStream out = response.getOutputStream();
|
||||||
|
// 终止后删除临时文件
|
||||||
|
file.deleteOnExit();
|
||||||
|
writer.flush(out, true);
|
||||||
|
//此处记得关闭输出Servlet流
|
||||||
|
IoUtil.close(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件类型
|
||||||
|
*/
|
||||||
|
public static String getFileType(String type) {
|
||||||
|
String documents = "txt doc pdf ppt pps xlsx xls docx";
|
||||||
|
String voice = "mp3 wav wma mpa ram ra aac aif m4a";
|
||||||
|
String video = "avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg";
|
||||||
|
String image = "bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg";
|
||||||
|
if (image.contains(type)) {
|
||||||
|
return FileTypeEnum.IMAGE.getCode();
|
||||||
|
} else if (documents.contains(type)) {
|
||||||
|
return FileTypeEnum.DOC.getCode();
|
||||||
|
} else if (voice.contains(type)) {
|
||||||
|
return FileTypeEnum.VOICE.getCode();
|
||||||
|
} else if (video.contains(type)) {
|
||||||
|
return FileTypeEnum.VIDEO.getCode();
|
||||||
|
} else {
|
||||||
|
return FileTypeEnum.OTHER.getCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件大小是否超出最大值
|
||||||
|
*/
|
||||||
|
public static void checkSize(long maxSize, long size) {
|
||||||
|
// 1M
|
||||||
|
int len = 1024 * 1024;
|
||||||
|
if (size > (maxSize * len)) {
|
||||||
|
throw new BadRequestException("文件超出规定大小:" + maxSize + "MB");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两个文件是否相同
|
||||||
|
*/
|
||||||
|
public static boolean check(File file1, File file2) {
|
||||||
|
String img1Md5 = getMd5(file1);
|
||||||
|
String img2Md5 = getMd5(file2);
|
||||||
|
if (img1Md5 != null) {
|
||||||
|
return img1Md5.equals(img2Md5);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断两个文件是否相同
|
||||||
|
*/
|
||||||
|
public static boolean check(String file1Md5, String file2Md5) {
|
||||||
|
return file1Md5.equals(file2Md5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件字节数组
|
||||||
|
*/
|
||||||
|
private static byte[] getByte(File file) {
|
||||||
|
byte[] b = new byte[(int) file.length()];
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = Files.newInputStream(file.toPath());
|
||||||
|
// 忽略read值
|
||||||
|
int read = in.read(b);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
CsCloseUtil.close(in);
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件MD5值
|
||||||
|
*/
|
||||||
|
private static String getMd5(byte[] bytes) {
|
||||||
|
// 16进制字符
|
||||||
|
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||||
|
try {
|
||||||
|
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
|
||||||
|
mdTemp.update(bytes);
|
||||||
|
byte[] md = mdTemp.digest();
|
||||||
|
int j = md.length;
|
||||||
|
char[] str = new char[j * 2];
|
||||||
|
int k = 0;
|
||||||
|
// 移位 输出字符串
|
||||||
|
for (byte byte0 : md) {
|
||||||
|
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
|
||||||
|
str[k++] = hexDigits[byte0 & 0xf];
|
||||||
|
}
|
||||||
|
return new String(str);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取文件MD5值失败", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
*
|
||||||
|
* @param request /
|
||||||
|
* @param response /
|
||||||
|
* @param file /
|
||||||
|
*/
|
||||||
|
public static void downloadFile(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
File file,
|
||||||
|
boolean deleteOnExit) {
|
||||||
|
response.setCharacterEncoding(request.getCharacterEncoding());
|
||||||
|
response.setContentType("application/octet-stream");
|
||||||
|
FileInputStream fis = null;
|
||||||
|
try {
|
||||||
|
fis = new FileInputStream(file);
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
|
||||||
|
IOUtils.copy(fis, response.getOutputStream());
|
||||||
|
response.flushBuffer();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载文件失败", e);
|
||||||
|
} finally {
|
||||||
|
if (fis != null) {
|
||||||
|
try {
|
||||||
|
fis.close();
|
||||||
|
if (deleteOnExit) {
|
||||||
|
file.deleteOnExit();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("关闭IO流失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证并过滤非法的文件名
|
||||||
|
*
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @return 文件名
|
||||||
|
*/
|
||||||
|
public static String verifyFilename(String fileName) {
|
||||||
|
// 过滤掉特殊字符
|
||||||
|
fileName = fileName.replaceAll("[\\\\/:*?\"<>|~\\s]", "");
|
||||||
|
|
||||||
|
// 去掉文件名开头和结尾的空格和点
|
||||||
|
fileName = fileName.trim().replaceAll("^[. ]+|[. ]+$", "");
|
||||||
|
|
||||||
|
// 不允许文件名超过255(在Mac和Linux中)或260(在Windows中)个字符
|
||||||
|
int maxFileNameLength = 255;
|
||||||
|
if (System.getProperty(SystemConst.PROPERTY_OS_NAME).startsWith(SystemConst.OS_NAME_WINDOWS)) {
|
||||||
|
maxFileNameLength = 260;
|
||||||
|
}
|
||||||
|
if (fileName.length() > maxFileNameLength) {
|
||||||
|
fileName = fileName.substring(0, maxFileNameLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤掉控制字符
|
||||||
|
fileName = fileName.replaceAll("[\\p{Cntrl}]", "");
|
||||||
|
|
||||||
|
// 过滤掉 ".." 路径
|
||||||
|
fileName = fileName.replaceAll("\\.{2,}", "");
|
||||||
|
|
||||||
|
// 去掉文件名开头的 ".."
|
||||||
|
fileName = fileName.replaceAll("^\\.+/", "");
|
||||||
|
|
||||||
|
// 保留文件名中最后一个 "." 字符,过滤掉其他 "."
|
||||||
|
fileName = fileName.replaceAll("^(.*)(\\.[^.]*)$", "$1").replaceAll("\\.", "") +
|
||||||
|
fileName.replaceAll("^(.*)(\\.[^.]*)$", "$2");
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMd5(File file) {
|
||||||
|
return getMd5(getByte(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.odboy.framework.context.CsSpringBeanHolder;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
|
||||||
|
import net.dreamlu.mica.ip2region.core.IpInfo;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP地址 相关
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsIPUtil {
|
||||||
|
/**
|
||||||
|
* 注入bean
|
||||||
|
*/
|
||||||
|
private final static Ip2regionSearcher IP_SEARCHER = CsSpringBeanHolder.getBean(Ip2regionSearcher.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ip获取详细地址
|
||||||
|
*/
|
||||||
|
public static String getCityInfo(String ip) {
|
||||||
|
IpInfo ipInfo = IP_SEARCHER.memorySearch(ip);
|
||||||
|
if (ipInfo != null) {
|
||||||
|
return ipInfo.getAddress();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前机器的IP
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static String getLocalIp() {
|
||||||
|
try {
|
||||||
|
InetAddress candidateAddress = null;
|
||||||
|
// 遍历所有的网络接口
|
||||||
|
for (Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||||
|
interfaces.hasMoreElements(); ) {
|
||||||
|
NetworkInterface anInterface = interfaces.nextElement();
|
||||||
|
// 在所有的接口下再遍历IP
|
||||||
|
for (Enumeration<InetAddress> inetAddresses = anInterface.getInetAddresses();
|
||||||
|
inetAddresses.hasMoreElements(); ) {
|
||||||
|
InetAddress inetAddr = inetAddresses.nextElement();
|
||||||
|
// 排除loopback类型地址
|
||||||
|
if (!inetAddr.isLoopbackAddress()) {
|
||||||
|
if (inetAddr.isSiteLocalAddress()) {
|
||||||
|
// 如果是site-local地址, 就是它了
|
||||||
|
return inetAddr.getHostAddress();
|
||||||
|
} else if (candidateAddress == null) {
|
||||||
|
// site-local类型的地址未被发现, 先记录候选地址
|
||||||
|
candidateAddress = inetAddr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (candidateAddress != null) {
|
||||||
|
return candidateAddress.getHostAddress();
|
||||||
|
}
|
||||||
|
// 如果没有发现 non-loopback地址.只能用最次选的方案
|
||||||
|
InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
|
||||||
|
if (jdkSuppliedAddress == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return jdkSuppliedAddress.getHostAddress();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.TypeReference;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Json工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2024-10-01
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsJsonUtil {
|
||||||
|
public static <K, V> Map<K, V> toMap(String jsonStr, Class<K> kClass, Class<V> vClass) {
|
||||||
|
return JSON.parseObject(jsonStr, new TypeReference<Map<K, V>>() {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> List<V> toList(String jsonStr, Class<V> vClass) {
|
||||||
|
return JSON.parseObject(jsonStr, new TypeReference<List<V>>() {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String mapStr = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
|
||||||
|
System.err.println(CsJsonUtil.toMap(mapStr, String.class, String.class));
|
||||||
|
String listStr = "[\"value1\", \"value2\"]";
|
||||||
|
System.err.println(CsJsonUtil.toList(listStr, String.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateTime;
|
||||||
|
import cn.odboy.base.CsBaseUserTimeTb;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List 工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2024-10-01
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsListUtil {
|
||||||
|
/**
|
||||||
|
* 根据对象T的属性去重
|
||||||
|
*
|
||||||
|
* @param items 对象T集合
|
||||||
|
* @param classifier 对象T属性表达式, 比如: User::getName
|
||||||
|
*/
|
||||||
|
public static <T, K> List<T> distinctByArgs(List<T> items, Function<? super T, ? extends K> classifier) {
|
||||||
|
return items.stream()
|
||||||
|
.collect(Collectors.groupingBy(classifier))
|
||||||
|
.values()
|
||||||
|
.stream()
|
||||||
|
.map(monitorItem -> monitorItem.iterator().next())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list转map, 保留新值 -> Map<String, User> userMap = items.stream().collect(Collectors.toMap(User::getName, user -> user)) <br> list转map, 保留旧值 -> Map<String,
|
||||||
|
* User> userMap = users.stream().collect(Collectors.toMap(User::getName, user -> user, (existing, replacement) -> existing));
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
List<CsBaseUserTimeTb> entities = new ArrayList<>();
|
||||||
|
int total = 50;
|
||||||
|
for (int i = 0; i < total; i++) {
|
||||||
|
CsBaseUserTimeTb entity = new CsBaseUserTimeTb();
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
entity.setCreateBy("odboy");
|
||||||
|
} else {
|
||||||
|
entity.setCreateBy("admin");
|
||||||
|
}
|
||||||
|
entity.setUpdateBy("admin");
|
||||||
|
entity.setCreateTime(DateTime.now().toTimestamp());
|
||||||
|
entity.setUpdateTime(DateTime.now().toTimestamp());
|
||||||
|
entities.add(entity);
|
||||||
|
}
|
||||||
|
List<CsBaseUserTimeTb> entities1 = CsListUtil.distinctByArgs(entities, CsBaseUserTimeTb::getCreateBy);
|
||||||
|
System.err.println(entities1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 适配 logger 占位符
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-13
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsLogFmtUtil {
|
||||||
|
/**
|
||||||
|
* 替换字符串中的占位符 {} 为传入的参数
|
||||||
|
*
|
||||||
|
* @param message 消息模板
|
||||||
|
* @param args 替换占位符的参数
|
||||||
|
* @return 替换后的消息
|
||||||
|
*/
|
||||||
|
public static String format(String message, Object... args) {
|
||||||
|
if (message == null || args == null || args.length == 0) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
// 使用 String.format 替换占位符
|
||||||
|
return String.format(message.replace("{}", "%s"), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 示例用法
|
||||||
|
String messageTemplate = "获取dingtalk accessToken失败, code={}, message={}";
|
||||||
|
String formattedMessage = format(messageTemplate, 400, "Bad Request");
|
||||||
|
// 输出: 获取dingtalk accessToken失败, code=400, message=Bad Request
|
||||||
|
System.out.println(formattedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.odboy.base.CsPageResult;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsPageUtil extends cn.hutool.core.util.PageUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List 分页
|
||||||
|
*/
|
||||||
|
public static <T> List<T> softPaging(long page, long size, List<T> list) {
|
||||||
|
int pageIndex = Math.toIntExact(page - 1);
|
||||||
|
int fromIndex = Math.toIntExact(pageIndex * size);
|
||||||
|
int toIndex = Math.toIntExact(pageIndex * size + size);
|
||||||
|
if (fromIndex > list.size()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else if (toIndex >= list.size()) {
|
||||||
|
return list.subList(fromIndex, list.size());
|
||||||
|
} else {
|
||||||
|
return list.subList(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page 数据处理
|
||||||
|
*/
|
||||||
|
public static <T> CsPageResult<T> toPage(IPage<T> page) {
|
||||||
|
return new CsPageResult<>(page.getRecords(), page.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义分页
|
||||||
|
*/
|
||||||
|
public static <T> CsPageResult<T> toPage(List<T> list) {
|
||||||
|
return new CsPageResult<>(list, list.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回空数据
|
||||||
|
*/
|
||||||
|
public static <T> CsPageResult<T> emptyListData() {
|
||||||
|
return new CsPageResult<>(new ArrayList<>(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回空数据
|
||||||
|
*/
|
||||||
|
public static <T> CsPageResult<T> emptyData() {
|
||||||
|
return new CsPageResult<>(null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义分页
|
||||||
|
*/
|
||||||
|
public static <T> CsPageResult<T> toPage(List<T> list, long totalElements) {
|
||||||
|
return new CsPageResult<>(list, totalElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Page<T> getCount(BaseMapper<T> baseMapper) {
|
||||||
|
return new Page<>(1, baseMapper.selectCount(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Dict;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.extra.template.Template;
|
||||||
|
import cn.hutool.extra.template.TemplateConfig;
|
||||||
|
import cn.hutool.extra.template.TemplateEngine;
|
||||||
|
import cn.hutool.extra.template.TemplateUtil;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources模版工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-06-22
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public class CsResourceTemplateUtil {
|
||||||
|
public static String render(String moduleName, String filename, Dict params) {
|
||||||
|
String path = StrUtil.isBlank(moduleName) ? "template" : "template/" + moduleName;
|
||||||
|
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig(path, TemplateConfig.ResourceMode.CLASSPATH));
|
||||||
|
Template template = engine.getTemplate(filename);
|
||||||
|
return template.render(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回值处理 工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-05-08
|
||||||
|
*/
|
||||||
|
public class CsReturnValueHandleUtil {
|
||||||
|
public static Object getDefaultValue(ProceedingJoinPoint joinPoint) {
|
||||||
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
Method method = signature.getMethod();
|
||||||
|
// 获取目标方法的返回值类型
|
||||||
|
Class<?> returnType = method.getReturnType();
|
||||||
|
if (returnType.isArray()) {
|
||||||
|
return getEmptyArray(returnType);
|
||||||
|
} else if (List.class.isAssignableFrom(returnType)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else if (Set.class.isAssignableFrom(returnType)) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
} else if (Collection.class.isAssignableFrom(returnType)) {
|
||||||
|
// 默认返回空 List
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
// 普通对象返回 null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据数组类型返回空数组
|
||||||
|
*/
|
||||||
|
private static Object getEmptyArray(Class<?> arrayType) {
|
||||||
|
if (arrayType == int[].class) {
|
||||||
|
return new int[0];
|
||||||
|
} else if (arrayType == long[].class) {
|
||||||
|
return new long[0];
|
||||||
|
} else if (arrayType == double[].class) {
|
||||||
|
return new double[0];
|
||||||
|
} else if (arrayType == String[].class) {
|
||||||
|
return new String[0];
|
||||||
|
} else {
|
||||||
|
// 其他引用类型数组
|
||||||
|
return java.lang.reflect.Array.newInstance(arrayType.getComponentType(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rsa 工具类, 公钥私钥生成, 加解密
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsRsaEncryptUtil {
|
||||||
|
|
||||||
|
private static final String SRC = "123456";
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
System.out.println("\n");
|
||||||
|
RsaKeyPair keyPair = generateKeyPair();
|
||||||
|
System.out.println("公钥:" + keyPair.getPublicKey());
|
||||||
|
System.out.println("私钥:" + keyPair.getPrivateKey());
|
||||||
|
System.out.println("\n");
|
||||||
|
test1(keyPair);
|
||||||
|
System.out.println("\n");
|
||||||
|
test2(keyPair);
|
||||||
|
System.out.println("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公钥加密私钥解密
|
||||||
|
*/
|
||||||
|
private static void test1(RsaKeyPair keyPair) throws Exception {
|
||||||
|
System.out.println("***************** 公钥加密私钥解密开始 *****************");
|
||||||
|
String text1 = encryptByPublicKey(keyPair.getPublicKey(), CsRsaEncryptUtil.SRC);
|
||||||
|
String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
|
||||||
|
System.out.println("加密前:" + CsRsaEncryptUtil.SRC);
|
||||||
|
System.out.println("加密后:" + text1);
|
||||||
|
System.out.println("解密后:" + text2);
|
||||||
|
if (CsRsaEncryptUtil.SRC.equals(text2)) {
|
||||||
|
System.out.println("解密字符串和原始字符串一致, 解密成功");
|
||||||
|
} else {
|
||||||
|
System.out.println("解密字符串和原始字符串不一致,解密失败");
|
||||||
|
}
|
||||||
|
System.out.println("***************** 公钥加密私钥解密结束 *****************");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私钥加密公钥解密
|
||||||
|
*
|
||||||
|
* @throws Exception /
|
||||||
|
*/
|
||||||
|
private static void test2(RsaKeyPair keyPair) throws Exception {
|
||||||
|
System.out.println("***************** 私钥加密公钥解密开始 *****************");
|
||||||
|
String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), CsRsaEncryptUtil.SRC);
|
||||||
|
String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
|
||||||
|
System.out.println("加密前:" + CsRsaEncryptUtil.SRC);
|
||||||
|
System.out.println("加密后:" + text1);
|
||||||
|
System.out.println("解密后:" + text2);
|
||||||
|
if (CsRsaEncryptUtil.SRC.equals(text2)) {
|
||||||
|
System.out.println("解密字符串和原始字符串一致,解密成功");
|
||||||
|
} else {
|
||||||
|
System.out.println("解密字符串和原始字符串不一致,解密失败");
|
||||||
|
}
|
||||||
|
System.out.println("***************** 私钥加密公钥解密结束 *****************");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公钥解密
|
||||||
|
*
|
||||||
|
* @param publicKeyText 公钥
|
||||||
|
* @param text 待解密的信息
|
||||||
|
* @return /
|
||||||
|
* @throws Exception /
|
||||||
|
*/
|
||||||
|
public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
|
||||||
|
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, publicKey);
|
||||||
|
byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
|
||||||
|
return new String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私钥加密
|
||||||
|
*
|
||||||
|
* @param privateKeyText 私钥
|
||||||
|
* @param text 待加密的信息
|
||||||
|
* @return /
|
||||||
|
* @throws Exception /
|
||||||
|
*/
|
||||||
|
public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
|
||||||
|
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||||
|
byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
|
||||||
|
return Base64.encodeBase64String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私钥解密
|
||||||
|
*
|
||||||
|
* @param privateKeyText 私钥
|
||||||
|
* @param text 待解密的文本
|
||||||
|
* @return /
|
||||||
|
* @throws Exception /
|
||||||
|
*/
|
||||||
|
public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
|
||||||
|
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||||
|
byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
|
||||||
|
return new String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公钥加密
|
||||||
|
*
|
||||||
|
* @param publicKeyText 公钥
|
||||||
|
* @param text 待加密的文本
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
|
||||||
|
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||||
|
byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
|
||||||
|
return Base64.encodeBase64String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] doLongerCipherFinal(int opMode, Cipher cipher, byte[] source) throws Exception {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
if (opMode == Cipher.DECRYPT_MODE) {
|
||||||
|
out.write(cipher.doFinal(source));
|
||||||
|
} else {
|
||||||
|
int offset = 0;
|
||||||
|
int totalSize = source.length;
|
||||||
|
while (totalSize - offset > 0) {
|
||||||
|
int size = Math.min(cipher.getOutputSize(0) - 11, totalSize - offset);
|
||||||
|
out.write(cipher.doFinal(source, offset, size));
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建RSA密钥对
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
* @throws NoSuchAlgorithmException /
|
||||||
|
*/
|
||||||
|
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
||||||
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||||
|
keyPairGenerator.initialize(1024);
|
||||||
|
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||||
|
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
|
||||||
|
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||||
|
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
|
||||||
|
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
|
||||||
|
return new RsaKeyPair(publicKeyString, privateKeyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSA密钥对对象
|
||||||
|
*/
|
||||||
|
public static class RsaKeyPair {
|
||||||
|
|
||||||
|
private final String publicKey;
|
||||||
|
private final String privateKey;
|
||||||
|
|
||||||
|
public RsaKeyPair(String publicKey, String privateKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrivateKey() {
|
||||||
|
return privateKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsStringUtil {
|
||||||
|
|
||||||
|
private static final char SEPARATOR = '_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驼峰命名法工具
|
||||||
|
*
|
||||||
|
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||||
|
*/
|
||||||
|
public static String toCamelCase(String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s.toLowerCase();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(s.length());
|
||||||
|
boolean upperCase = false;
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
|
||||||
|
if (c == SEPARATOR) {
|
||||||
|
upperCase = true;
|
||||||
|
} else if (upperCase) {
|
||||||
|
sb.append(Character.toUpperCase(c));
|
||||||
|
upperCase = false;
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驼峰命名法工具
|
||||||
|
*
|
||||||
|
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||||
|
*/
|
||||||
|
public static String toCapitalizeCamelCase(String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
s = toCamelCase(s);
|
||||||
|
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驼峰命名法工具
|
||||||
|
*
|
||||||
|
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||||
|
*/
|
||||||
|
public static String toUnderScoreCase(String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean upperCase = false;
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
|
||||||
|
boolean nextUpperCase = true;
|
||||||
|
|
||||||
|
if (i < (s.length() - 1)) {
|
||||||
|
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((i > 0) && Character.isUpperCase(c)) {
|
||||||
|
if (!upperCase || !nextUpperCase) {
|
||||||
|
sb.append(SEPARATOR);
|
||||||
|
}
|
||||||
|
upperCase = true;
|
||||||
|
} else {
|
||||||
|
upperCase = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(Character.toLowerCase(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得当天是周几
|
||||||
|
*/
|
||||||
|
public static String getWeekDay() {
|
||||||
|
// String[] weekDays = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||||
|
String[] weekDays = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"};
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
cal.setTime(new Date());
|
||||||
|
int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
|
||||||
|
if (w < 0) {
|
||||||
|
w = 0;
|
||||||
|
}
|
||||||
|
return weekDays[w];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import oshi.SystemInfo;
|
||||||
|
import oshi.hardware.CentralProcessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统层相关
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-08-11
|
||||||
|
*/
|
||||||
|
public class CsSystemUtil {
|
||||||
|
public static int getCpuCount() {
|
||||||
|
SystemInfo systemInfo = new SystemInfo();
|
||||||
|
CentralProcessor processor = systemInfo.getHardware().getProcessor();
|
||||||
|
return processor.getPhysicalProcessorCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021-2025 Odboy
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cn.odboy.util;
|
||||||
|
|
||||||
|
import cn.odboy.framework.exception.BadRequestException;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.Validation;
|
||||||
|
import javax.validation.Validator;
|
||||||
|
import javax.validation.ValidatorFactory;
|
||||||
|
import javax.validation.groups.Default;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bean验证工具
|
||||||
|
*
|
||||||
|
* @author odboy
|
||||||
|
* @date 2025-01-15
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class CsValidUtil {
|
||||||
|
private static final Validator VALIDATOR;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
|
||||||
|
VALIDATOR = factory.getValidator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void validate(T object) {
|
||||||
|
Set<ConstraintViolation<T>> violations = VALIDATOR.validate(object, Default.class);
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
throw new BadRequestException(violations.stream()
|
||||||
|
.map(ConstraintViolation::getMessage)
|
||||||
|
.collect(Collectors.joining(",")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>cutejava</artifactId>
|
||||||
|
<groupId>cn.odboy</groupId>
|
||||||
|
<version>1.4.1</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>cutejava-module-system</artifactId>
|
||||||
|
<name>核心模块</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.odboy</groupId>
|
||||||
|
<artifactId>cutejava-framework</artifactId>
|
||||||
|
<version>1.4.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.minio</groupId>
|
||||||
|
<artifactId>minio</artifactId>
|
||||||
|
<version>8.5.17</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user