├── .DS_Store ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── src ├── main │ ├── resources │ │ ├── ip2region │ │ │ └── ip2region.xdb │ │ ├── META-INF │ │ │ └── spring.factories │ │ ├── application.properties │ │ ├── i18n.yml │ │ └── logback-spring.xml │ └── java │ │ └── cn │ │ └── soboys │ │ └── restapispringbootstarter │ │ ├── enums │ │ ├── LogTypeEnum.java │ │ ├── t.java │ │ ├── LogCURDTypeEnum.java │ │ ├── LimitType.java │ │ └── LogApiTypeEnum.java │ │ ├── utils │ │ ├── WebConst.java │ │ ├── YamlPropertySourceFactory.java │ │ ├── Strings.java │ │ ├── RestFulTemp.java │ │ ├── JwtUtil.java │ │ ├── MyBatisPlusGenerator.java │ │ ├── HttpUserAgent.java │ │ └── RequestUtil.java │ │ ├── serializer │ │ ├── SerializableType.java │ │ ├── DoubleValueSerializer.java │ │ ├── BigDecimalSerializer.java │ │ ├── LocalDateTimeSerializer.java │ │ ├── DateSerializer.java │ │ ├── NullAbleSerializer.java │ │ ├── JsonSerializerConfig.java │ │ └── BeanSerializerModifierFactory.java │ │ ├── i18n │ │ ├── I18NKey.java │ │ ├── DefaultMessage.java │ │ └── I18NMessage.java │ │ ├── exception │ │ ├── CacheException.java │ │ ├── LimitAccessException.java │ │ ├── JsonSerializableException.java │ │ └── BusinessException.java │ │ ├── domain │ │ ├── BaseObj.java │ │ └── EntityParam.java │ │ ├── ResultCode.java │ │ ├── log │ │ ├── LogDataSource.java │ │ ├── LogFileDefaultDataSource.java │ │ ├── Log.java │ │ └── LogEntry.java │ │ ├── interceptor │ │ ├── WhiteApiList.java │ │ ├── WebMvcHandleConfig.java │ │ └── JwtTokenInterceptor.java │ │ ├── test │ │ ├── Test.java │ │ ├── Entity.java │ │ ├── TestController.java │ │ └── Config.java │ │ ├── annotation │ │ ├── NoRestFulApi.java │ │ ├── hasAnyRoles.java │ │ ├── hasRole.java │ │ ├── hasPerm.java │ │ ├── hasAnyPerm.java │ │ ├── Limit.java │ │ └── EnableRestFullApi.java │ │ ├── authorization │ │ ├── UserSignWith.java │ │ ├── LoginAuthorization.java │ │ ├── UserSign.java │ │ ├── LoginAuthorizationSubject.java │ │ ├── UrlMatcher.java │ │ └── UserJwtToken.java │ │ ├── validator │ │ ├── IsCron.java │ │ ├── IsMobile.java │ │ ├── IsMoney.java │ │ ├── IsEnum.java │ │ ├── CronValidator.java │ │ ├── IsDateTime.java │ │ ├── ValidatorConfig.java │ │ ├── IsMoneyValidator.java │ │ ├── IsMobileValidator.java │ │ ├── IsEnumValidator.java │ │ ├── IsDateTimeValidator.java │ │ └── ValidatorUtil.java │ │ ├── config │ │ ├── GenerateCodeConfig.java │ │ ├── UserJwtAutoConfig.java │ │ ├── StartupApplicationListener.java │ │ ├── OpenApiConfig.java │ │ ├── RestTemplateConfig.java │ │ ├── BeanAutoConfiguration.java │ │ └── RestApiProperties.java │ │ ├── condition │ │ └── OnEnableRestFullApiAnnotationCondition.java │ │ ├── cache │ │ ├── CacheKey.java │ │ ├── SpringCacheUtil.java │ │ ├── CacheTmp.java │ │ ├── SpringCacheConfig.java │ │ ├── CacheAutoConfiguration.java │ │ ├── RedisConfig.java │ │ └── RedisTempUtil.java │ │ ├── ApplicationRunner.java │ │ ├── ResultPage.java │ │ ├── RestApiSpringBootStarterApplication.java │ │ ├── HttpStatus.java │ │ ├── aop │ │ ├── BaseAspectSupport.java │ │ ├── LimitAspect.java │ │ └── LogAspect.java │ │ ├── Result.java │ │ └── ExceptionHandler.java └── test │ └── java │ └── cn │ └── soboys │ └── restapispringbootstarter │ └── RestApiSpringBootStarterApplicationTests.java ├── .gitignore ├── README.md ├── mvnw.cmd └── pom.xml /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-amiao/simplest-web/HEAD/.DS_Store -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-amiao/simplest-web/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/ip2region/ip2region.xdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coder-amiao/simplest-web/HEAD/src/main/resources/ip2region/ip2region.xdb -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | cn.soboys.restapispringbootstarter.i18n.DefaultMessage\ 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #出现错误时, 直接抛出异常 2 | spring.mvc.throw-exception-if-no-handler-found=true 3 | #不要为我们工程中的资源文件建立映射 4 | spring.web.resources.add-mappings=false 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/enums/LogTypeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum LogTypeEnum { 7 | 8 | INFO, 9 | ERROR; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/enums/t.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.enums; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/7/6 21:48 7 | * @webSite https://github.com/coder-amiao 8 | */ 9 | public enum t { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/enums/LogCURDTypeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum LogCURDTypeEnum { 7 | 8 | CREATE, 9 | UPDATE, 10 | RETRIEVE, 11 | DELETE; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/WebConst.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/7/2 11:18 7 | * @webSite https://github.com/coder-amiao 8 | * 扩展常量 9 | */ 10 | public class WebConst { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/enums/LimitType.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.enums; 2 | 3 | /** 4 | * @author kenx 5 | */ 6 | public enum LimitType { 7 | /** 8 | * 传统类型 9 | */ 10 | CUSTOMER, 11 | /** 12 | * 根据 IP地址限制 13 | */ 14 | IP 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/enums/LogApiTypeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.enums; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum LogApiTypeEnum { 7 | 8 | USER("用户"), 9 | SYSTEM("系统"); 10 | 11 | private String describe; 12 | 13 | LogApiTypeEnum(String describe){ 14 | this.describe = describe; 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/SerializableType.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author 公众号 程序员三时 7 | * @version 1.0 8 | * @date 2023/7/22 18:41 9 | * @webSite https://github.com/coder-amiao 10 | */ 11 | @Data 12 | public class SerializableType{ 13 | private String type; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/i18n/I18NKey.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.i18n; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/6/26 11:56 7 | * @webSite https://github.com/coder-amiao 8 | * 国际化接口定义 9 | */ 10 | public interface I18NKey { 11 | /** 12 | * get i18n message key 13 | * 14 | * @return 15 | */ 16 | String key(); 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/cn/soboys/restapispringbootstarter/RestApiSpringBootStarterApplicationTests.java: -------------------------------------------------------------------------------- 1 | //package cn.soboys.restapispringbootstarter; 2 | // 3 | //import org.junit.jupiter.api.Test; 4 | //import org.springframework.boot.test.context.SpringBootTest; 5 | // 6 | //@SpringBootTest 7 | //class RestApiSpringBootStarterApplicationTests { 8 | // 9 | // @Test 10 | // void contextLoads() { 11 | // } 12 | // 13 | //} 14 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/exception/CacheException.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.exception; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/7/6 20:57 7 | * @webSite https://github.com/coder-amiao 8 | */ 9 | public class CacheException extends RuntimeException { 10 | public CacheException(String message) { 11 | super(message); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/exception/LimitAccessException.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.exception; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/7/2 11:38 7 | * @webSite https://github.com/coder-amiao 8 | */ 9 | public class LimitAccessException extends RuntimeException{ 10 | public LimitAccessException(String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/domain/BaseObj.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.domain; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.io.Serializable; 9 | 10 | @Getter 11 | @Setter 12 | public abstract class BaseObj implements Serializable { 13 | 14 | 15 | private static final long serialVersionUID = 5851377280115218282L; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/exception/JsonSerializableException.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.exception; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/7/22 21:16 7 | * @webSite https://github.com/coder-amiao 8 | */ 9 | public class JsonSerializableException extends RuntimeException{ 10 | public JsonSerializableException(String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/ResultCode.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter; 2 | 3 | import cn.soboys.restapispringbootstarter.i18n.I18NKey; 4 | 5 | /** 6 | * @author 公众号 程序员三时 7 | * @version 1.0 8 | * @date 2023/6/26 10:21 9 | * @webSite https://github.com/coder-amiao 10 | * 响应码接口,自定义响应码,实现此接口 11 | */ 12 | public interface ResultCode extends I18NKey { 13 | 14 | String getCode(); 15 | 16 | String getMessage(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/log/LogDataSource.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.log; 2 | 3 | import org.springframework.scheduling.annotation.Async; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @Author: kenx 9 | * @Since: 2021/6/23 13:55 10 | * @Description: 11 | */ 12 | public interface LogDataSource { 13 | 14 | /** 15 | * 获取拓展数据 16 | * @return 17 | * @param logEntry 18 | */ 19 | @Async 20 | void save(LogEntry logEntry); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/interceptor/WhiteApiList.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.interceptor; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * @author 公众号 程序员三时 8 | * @version 1.0 9 | * @date 2023/7/10 10:33 10 | * @webSite https://github.com/coder-amiao 11 | */ 12 | public class WhiteApiList { 13 | public static List list = new ArrayList<>(); 14 | 15 | static { 16 | list.add("/user/token"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | /logs/ 35 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/test/Test.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.test; 2 | 3 | import cn.soboys.restapispringbootstarter.utils.HttpUserAgent; 4 | import org.dromara.hutool.core.text.StrUtil; 5 | 6 | /** 7 | * @author 公众号 程序员三时 8 | * @version 1.0 9 | * @date 2023/7/9 17:27 10 | * @webSite https://github.com/coder-amiao 11 | */ 12 | public class Test { 13 | public static void main(String[] args) { 14 | String ipCity=HttpUserAgent.getIpToCityInfo("1.2.3.4"); 15 | System.out.printf(ipCity); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/annotation/NoRestFulApi.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.annotation; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/6/27 21:16 7 | * @webSite https://github.com/coder-amiao 8 | */ 9 | 10 | import java.lang.annotation.ElementType; 11 | import java.lang.annotation.Retention; 12 | import java.lang.annotation.RetentionPolicy; 13 | import java.lang.annotation.Target; 14 | 15 | /** 16 | * 不进行结果封装的注解 17 | */ 18 | @Target({ElementType.METHOD}) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | public @interface NoRestFulApi { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/domain/EntityParam.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.NotNull; 8 | 9 | /** 10 | * @author 公众号 程序员三时 11 | * @version 1.0 12 | * @date 2023/6/26 22:10 13 | * @webSite https://github.com/coder-amiao 14 | */ 15 | @Data 16 | 17 | public class EntityParam { 18 | @NotBlank 19 | private String name; 20 | @NotBlank 21 | private String hobby; 22 | @NotNull 23 | private Integer age; 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/log/LogFileDefaultDataSource.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.log; 2 | 3 | 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.dromara.hutool.json.JSONUtil; 6 | 7 | /** 8 | * @Author: kenx 9 | * @Since: 2021/6/23 13:55 10 | * @Description: 11 | */ 12 | @Slf4j 13 | public class LogFileDefaultDataSource implements LogDataSource { 14 | 15 | /** 16 | * 自定义保存数据源 17 | * 18 | * @param 19 | * @return LogEntry 20 | */ 21 | @Override 22 | public void save(LogEntry logEntry) { 23 | log.info(JSONUtil.toJsonPrettyStr(logEntry)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/authorization/UserSignWith.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.authorization; 2 | 3 | import io.jsonwebtoken.SignatureAlgorithm; 4 | import org.dromara.hutool.core.data.id.IdUtil; 5 | 6 | /** 7 | * @author 公众号 程序员三时 8 | * @version 1.0 9 | * @date 2023/7/13 22:01 10 | * @webSite https://github.com/coder-amiao 11 | */ 12 | public class UserSignWith implements UserSign { 13 | 14 | @Override 15 | public SignatureAlgorithm sign() { 16 | return SignatureAlgorithm.HS256; 17 | } 18 | 19 | //获取密钥,可以动态配置 20 | @Override 21 | public String AuthKey() { 22 | return null; 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/annotation/hasAnyRoles.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.annotation; 2 | 3 | import cn.soboys.restapispringbootstarter.utils.Strings; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * @author 公众号 程序员三时 12 | * @version 1.0 13 | * @date 2023/7/16 12:21 14 | * @webSite https://github.com/coder-amiao 15 | * 验证用户是否具有以下任意一个角色 16 | */ 17 | @Target({ElementType.TYPE}) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | public @interface hasAnyRoles { 20 | String role() default Strings.EMPTY; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/authorization/LoginAuthorization.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.authorization; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | /** 7 | * @author 公众号 程序员三时 8 | * @version 1.0 9 | * @date 2023/7/16 10:40 10 | * @webSite https://github.com/coder-amiao 11 | * 登录授权接口 12 | */ 13 | public interface LoginAuthorization { 14 | 15 | /** 16 | * 登录授权验证 通过后才能进行下一步操作 17 | * @param request 18 | * @param response 19 | * @param handler 20 | * @return 21 | */ 22 | public Boolean authorization(HttpServletRequest request, HttpServletResponse response, Object handler); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsCron.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | 4 | 5 | import javax.validation.Constraint; 6 | import javax.validation.Payload; 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * @author MrBird 14 | */ 15 | @Target({ElementType.FIELD}) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Constraint(validatedBy = CronValidator.class) 18 | public @interface IsCron { 19 | 20 | String message(); 21 | 22 | Class[] groups() default {}; 23 | 24 | Class[] payload() default {}; 25 | } -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/annotation/hasRole.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.annotation; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/7/16 12:18 7 | * @webSite https://github.com/coder-amiao 8 | */ 9 | 10 | import cn.soboys.restapispringbootstarter.utils.Strings; 11 | 12 | import java.lang.annotation.ElementType; 13 | import java.lang.annotation.Retention; 14 | import java.lang.annotation.RetentionPolicy; 15 | import java.lang.annotation.Target; 16 | 17 | /** 18 | * 判断用户是否拥有某个角色 19 | * 20 | * @param role 角色CODE 21 | * @return 用户是否具备某角色 22 | */ 23 | @Target({ElementType.TYPE}) 24 | @Retention(RetentionPolicy.RUNTIME) 25 | public @interface hasRole { 26 | String role() default Strings.EMPTY; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/annotation/hasPerm.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.annotation; 2 | 3 | import cn.soboys.restapispringbootstarter.utils.Strings; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * @author 公众号 程序员三时 12 | * @version 1.0 13 | * @date 2023/7/16 12:16 14 | * @webSite https://github.com/coder-amiao 15 | */ 16 | @Target({ElementType.TYPE}) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | /** 19 | * 验证用户是否具备某权限 20 | * 21 | * @param permission 权限字符串 22 | * @return 用户是否具备某权限 23 | */ 24 | public @interface hasPerm { 25 | String permission() default Strings.EMPTY; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsMobile.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | 8 | /** 9 | * @author kenx 10 | * @version 1.0 11 | * @date 2021/1/21 20:49 12 | * 自定义验证手机号码 13 | */ 14 | @Target({ElementType.METHOD,ElementType.FIELD, ElementType.TYPE}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | @Constraint(validatedBy = {IsMobileValidator.class }) 18 | public @interface IsMobile { 19 | 20 | boolean required() default true; 21 | 22 | String message() default "手机号码格式错误"; 23 | 24 | Class[] groups() default { }; 25 | 26 | Class[] payload() default { }; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsMoney.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | 8 | /** 9 | * @author kenx 10 | * @version 1.0 11 | * @date 2021/1/21 20:49 12 | * 自定义金额认证 13 | */ 14 | @Target({ElementType.METHOD,ElementType.FIELD, ElementType.TYPE,ElementType.PARAMETER}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | @Constraint(validatedBy = {IsMoneyValidator.class }) 18 | public @interface IsMoney { 19 | 20 | boolean required() default true; 21 | 22 | String message() default "金额格式错误"; 23 | 24 | Class[] groups() default { }; 25 | 26 | Class[] payload() default { }; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/test/Entity.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.test; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | import java.math.BigDecimal; 8 | import java.time.LocalDateTime; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | 13 | /** 14 | * @author 公众号 程序员三时 15 | * @version 1.0 16 | * @date 2023/7/21 18:06 17 | * @webSite https://github.com/coder-amiao 18 | */ 19 | @Data 20 | @AllArgsConstructor 21 | @JsonInclude(JsonInclude.Include.ALWAYS) 22 | public class Entity { 23 | private LocalDateTime createTime; 24 | private BigDecimal price; 25 | private Double sku; 26 | private List t; 27 | private Integer age; 28 | private String hobby; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsEnum.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * @author kenx 9 | * @version 1.0 10 | * @date 2021/1/22 09:03 11 | * 枚举类属性字段定义必须是code,desc 12 | */ 13 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Documented 16 | @Constraint(validatedBy = {IsEnumValidator.class}) 17 | public @interface IsEnum { 18 | boolean required() default true; 19 | 20 | String message() default "状态值不正确"; 21 | 22 | Class[] groups() default {}; 23 | 24 | Class[] payload() default {}; 25 | 26 | Class> enumClass(); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/annotation/hasAnyPerm.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.annotation; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/7/16 12:19 7 | * @webSite https://github.com/coder-amiao 8 | * 9 | */ 10 | 11 | import cn.soboys.restapispringbootstarter.utils.Strings; 12 | 13 | import java.lang.annotation.ElementType; 14 | import java.lang.annotation.Retention; 15 | import java.lang.annotation.RetentionPolicy; 16 | import java.lang.annotation.Target; 17 | 18 | /** 19 | * 验证用户是否具有以下任意一个权限 20 | * 21 | * @param permissions 以 PERMISSION_SEPARATOR 为分隔符的权限列表 22 | * @return 用户是否具有以下任意一个权限 23 | */ 24 | @Target({ElementType.TYPE}) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface hasAnyPerm { 27 | String permission() default Strings.EMPTY; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/config/GenerateCodeConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.config; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author 公众号 程序员三时 7 | * @version 1.0 8 | * @date 2023/7/5 00:05 9 | * @webSite https://github.com/coder-amiao 10 | */ 11 | @Data 12 | public class GenerateCodeConfig { 13 | /** 14 | * 数据库驱动 15 | */ 16 | private String driverName; 17 | /** 18 | * 数据库连接用户名 19 | */ 20 | private String username; 21 | /** 22 | * 数据库连接密码 23 | */ 24 | private String password; 25 | /** 26 | * 数据库连接url 27 | */ 28 | private String url; 29 | /** 30 | * 生成代码 保存路径。默认当前项目下。 31 | * 如需修改,使用绝对得路径 32 | */ 33 | private String projectPath; 34 | /** 35 | * 代码生成包位置 36 | */ 37 | private String packages; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/CronValidator.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | 4 | 5 | import org.springframework.scheduling.support.CronExpression; 6 | 7 | import javax.validation.ConstraintValidator; 8 | import javax.validation.ConstraintValidatorContext; 9 | 10 | /** 11 | * 校验是否为合法的 Cron表达式 12 | * 13 | * @author MrBird 14 | */ 15 | public class CronValidator implements ConstraintValidator { 16 | 17 | @Override 18 | public void initialize(IsCron isCron) { 19 | } 20 | 21 | @Override 22 | public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { 23 | try { 24 | return CronExpression.isValidExpression(value); 25 | } catch (Exception e) { 26 | return false; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/i18n/DefaultMessage.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.i18n; 2 | 3 | import cn.soboys.restapispringbootstarter.utils.YamlPropertySourceFactory; 4 | import lombok.Data; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.PropertySource; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author 公众号 程序员三时 13 | * @version 1.0 14 | * @date 2023/7/4 14:50 15 | * @webSite https://github.com/coder-amiao 16 | */ 17 | @Configuration 18 | @PropertySource(value = "classpath:i18n.yml", factory = YamlPropertySourceFactory.class) 19 | @ConfigurationProperties(prefix = "i18n") 20 | @Data 21 | public class DefaultMessage { 22 | 23 | private Map> message; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/condition/OnEnableRestFullApiAnnotationCondition.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.condition; 2 | 3 | import cn.soboys.restapispringbootstarter.annotation.EnableRestFullApi; 4 | import org.springframework.context.annotation.Condition; 5 | import org.springframework.context.annotation.ConditionContext; 6 | import org.springframework.core.type.AnnotatedTypeMetadata; 7 | 8 | /** 9 | * @author 公众号 程序员三时 10 | * @version 1.0 11 | * @date 2023/7/5 00:17 12 | * @webSite https://github.com/coder-amiao 13 | * 自定义注解条件注入rest-api 自动配置 14 | */ 15 | public class OnEnableRestFullApiAnnotationCondition implements Condition { 16 | 17 | @Override 18 | public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { 19 | return context.getBeanFactory().getBeanNamesForAnnotation(EnableRestFullApi.class).length > 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/test/TestController.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.test; 2 | 3 | import org.dromara.hutool.core.date.DateUnit; 4 | import org.dromara.hutool.core.date.DateUtil; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.math.BigDecimal; 9 | import java.time.LocalDateTime; 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * @author 公众号 程序员三时 16 | * @version 1.0 17 | * @date 2023/7/19 22:25 18 | * @webSite https://github.com/coder-amiao 19 | */ 20 | @RestController 21 | public class TestController { 22 | 23 | @GetMapping("/test") 24 | public Entity test() { 25 | Entity entity = new Entity(LocalDateTime.now(), null, new Double(20.469),null,null,null); 26 | return entity; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsDateTime.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | import javax.validation.Constraint; 4 | import javax.validation.Payload; 5 | import java.lang.annotation.*; 6 | 7 | 8 | /** 9 | * @author kenx 10 | * @version 1.0 11 | * @date 2021/1/21 20:49 12 | * 日期验证 13 | */ 14 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | @Constraint(validatedBy = {IsDateTimeValidator.class}) // 标明由哪个类执行校验逻辑 18 | public @interface IsDateTime { 19 | 20 | // 校验出错时默认返回的消息 21 | String message() default "日期格式错误"; 22 | //分组校验 23 | Class[] groups() default {}; 24 | 25 | Class[] payload() default {}; 26 | 27 | 28 | 29 | //下面是我自己定义属性 30 | boolean required() default true; 31 | 32 | String dateFormat() default "yyyy-MM-dd"; 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/cache/CacheKey.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.cache; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * @author 公众号 程序员三时 7 | * @version 1.0 8 | * @date 2023/7/2 11:04 9 | * @webSite https://github.com/coder-amiao 10 | * 缓存枚举 11 | */ 12 | @Getter 13 | public enum CacheKey implements CacheTmp { 14 | 15 | 16 | // 密码的重置码 17 | PWD_RESET_CODE("reset:code:", true), 18 | ; 19 | 20 | private String key; 21 | 22 | /** 23 | * Key是否是Key前缀, true时直接取key=key,如果false时key=key+suffix 24 | */ 25 | private boolean hasPrefix; 26 | 27 | CacheKey(String key, boolean hasPrefix) { 28 | this.key = key; 29 | this.hasPrefix = hasPrefix; 30 | } 31 | 32 | 33 | @Override 34 | public Boolean getHasPrefix() { 35 | return this.hasPrefix; 36 | } 37 | 38 | @Override 39 | public String getKey() { 40 | return this.key; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/i18n.yml: -------------------------------------------------------------------------------- 1 | i18n: 2 | message: 3 | # admin 4 | internal_server_error: 5 | en: Internal Server Error {} 6 | cn: 系统错误 {} 7 | bad_gateway: 8 | en: Bad Gateway {} 9 | cn: 错误的请求 {} 10 | unauthorized: 11 | en: Unauthorized {} 12 | cn: 未授权 {} 13 | unauthorized_expired: 14 | en: unauthorized expired {} 15 | cn: 授权信息过期 {} 16 | forbidden: 17 | en: Forbidden {} 18 | cn: 资源禁止访问 {} 19 | method_not_allowed: 20 | en: Method Not Allowed {} 21 | cn: 方法不被允许 {} 22 | request_timeout: 23 | en: Request Timeout {} 24 | cn: 请求超时 {} 25 | invalid_argument: 26 | en: Invalid Argument {} 27 | cn: 参数错误 {} 28 | argument_analyze: 29 | en: Argument Analyze {} 30 | cn: 参数解析异常 {} 31 | business_exception: 32 | en: Business Exception {} 33 | cn: 业务错误 {} 34 | cache_exception: 35 | en: Cache Exception {} 36 | cn: 缓存错误 {} 37 | not_found: 38 | en: Not Found {} 39 | cn: 请求资源不存在 {} -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/ValidatorConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | import org.hibernate.validator.HibernateValidator; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import javax.validation.Validation; 8 | import javax.validation.Validator; 9 | import javax.validation.ValidatorFactory; 10 | 11 | /** 12 | * @author 公众号 程序员三时 13 | * @version 1.0 14 | * @date 2023/4/30 21:59 15 | * @webSite https://github.com/coder-amiao 16 | */ 17 | @Configuration 18 | public class ValidatorConfig { 19 | @Bean 20 | public Validator validator() { 21 | ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) 22 | .configure() 23 | //failFast的意思只要出现校验失败的情况,就立即结束校验,不再进行后续的校验。 24 | .failFast(true) 25 | .buildValidatorFactory(); 26 | 27 | return validatorFactory.getValidator(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/ApplicationRunner.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter; 2 | 3 | 4 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.dromara.hutool.core.text.StrUtil; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.CommandLineRunner; 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * @author 公众号 程序员三时 13 | * @version 1.0 14 | * @date 2023/6/27 23:33 15 | * @webSite https://github.com/coder-amiao 16 | */ 17 | @Component 18 | @Slf4j 19 | public class ApplicationRunner implements CommandLineRunner { 20 | 21 | @Autowired 22 | private RestApiProperties.LoggingProperties restApiProperties; 23 | 24 | @Override 25 | public void run(String... args) throws Exception { 26 | String path = restApiProperties.getPath(); 27 | if (StrUtil.isBlank(path)) { 28 | //log.error("找不大restApi接口日志配置路径请设置路径"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsMoneyValidator.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | import javax.validation.ConstraintValidator; 4 | import javax.validation.ConstraintValidatorContext; 5 | import java.math.BigDecimal; 6 | 7 | /** 8 | * @author kenx 9 | * @version 1.0 10 | * @date 2021/1/21 20:51 11 | * 金额验证器 12 | */ 13 | public class IsMoneyValidator implements ConstraintValidator { 14 | 15 | private boolean required = false; 16 | 17 | public void initialize(IsMoney constraintAnnotation) { 18 | required = constraintAnnotation.required(); 19 | } 20 | 21 | public boolean isValid(BigDecimal value, ConstraintValidatorContext context) { 22 | if (required) { 23 | return ValidatorUtil.isMoney(value); 24 | } else { 25 | if (value==null) { 26 | return true; 27 | } else { 28 | return ValidatorUtil.isMoney(value); 29 | } 30 | } 31 | } 32 | 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/ResultPage.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter; 2 | 3 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 4 | import lombok.Data; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * @author 公众号 程序员三时 9 | * @version 1.0 10 | * @date 2023/7/8 22:46 11 | * @webSite https://github.com/coder-amiao 12 | * 自定义发挥响应结果 13 | */ 14 | @Slf4j 15 | @Data 16 | @JsonPropertyOrder({"success", "code","msg","requestId","timestamp","previousPage","nextPage","pageSize","totalPageSize","hasNext","data","pageData"}) 17 | public class ResultPage extends Result { 18 | /** 19 | * 当前页 20 | */ 21 | private Integer previousPage=1; 22 | /** 23 | * 下一页 24 | */ 25 | private Integer nextPage=1; 26 | 27 | /** 28 | * 每一页显示条数 29 | */ 30 | private Integer pageSize=1; 31 | 32 | /** 33 | * 总条数 34 | */ 35 | private Integer totalPageSize=1; 36 | 37 | 38 | /** 39 | * 是否有下一页 40 | */ 41 | private String hasNext="false"; 42 | 43 | 44 | private T pageData; 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/authorization/UserSign.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.authorization; 2 | 3 | 4 | import io.jsonwebtoken.SignatureAlgorithm; 5 | 6 | import javax.crypto.spec.SecretKeySpec; 7 | import javax.xml.bind.DatatypeConverter; 8 | import java.security.Key; 9 | 10 | /** 11 | * @author 公众号 程序员三时 12 | * @version 1.0 13 | * @date 2023/7/13 21:38 14 | * @webSite https://github.com/coder-amiao 15 | */ 16 | public interface UserSign { 17 | 18 | /** 19 | * 自定义签名 20 | * @return 21 | */ 22 | public SignatureAlgorithm sign(); 23 | 24 | /** 25 | * 自定义秘钥 26 | * @return 27 | */ 28 | public String AuthKey(); 29 | 30 | 31 | /** 32 | * 获取密钥 33 | * 34 | * @return Key 35 | */ 36 | default Key getSignedKey(String key) { 37 | byte[] apiKeySecretBytes = DatatypeConverter 38 | .parseBase64Binary(key); 39 | Key signingKey = new SecretKeySpec(apiKeySecretBytes, 40 | sign().getJcaName()); 41 | return signingKey; 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsMobileValidator.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | 4 | 5 | import org.dromara.hutool.core.text.StrUtil; 6 | 7 | import javax.validation.ConstraintValidator; 8 | import javax.validation.ConstraintValidatorContext; 9 | 10 | /** 11 | * @author kenx 12 | * @version 1.0 13 | * @date 2021/1/21 20:51 14 | * 手机验证器 15 | */ 16 | public class IsMobileValidator implements ConstraintValidator { 17 | 18 | private boolean required = false; 19 | 20 | public void initialize(IsMobile constraintAnnotation) { 21 | required = constraintAnnotation.required(); 22 | } 23 | 24 | public boolean isValid(String value, ConstraintValidatorContext context) { 25 | if (required) { 26 | return ValidatorUtil.isMobile(value); 27 | } else { 28 | if (StrUtil.isBlank(value)) { 29 | return true; 30 | } else { 31 | return ValidatorUtil.isMobile(value); 32 | } 33 | } 34 | } 35 | 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.exception; 2 | 3 | import cn.soboys.restapispringbootstarter.HttpStatus; 4 | import cn.soboys.restapispringbootstarter.ResultCode; 5 | import lombok.Data; 6 | 7 | /** 8 | * @author 公众号 程序员三时 9 | * @version 1.0 10 | * @date 2023/6/26 16:45 11 | * @webSite https://github.com/coder-amiao 12 | */ 13 | @Data 14 | public class BusinessException extends RuntimeException { 15 | 16 | /** 17 | * 错误码 18 | */ 19 | private String code="20000"; 20 | 21 | /** 22 | * 错误提示 23 | */ 24 | private String message; 25 | 26 | 27 | public BusinessException(String message) { 28 | this.message = message; 29 | 30 | } 31 | 32 | public BusinessException(String message, String code) { 33 | this.message = message; 34 | this.code = code; 35 | 36 | } 37 | 38 | public BusinessException(ResultCode resultCode) { 39 | this.message = resultCode.getMessage(); 40 | this.code = resultCode.getCode(); 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/log/Log.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.log; 2 | 3 | 4 | 5 | import cn.soboys.restapispringbootstarter.enums.LogApiTypeEnum; 6 | import cn.soboys.restapispringbootstarter.enums.LogCURDTypeEnum; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * @version: 2.2 15 | * @className: Log.java 16 | * @author: kenx 17 | * @description: 日志注解 18 | */ 19 | @Target(ElementType.METHOD) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | public @interface Log { 22 | String value() default ""; 23 | 24 | Class[] logExpandHandles() default {LogFileDefaultDataSource.class}; 25 | 26 | LogApiTypeEnum apiType() default LogApiTypeEnum.USER; 27 | 28 | LogCURDTypeEnum CURDType() default LogCURDTypeEnum.RETRIEVE; 29 | 30 | /** 31 | * 记录ip对应城市 32 | * @return 33 | */ 34 | boolean ipCity() default true; 35 | 36 | /** 37 | * 是否记录接口整个返回。 38 | * @return 39 | */ 40 | boolean apiResult() default false; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/RestApiSpringBootStarterApplication.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter; 2 | 3 | 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.dromara.hutool.extra.spring.EnableSpringUtil; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.scheduling.annotation.EnableAsync; 10 | 11 | @SpringBootApplication 12 | @EnableSpringUtil 13 | @EnableAsync 14 | @Slf4j 15 | public class RestApiSpringBootStarterApplication { 16 | 17 | 18 | public static void main(String[] args) { 19 | 20 | ApplicationContext ctx = SpringApplication.run(RestApiSpringBootStarterApplication.class, args); 21 | listBeans(ctx); 22 | } 23 | public static void listBeans(ApplicationContext ctx) { 24 | 25 | log.info("bean总数:{}", ctx.getBeanDefinitionCount()); 26 | String[] allBeanNames = ctx.getBeanDefinitionNames(); 27 | for (String beanName : allBeanNames) { 28 | System.out.println(beanName); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/test/Config.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.test; 2 | 3 | import cn.soboys.restapispringbootstarter.serializer.JsonSerializerConfig; 4 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | /** 10 | * @author 公众号 程序员三时 11 | * @version 1.0 12 | * @date 2023/7/19 22:18 13 | * @webSite https://github.com/coder-amiao 14 | */ 15 | //@ImportAutoConfiguration(JsonSerializerConfig.class) //测试 16 | @Configuration 17 | public class Config implements WebMvcConfigurer { 18 | 19 | 20 | @Override 21 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 22 | registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); 23 | registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); 24 | WebMvcConfigurer.super.addResourceHandlers(registry); 25 | } 26 | 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/annotation/Limit.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.annotation; 2 | 3 | import cn.soboys.restapispringbootstarter.enums.LimitType; 4 | import cn.soboys.restapispringbootstarter.utils.Strings; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * @author 公众号 程序员三时 13 | * @version 1.0 14 | * @date 2023/7/2 11:22 15 | * @webSite https://github.com/coder-amiao 16 | * 接口限流 17 | */ 18 | @Target(ElementType.METHOD) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | public @interface Limit { 21 | /** 22 | * 资源名称,用于描述接口功能 23 | */ 24 | String name() default Strings.EMPTY; 25 | 26 | /** 27 | * 资源 key 28 | */ 29 | String key() default Strings.EMPTY; 30 | 31 | /** 32 | * key prefix 33 | */ 34 | String prefix() default Strings.EMPTY; 35 | 36 | /** 37 | * 时间范围,单位秒 38 | */ 39 | int period(); 40 | 41 | /** 42 | * 限制访问次数 43 | */ 44 | int count(); 45 | 46 | /** 47 | * 限制类型 48 | */ 49 | LimitType limitType() default LimitType.CUSTOMER; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/config/UserJwtAutoConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.config; 2 | 3 | import cn.soboys.restapispringbootstarter.authorization.*; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | /** 9 | * @author 公众号 程序员三时 10 | * @version 1.0 11 | * @date 2023/7/15 09:49 12 | * @webSite https://github.com/coder-amiao 13 | * 用户jwt token生成配置 14 | */ 15 | 16 | public class UserJwtAutoConfig { 17 | 18 | @Bean 19 | @ConditionalOnMissingBean 20 | public UserSign userSignWith() { 21 | return new UserSignWith(); 22 | } 23 | 24 | @Bean 25 | @ConditionalOnMissingBean 26 | public LoginAuthorization loginAuthorizationSubject() { 27 | return new LoginAuthorizationSubject(); 28 | } 29 | 30 | 31 | @Bean 32 | @ConditionalOnMissingBean 33 | public UserJwtToken userJwtToken(@Autowired(required = false) UserSignWith userSignWith) { 34 | UserJwtToken userJwtToken = new UserJwtToken(); 35 | userJwtToken.setUserSign(userSignWith); 36 | return userJwtToken; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/DoubleValueSerializer.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/4/29 23:19 7 | * @webSite https://github.com/coder-amiao 8 | * 自定义对Double 类型json数据序列化返回 9 | */ 10 | 11 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 12 | import com.fasterxml.jackson.core.JsonGenerator; 13 | import com.fasterxml.jackson.databind.JsonSerializer; 14 | import com.fasterxml.jackson.databind.SerializerProvider; 15 | import org.dromara.hutool.core.math.NumberUtil; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | 18 | import java.io.IOException; 19 | public class DoubleValueSerializer extends JsonSerializer { 20 | 21 | @Autowired 22 | private RestApiProperties.JsonSerializeProperties jsonSerializeProperties; 23 | 24 | @Override 25 | public void serialize(Double value, JsonGenerator jgen, SerializerProvider provider) throws IOException { 26 | if (value != null) { 27 | jgen.writeString(NumberUtil.format(jsonSerializeProperties.getNumberForm(),value)); 28 | }else { 29 | jgen.writeString("0.00"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/authorization/LoginAuthorizationSubject.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.authorization; 2 | 3 | import cn.soboys.restapispringbootstarter.Assert; 4 | import cn.soboys.restapispringbootstarter.HttpStatus; 5 | import org.dromara.hutool.core.text.StrUtil; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | /** 13 | * @author 公众号 程序员三时 14 | * @version 1.0 15 | * @date 2023/7/16 12:12 16 | * @webSite https://github.com/coder-amiao 17 | */ 18 | public class LoginAuthorizationSubject implements LoginAuthorization { 19 | 20 | @Autowired 21 | private UserJwtToken userJwtToken; 22 | 23 | @Override 24 | public Boolean authorization(HttpServletRequest request, HttpServletResponse response, Object handler) { 25 | String token = request.getHeader(userJwtToken.getJwtProperties().getTokenHeader()); 26 | Assert.isFalse(StrUtil.isEmpty(token),HttpStatus.UNAUTHORIZED); 27 | String userId = userJwtToken.getUserId(token); //验证token有效合法性。 28 | 29 | //其他数据库 或者业务操作 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/YamlPropertySourceFactory.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; 4 | import org.springframework.core.env.PropertiesPropertySource; 5 | import org.springframework.core.env.PropertySource; 6 | import org.springframework.core.io.support.EncodedResource; 7 | import org.springframework.core.io.support.PropertySourceFactory; 8 | 9 | import java.io.IOException; 10 | import java.util.Properties; 11 | 12 | /** 13 | * @author 公众号 程序员三时 14 | * @version 1.0 15 | * @date 2023/6/26 17:39 16 | * @webSite https://github.com/coder-amiao 17 | * 自定义实现 @PropertySource读取YAML文件 18 | */ 19 | public class YamlPropertySourceFactory implements PropertySourceFactory { 20 | @Override 21 | public PropertySource createPropertySource(String name, EncodedResource encodedResource) 22 | throws IOException { 23 | YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); 24 | factory.setResources(encodedResource.getResource()); 25 | 26 | Properties properties = factory.getObject(); 27 | 28 | return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/BigDecimalSerializer.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.JsonSerializer; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.dromara.hutool.core.math.NumberUtil; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 11 | 12 | import java.io.IOException; 13 | import java.math.BigDecimal; 14 | 15 | /** 16 | * @author: siyulong 17 | * @date: 2021/10/31 01:49 18 | **/ 19 | @Slf4j 20 | public class BigDecimalSerializer extends JsonSerializer { 21 | 22 | @Autowired 23 | private RestApiProperties.JsonSerializeProperties jsonSerializeProperties; 24 | 25 | 26 | @Override 27 | public void serialize(BigDecimal value, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { 28 | if (value != null) { 29 | jgen.writeString( NumberUtil.format(jsonSerializeProperties.getNumberForm(),value)); 30 | }else { 31 | jgen.writeString("0.00"); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/LocalDateTimeSerializer.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.JsonSerializer; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | import org.dromara.hutool.core.date.DateUtil; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import java.io.IOException; 11 | import java.time.LocalDateTime; 12 | import java.time.ZoneId; 13 | 14 | /** 15 | * @author 公众号 程序员三时 16 | * @version 1.0 17 | * @date 2023/7/21 23:03 18 | * @webSite https://github.com/coder-amiao 19 | */ 20 | public class LocalDateTimeSerializer extends JsonSerializer { 21 | 22 | @Autowired 23 | private RestApiProperties.JsonSerializeProperties jsonSerializeProperties; 24 | 25 | @Override 26 | public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { 27 | if (jsonSerializeProperties.getDateForm().equals("timestamp")) { 28 | jgen.writeString(String.valueOf(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())); 29 | } else { 30 | jgen.writeString(DateUtil.format(value, jsonSerializeProperties.getDateForm())); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/DateSerializer.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.JsonSerializer; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | import org.dromara.hutool.core.date.DateUtil; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import java.io.IOException; 11 | import java.util.Date; 12 | 13 | /** 14 | * @author 公众号 程序员三时 15 | * @version 1.0 16 | * @date 2023/7/21 23:03 17 | * @webSite https://github.com/coder-amiao 18 | */ 19 | public class DateSerializer extends JsonSerializer { 20 | 21 | @Autowired 22 | private RestApiProperties.JsonSerializeProperties jsonSerializeProperties; 23 | 24 | @Override 25 | public void serialize(Date value, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { 26 | if (value != null) { 27 | //DateUtil.format(value, DatePattern.NORM_DATETIME_MS_PATTERN); 28 | if(jsonSerializeProperties.getDateForm().equals("timestamp")){ 29 | jgen.writeString(String.valueOf(value.getTime())); 30 | }else { 31 | jgen.writeString( DateUtil.format(value, jsonSerializeProperties.getDateForm())); 32 | } 33 | 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsEnumValidator.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | 4 | 5 | 6 | import org.dromara.hutool.core.text.StrUtil; 7 | 8 | import javax.validation.ConstraintValidator; 9 | import javax.validation.ConstraintValidatorContext; 10 | 11 | /** 12 | * @author kenx 13 | * @version 1.0 14 | * @date 2021/1/22 09:05 15 | * 枚举验证器 16 | */ 17 | public class IsEnumValidator implements ConstraintValidator { 18 | private Class> enumClass; 19 | private final String ENUM_METHOD = "isValidName"; 20 | private boolean required = false; 21 | 22 | @Override 23 | public void initialize(IsEnum constraintAnnotation) { 24 | enumClass = constraintAnnotation.enumClass(); 25 | required = constraintAnnotation.required(); 26 | } 27 | 28 | @Override 29 | public boolean isValid(String o, ConstraintValidatorContext constraintValidatorContext) { 30 | try { 31 | if (required) { 32 | return ValidatorUtil.isEnum(enumClass, o); 33 | } else { 34 | if (StrUtil.isEmptyIfStr(o)) { 35 | return true; 36 | } else { 37 | return ValidatorUtil.isEnum(enumClass, o); 38 | } 39 | } 40 | } catch (Exception e) { 41 | return Boolean.FALSE; 42 | } 43 | 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/IsDateTimeValidator.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | 4 | 5 | import org.dromara.hutool.core.text.StrUtil; 6 | 7 | import javax.validation.ConstraintValidator; 8 | import javax.validation.ConstraintValidatorContext; 9 | 10 | /** 11 | * @author kenx 12 | * @version 1.0 13 | * @date 2021/1/21 20:51 14 | * 日期验证器 15 | */ 16 | public class IsDateTimeValidator implements ConstraintValidator { 17 | 18 | private boolean required = false; 19 | private String dateFormat = "yyyy-MM-dd"; 20 | 21 | /** 22 | * 用于初始化注解上的值到这个validator 23 | * @param constraintAnnotation 24 | */ 25 | @Override 26 | public void initialize(IsDateTime constraintAnnotation) { 27 | required = constraintAnnotation.required(); 28 | dateFormat = constraintAnnotation.dateFormat(); 29 | } 30 | 31 | /** 32 | * 具体的校验逻辑 33 | * @param value 34 | * @param context 35 | * @return 36 | */ 37 | public boolean isValid(String value, ConstraintValidatorContext context) { 38 | if (required) { 39 | return ValidatorUtil.isDateTime(value, dateFormat); 40 | } else { 41 | if (StrUtil.isBlank(value)) { 42 | return true; 43 | } else { 44 | return ValidatorUtil.isDateTime(value, dateFormat); 45 | } 46 | } 47 | } 48 | 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/annotation/EnableRestFullApi.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.annotation; 2 | 3 | import cn.soboys.restapispringbootstarter.cache.CacheAutoConfiguration; 4 | import cn.soboys.restapispringbootstarter.cache.RedisConfig; 5 | import cn.soboys.restapispringbootstarter.cache.SpringCacheConfig; 6 | import cn.soboys.restapispringbootstarter.config.BeanAutoConfiguration; 7 | 8 | import cn.soboys.restapispringbootstarter.config.OpenApiConfig; 9 | import cn.soboys.restapispringbootstarter.config.UserJwtAutoConfig; 10 | import cn.soboys.restapispringbootstarter.interceptor.WebMvcHandleConfig; 11 | import cn.soboys.restapispringbootstarter.serializer.JsonSerializerConfig; 12 | import org.springframework.context.annotation.Import; 13 | 14 | import java.lang.annotation.ElementType; 15 | import java.lang.annotation.Retention; 16 | import java.lang.annotation.RetentionPolicy; 17 | import java.lang.annotation.Target; 18 | 19 | /** 20 | * @author 公众号 程序员三时 21 | * @version 1.0 22 | * @date 2023/7/5 00:10 23 | * @webSite https://github.com/coder-amiao 24 | * 开启rest-api 功能 25 | */ 26 | @Target({ElementType.TYPE}) 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @Import( 29 | {BeanAutoConfiguration.class,BeanAutoConfiguration.RestTemplateConfig.class, 30 | SpringCacheConfig.class, CacheAutoConfiguration.class, 31 | OpenApiConfig.class, WebMvcHandleConfig.class, UserJwtAutoConfig.class, 32 | JsonSerializerConfig.class}) 33 | 34 | public @interface EnableRestFullApi { 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/config/StartupApplicationListener.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.config; 2 | 3 | /** 4 | * @author 公众号 程序员三时 5 | * @version 1.0 6 | * @date 2023/6/27 22:35 7 | * @webSite https://github.com/coder-amiao 8 | */ 9 | 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.context.ApplicationListener; 14 | import org.springframework.boot.context.event.ApplicationReadyEvent; 15 | import org.springframework.core.env.MapPropertySource; 16 | import org.springframework.core.env.MutablePropertySources; 17 | import org.springframework.stereotype.Component; 18 | import org.springframework.core.env.ConfigurableEnvironment; 19 | 20 | 21 | import java.io.File; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | @Component 26 | @Slf4j 27 | public class StartupApplicationListener implements ApplicationListener { 28 | 29 | @Autowired 30 | private ConfigurableEnvironment environment; 31 | 32 | @Override 33 | public void onApplicationEvent(ApplicationReadyEvent event) { 34 | MutablePropertySources propertySources = environment.getPropertySources(); 35 | //Object path= propertySources.get("application"); 36 | 37 | // Map myMap = new HashMap<>(); 38 | //myMap.put("rest-api.logging.path", System.getProperty("user.dir")+ File.separator+"logs"); 39 | // propertySources.addFirst(new MapPropertySource("MY_MAP", myMap)); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/HttpStatus.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter; 2 | 3 | import cn.soboys.restapispringbootstarter.i18n.I18NKey; 4 | 5 | /** 6 | * @author 公众号 程序员三时 7 | * @version 1.0 8 | * @date 2023/6/26 11:01 9 | * @webSite https://github.com/coder-amiao 10 | */ 11 | public enum HttpStatus implements ResultCode, I18NKey { 12 | /** 13 | * 系统内部错误 14 | */ 15 | INTERNAL_SERVER_ERROR("500", "internal_server_error"), 16 | BAD_GATEWAY("502", "bad_gateway"), 17 | NOT_FOUND("404", "not_found"), 18 | UNAUTHORIZED("401", "unauthorized"), 19 | UNAUTHORIZED_EXPIRED("402","unauthorized_expired"), 20 | FORBIDDEN("403", "forbidden"), 21 | METHOD_NOT_ALLOWED("405", "method_not_allowed"), 22 | REQUEST_TIMEOUT("408", "request_timeout"), 23 | 24 | INVALID_ARGUMENT("10000", "invalid_argument"), 25 | ARGUMENT_ANALYZE("10001", "argument_analyze"), 26 | 27 | BUSINESS_EXCEPTION("20000", "business_exception"), 28 | CACHE_EXCEPTION("20001", "cache_exception"); 29 | 30 | 31 | 32 | private final String value; 33 | 34 | private final String message; 35 | 36 | HttpStatus(String value, String message) { 37 | this.value = value; 38 | this.message = message; 39 | } 40 | 41 | 42 | @Override 43 | public String getCode() { 44 | return value; 45 | } 46 | 47 | @Override 48 | public String getMessage() { 49 | return message; 50 | } 51 | 52 | 53 | @Override 54 | public String key() { 55 | return message; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/interceptor/WebMvcHandleConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.interceptor; 2 | 3 | import org.dromara.hutool.extra.spring.SpringUtil; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | /** 11 | * @author 公众号 程序员三时 12 | * @version 1.0 13 | * @date 2023/6/28 00:21 14 | * @webSite https://github.com/coder-amiao 15 | * 封装全局的 资源配置。路由配置 拦截处理 16 | */ 17 | public class WebMvcHandleConfig implements WebMvcConfigurer { 18 | 19 | 20 | /** 21 | * 授权登录拦截器 22 | * @return 23 | */ 24 | @Bean 25 | public JwtTokenInterceptor jwtTokenInterceptor() { 26 | return new JwtTokenInterceptor(); 27 | } 28 | 29 | @Override 30 | public void addInterceptors(InterceptorRegistry registry) { 31 | registry.addInterceptor(SpringUtil.getBean(JwtTokenInterceptor.class)); 32 | WebMvcConfigurer.super.addInterceptors(registry); 33 | } 34 | 35 | @Override 36 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 37 | registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); 38 | registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); 39 | WebMvcConfigurer.super.addResourceHandlers(registry); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/cache/SpringCacheUtil.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.cache; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 7 | import org.springframework.cache.Cache; 8 | import org.springframework.cache.CacheManager; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | * @author 公众号 程序员三时 16 | * @version 1.0 17 | * @date 2023/7/7 10:13 18 | * @webSite https://github.com/coder-amiao 19 | */ 20 | 21 | public class SpringCacheUtil { 22 | 23 | @Resource 24 | private CacheManager cacheManager; 25 | 26 | @SuppressWarnings({"unchecked"}) 27 | public T getCache(String cacheName,String key) { 28 | Cache cache = cacheManager.getCache(cacheName); 29 | if (cache == null) { 30 | return null; 31 | } 32 | Cache.ValueWrapper valueWrapper = cache.get(key); 33 | if (valueWrapper == null) { 34 | return null; 35 | } 36 | return (T)valueWrapper.get(); 37 | } 38 | 39 | public void putCache(String cacheName,String key, Object value) { 40 | Cache cache = cacheManager.getCache(cacheName); 41 | if (cache != null) { 42 | cache.put(key, value); 43 | } 44 | } 45 | 46 | 47 | 48 | public void evictCache(String cacheName,String key) { 49 | Cache cache = cacheManager.getCache(cacheName); 50 | if (cache != null) { 51 | cache.evict(key); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/i18n/I18NMessage.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.i18n; 2 | 3 | 4 | import lombok.Data; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | 10 | import java.util.Map; 11 | import java.util.Optional; 12 | 13 | /** 14 | * @author 公众号 程序员三时 15 | * @version 1.0 16 | * @date 2023/6/26 11:55 17 | * @webSite https://github.com/coder-amiao 18 | */ 19 | @Configuration 20 | @ConfigurationProperties(prefix = "rest-api.i18n") 21 | @Data 22 | public class I18NMessage { 23 | /** 24 | * message-key: 25 | */ 26 | 27 | private Map> message; 28 | /** 29 | * Default language setting (Default "cn"). 30 | */ 31 | private String defaultLang = "cn"; 32 | 33 | 34 | private String i18nHeader = "Lang"; 35 | 36 | /** 37 | * 初始化message 国际化数据 38 | */ 39 | static { 40 | 41 | } 42 | 43 | /** 44 | * get i18n message 45 | * 46 | * @param key 47 | * @param language 48 | * @return 49 | */ 50 | public String message(I18NKey key, String language) { 51 | return Optional.ofNullable(message.get(key.key())) 52 | .map(map -> map.get(language == null ? defaultLang : language)) 53 | .orElse(key.key()); 54 | } 55 | 56 | /** 57 | * get i18n message 58 | * 59 | * @param key 60 | * @param language 61 | * @return 62 | */ 63 | public String message(String key, String language) { 64 | return Optional.ofNullable(message.get(key)) 65 | .map(map -> map.get(language == null ? defaultLang : language)) 66 | .orElse(key); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/cache/CacheTmp.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.cache; 2 | 3 | import cn.soboys.restapispringbootstarter.exception.CacheException; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * @author 公众号 程序员三时 10 | * @version 1.0 11 | * @date 2023/7/6 21:55 12 | * @webSite https://github.com/coder-amiao 13 | */ 14 | public interface CacheTmp { 15 | 16 | String getKey(); 17 | 18 | Boolean getHasPrefix(); 19 | 20 | /** 21 | * 调用key()时,keyParts不为null,是空数组 22 | * 调用key(null)时,keyParts为null 23 | */ 24 | default String key(String... keyParts) { 25 | return key(this.getKey(), this.getHasPrefix(), keyParts); 26 | } 27 | 28 | default boolean valid(String... keyParts) { 29 | return RedisTempUtil.getInstance().hasKey(this.key(keyParts)); 30 | } 31 | 32 | static String key(String key, boolean hasPrefix, String[] keyParts) { 33 | if (keyParts == null) { 34 | throw new CacheException("keyParts不能为null"); 35 | } else if (hasPrefix && keyParts.length == 0) { 36 | throw new CacheException("有前缀,需要传keyParts"); 37 | } else if (!hasPrefix && keyParts.length > 0) { 38 | throw new CacheException("无前缀,不需要传keyParts"); 39 | } 40 | if (keyParts.length == 0) { 41 | return key; 42 | } else { 43 | return key + (key.endsWith(":") ? "" : ":") + StringUtils.join(keyParts, ":"); 44 | } 45 | } 46 | 47 | default void valueSet(Object value, String... keyParts) { 48 | RedisTempUtil.getInstance().set(this.key(keyParts), value); 49 | } 50 | 51 | default void valueSetAndExpire(Object value, long timeout, TimeUnit unit, String... keyParts) { 52 | RedisTempUtil.getInstance().set(this.key(keyParts), value, timeout, unit); 53 | } 54 | 55 | default T valueGet(String... keyParts) { 56 | return (T) RedisTempUtil.getInstance().get(this.key(keyParts)); 57 | } 58 | 59 | default Boolean delete(String... keyParts) { 60 | return RedisTempUtil.getInstance().delete(this.key(keyParts)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/Strings.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | /** 4 | * String常量 5 | * 6 | * @author kenx 7 | */ 8 | public interface Strings { 9 | 10 | String AMPERSAND = "&"; 11 | String AND = "and"; 12 | String AT = "@"; 13 | String ASTERISK = "*"; 14 | String STAR = "*"; 15 | String BACK_SLASH = "\\"; 16 | String COLON = ":"; 17 | String DOUBLE_COLON = "::"; 18 | String COMMA = ","; 19 | String DASH = "-"; 20 | String DOLLAR = "$"; 21 | String DOT = "."; 22 | String UNDER_LINE = "_"; 23 | String DOUBLE_DOT = ".."; 24 | String DOT_CLASS = ".class"; 25 | String DOT_JAVA = ".java"; 26 | String DOT_XML = ".xml"; 27 | String EMPTY = ""; 28 | String EQUALS = "="; 29 | String FALSE = "false"; 30 | String SLASH = "/"; 31 | String HASH = "#"; 32 | String HAT = "^"; 33 | String LEFT_BRACE = "{"; 34 | String LEFT_BRACKET = "("; 35 | String LEFT_CHEV = "<"; 36 | String DOT_NEWLINE = ",\n"; 37 | String NEWLINE = "\n"; 38 | String N = "n"; 39 | String NO = "no"; 40 | String NULL = "null"; 41 | String OFF = "off"; 42 | String ON = "on"; 43 | String PERCENT = "%"; 44 | String PIPE = "|"; 45 | String PLUS = "+"; 46 | String QUESTION_MARK = "?"; 47 | String EXCLAMATION_MARK = "!"; 48 | String QUOTE = "\""; 49 | String RETURN = "\r"; 50 | String TAB = "\t"; 51 | String RIGHT_BRACE = "}"; 52 | String RIGHT_BRACKET = ")"; 53 | String RIGHT_CHEV = ">"; 54 | String SEMICOLON = ";"; 55 | String SINGLE_QUOTE = "'"; 56 | String BACKTICK = "`"; 57 | String SPACE = " "; 58 | String TILDA = "~"; 59 | String LEFT_SQ_BRACKET = "["; 60 | String RIGHT_SQ_BRACKET = "]"; 61 | String TRUE = "true"; 62 | String UTF_8 = "UTF-8"; 63 | String US_ASCII = "US-ASCII"; 64 | String ISO_8859_1 = "ISO-8859-1"; 65 | String Y = "y"; 66 | String YES = "yes"; 67 | String ONE = "1"; 68 | String ZERO = "0"; 69 | String DOLLAR_LEFT_BRACE = "${"; 70 | String HASH_LEFT_BRACE = "#{"; 71 | String CRLF = "\r\n"; 72 | String NORMAL ="1"; 73 | String DELETE="-1"; 74 | String FORBID ="0"; 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/log/LogEntry.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.log; 2 | 3 | import cn.soboys.restapispringbootstarter.Result; 4 | import lombok.Data; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Date; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author 公众号 程序员三时 12 | * @version 1.0 13 | * @date 2023/7/9 01:02 14 | * @webSite https://github.com/coder-amiao 15 | */ 16 | @Data 17 | public class LogEntry { 18 | /** 19 | * 操作用户 20 | */ 21 | private String username; 22 | 23 | /** 24 | * 描述 25 | */ 26 | private String description; 27 | 28 | /** 29 | * 方法名 30 | */ 31 | private String method; 32 | 33 | /** 34 | * 请求参数 35 | */ 36 | private Object params; 37 | 38 | /** 39 | * 日志类型 INFO ERROR 40 | */ 41 | private String logType; 42 | 43 | /** 44 | * 请求ip 45 | */ 46 | private String requestIp; 47 | 48 | /** 49 | * 请求接口地址 50 | */ 51 | private String path; 52 | 53 | /** 54 | * ip地址所对应的物理地址 55 | */ 56 | private String address; 57 | 58 | /** 59 | * 请求耗时 60 | */ 61 | private Long time = 0L; 62 | 63 | /** 64 | * 异常详细 65 | */ 66 | private String exceptionDetail; 67 | 68 | /** 69 | * 操作系统 70 | */ 71 | private String os; 72 | 73 | /** 74 | * 浏览器 75 | */ 76 | private String browser; 77 | 78 | /** 79 | * 接口返回数据 80 | */ 81 | private Object result; 82 | 83 | /** 84 | * 日志类型(例如:接口日志,操作日志...各个系统可以自定义) 85 | */ 86 | private String apiType; 87 | 88 | /** 89 | * 用户编码 90 | */ 91 | private Long userId; 92 | 93 | /** 94 | * 创建日期 95 | */ 96 | private Date createTime; 97 | 98 | /** 99 | * 请求唯一id 100 | */ 101 | private String requestId; 102 | 103 | /** 104 | * 完整请求设备信息 105 | */ 106 | private String device; 107 | 108 | public LogEntry() { 109 | 110 | } 111 | 112 | public LogEntry(String logType, Long time) { 113 | this.logType = logType; 114 | this.time = time; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/NullAbleSerializer.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import cn.soboys.restapispringbootstarter.exception.JsonSerializableException; 5 | import com.fasterxml.jackson.core.JsonGenerator; 6 | import com.fasterxml.jackson.databind.JsonSerializer; 7 | import com.fasterxml.jackson.databind.SerializerProvider; 8 | import lombok.Data; 9 | import org.dromara.hutool.core.date.DateUtil; 10 | import org.dromara.hutool.core.text.StrUtil; 11 | import org.dromara.hutool.extra.spring.SpringUtil; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | 14 | import java.io.IOException; 15 | import java.lang.reflect.Array; 16 | import java.math.BigDecimal; 17 | import java.util.*; 18 | 19 | /** 20 | * @author 公众号 程序员三时 21 | * @version 1.0 22 | * @date 2023/7/21 23:03 23 | * @webSite https://github.com/coder-amiao 24 | */ 25 | public class NullAbleSerializer extends JsonSerializer { 26 | 27 | private SerializableType serializableType; 28 | 29 | 30 | public void setSerializableType(SerializableType serializableType) { 31 | this.serializableType = serializableType; 32 | } 33 | 34 | @Override 35 | public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializerProvider) throws IOException { 36 | if (value == null && this.serializableType != null) { 37 | 38 | String t = this.serializableType.getType(); //original 39 | if (StrUtil.isNotEmpty(t)) { 40 | if (t.equals("array")) { 41 | jgen.writeStartArray(); 42 | jgen.writeEndArray(); 43 | } else if (t.equals("string")) { 44 | jgen.writeString(""); 45 | } else if (t.equals("double")) { 46 | jgen.writeString("0.00"); 47 | } else if (t.equals("number")) { 48 | jgen.writeString("0"); 49 | //jgen.writeNumber(0); 50 | }else { 51 | throw new JsonSerializableException("找不到对应 nullAble 类型处理"); 52 | } 53 | } 54 | } 55 | } 56 | 57 | 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/authorization/UrlMatcher.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.authorization; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * @author kenx 11 | * @version 1.0 12 | * @date 2020/11/12 13 | * url 匹配认证 14 | */ 15 | public class UrlMatcher { 16 | private static final String TMP_PLACEHOLDER = "@@@@@#####$$$$$"; 17 | private List includePatterns; 18 | private List excludePatterns; 19 | 20 | public UrlMatcher(String includes, String excludes) { 21 | this.includePatterns = valueToPatterns(includes); 22 | this.excludePatterns = valueToPatterns(excludes); 23 | } 24 | 25 | private List valueToPatterns(String value) { 26 | List patterns = new ArrayList<>(); 27 | if (value == null) { 28 | return patterns; 29 | } 30 | 31 | String[] patternItems = value.split(","); 32 | for (String patternItem : patternItems) { 33 | patternItem = patternItem.trim(); 34 | if ("".equals(patternItem)) { 35 | continue; 36 | } 37 | 38 | patternItem = patternItem.replace("**", TMP_PLACEHOLDER); 39 | patternItem = patternItem.replace("*", "[^/]*?");//替换* 40 | patternItem = patternItem.replace(TMP_PLACEHOLDER, "**"); 41 | patternItem = patternItem.replace("**", ".*?");//替换** 42 | patterns.add(Pattern.compile(patternItem)); 43 | } 44 | 45 | return patterns; 46 | } 47 | 48 | public boolean matches(String url) { 49 | return matches(includePatterns, url) && !matches(excludePatterns, url); 50 | } 51 | 52 | private boolean matches(List patterns, String url) { 53 | for (Pattern pattern : patterns) { 54 | Matcher matcher = pattern.matcher(url); 55 | if (matcher.matches()) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | public static void main(String[] args) { 63 | UrlMatcher matcher = new UrlMatcher("/**", ""); 64 | System.out.println(matcher.matches("/v3/api-docs")); 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/config/OpenApiConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.config; 2 | 3 | import io.swagger.v3.oas.models.OpenAPI; 4 | import io.swagger.v3.oas.models.info.Contact; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import org.dromara.hutool.core.text.StrUtil; 7 | import org.springdoc.core.GroupedOpenApi; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 11 | import org.springframework.context.annotation.Bean; 12 | 13 | 14 | /** 15 | * @author 公众号 程序员三时 16 | * @version 1.0 17 | * @date 2023/7/10 21:14 18 | * @webSite https://github.com/coder-amiao 19 | */ 20 | @EnableConfigurationProperties(value = {RestApiProperties.OpenApiProperties.class}) 21 | public class OpenApiConfig { 22 | 23 | @Autowired 24 | private RestApiProperties.OpenApiProperties openApiProperties; 25 | 26 | @Bean 27 | public OpenAPI openApi() { 28 | Info info = new Info(); 29 | Contact contact = new Contact(); 30 | if (openApiProperties != null) { 31 | if (StrUtil.isNotEmpty(openApiProperties.getTitle())) { 32 | info.setTitle(openApiProperties.getTitle()); 33 | } 34 | if (StrUtil.isNotEmpty(openApiProperties.getDescription())) { 35 | info.setTitle(openApiProperties.getDescription()); 36 | } 37 | 38 | if (openApiProperties.getContact() != null) { 39 | 40 | if (StrUtil.isNotEmpty(openApiProperties.getContact().getName())) { 41 | contact.setName(openApiProperties.getContact().getName()); 42 | } 43 | if (StrUtil.isNotEmpty(openApiProperties.getContact().getEmail())) { 44 | contact.setEmail(openApiProperties.getContact().getEmail()); 45 | } 46 | if (StrUtil.isNotEmpty(openApiProperties.getContact().getUrl())) { 47 | contact.setUrl(openApiProperties.getContact().getUrl()); 48 | } 49 | } 50 | } 51 | return new OpenAPI() 52 | .info(info 53 | .contact(contact) 54 | .version(openApiProperties.getVersion())); 55 | } 56 | 57 | @Bean 58 | @ConditionalOnMissingBean 59 | public GroupedOpenApi adminApi() { 60 | return GroupedOpenApi.builder() 61 | .group("rest") 62 | .packagesToScan("cn.soboys.restapispringbootstarter.controller") 63 | .build(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.config; 2 | 3 | import cn.soboys.restapispringbootstarter.utils.RestFulTemp; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.context.annotation.Primary; 8 | import org.springframework.http.client.ClientHttpRequestFactory; 9 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 10 | import org.springframework.http.converter.HttpMessageConverter; 11 | import org.springframework.http.converter.StringHttpMessageConverter; 12 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 13 | import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | import java.nio.charset.Charset; 17 | import java.util.List; 18 | 19 | /** 20 | * @author 公众号 程序员三时 21 | * @version 1.0 22 | * @date 2023/7/2 12:07 23 | * @webSite https://github.com/coder-amiao 24 | */ 25 | @Configuration 26 | public class RestTemplateConfig { 27 | /** 28 | * 第三方请求要求的默认编码 29 | */ 30 | private final Charset thirdRequest = Charset.forName("utf-8"); 31 | 32 | @Bean 33 | public RestTemplate restTemplate(ClientHttpRequestFactory factory) { 34 | RestTemplate restTemplate = new RestTemplate(factory); 35 | // 处理请求中文乱码问题 36 | List> messageConverters = restTemplate.getMessageConverters(); 37 | for (HttpMessageConverter messageConverter : messageConverters) { 38 | if (messageConverter instanceof StringHttpMessageConverter) { 39 | ((StringHttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest); 40 | } 41 | if (messageConverter instanceof MappingJackson2HttpMessageConverter) { 42 | ((MappingJackson2HttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest); 43 | } 44 | if (messageConverter instanceof AllEncompassingFormHttpMessageConverter) { 45 | ((AllEncompassingFormHttpMessageConverter) messageConverter).setCharset(thirdRequest); 46 | } 47 | } 48 | return restTemplate; 49 | } 50 | 51 | @Bean 52 | public ClientHttpRequestFactory simpleClientHttpRequestFactory() { 53 | SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); 54 | factory.setConnectTimeout(15000); 55 | factory.setReadTimeout(5000); 56 | return factory; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 基于SpringBoot Web 快速构建API脚手架 解决重复繁琐工作 高效开发SpringBoot Web API 接口 2 | 之前我们已经,出了一些列文章。 讲解如何封统一全局响应Restful API。 3 | 感兴趣的可以看我前面几篇文章 (整个starter项目发展史) 文章都在公众号 程序员三时 上面。 4 | [SpringBoot定义优雅全局统一Restful API 响应框架](https://mp.weixin.qq.com/s?__biz=Mzg4OTkwNjc2MQ==&mid=2247483741&idx=1&sn=2734d2ef008edcf1369dd7a31a88a142&chksm=cfe5f27bf8927b6d468a411fe2eaeeeb6dbdd5527dec77a77a0e580ea32b2b58f38922836552#rd) 5 | [SpringBoot定义优雅全局统一Restful API 响应框架二](https://mp.weixin.qq.com/s?__biz=Mzg4OTkwNjc2MQ==&mid=2247483752&idx=1&sn=eab94282e3f1e62682d2106cfb2949d1&chksm=cfe5f24ef8927b582e01863881a88452dcbcb102afdb9b50985304b97dfd74cd0c3ed0b8c2c3#rd) 6 | [SpringBoot定义优雅全局统一Restful API 响应框架三](https://mp.weixin.qq.com/s?__biz=Mzg4OTkwNjc2MQ==&mid=2247483761&idx=1&sn=dbc516d0ba14228c1f091dfa39d85209&chksm=cfe5f257f8927b41635fe1508c2829d73e2b788105b145bc51525be4574b83a1a8da23c49ec2#rd) 7 | [SpringBoot定义优雅全局统一Restful API 响应框架四](https://mp.weixin.qq.com/s?__biz=Mzg4OTkwNjc2MQ==&mid=2247483887&idx=1&sn=cb737d573adca7eaea7a59cad2a7bbfe&chksm=cfe5f2c9f8927bdfaf7e46fd38d26f56fffbb1ece8460eaffe2addee592e42ffee6ba49530b7#rd) 8 | [SpringBoot定义优雅全局统一Restful API 响应框架五](https://mp.weixin.qq.com/s?__biz=Mzg4OTkwNjc2MQ==&mid=2247484102&idx=1&sn=e17772a12e6548755c341c2d9300e235&chksm=cfe5f1e0f89278f669c4c89548b3fdeec4dd58d6d7d7580315d07f20c9b52ffdd21ef5a7c232&token=691863430&lang=zh_CN#rd) 9 | [SpringBoot定义优雅全局统一Restful API 响应框架六](https://mp.weixin.qq.com/s?__biz=Mzg4OTkwNjc2MQ==&mid=2247484160&idx=1&sn=37eea0079dd175634437f01dde38bb4c&chksm=cfe5f026f892793045fb242a556ce4e3010f4a4aae867a84fe3542ed18ac3c64b85e87fa536a#rd) 10 | 后续我萌生里新的想法,SpringBoot 不是提供了自己的starter。我们也可以自定义**starter**吗,于是我定义了**rest-api-spring-boot-starter**,已经发布到maven中央仓库,对之前Restful API 响应框架 做了集成和重构, 11 | 在这个基础上我又总结封装了我自己工作常用的很多工具,结合SpringBoot 封装了全能的工具。 已经更新到了1.3.0 不耦合任何依赖 12 | 13 | 目前更新版本到2.0.0 支持功能如下 14 | 1. 支持一键配置自定义RestFull API 统一格式返回 15 | 2. 支持RestFull API 错误国际化 16 | 3. 支持全局异常处理,全局参数验证处理 17 | 4. 业务错误断言工具封装,遵循错误优先返回原则 18 | 5. 封装Redis key,value 操作工具类。统一key管理 spring cache缓存实现 19 | 6. RestTemplate 封装 POST,GET 请求工具 20 | 7. 日志集成。自定义日志路径,按照日志等级分类,支持压缩和文件大小分割。按时间显示 21 | 8. 工具库集成 集成了lombok,hutool,commons-lang3,guava。不需要自己单个引入 22 | 9. 集成mybatisPlus一键代码生成 23 | 10. 日志记录,服务监控,支持日志链路查询。自定义数据源 24 | 11. OpenApi3文档一键配置。支持多种文档和自动配置 25 | 12. 生成JWT标准Token和权限认证 26 | 13. 全局自定义Json序列化处理对空,浮点,时间等类型格式返回 27 | 14. 接口限流,Ip城市回显 28 | 15. HttpUserAgent请求设备工具封装 29 | 16. RequestUtil参数解析封装工具 30 | 31 | 32 | [详细使用文档官网](https://boot.soboys.cn/simplest/) 33 | 34 | 在使用过程中尽量使用最新版本。我会持续更新更多的内容。 会第一时间发布在我的公众号 35 | 程序员三时,全网同名 36 | 37 | 38 | 可以关注 公众号 程序员三时。用心分享持续输出优质内容。 39 | 40 | ![](https://images.soboys.cn/202307052317593.jpg) 41 | 42 | 43 | 点赞支持,请作者喝一杯☕️ 44 | ![](https://images.soboys.cn/202307102343241.png) 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/interceptor/JwtTokenInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.interceptor; 2 | 3 | import cn.soboys.restapispringbootstarter.authorization.LoginAuthorization; 4 | import cn.soboys.restapispringbootstarter.authorization.UrlMatcher; 5 | import cn.soboys.restapispringbootstarter.authorization.UserJwtToken; 6 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.dromara.hutool.core.map.MapUtil; 9 | import org.dromara.hutool.core.text.StrUtil; 10 | 11 | import org.dromara.hutool.extra.spring.SpringUtil; 12 | import org.springframework.beans.factory.BeanCreationException; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.stereotype.Component; 17 | import org.springframework.web.servlet.HandlerInterceptor; 18 | import org.springframework.web.servlet.ModelAndView; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import java.util.Map; 23 | 24 | /** 25 | * @author 公众号 程序员三时 26 | * @version 1.0 27 | * @date 2023/7/16 10:32 28 | * @webSite https://github.com/coder-amiao 29 | */ 30 | @Slf4j 31 | public class JwtTokenInterceptor implements HandlerInterceptor { 32 | 33 | 34 | @Autowired 35 | private UserJwtToken userJwtToken; 36 | 37 | @Autowired 38 | private LoginAuthorization loginAuthorization; 39 | 40 | 41 | @Override 42 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 43 | RestApiProperties.JwtProperties jwtProperties = userJwtToken.getJwtProperties(); 44 | if (jwtProperties != null && jwtProperties.getAuthorization().getHasAuthorization() 45 | && StrUtil.isNotEmpty(jwtProperties.getAuthorization().getIncludesUrl())) { 46 | 47 | 48 | //Map obj= SpringUtil.getBeansOfType(LoginAuthorization.class); 49 | //获取第一个实例对象值 50 | //LoginAuthorization loginAuthorization= 51 | 52 | if(loginAuthorization==null) throw new BeanCreationException("Unable to inject the LoginAuthorization bean"); 53 | UrlMatcher matcher = new UrlMatcher(jwtProperties.getAuthorization().getIncludesUrl(), jwtProperties.getAuthorization().getExcludesUrl()); 54 | if (matcher.matches(request.getRequestURI())) { 55 | loginAuthorization.authorization(request, response, handler); 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | @Override 62 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 63 | } 64 | 65 | @Override 66 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/JsonSerializerConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import com.fasterxml.jackson.databind.module.SimpleModule; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 10 | 11 | import java.math.BigDecimal; 12 | import java.time.LocalDateTime; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | /** 17 | * @author 公众号 程序员三时 18 | * @version 1.0 19 | * @date 2023/4/29 23:27 20 | * @webSite https://github.com/coder-amiao 21 | * 注册自定义json序列化器 22 | */ 23 | @JsonInclude(JsonInclude.Include.ALWAYS) 24 | public class JsonSerializerConfig { 25 | 26 | @Bean 27 | public RestApiProperties.JsonSerializeProperties jsonSerializeProperties() { 28 | return new RestApiProperties.JsonSerializeProperties(); 29 | } 30 | 31 | @Bean 32 | public DoubleValueSerializer doubleValueSerializer() { 33 | return new DoubleValueSerializer(); 34 | } 35 | 36 | @Bean 37 | public BigDecimalSerializer bigDecimalSerializer() { 38 | return new BigDecimalSerializer(); 39 | } 40 | 41 | @Bean 42 | public DateSerializer dateSerializer() { 43 | return new DateSerializer(); 44 | } 45 | 46 | @Bean 47 | public LocalDateTimeSerializer localDateSerializer() { 48 | return new LocalDateTimeSerializer(); 49 | } 50 | 51 | @Bean 52 | public BeanSerializerModifierFactory beanSerializerModifierFactory() { 53 | return new BeanSerializerModifierFactory(); 54 | } 55 | 56 | 57 | @Bean 58 | public ObjectMapper objectMapper(DoubleValueSerializer doubleValueSerializer, BigDecimalSerializer bigDecimalSerializer, 59 | DateSerializer dateSerializer, BeanSerializerModifierFactory beanSerializerModifierFactory, 60 | LocalDateTimeSerializer localDateSerializer 61 | ) { 62 | ObjectMapper objectMapper = new ObjectMapper(); 63 | //Jackson 当属性null 不会序列化。 64 | //objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); 65 | 66 | SimpleModule module = new SimpleModule(); 67 | module.addSerializer(Double.class, doubleValueSerializer); 68 | module.addSerializer(BigDecimal.class, bigDecimalSerializer); 69 | module.addSerializer(Date.class, dateSerializer); 70 | module.addSerializer(LocalDateTime.class, localDateSerializer); 71 | objectMapper.registerModule(module); 72 | 73 | // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:判断序列化类型,根据类型指定为null时的值 74 | objectMapper.setSerializerFactory(objectMapper.getSerializerFactory().withSerializerModifier(beanSerializerModifierFactory)); 75 | 76 | return objectMapper; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | rest-api-starter 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ${log.colorPattern} 22 | 23 | 24 | 25 | 26 | 27 | 28 | ${LOG_PATH}/info/info.%d{yyyy-MM-dd}-%i.log 29 | ${LOG_MAX_FILE_SIZE} 30 | ${LOG_MAX_HISTORY} 31 | ${LOG_MAX_TOTAL_SIZE_CAP} 32 | 33 | 34 | ${log.pattern} 35 | 36 | 37 | INFO 38 | ACCEPT 39 | DENY 40 | 41 | 42 | 43 | 44 | 45 | ${LOG_PATH}/error/error.%d{yyyy-MM-dd}-%i.log 46 | ${LOG_MAX_FILE_SIZE} 47 | ${LOG_MAX_HISTORY} 48 | ${LOG_MAX_TOTAL_SIZE_CAP} 49 | 50 | 51 | ${log.pattern} 52 | 53 | 54 | ERROR 55 | ACCEPT 56 | DENY 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/RestFulTemp.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.core.io.FileSystemResource; 5 | import org.springframework.http.*; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.LinkedMultiValueMap; 8 | import org.springframework.util.MultiValueMap; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | import javax.annotation.Resource; 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.Map; 15 | 16 | /** 17 | * @author 公众号 程序员三时 18 | * @version 1.0 19 | * @date 2023/7/2 10:23 20 | * @webSite https://github.com/coder-amiao 21 | */ 22 | @Component 23 | public class RestFulTemp { 24 | 25 | @Resource 26 | private RestTemplate restTemplate; 27 | 28 | public ResponseEntity doGet(String url) { 29 | ResponseEntity response = this.restTemplate.getForEntity(url, String.class); 30 | return response; 31 | } 32 | 33 | public ResponseEntity doPost(String url, Object jsonData) { 34 | HttpHeaders headers = new HttpHeaders(); 35 | headers.setContentType(MediaType.APPLICATION_JSON); 36 | HttpEntity requestEntity = new HttpEntity<>(jsonData, headers); 37 | ResponseEntity response = this.restTemplate.postForEntity(url, requestEntity, String.class); 38 | return response; 39 | } 40 | 41 | 42 | public ResponseEntity doPostForm(String url, Map map) { 43 | HttpHeaders headers = new HttpHeaders(); 44 | headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); 45 | MultiValueMap params = new LinkedMultiValueMap<>(); 46 | for (Map.Entry entry : map.entrySet()) { 47 | params.add(entry.getKey(), entry.getValue().toString()); 48 | } 49 | HttpEntity> entity = new HttpEntity<>(params, headers); 50 | return restTemplate.postForEntity(url, entity, String.class); 51 | } 52 | 53 | public String sendFilePost(String url, File file) throws IOException { 54 | LinkedMultiValueMap map = new LinkedMultiValueMap<>(); 55 | FileSystemResource resource = new FileSystemResource(file); 56 | map.add("file", resource); 57 | HttpHeaders headers = new HttpHeaders(); 58 | headers.setContentType(MediaType.MULTIPART_FORM_DATA); 59 | HttpEntity> httpEntity = new HttpEntity<>(map, headers); 60 | 61 | return restTemplate.postForObject(url, httpEntity, String.class); 62 | } 63 | 64 | public String doPut(String url, Object jsonData) { 65 | HttpHeaders headers = new HttpHeaders(); 66 | headers.setContentType(MediaType.APPLICATION_JSON); 67 | HttpEntity requestEntity = new HttpEntity<>(jsonData, headers); 68 | ResponseEntity responseEntity = this.restTemplate.exchange(url, HttpMethod.PUT, requestEntity, String.class); 69 | return responseEntity.getBody(); 70 | } 71 | 72 | public ResponseEntity doDelete(String url) { 73 | ResponseEntity responseEntity = this.restTemplate.exchange(url, HttpMethod.DELETE, null, String.class); 74 | return responseEntity; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/validator/ValidatorUtil.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.validator; 2 | 3 | 4 | import org.dromara.hutool.core.date.DateUtil; 5 | import org.dromara.hutool.core.math.NumberUtil; 6 | import org.dromara.hutool.core.text.StrUtil; 7 | import org.dromara.hutool.core.text.placeholder.StrFormatter; 8 | 9 | import java.lang.reflect.Method; 10 | import java.math.BigDecimal; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * @author kenx 16 | * @version 1.0 17 | * @date 2021/1/21 20:51 18 | * 验证表达式 19 | */ 20 | public class ValidatorUtil { 21 | private static final Pattern mobile_pattern = Pattern.compile("1\\d{10}"); 22 | private static final Pattern money_pattern = Pattern.compile("^[0-9]+\\.?[0-9]{0,2}$"); 23 | 24 | /** 25 | * 验证手机号 26 | * 27 | * @param src 28 | * @return 29 | */ 30 | public static boolean isMobile(String src) { 31 | if (StrUtil.isBlank(src)) { 32 | return false; 33 | } 34 | Matcher m = mobile_pattern.matcher(src); 35 | return m.matches(); 36 | } 37 | 38 | 39 | /** 40 | * 验证枚举值是否合法 ,所有枚举需要继承此方法重写 41 | * 42 | * @param beanClass 枚举类 43 | * @param status 对应code 44 | * @return 45 | * @throws Exception 46 | */ 47 | public static boolean isEnum(Class beanClass, String status) throws Exception { 48 | if (StrUtil.isBlank(status)) { 49 | return false; 50 | } 51 | 52 | //转换枚举类 53 | Class clazz = (Class) beanClass; 54 | /** 55 | * 其实枚举是语法糖 56 | * 是封装好的多个Enum类的实列 57 | * 获取所有枚举实例 58 | */ 59 | Enum[] enumConstants = clazz.getEnumConstants(); 60 | 61 | //根据方法名获取方法 62 | Method getCode = clazz.getMethod("getCode"); 63 | Method getDesc = clazz.getMethod("getDesc"); 64 | for (Enum enums : enumConstants) { 65 | //得到枚举实例名 66 | String instance = enums.name(); 67 | //执行枚举方法获得枚举实例对应的值 68 | String code = getCode.invoke(enums).toString(); 69 | if (code.equals(status)) { 70 | return true; 71 | } 72 | String desc = getDesc.invoke(enums).toString(); 73 | System.out.println(StrFormatter.format("实列{}---code:{}desc{}", instance, code, desc)); 74 | } 75 | return false; 76 | } 77 | 78 | /** 79 | * 验证金额0.00 80 | * 81 | * @param money 82 | * @return 83 | */ 84 | public static boolean isMoney(BigDecimal money) { 85 | if (StrUtil.isEmptyIfStr(money)) { 86 | return false; 87 | } 88 | if (!NumberUtil.isNumber(String.valueOf(money.doubleValue()))) { 89 | return false; 90 | } 91 | if (money.doubleValue() == 0) { 92 | return false; 93 | } 94 | Matcher m = money_pattern.matcher(String.valueOf(money.doubleValue())); 95 | return m.matches(); 96 | } 97 | 98 | 99 | /** 100 | * 验证 日期 101 | * 102 | * @param date 103 | * @param dateFormat 104 | * @return 105 | */ 106 | public static boolean isDateTime(String date, String dateFormat) { 107 | if (StrUtil.isBlank(date)) { 108 | return false; 109 | } 110 | try { 111 | DateUtil.parse(date, dateFormat); 112 | return true; 113 | } catch (Exception e) { 114 | return false; 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/serializer/BeanSerializerModifierFactory.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.serializer; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import com.fasterxml.jackson.databind.BeanDescription; 5 | import com.fasterxml.jackson.databind.SerializationConfig; 6 | import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; 7 | import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; 8 | import org.dromara.hutool.core.text.StrUtil; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 12 | 13 | import java.math.BigDecimal; 14 | import java.util.Date; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | /** 19 | * @author 公众号 程序员三时 20 | * @version 1.0 21 | * @date 2023/7/22 16:12 22 | * @webSite https://github.com/coder-amiao 23 | */ 24 | @EnableConfigurationProperties(RestApiProperties.JsonSerializeProperties.class) //开启属性绑定 25 | public class BeanSerializerModifierFactory extends BeanSerializerModifier { 26 | 27 | @Autowired 28 | private RestApiProperties.JsonSerializeProperties jsonSerializeProperties; 29 | 30 | @Override 31 | public List changeProperties(SerializationConfig config, BeanDescription beanDesc, List beanProperties) { 32 | if (!jsonSerializeProperties.getNullAble().getHasNullAble()) return beanProperties; //不开启空序列化 33 | for (int i = 0; i < beanProperties.size(); i++) { 34 | BeanPropertyWriter writer = (BeanPropertyWriter) beanProperties.get(i); 35 | SerializableType type = new SerializableType(); 36 | RestApiProperties.NullAble nullAble = jsonSerializeProperties.getNullAble(); 37 | // 判断字段的类型,如果是array,list,set则注册nullSerializer 38 | if (isArrayType(writer)) { //给writer注册一个自己的nullSerializer 39 | type.setType(nullAble.getArrayType()); 40 | 41 | } else if (isIntegerType(writer)) { 42 | type.setType(nullAble.getNumberType()); 43 | 44 | } else if (isStringType(writer)) { 45 | type.setType(nullAble.getObjectType()); 46 | } else if (isDoubleType(writer)) { 47 | type.setType(nullAble.getDoubleType()); 48 | } 49 | if (StrUtil.isNotEmpty(type.getType()) && !type.getType().equals("original")) { 50 | NullAbleSerializer nullAbleSerializer = new NullAbleSerializer(); 51 | nullAbleSerializer.setSerializableType(type); 52 | writer.assignNullSerializer(nullAbleSerializer); 53 | } 54 | } 55 | return beanProperties; 56 | } 57 | 58 | protected boolean isArrayType(BeanPropertyWriter writer) { 59 | Class clazz = writer.getPropertyType(); 60 | return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class); 61 | } 62 | 63 | protected boolean isStringType(BeanPropertyWriter writer) { 64 | Class clazz = writer.getPropertyType(); 65 | return clazz.equals(String.class) || clazz.equals(Date.class); 66 | } 67 | 68 | protected boolean isIntegerType(BeanPropertyWriter writer) { 69 | Class clazz = writer.getPropertyType(); 70 | return clazz.equals(Integer.class) || clazz.equals(int.class) || clazz.equals(Long.class) || clazz.equals(long.class); 71 | } 72 | 73 | protected boolean isDoubleType(BeanPropertyWriter writer) { 74 | Class clazz = writer.getPropertyType(); 75 | return clazz.equals(Double.class) || clazz.equals(BigDecimal.class) || clazz.equals(Float.class); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/cache/SpringCacheConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.cache; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 10 | import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 12 | import org.springframework.cache.CacheManager; 13 | import org.springframework.cache.annotation.EnableCaching; 14 | import org.springframework.cache.interceptor.KeyGenerator; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.data.redis.cache.RedisCacheConfiguration; 18 | import org.springframework.data.redis.cache.RedisCacheManager; 19 | import org.springframework.data.redis.connection.RedisConnectionFactory; 20 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 21 | import org.springframework.data.redis.serializer.RedisSerializationContext; 22 | import org.springframework.data.redis.serializer.RedisSerializer; 23 | import org.springframework.data.redis.serializer.StringRedisSerializer; 24 | 25 | import java.lang.reflect.Method; 26 | import java.util.Arrays; 27 | 28 | /** 29 | * @author 公众号 程序员三时 30 | * @version 1.0 31 | * @date 2023/7/7 10:46 32 | * @webSite https://github.com/coder-amiao 33 | */ 34 | @Configuration 35 | @EnableCaching //开启缓存注解驱动,否则后面使用的缓存都是无效的 36 | @ConditionalOnClass(name = "org.springframework.data.redis.core.RedisOperations") 37 | public class SpringCacheConfig { 38 | 39 | 40 | @Autowired 41 | private RedisConnectionFactory redisConnectionFactory; 42 | 43 | /** 44 | * 自定义key生成策略 45 | * @return 46 | */ 47 | @Bean("keyGeneratorStrategy") 48 | public KeyGenerator keyGeneratorStrategy(){ 49 | return new KeyGenerator() { 50 | @Override 51 | public Object generate(Object target, Method method, Object... params) { 52 | return method.getName()+"["+ Arrays.asList(params).toString() +"]"; 53 | } 54 | }; 55 | } 56 | 57 | 58 | @Bean 59 | public SpringCacheUtil springCacheUtil(){ 60 | return new SpringCacheUtil(); 61 | } 62 | 63 | 64 | /** 65 | * 定义缓存redis json 序列化 66 | * @return 67 | */ 68 | @Bean 69 | public CacheManager cacheManager() { 70 | RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() 71 | // .entryTtl(Duration.ofSeconds(600)) // 设置缓存有效期一小时 72 | .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) 73 | .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer())) 74 | .disableCachingNullValues(); 75 | 76 | RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory) 77 | .cacheDefaults(config) 78 | .transactionAware() 79 | .build(); 80 | 81 | return redisCacheManager; 82 | } 83 | 84 | private RedisSerializer keySerializer() { 85 | return new StringRedisSerializer(); 86 | } 87 | 88 | private RedisSerializer valueSerializer() { 89 | // 使用 Jackson 序列化库进行 JSON 序列化 90 | ObjectMapper om = new ObjectMapper(); 91 | om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 92 | om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL); 93 | return new GenericJackson2JsonRedisSerializer(om); 94 | } 95 | 96 | 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | import cn.soboys.restapispringbootstarter.authorization.UserSign; 4 | import io.jsonwebtoken.*; 5 | 6 | import javax.crypto.spec.SecretKeySpec; 7 | import javax.xml.bind.DatatypeConverter; 8 | import java.security.Key; 9 | import java.util.Date; 10 | 11 | /** 12 | * @author 公众号 程序员三时 13 | * @version 1.0 14 | * @date 2023/7/13 20:09 15 | * @webSite https://github.com/coder-amiao 16 | */ 17 | public class JwtUtil { 18 | 19 | //创建jwt 20 | public static String createJWT(String subject, String issue, Object claim, 21 | long ttlMillis) { 22 | 23 | 24 | //过期时间 25 | Date now = new Date(); 26 | Date exp = new Date(now.getTime() + ttlMillis * 1000); 27 | 28 | 29 | String result = Jwts.builder() 30 | .setSubject(subject) //设置主题 31 | .setIssuer(issue) //发行者 32 | .setId(issue)//jwtID 33 | .setExpiration(exp)//设置过期日期 34 | .claim("user", claim)//主题,可以包含用户信息 35 | .signWith(getSignatureAlgorithm(), getAuthKey())//加密算法 36 | .compressWith(CompressionCodecs.DEFLATE).compact();//对载荷进行压缩 37 | return result; 38 | } 39 | 40 | 41 | public static String createJWT(String subject, String issue, Object claim, 42 | long ttlMillis, SignatureAlgorithm signatureAlgorithm, String key) { 43 | 44 | //过期时间 45 | Date now = new Date(); 46 | Date exp = new Date(now.getTime() + ttlMillis * 1000); 47 | 48 | String result = Jwts.builder() 49 | .setSubject(subject) //设置主题 50 | .setIssuer(issue) //发行者 51 | .setId(issue)//jwtID 52 | .setExpiration(exp) //设置过期日期 53 | .claim("user", claim)//主题,可以包含用户信息 54 | .signWith(signatureAlgorithm, key)//加密算法 55 | .compressWith(CompressionCodecs.DEFLATE).compact();//对载荷进行压缩 56 | 57 | return result; 58 | } 59 | 60 | 61 | // 解析jwt 62 | public static Jws parseJWT(String jwt) { 63 | Jws claims = Jwts.parser().setSigningKey(getSignedKey()) 64 | .parseClaimsJws(jwt); 65 | // try { 66 | // claims = Jwts.parser().setSigningKey(getSignedKey()) 67 | // .parseClaimsJws(jwt); 68 | // } catch (Exception ex) { 69 | // claims = null; 70 | // } 71 | return claims; 72 | } 73 | 74 | public static Jws parseJWT(String jwt, Key key) { 75 | Jws claims = Jwts.parser().setSigningKey(key) 76 | .parseClaimsJws(jwt); 77 | return claims; 78 | } 79 | 80 | 81 | //获取主题信息 82 | public static Claims getClaims(String jwt) { 83 | Claims claims = Jwts.parser().setSigningKey(getSignedKey()) 84 | .parseClaimsJws(jwt).getBody(); 85 | 86 | return claims; 87 | } 88 | 89 | 90 | public static Claims getClaims(String jwt, Key key) { 91 | Claims claims = Jwts.parser().setSigningKey(key) 92 | .parseClaimsJws(jwt).getBody(); 93 | return claims; 94 | } 95 | 96 | 97 | /** 98 | * 获取密钥 99 | * 100 | * @return Key 101 | */ 102 | private static Key getSignedKey() { 103 | byte[] apiKeySecretBytes = DatatypeConverter 104 | .parseBase64Binary(getAuthKey()); 105 | Key signingKey = new SecretKeySpec(apiKeySecretBytes, 106 | getSignatureAlgorithm().getJcaName()); 107 | return signingKey; 108 | } 109 | 110 | 111 | /** 112 | * 自定义秘钥 113 | * 114 | * @return 115 | */ 116 | public static String getAuthKey() { 117 | String auth = "2af57b969bac152d"; 118 | return auth; 119 | } 120 | 121 | /** 122 | * 自定义签名 123 | * 124 | * @return 125 | */ 126 | private static SignatureAlgorithm getSignatureAlgorithm() { 127 | return SignatureAlgorithm.HS256; 128 | } 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/aop/BaseAspectSupport.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.aop; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.reflect.MethodSignature; 5 | import org.springframework.core.LocalVariableTableParameterNameDiscoverer; 6 | import org.springframework.expression.ExpressionParser; 7 | import org.springframework.expression.spel.standard.SpelExpressionParser; 8 | import org.springframework.expression.spel.support.StandardEvaluationContext; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Parameter; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author 公众号 程序员三时 21 | * @version 1.0 22 | * @date 2023/7/2 11:28 23 | * @webSite https://github.com/coder-amiao 24 | */ 25 | public class BaseAspectSupport { 26 | public Method resolveMethod(ProceedingJoinPoint point) { 27 | MethodSignature signature = (MethodSignature) point.getSignature(); 28 | Class targetClass = point.getTarget().getClass(); 29 | 30 | Method method = getDeclaredMethod(targetClass, signature.getName(), 31 | signature.getMethod().getParameterTypes()); 32 | if (method == null) { 33 | throw new IllegalStateException("无法解析目标方法: " + signature.getMethod().getName()); 34 | } 35 | return method; 36 | } 37 | 38 | /** 39 | * 根据方法和传入的参数获取请求参数 40 | */ 41 | public Object getParameter(Method method, Object[] args) { 42 | List argList = new ArrayList<>(); 43 | Parameter[] parameters = method.getParameters(); 44 | Map map = new HashMap<>(); 45 | for (int i = 0; i < parameters.length; i++) { 46 | //将RequestBody注解修饰的参数作为请求参数 47 | RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); 48 | //将RequestParam注解修饰的参数作为请求参数 49 | RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); 50 | String key = parameters[i].getName(); 51 | if (requestBody != null) { 52 | argList.add(args[i]); 53 | } else if (requestParam != null) { 54 | map.put(key, args[i]); 55 | } else { 56 | map.put(key, args[i]); 57 | } 58 | } 59 | if (map.size() > 0) { 60 | argList.add(map); 61 | } 62 | if (argList.size() == 0) { 63 | return null; 64 | } else if (argList.size() == 1) { 65 | return argList.get(0); 66 | } else { 67 | return argList; 68 | } 69 | } 70 | 71 | public Method getDeclaredMethod(Class clazz, String name, Class... parameterTypes) { 72 | try { 73 | return clazz.getDeclaredMethod(name, parameterTypes); 74 | } catch (NoSuchMethodException e) { 75 | Class superClass = clazz.getSuperclass(); 76 | if (superClass != null) { 77 | return getDeclaredMethod(superClass, name, parameterTypes); 78 | } 79 | } 80 | return null; 81 | } 82 | 83 | 84 | /** 85 | * 使用SPEL进行key的解析 86 | * 87 | * @param expressionString 表达式字符串 88 | * @param method 方法对象,用于获取参数名 89 | * @param args 方法的参数值 90 | * @return 91 | */ 92 | public String parseExpression(String expressionString, Method method, Object[] args) { 93 | //获取被拦截方法参数名列表 94 | LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); 95 | String[] paramNameArr = discoverer.getParameterNames(method); 96 | //SPEL解析 97 | ExpressionParser parser = new SpelExpressionParser(); 98 | StandardEvaluationContext context = new StandardEvaluationContext(); 99 | for (int i = 0; i < paramNameArr.length; i++) { 100 | context.setVariable(paramNameArr[i], args[i]); 101 | } 102 | String result = parser.parseExpression(expressionString).getValue(context, String.class); 103 | return result; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/aop/LimitAspect.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.aop; 2 | 3 | import cn.soboys.restapispringbootstarter.annotation.Limit; 4 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 5 | import cn.soboys.restapispringbootstarter.enums.LimitType; 6 | import cn.soboys.restapispringbootstarter.exception.LimitAccessException; 7 | import cn.soboys.restapispringbootstarter.utils.HttpUserAgent; 8 | import cn.soboys.restapispringbootstarter.utils.Strings; 9 | import com.google.common.collect.ImmutableList; 10 | import lombok.RequiredArgsConstructor; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.aspectj.lang.ProceedingJoinPoint; 14 | import org.aspectj.lang.annotation.Around; 15 | import org.aspectj.lang.annotation.Aspect; 16 | import org.aspectj.lang.annotation.Pointcut; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.context.annotation.Import; 21 | import org.springframework.data.redis.core.RedisTemplate; 22 | import org.springframework.data.redis.core.script.DefaultRedisScript; 23 | import org.springframework.data.redis.core.script.RedisScript; 24 | import org.springframework.stereotype.Component; 25 | 26 | import javax.annotation.Resource; 27 | import java.lang.reflect.Method; 28 | 29 | /** 30 | * @author 公众号 程序员三时 31 | * @version 1.0 32 | * @date 2023/7/2 11:28 33 | * @webSite https://github.com/coder-amiao 34 | */ 35 | @Slf4j 36 | @Aspect 37 | @Component 38 | public class LimitAspect extends BaseAspectSupport{ 39 | @Resource 40 | private RedisTemplate redisTemplate; 41 | 42 | @Autowired 43 | private RestApiProperties.RedisProperties redisProperties; 44 | 45 | public LimitAspect() { 46 | } 47 | 48 | @Pointcut("@annotation(cn.soboys.restapispringbootstarter.annotation.Limit)") 49 | public void pointcut() { 50 | } 51 | 52 | @Around("pointcut()") 53 | public Object around(ProceedingJoinPoint point) throws Throwable { 54 | Method method = resolveMethod(point); 55 | Limit limitAnnotation = method.getAnnotation(Limit.class); 56 | LimitType limitType = limitAnnotation.limitType(); 57 | String name = limitAnnotation.name(); 58 | String key; 59 | String ip = HttpUserAgent.getIpAddr(); 60 | int limitPeriod = limitAnnotation.period(); 61 | int limitCount = limitAnnotation.count(); 62 | switch (limitType) { 63 | case IP: 64 | key = ip; 65 | break; 66 | case CUSTOMER: 67 | key = limitAnnotation.key(); 68 | break; 69 | default: 70 | key = StringUtils.upperCase(method.getName()); 71 | } 72 | if(redisProperties!=null&&StringUtils.isNotBlank(redisProperties.getKeyPrefix())){ 73 | key=redisProperties.getKeyPrefix()+Strings.COLON+key; 74 | } 75 | ImmutableList keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix() + Strings.UNDER_LINE, key, ip)); 76 | String luaScript = buildLuaScript(); 77 | RedisScript redisScript = new DefaultRedisScript<>(luaScript, Long.class); 78 | Long count = redisTemplate.execute(redisScript, keys, limitCount, limitPeriod); 79 | if (count != null && count.intValue() <= limitCount) { 80 | log.info("IP:{} 第 {} 次访问key为 {},描述为 [{}] 的接口", ip, count, keys, name); 81 | return point.proceed(); 82 | } else { 83 | log.error("key为 {},描述为 [{}] 的接口访问超出频率限制", keys, name); 84 | throw new LimitAccessException("访问频率过快请稍后再试"); 85 | } 86 | } 87 | 88 | /** 89 | * 限流脚本 90 | * 调用的时候不超过阈值,则直接返回并执行计算器自加。 91 | * 92 | * @return lua脚本 93 | */ 94 | private String buildLuaScript() { 95 | return "local c" + 96 | "\nc = redis.call('get',KEYS[1])" + 97 | "\nif c and tonumber(c) > tonumber(ARGV[1]) then" + 98 | "\nreturn c;" + 99 | "\nend" + 100 | "\nc = redis.call('incr',KEYS[1])" + 101 | "\nif tonumber(c) == 1 then" + 102 | "\nredis.call('expire',KEYS[1],ARGV[2])" + 103 | "\nend" + 104 | "\nreturn c;"; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/cache/CacheAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.cache; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.dromara.hutool.core.text.StrUtil; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 10 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.core.RedisOperations; 14 | import org.springframework.data.redis.core.RedisTemplate; 15 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 16 | import org.springframework.data.redis.serializer.RedisSerializer; 17 | import org.springframework.data.redis.serializer.SerializationException; 18 | import org.springframework.data.redis.serializer.StringRedisSerializer; 19 | 20 | /** 21 | * @author 公众号 程序员三时 22 | * @version 1.0 23 | * @date 2023/7/10 00:50 24 | * @webSite https://github.com/coder-amiao 25 | */ 26 | @EnableConfigurationProperties(value = {RestApiProperties.RedisProperties.class}) 27 | @ConditionalOnClass(name = "org.springframework.data.redis.core.RedisOperations") 28 | public class CacheAutoConfiguration { 29 | 30 | @Autowired 31 | private RestApiProperties.RedisProperties redisProperties; 32 | 33 | @Autowired 34 | private RedisConnectionFactory redisConnectionFactory; 35 | 36 | 37 | 38 | @Bean 39 | public RedisTempUtil redisTempUtil(){ 40 | return new RedisTempUtil(); 41 | } 42 | 43 | /** 44 | * Jackson序列化 json 45 | * 46 | * @param factory 47 | * @return 48 | */ 49 | @Bean 50 | public RedisTemplate redisTemplate( RedisConnectionFactory factory) { 51 | RedisTemplate template = new RedisTemplate<>(); 52 | template.setConnectionFactory(factory); 53 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); 54 | //序列化包括类型描述 否则反向序列化实体会报错,一律都为JsonObject 55 | ObjectMapper mapper = new ObjectMapper(); 56 | mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 57 | mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); 58 | // 使用 Jackson2JsonRedisSerializer 作为 value 的序列化器 59 | jackson2JsonRedisSerializer.setObjectMapper(mapper); 60 | /// 使用 StringRedisSerializer 作为 key 的序列化器 61 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 62 | // key采用 String的序列化方式 如果有前缀的时候。加入全局前缀 63 | if(redisProperties!=null&& StrUtil.isNotEmpty(redisProperties.getKeyPrefix())){ 64 | template.setKeySerializer(new CacheAutoConfiguration.PrefixStringRedisSerializer(redisProperties.getKeyPrefix()+":", stringRedisSerializer)); 65 | template.setHashKeySerializer(new CacheAutoConfiguration.PrefixStringRedisSerializer(redisProperties.getKeyPrefix()+":", stringRedisSerializer)); 66 | }else { 67 | template.setKeySerializer(stringRedisSerializer); 68 | // hash的 key也采用 String的序列化方式 69 | template.setHashKeySerializer(stringRedisSerializer); 70 | } 71 | 72 | // value序列化方式采用 jackson 73 | template.setValueSerializer(jackson2JsonRedisSerializer); 74 | // hash的 value序列化方式采用 jackson 75 | template.setHashValueSerializer(jackson2JsonRedisSerializer); 76 | template.afterPropertiesSet(); 77 | 78 | return template; 79 | } 80 | 81 | 82 | 83 | 84 | public class PrefixStringRedisSerializer implements RedisSerializer { 85 | private final String prefix; 86 | private final StringRedisSerializer serializer; 87 | 88 | public PrefixStringRedisSerializer(String prefix, StringRedisSerializer serializer) { 89 | this.prefix = prefix; 90 | this.serializer = serializer; 91 | } 92 | 93 | @Override 94 | public byte[] serialize(String s) throws SerializationException { 95 | return serializer.serialize(prefix + s); 96 | } 97 | 98 | @Override 99 | public String deserialize(byte[] bytes) throws SerializationException { 100 | String key = serializer.deserialize(bytes); 101 | if (key != null && key.startsWith(prefix)) { 102 | return key.substring(prefix.length()); 103 | } 104 | return key; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/cache/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.cache; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.dromara.hutool.core.text.StrUtil; 8 | import org.dromara.hutool.extra.spring.SpringUtil; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 11 | import org.springframework.boot.context.properties.ConfigurationProperties; 12 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 13 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Primary; 17 | import org.springframework.data.redis.connection.RedisConnectionFactory; 18 | import org.springframework.data.redis.core.RedisOperations; 19 | import org.springframework.data.redis.core.RedisTemplate; 20 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 21 | import org.springframework.data.redis.serializer.RedisSerializer; 22 | import org.springframework.data.redis.serializer.SerializationException; 23 | import org.springframework.data.redis.serializer.StringRedisSerializer; 24 | 25 | import javax.annotation.Resource; 26 | import java.time.Duration; 27 | 28 | /** 29 | * @author 公众号 程序员三时 30 | * @version 1.0 31 | * @date 2023/7/7 09:51 32 | * @webSite https://github.com/coder-amiao 33 | */ 34 | @Configuration 35 | public class RedisConfig { 36 | 37 | 38 | @Autowired 39 | private RestApiProperties.RedisProperties redisProperties; 40 | 41 | /** 42 | * Jackson序列化 json 43 | * 44 | * @param factory 45 | * @return 46 | */ 47 | @Bean 48 | @ConditionalOnClass(RedisOperations.class) 49 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 50 | RedisTemplate template = new RedisTemplate<>(); 51 | template.setConnectionFactory(factory); 52 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); 53 | //序列化包括类型描述 否则反向序列化实体会报错,一律都为JsonObject 54 | ObjectMapper mapper = new ObjectMapper(); 55 | mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 56 | mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); 57 | // 使用 Jackson2JsonRedisSerializer 作为 value 的序列化器 58 | jackson2JsonRedisSerializer.setObjectMapper(mapper); 59 | /// 使用 StringRedisSerializer 作为 key 的序列化器 60 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 61 | // key采用 String的序列化方式 如果有前缀的时候。加入全局前缀 62 | if(redisProperties!=null&& StrUtil.isNotEmpty(redisProperties.getKeyPrefix())){ 63 | template.setKeySerializer(new PrefixStringRedisSerializer(redisProperties.getKeyPrefix()+":", stringRedisSerializer)); 64 | template.setHashKeySerializer(new PrefixStringRedisSerializer(redisProperties.getKeyPrefix()+":", stringRedisSerializer)); 65 | }else { 66 | template.setKeySerializer(stringRedisSerializer); 67 | // hash的 key也采用 String的序列化方式 68 | template.setHashKeySerializer(stringRedisSerializer); 69 | } 70 | 71 | // value序列化方式采用 jackson 72 | template.setValueSerializer(jackson2JsonRedisSerializer); 73 | // hash的 value序列化方式采用 jackson 74 | template.setHashValueSerializer(jackson2JsonRedisSerializer); 75 | template.afterPropertiesSet(); 76 | 77 | return template; 78 | } 79 | 80 | public class PrefixStringRedisSerializer implements RedisSerializer { 81 | private final String prefix; 82 | private final StringRedisSerializer serializer; 83 | 84 | public PrefixStringRedisSerializer(String prefix, StringRedisSerializer serializer) { 85 | this.prefix = prefix; 86 | this.serializer = serializer; 87 | } 88 | 89 | @Override 90 | public byte[] serialize(String s) throws SerializationException { 91 | return serializer.serialize(prefix + s); 92 | } 93 | 94 | @Override 95 | public String deserialize(byte[] bytes) throws SerializationException { 96 | String key = serializer.deserialize(bytes); 97 | if (key != null && key.startsWith(prefix)) { 98 | return key.substring(prefix.length()); 99 | } 100 | return key; 101 | } 102 | } 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/config/BeanAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.config; 2 | 3 | 4 | import cn.soboys.restapispringbootstarter.ApplicationRunner; 5 | import cn.soboys.restapispringbootstarter.ExceptionHandler; 6 | import cn.soboys.restapispringbootstarter.ResultHandler; 7 | import cn.soboys.restapispringbootstarter.aop.LimitAspect; 8 | import cn.soboys.restapispringbootstarter.aop.LogAspect; 9 | 10 | import cn.soboys.restapispringbootstarter.cache.SpringCacheUtil; 11 | import cn.soboys.restapispringbootstarter.i18n.I18NMessage; 12 | 13 | 14 | import cn.soboys.restapispringbootstarter.utils.RestFulTemp; 15 | 16 | 17 | import io.swagger.v3.oas.models.OpenAPI; 18 | import io.swagger.v3.oas.models.info.Contact; 19 | import io.swagger.v3.oas.models.info.Info; 20 | import org.dromara.hutool.extra.spring.EnableSpringUtil; 21 | import org.hibernate.validator.HibernateValidator; 22 | import org.springframework.cache.Cache; 23 | import org.springframework.context.annotation.Bean; 24 | 25 | import org.springframework.http.client.ClientHttpRequestFactory; 26 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 27 | import org.springframework.http.converter.HttpMessageConverter; 28 | import org.springframework.http.converter.StringHttpMessageConverter; 29 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 30 | import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; 31 | import org.springframework.web.client.RestTemplate; 32 | 33 | import javax.validation.Validation; 34 | import javax.validation.Validator; 35 | import javax.validation.ValidatorFactory; 36 | 37 | import java.nio.charset.Charset; 38 | import java.util.List; 39 | 40 | 41 | /** 42 | * @author 公众号 程序员三时 43 | * @version 1.0 44 | * @date 2023/6/27 11:36 45 | * @webSite https://github.com/coder-amiao 46 | */ 47 | //@Configuration 48 | //@ConditionalOnProperty(name = "rest-api.enabled", havingValue = "true") 49 | @EnableSpringUtil 50 | public class BeanAutoConfiguration { 51 | 52 | 53 | @Bean 54 | public I18NMessage i18NMessage() { 55 | return new I18NMessage(); 56 | } 57 | 58 | @Bean 59 | public ResultHandler resultHandler() { 60 | return new ResultHandler(); 61 | } 62 | 63 | @Bean 64 | public ExceptionHandler exceptionHandler() { 65 | return new ExceptionHandler(); 66 | } 67 | 68 | @Bean 69 | public StartupApplicationListener startupApplicationListener() { 70 | return new StartupApplicationListener(); 71 | } 72 | 73 | 74 | @Bean 75 | public RestApiProperties restApiProperties() { 76 | return new RestApiProperties(); 77 | } 78 | 79 | @Bean 80 | public RestApiProperties.LoggingProperties loggingProperties(RestApiProperties restApiProperties) { 81 | return restApiProperties.new LoggingProperties(); 82 | } 83 | 84 | @Bean 85 | public RestApiProperties.Ip2regionProperties ip2regionProperties(RestApiProperties restApiProperties) { 86 | return restApiProperties.new Ip2regionProperties(); 87 | } 88 | 89 | @Bean 90 | public RestApiProperties.JwtProperties jwtProperties(RestApiProperties restApiProperties) { 91 | return restApiProperties.new JwtProperties(); 92 | } 93 | 94 | @Bean 95 | public ApplicationRunner applicationRunner() { 96 | return new ApplicationRunner(); 97 | } 98 | 99 | @Bean 100 | public LogAspect logAspect() { 101 | return new LogAspect(); 102 | } 103 | 104 | @Bean 105 | public LimitAspect limitAspect() { 106 | return new LimitAspect(); 107 | } 108 | 109 | 110 | /** 111 | * 参数校验快速失败返回 提升性能 112 | */ 113 | @Bean 114 | public Validator validator() { 115 | ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) 116 | .configure() 117 | // 快速失败模式 118 | .failFast(true) 119 | .buildValidatorFactory(); 120 | return validatorFactory.getValidator(); 121 | } 122 | 123 | public class RestTemplateConfig { 124 | 125 | /** 126 | * 第三方请求要求的默认编码 127 | */ 128 | private final Charset thirdRequest = Charset.forName("utf-8"); 129 | 130 | @Bean 131 | public RestTemplate restTemplate(ClientHttpRequestFactory factory) { 132 | RestTemplate restTemplate = new RestTemplate(factory); 133 | // 处理请求中文乱码问题 134 | List> messageConverters = restTemplate.getMessageConverters(); 135 | for (HttpMessageConverter messageConverter : messageConverters) { 136 | if (messageConverter instanceof StringHttpMessageConverter) { 137 | ((StringHttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest); 138 | } 139 | if (messageConverter instanceof MappingJackson2HttpMessageConverter) { 140 | ((MappingJackson2HttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest); 141 | } 142 | if (messageConverter instanceof AllEncompassingFormHttpMessageConverter) { 143 | ((AllEncompassingFormHttpMessageConverter) messageConverter).setCharset(thirdRequest); 144 | } 145 | } 146 | return restTemplate; 147 | } 148 | 149 | @Bean 150 | public ClientHttpRequestFactory simpleClientHttpRequestFactory() { 151 | SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); 152 | factory.setConnectTimeout(15000); 153 | factory.setReadTimeout(5000); 154 | return factory; 155 | } 156 | 157 | 158 | @Bean 159 | public RestFulTemp restFulTemp() { 160 | return new RestFulTemp(); 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/MyBatisPlusGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | import java.util.ArrayList; 4 | 5 | 6 | 7 | import cn.soboys.restapispringbootstarter.config.GenerateCodeConfig; 8 | import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; 9 | import com.baomidou.mybatisplus.core.toolkit.StringPool; 10 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 11 | import com.baomidou.mybatisplus.generator.AutoGenerator; 12 | import com.baomidou.mybatisplus.generator.InjectionConfig; 13 | import com.baomidou.mybatisplus.generator.config.*; 14 | import com.baomidou.mybatisplus.generator.config.po.TableInfo; 15 | import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; 16 | import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; 17 | import org.dromara.hutool.core.text.StrUtil; 18 | 19 | import javax.annotation.Resource; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Scanner; 23 | import java.util.Scanner; 24 | 25 | /** 26 | * @author 公众号 程序员三时 27 | * @version 1.0 28 | * @date 2023/6/27 23:52 29 | * @webSite https://github.com/coder-amiao 30 | */ 31 | 32 | public class MyBatisPlusGenerator { 33 | 34 | 35 | /** 36 | *

37 | * 读取控制台内容 38 | *

39 | */ 40 | public static String scanner(String tip) { 41 | Scanner scanner = new Scanner(System.in); 42 | StringBuilder help = new StringBuilder(); 43 | help.append("请输入" + tip + ":"); 44 | System.out.println(help.toString()); 45 | if (scanner.hasNext()) { 46 | String ipt = scanner.next(); 47 | if (StringUtils.isNotBlank(ipt)) { 48 | return ipt; 49 | } 50 | } 51 | throw new MybatisPlusException("请输入正确的" + tip + "!"); 52 | } 53 | 54 | /** 55 | * 代码生成配置 56 | * 57 | * @param config 58 | */ 59 | public static void generate(GenerateCodeConfig config) { 60 | // 代码生成器 61 | AutoGenerator mpg = new AutoGenerator(); 62 | 63 | // 全局配置 64 | GlobalConfig gc = new GlobalConfig(); 65 | final String[] projectPath = new String[1]; 66 | projectPath[0] = System.getProperty("user.dir"); 67 | //String projectPath = System.getProperty("user.dir"); 68 | 69 | if (StrUtil.isNotEmpty(config.getProjectPath())) { 70 | projectPath[0] = config.getProjectPath(); 71 | } 72 | gc.setOutputDir(projectPath[0] + "/src/main/java"); 73 | gc.setAuthor("公众号 程序员三时"); 74 | gc.setOpen(false); 75 | // gc.setSwagger2(true); 实体属性 Swagger2 注解 76 | mpg.setGlobalConfig(gc); 77 | 78 | // 数据源配置 79 | DataSourceConfig dsc = new DataSourceConfig(); 80 | dsc.setUrl(config.getUrl()); 81 | // dsc.setSchemaName("public"); 82 | dsc.setDriverName(config.getDriverName()); 83 | dsc.setUsername(config.getUsername()); 84 | dsc.setPassword(config.getPassword()); 85 | mpg.setDataSource(dsc); 86 | 87 | // 包配置 88 | PackageConfig pc = new PackageConfig(); 89 | //pc.setModuleName(scanner("模块名")); 90 | pc.setParent(config.getPackages()); 91 | mpg.setPackageInfo(pc); 92 | 93 | // 自定义配置 94 | InjectionConfig cfg = new InjectionConfig() { 95 | @Override 96 | public void initMap() { 97 | // to do nothing 98 | } 99 | }; 100 | 101 | // 如果模板引擎是 freemarker 102 | String templatePath = "/templates/mapper.xml.ftl"; 103 | // 如果模板引擎是 velocity 104 | // String templatePath = "/templates/mapper.xml.vm"; 105 | 106 | // 自定义输出配置 107 | List focList = new ArrayList<>(); 108 | // 自定义配置会被优先输出 109 | focList.add(new FileOutConfig(templatePath) { 110 | @Override 111 | public String outputFile(TableInfo tableInfo) { 112 | // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! 113 | return projectPath[0] + "/src/main/resources/mapper/" + pc.getModuleName() 114 | + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; 115 | } 116 | }); 117 | /* 118 | cfg.setFileCreate(new IFileCreate() { 119 | @Override 120 | public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { 121 | // 判断自定义文件夹是否需要创建 122 | checkDir("调用默认方法创建的目录,自定义目录用"); 123 | if (fileType == FileType.MAPPER) { 124 | // 已经生成 mapper 文件判断存在,不想重新生成返回 false 125 | return !new File(filePath).exists(); 126 | } 127 | // 允许生成模板文件 128 | return true; 129 | } 130 | }); 131 | */ 132 | cfg.setFileOutConfigList(focList); 133 | mpg.setCfg(cfg); 134 | 135 | // 配置模板 136 | TemplateConfig templateConfig = new TemplateConfig(); 137 | 138 | // 配置自定义输出模板 139 | //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 140 | // templateConfig.setEntity("templates/entity2.java"); 141 | // templateConfig.setService(); 142 | // templateConfig.setController(); 143 | 144 | templateConfig.setXml(null); 145 | mpg.setTemplate(templateConfig); 146 | 147 | // 策略配置 148 | StrategyConfig strategy = new StrategyConfig(); 149 | strategy.setNaming(NamingStrategy.underline_to_camel); 150 | strategy.setColumnNaming(NamingStrategy.underline_to_camel); 151 | //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!"); 152 | strategy.setEntityLombokModel(true); 153 | strategy.setRestControllerStyle(true); 154 | // 公共父类 155 | //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!"); 156 | // 写于父类中的公共字段 157 | strategy.setTablePrefix("tz"); 158 | strategy.setSuperEntityColumns("id"); 159 | strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); 160 | strategy.setControllerMappingHyphenStyle(true); 161 | strategy.setTablePrefix(pc.getModuleName() + "_"); 162 | mpg.setStrategy(strategy); 163 | mpg.setTemplateEngine(new FreemarkerTemplateEngine()); 164 | mpg.execute(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/Result.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter; 2 | 3 | 4 | import cn.soboys.restapispringbootstarter.domain.BaseObj; 5 | import cn.soboys.restapispringbootstarter.i18n.DefaultMessage; 6 | import cn.soboys.restapispringbootstarter.i18n.I18NMessage; 7 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 8 | import lombok.Data; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.dromara.hutool.core.collection.CollUtil; 11 | import org.dromara.hutool.core.data.id.IdUtil; 12 | import org.dromara.hutool.core.date.DateUtil; 13 | import org.dromara.hutool.core.text.StrUtil; 14 | import org.dromara.hutool.extra.spring.SpringUtil; 15 | import org.springframework.web.context.request.RequestContextHolder; 16 | import org.springframework.web.context.request.ServletRequestAttributes; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.io.Serializable; 20 | import java.util.Collection; 21 | import java.util.Map; 22 | import java.util.stream.Collectors; 23 | import java.util.stream.Stream; 24 | 25 | 26 | /** 27 | * @author 公众号 程序员三时 28 | * @version 1.0 29 | * @date 2023/6/26 09:08 30 | * @webSite https://github.com/coder-amiao 31 | */ 32 | @Data 33 | @Slf4j 34 | //设置属性返回顺序 35 | @JsonPropertyOrder({"success", "code","msg","requestId","timestamp","data"}) 36 | public class Result extends BaseObj{ 37 | 38 | public static final String SUCCESS_CODE = "OK"; 39 | public static final String ERROR_CODE = "FAIL"; 40 | public static final String MSG = "操作成功"; 41 | public static final String ERROR_MSG = "操作失败"; 42 | private static final String TIMESTAMP = DateUtil.formatNow(); 43 | 44 | private static final String I18N_HEADER = "Lang"; 45 | 46 | 47 | 48 | 49 | private static final I18NMessage i18NMessage = SpringUtil.getBean(I18NMessage.class); 50 | private static final DefaultMessage defaultMessage = SpringUtil.getBean(DefaultMessage.class); 51 | 52 | 53 | private Boolean success; 54 | 55 | private String code; 56 | 57 | private String msg; 58 | 59 | private String requestId= IdUtil.nanoId(20); 60 | 61 | private String timestamp = TIMESTAMP; 62 | 63 | 64 | private T data; 65 | 66 | public Result() { 67 | } 68 | 69 | public Result(Boolean success, String code, String msg, T data) { 70 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 71 | HttpServletRequest request = attributes.getRequest(); 72 | 73 | if (CollUtil.isEmpty(i18NMessage.getMessage())) { 74 | i18NMessage.setMessage(defaultMessage.getMessage()); 75 | } else { 76 | /** 77 | * 系统默认的国际化。加上用户自定义,如果用户覆盖使用用户的 78 | */ 79 | // 使用 Stream API 合并两个 Map 80 | Map> result = Stream.of(i18NMessage.getMessage(), defaultMessage.getMessage()) 81 | .flatMap(map -> map.entrySet().stream()) 82 | .collect(Collectors.toMap( 83 | Map.Entry::getKey, 84 | Map.Entry::getValue, 85 | (v1, v2) -> v1 // 如果有重复的键,选择第一个 Map 中的值 86 | )); 87 | i18NMessage.setMessage(result); 88 | } 89 | this.success = success; 90 | this.code = code; 91 | this.msg = StrUtil.format(i18NMessage.message(msg, request.getHeader(i18NMessage.getI18nHeader())), request.getAttribute("argument_error") == null ? "" : request.getAttribute("argument_error")); 92 | this.data = data; 93 | } 94 | 95 | 96 | /** 97 | * 构建返回结果 98 | * 99 | * @param success 100 | * @param resultCode 101 | * @param result 102 | * @param 103 | * @return 104 | */ 105 | public static Result build(Boolean success, ResultCode resultCode, T result) { 106 | return new Result(success, resultCode.getCode(), resultCode.getMessage(), result); 107 | } 108 | 109 | /** 110 | * 构建返回结果 自定义响应码信息 111 | * 112 | * @param success 113 | * @param msg 114 | * @param code 115 | * @param result 116 | * @return 117 | */ 118 | @SuppressWarnings({"unchecked", "rawtypes"}) 119 | public static Result build(Boolean success, String code, String msg, T result) { 120 | return new Result(success, code, msg, result); 121 | } 122 | 123 | 124 | /** 125 | * 构建成功结果 126 | * 127 | * @param msg 128 | * @param result 129 | * @param 130 | * @return 131 | */ 132 | public static Result buildSuccess(String msg, T result) { 133 | return build(Boolean.TRUE, SUCCESS_CODE, msg, result); 134 | } 135 | 136 | 137 | /** 138 | * 构建成功结果 139 | * 140 | * @param result 141 | * @return 142 | */ 143 | public static Result buildSuccess(T result) { 144 | return build(Boolean.TRUE, SUCCESS_CODE, MSG, result); 145 | } 146 | 147 | /** 148 | * 构建成功结果 149 | * 150 | * @return 151 | */ 152 | public static Result buildSuccess() { 153 | return build(Boolean.TRUE, SUCCESS_CODE, MSG, null); 154 | } 155 | 156 | 157 | /** 158 | * 构建失败结果 159 | * 160 | * @param msg 161 | * @param code 162 | * @param result 163 | * @return 164 | */ 165 | public static Result buildFailure(String code, String msg, T result) { 166 | return build(Boolean.FALSE, code, msg, result); 167 | } 168 | 169 | 170 | /** 171 | * 构建失败结果 172 | * 173 | * @param msg 174 | * @param code 175 | * @return 176 | */ 177 | public static Result buildFailure(String code, String msg) { 178 | return build(Boolean.FALSE, code, msg, null); 179 | } 180 | 181 | /** 182 | * 构建失败结果 183 | * 184 | * @param resultCode 185 | * @param result 186 | * @param 187 | * @return 188 | */ 189 | public static Result buildFailure(ResultCode resultCode, T result) { 190 | return build(Boolean.FALSE, resultCode, result); 191 | } 192 | 193 | /** 194 | * 构建失败结果 195 | * 196 | * @param resultCode 197 | * @param 198 | * @return 199 | */ 200 | public static Result buildFailure(ResultCode resultCode) { 201 | return build(Boolean.FALSE, resultCode, null); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/aop/LogAspect.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.aop; 2 | 3 | 4 | import cn.soboys.restapispringbootstarter.Result; 5 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 6 | import cn.soboys.restapispringbootstarter.enums.LogTypeEnum; 7 | import cn.soboys.restapispringbootstarter.log.Log; 8 | import cn.soboys.restapispringbootstarter.log.LogDataSource; 9 | import cn.soboys.restapispringbootstarter.log.LogEntry; 10 | import cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource; 11 | import cn.soboys.restapispringbootstarter.utils.HttpUserAgent; 12 | import cn.soboys.restapispringbootstarter.utils.RequestUtil; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.aspectj.lang.JoinPoint; 15 | import org.aspectj.lang.ProceedingJoinPoint; 16 | import org.aspectj.lang.annotation.AfterThrowing; 17 | import org.aspectj.lang.annotation.Around; 18 | import org.aspectj.lang.annotation.Aspect; 19 | import org.aspectj.lang.annotation.Pointcut; 20 | import org.aspectj.lang.reflect.MethodSignature; 21 | import org.dromara.hutool.core.exception.ExceptionUtil; 22 | import org.dromara.hutool.core.text.StrUtil; 23 | import org.dromara.hutool.json.JSON; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.scheduling.annotation.EnableAsync; 26 | import org.springframework.stereotype.Component; 27 | import org.springframework.web.context.request.RequestContextHolder; 28 | import org.springframework.web.context.request.ServletRequestAttributes; 29 | 30 | 31 | import javax.servlet.http.HttpServletRequest; 32 | import java.lang.reflect.Method; 33 | 34 | /** 35 | * @author 公众号 程序员三时 36 | * @version 1.0 37 | * @date 2023/7/9 00:59 38 | * @webSite https://github.com/coder-amiao 39 | */ 40 | @Component 41 | @Aspect 42 | @Slf4j 43 | @EnableAsync 44 | public class LogAspect extends BaseAspectSupport { 45 | 46 | @Autowired 47 | private RestApiProperties.LoggingProperties loggingProperties; 48 | 49 | /** 50 | * 配置切入点 51 | * 该方法无方法体,主要为了让同类中其他方法使用此切入点 52 | */ 53 | @Pointcut("@annotation(cn.soboys.restapispringbootstarter.log.Log)") 54 | public void logPointcut() { 55 | } 56 | 57 | 58 | private long currentTime = 0L; 59 | 60 | /** 61 | * 配置环绕通知,使用在方法logPointcut()上注册的切入点 62 | * 63 | * @param joinPoint join point for advice 64 | */ 65 | @Around("logPointcut()") 66 | public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { 67 | currentTime = System.currentTimeMillis(); 68 | Object result = joinPoint.proceed(); 69 | LogEntry logBean = analyResult(result); 70 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 71 | HttpServletRequest request = attributes.getRequest(); 72 | if (result != null) { 73 | logBean.setPath(request.getRequestURI()); 74 | } 75 | saveLog(joinPoint, logBean); 76 | return result; 77 | } 78 | 79 | /** 80 | * 配置异常通知 81 | * 82 | * @param joinPoint join point for advice 83 | * @param e exception 84 | */ 85 | @AfterThrowing(pointcut = "logPointcut()", throwing = "e") 86 | public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { 87 | 88 | LogEntry logBean = new LogEntry(LogTypeEnum.ERROR.name(), System.currentTimeMillis() - currentTime); 89 | logBean.setExceptionDetail(ExceptionUtil.stacktraceToString(e)); 90 | 91 | } 92 | 93 | 94 | /** 95 | * 解析正常返回结果 96 | * 如果状态不为true或者状态码不为200算为失败 97 | * 98 | * @param result 99 | * @return 100 | */ 101 | private LogEntry analyResult(Object result) { 102 | //判断返回结果 103 | LogEntry logBean = new LogEntry(LogTypeEnum.INFO.name(), System.currentTimeMillis() - currentTime); 104 | 105 | if (result instanceof Result) { 106 | Result res = (Result) result; 107 | if (!res.getSuccess() || res.getCode() != Result.SUCCESS_CODE) { 108 | logBean.setLogType(LogTypeEnum.ERROR.name()); 109 | logBean.setExceptionDetail(res.getMsg()); 110 | logBean.setRequestId(res.getRequestId()); 111 | } else { 112 | logBean.setResult(res.getData()); 113 | } 114 | } 115 | return logBean; 116 | } 117 | 118 | 119 | private void saveLog(JoinPoint joinPoint, LogEntry logBean) { 120 | try { 121 | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 122 | Method method = signature.getMethod(); 123 | Log logAnnotation = method.getAnnotation(cn.soboys.restapispringbootstarter.log.Log.class); 124 | String methodName = signature.getName(); 125 | //注解入参 126 | logBean.setDescription(logAnnotation.value()); 127 | logBean.setApiType(logAnnotation.apiType().name()); 128 | //方法全路径 129 | logBean.setMethod(joinPoint.getTarget().getClass().getName() + "." + methodName + "()"); 130 | //如果请求对象存在则处理请求头和请求参数 131 | HttpServletRequest req = HttpUserAgent.getRequest(); 132 | 133 | String ip = HttpUserAgent.getIpAddr(); 134 | logBean.setRequestIp(ip); 135 | if (logAnnotation.ipCity()) { 136 | logBean.setAddress(HttpUserAgent.getIpToCityInfo(ip)); 137 | } 138 | if (!logAnnotation.apiResult()) { 139 | logBean.setResult(""); 140 | } 141 | logBean.setOs(HttpUserAgent.getDeviceSystem()); 142 | logBean.setBrowser(HttpUserAgent.getDeviceBrowser()); 143 | logBean.setDevice(HttpUserAgent.getDevice()); 144 | 145 | /** 146 | * 从切面拿参数有可能因为参数名和实体对象命一致 json序列化时出现jpa循环加载 所以从request获取 147 | */ 148 | JSON params = RequestUtil.getRequestParams(req); 149 | logBean.setParams(params == null ? null : params); 150 | 151 | if (loggingProperties != null && StrUtil.isNotEmpty(loggingProperties.getLogDataSourceClass())) { 152 | 153 | Class clazz = Class.forName(loggingProperties.getLogDataSourceClass()); // 使用全类名 154 | Object instance = clazz.getDeclaredConstructor().newInstance(); // 创建实例 155 | ((LogDataSource) instance).save(logBean); 156 | } else { 157 | LogFileDefaultDataSource fileDefaultDataSource = new LogFileDefaultDataSource(); 158 | fileDefaultDataSource.save(logBean); 159 | } 160 | 161 | } catch (Exception e) { 162 | log.error("日志AOP封装log对象异常:", ExceptionUtil.stacktraceToString(e)); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/authorization/UserJwtToken.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.authorization; 2 | 3 | import cn.soboys.restapispringbootstarter.Assert; 4 | import cn.soboys.restapispringbootstarter.HttpStatus; 5 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 6 | import cn.soboys.restapispringbootstarter.exception.BusinessException; 7 | import cn.soboys.restapispringbootstarter.utils.JwtUtil; 8 | import io.jsonwebtoken.*; 9 | 10 | import lombok.Data; 11 | import org.dromara.hutool.core.bean.BeanUtil; 12 | import org.dromara.hutool.core.text.StrUtil; 13 | import org.dromara.hutool.extra.spring.SpringUtil; 14 | 15 | import java.util.LinkedHashMap; 16 | 17 | 18 | /** 19 | * @author 公众号 程序员三时 20 | * @version 1.0 21 | * @date 2023/7/13 21:06 22 | * @webSite https://github.com/coder-amiao 23 | */ 24 | @Data 25 | public class UserJwtToken { 26 | 27 | /** 28 | * 是否记住 29 | */ 30 | public Boolean rememberMe = Boolean.FALSE; 31 | 32 | /** 33 | * 用户签名 34 | */ 35 | private UserSign userSign; 36 | 37 | 38 | 39 | 40 | 41 | private RestApiProperties.JwtProperties jwtProperties = SpringUtil.getBean(RestApiProperties.JwtProperties.class); 42 | 43 | 44 | public String createdToken(String userId, String username, Object users) { 45 | String token = ""; 46 | Long expiration = jwtProperties.getExpiration(); 47 | if (rememberMe) { 48 | expiration = jwtProperties.getRememberMeExpiration(); 49 | } 50 | if (jwtProperties.getUserSign()) { 51 | Assert.notNull(userSign, HttpStatus.UNAUTHORIZED.getCode(), 52 | StrUtil.format(HttpStatus.UNAUTHORIZED.getMessage() + " {}", "找不到签名类和方法")); 53 | /** 54 | * 自定义key 动态实现 ,没有实现即"" 获取属性配置key 55 | */ 56 | String key = userSign.AuthKey(); 57 | if (StrUtil.isEmpty(key)) { 58 | key = jwtProperties.getSecret(); 59 | } 60 | token = JwtUtil.createJWT(username, userId, users, expiration, userSign.sign(), key); 61 | } else { 62 | token = JwtUtil.createJWT(username, userId, users, expiration); 63 | } 64 | return token; 65 | } 66 | 67 | 68 | /** 69 | * 解析user token用户信息。 70 | * 71 | * @param userToken 72 | * @return 73 | */ 74 | public T parseUserToken(String userToken, Class returnCls) { 75 | Jws claims; 76 | try { 77 | if (jwtProperties.getUserSign()) { 78 | Assert.notNull(userSign, HttpStatus.UNAUTHORIZED.getCode(), 79 | StrUtil.format(HttpStatus.UNAUTHORIZED.getMessage() + " {}", "找不到签名类和方法")); 80 | String key = userSign.AuthKey(); 81 | if (StrUtil.isEmpty(key)) { 82 | key = jwtProperties.getSecret(); 83 | } 84 | claims = JwtUtil.parseJWT(userToken, userSign.getSignedKey(key)); 85 | } else { 86 | claims = JwtUtil.parseJWT(userToken); 87 | } 88 | } catch (ExpiredJwtException e) { 89 | throw new BusinessException(HttpStatus.UNAUTHORIZED_EXPIRED); 90 | }catch (SignatureException e) { 91 | throw new BusinessException("Token验证签名密钥不正确",HttpStatus.UNAUTHORIZED.getCode()); 92 | } catch (MalformedJwtException e) { 93 | throw new BusinessException("Token无效或者不存在",HttpStatus.UNAUTHORIZED.getCode()); 94 | } 95 | LinkedHashMap linkedHashMap = claims.getBody().get("user", LinkedHashMap.class); 96 | return BeanUtil.toBean(linkedHashMap, returnCls); 97 | 98 | } 99 | 100 | 101 | /** 102 | * 返回用户ID 103 | * 104 | * @param userToken 105 | * @return 106 | */ 107 | public String getUserId(String userToken) { 108 | Claims claims = this.getClaims(userToken); 109 | return claims.getId(); 110 | } 111 | 112 | /** 113 | * 返回用户名称 114 | * 115 | * @param userToken 116 | * @return 117 | */ 118 | public String getUserName(String userToken) { 119 | Claims claims = this.getClaims(userToken); 120 | return claims.getSubject(); 121 | } 122 | 123 | 124 | /** 125 | * 返回整个原始jwt信息 126 | * 127 | * @param jwt 128 | * @return 129 | */ 130 | public Jws parseJWT(String jwt) { 131 | Jws claimsJws; 132 | try { 133 | if (jwtProperties.getUserSign()) { 134 | Assert.notNull(userSign, HttpStatus.UNAUTHORIZED.getCode(), 135 | StrUtil.format(HttpStatus.UNAUTHORIZED.getMessage() + " {}", "找不到签名类和方法")); 136 | String key = userSign.AuthKey(); 137 | if (StrUtil.isEmpty(key)) { 138 | key = jwtProperties.getSecret(); 139 | } 140 | claimsJws = JwtUtil.parseJWT(jwt, userSign.getSignedKey(key)); 141 | } else { 142 | claimsJws = JwtUtil.parseJWT(jwt); 143 | } 144 | } catch (ExpiredJwtException e) { 145 | throw new BusinessException(HttpStatus.UNAUTHORIZED_EXPIRED); 146 | }catch (SignatureException e) { 147 | throw new BusinessException("Token验证签名密钥不正确",HttpStatus.UNAUTHORIZED.getCode()); 148 | } catch (MalformedJwtException e) { 149 | throw new BusinessException("Token无效或者不存在",HttpStatus.UNAUTHORIZED.getCode()); 150 | } 151 | return claimsJws; 152 | } 153 | 154 | /** 155 | * 返回Claims主题信息 156 | * 157 | * @param jwt 158 | * @return 159 | */ 160 | public Claims getClaims(String jwt) { 161 | Claims claims; 162 | try { 163 | if (jwtProperties.getUserSign()) { 164 | Assert.notNull(userSign, HttpStatus.UNAUTHORIZED.getCode(), 165 | StrUtil.format(HttpStatus.UNAUTHORIZED.getMessage() + " {}", "找不到签名类和方法")); 166 | String key = userSign.AuthKey(); 167 | if (StrUtil.isEmpty(key)) { 168 | key = jwtProperties.getSecret(); 169 | } 170 | claims = JwtUtil.getClaims(jwt, userSign.getSignedKey(key)); 171 | } else { 172 | claims = JwtUtil.getClaims(jwt); 173 | } 174 | } catch (ExpiredJwtException e) { 175 | throw new BusinessException(HttpStatus.UNAUTHORIZED_EXPIRED); 176 | } catch (SignatureException e) { 177 | throw new BusinessException("Token验证签名密钥不正确",HttpStatus.UNAUTHORIZED.getCode()); 178 | } catch (MalformedJwtException e) { 179 | throw new BusinessException("Token无效或者不存在",HttpStatus.UNAUTHORIZED.getCode()); 180 | } 181 | return claims; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/HttpUserAgent.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | import cn.soboys.restapispringbootstarter.config.RestApiProperties; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import org.dromara.hutool.core.exception.ExceptionUtil; 7 | import org.dromara.hutool.core.io.file.FileUtil; 8 | import org.springframework.core.io.ClassPathResource; 9 | import org.springframework.core.io.DefaultResourceLoader; 10 | import org.springframework.core.io.ResourceLoader; 11 | import org.dromara.hutool.extra.spring.SpringUtil; 12 | import org.dromara.hutool.http.useragent.UserAgent; 13 | import org.dromara.hutool.http.useragent.UserAgentUtil; 14 | import org.lionsoul.ip2region.xdb.Searcher; 15 | import org.springframework.core.io.Resource; 16 | import org.springframework.stereotype.Component; 17 | import org.springframework.web.context.request.RequestContextHolder; 18 | import org.springframework.web.context.request.ServletRequestAttributes; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | import java.io.File; 22 | import java.io.InputStream; 23 | import java.net.URL; 24 | import java.text.MessageFormat; 25 | import java.util.Objects; 26 | 27 | /** 28 | * @author 公众号 程序员三时 29 | * @version 1.0 30 | * @date 2023/7/2 11:40 31 | * @webSite https://github.com/coder-amiao 32 | */ 33 | @Slf4j 34 | public class HttpUserAgent { 35 | private static final String UNKNOWN = "unknown"; 36 | 37 | 38 | private static RestApiProperties.Ip2regionProperties ip2regionProperties = SpringUtil.getBean(RestApiProperties.Ip2regionProperties.class); 39 | 40 | 41 | /** 42 | * @return HttpServletRequest 43 | */ 44 | public static HttpServletRequest getRequest() { 45 | return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 46 | } 47 | 48 | /** 49 | * @return 请求头信息 50 | */ 51 | public static String getDevice() { 52 | HttpServletRequest request = getRequest(); 53 | String uaStr = request.getHeader("User-Agent"); 54 | UserAgent ua = UserAgentUtil.parse(uaStr); 55 | return uaStr; 56 | } 57 | 58 | /** 59 | * @return 请求头信息 60 | */ 61 | public static String getDeviceBrowser() { 62 | HttpServletRequest request = getRequest(); 63 | String uaStr = request.getHeader("User-Agent"); 64 | UserAgent ua = UserAgentUtil.parse(uaStr); 65 | String browser = ua.getBrowser().toString(); 66 | return browser; 67 | } 68 | 69 | /** 70 | * @return 请求头信息 71 | */ 72 | public static String getDeviceSystem() { 73 | HttpServletRequest request = getRequest(); 74 | String uaStr = request.getHeader("User-Agent"); 75 | UserAgent ua = UserAgentUtil.parse(uaStr); 76 | String platform = ua.getPlatform().toString(); 77 | return platform; 78 | } 79 | 80 | public static String getIpAddr() { 81 | String ip = null; 82 | HttpServletRequest request = RequestUtil.getReq(); 83 | String ipAddresses = request.getHeader("X-Real-IP"); 84 | if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { 85 | ipAddresses = request.getHeader("X-Forwarded-For"); 86 | } 87 | if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { 88 | ipAddresses = request.getHeader("Proxy-Client-IP"); 89 | } 90 | if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { 91 | ipAddresses = request.getHeader("WL-Proxy-Client-IP"); 92 | } 93 | if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { 94 | ipAddresses = request.getHeader("HTTP_CLIENT_IP"); 95 | } 96 | if (ipAddresses != null && ipAddresses.length() != 0) { 97 | ip = ipAddresses.split(",")[0]; 98 | } 99 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { 100 | ip = request.getRemoteAddr(); 101 | } 102 | return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; 103 | } 104 | 105 | /** 106 | * 根据ip获取城市地理位置信息 107 | * 108 | * @param ip 109 | * @return 110 | */ 111 | public static String getIpToCityInfo(String ip) { 112 | try { 113 | 114 | ResourceLoader resourceLoader = new DefaultResourceLoader(); 115 | Resource resource = resourceLoader.getResource(ip2regionProperties.getLocation()); 116 | String dbPath = ""; 117 | if (resource.getURI().getScheme().equals("jar")) { 118 | 119 | File file=new File("src/main/resources/"+((ClassPathResource) resource).getPath()); 120 | FileUtil.writeFromStream(resource.getInputStream(),file); 121 | dbPath=file.getAbsolutePath(); 122 | } else { 123 | dbPath = resource.getFile().getPath(); 124 | } 125 | 126 | // if (ip2regionProperties.isExternal()) { 127 | // ClassPathResource resource = new ClassPathResource(ip2regionProperties.getLocation()); 128 | // dbPath = resource.getFile().getAbsolutePath(); 129 | // } else { 130 | // // 获取当前默认记录地址位置的文件 131 | // ResourceLoader resourceLoader = new DefaultResourceLoader(); 132 | // Resource resource = resourceLoader.getResource("classpath:resource.txt"); 133 | // 134 | // 135 | // URL u =null; //HttpUserAgent.class.getClass().getResource(dbPath); 136 | // 137 | // dbPath= HttpUserAgent.class.getClassLoader().getResource("ip2region/ip2region.xdb").getPath(); 138 | //// if(u==null||u.getPath().contains("file")){ 139 | //// dbPath = System.getProperty("user.dir") + "/ip.db"; 140 | //// HttpUserAgent.class.getClassLoader().getResource("ip2region/ip2region.xdb").getPath(); 141 | //// File file=new File(dbPath); 142 | //// FileUtil.writeFromStream(HttpUserAgent.class.getClassLoader().getResourceAsStream("ip2region/ip2region.xdb"),file); 143 | //// }else { 144 | //// dbPath=u.getPath(); 145 | //// } 146 | // } 147 | 148 | File file = new File(dbPath); 149 | //如果当前文件不存在,则从缓存中复制一份 150 | if (!file.exists()) { 151 | log.error("ip2region.xdb文件找不到请填写类路径"); 152 | return "UNKNOWN"; 153 | } 154 | //创建查询对象 155 | Searcher searcher = Searcher.newWithFileOnly(dbPath); 156 | //开始查询 157 | return searcher.search(ip); 158 | } catch (Exception e) { 159 | log.error("Ip查询城市地址解析失败{}", ExceptionUtil.stacktraceToString(e)); 160 | e.printStackTrace(); 161 | } 162 | //默认返回空字符串 163 | return "UNKNOWN"; 164 | 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/cache/RedisTempUtil.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.cache; 2 | 3 | import org.dromara.hutool.extra.spring.SpringUtil; 4 | import org.springframework.boot.autoconfigure.ImportAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.data.redis.core.ValueOperations; 9 | import org.springframework.data.redis.support.atomic.RedisAtomicLong; 10 | 11 | 12 | import javax.annotation.Resource; 13 | import java.util.*; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * @author 公众号 程序员三时 18 | * @version 1.0 19 | * @date 2023/7/1 18:00 20 | * @webSite https://github.com/coder-amiao 21 | */ 22 | @Configuration 23 | public class RedisTempUtil { 24 | 25 | private static RedisTempUtil instance; 26 | 27 | 28 | public static RedisTempUtil getInstance() { 29 | return SpringUtil.getBean(RedisTempUtil.class); 30 | } 31 | 32 | 33 | 34 | @Resource 35 | private RedisTemplate redisTemplate; 36 | 37 | 38 | // =============================common============================ 39 | public Boolean hasKey(String key) { 40 | return redisTemplate.hasKey(key); 41 | } 42 | 43 | public Boolean delete(String key) { 44 | return redisTemplate.delete(key); 45 | } 46 | 47 | public boolean exists(String key) { 48 | return redisTemplate.hasKey(key); 49 | } 50 | 51 | /** 52 | * 返回对应key过去时间 53 | * @param key 54 | *返回以秒为单位的剩余过期时间。如果返回值为负数 55 | * @return 56 | */ 57 | public Long getRemainingTime(String key) { 58 | ValueOperations ops = redisTemplate.opsForValue(); 59 | return ops.getOperations().getExpire(key); 60 | } 61 | 62 | /** 63 | * 获得某个key剩余时间 64 | * 65 | * @param key key 66 | */ 67 | public Long ttl(final String key) { 68 | return redisTemplate.getExpire(key); 69 | } 70 | 71 | /** 72 | * 获得缓存的基本对象列表 73 | * 74 | * @param pattern 字符串前缀 75 | * @return 对象列表 76 | */ 77 | public Collection keys(final String pattern) { 78 | return redisTemplate.keys(pattern); 79 | } 80 | 81 | /** 82 | * 获取所有的key 83 | * @param keys 84 | * @return 85 | */ 86 | public Set getAllKey(String keys) { 87 | Set key = redisTemplate.keys(keys + "*"); 88 | return key; 89 | } 90 | 91 | /** 92 | * 清除所有key 93 | */ 94 | public void clean(){ 95 | Set keys = redisTemplate.keys("*"); 96 | redisTemplate.delete(keys); 97 | } 98 | 99 | 100 | /** 101 | * 清除指定前缀key 102 | * @param key 103 | */ 104 | public void cleanAllKey(String key){ 105 | Set keys = redisTemplate.keys(key + "*"); 106 | redisTemplate.delete(keys); 107 | } 108 | 109 | /** 110 | * 删除单个对象 111 | * 112 | * @param key 113 | */ 114 | public boolean deleteObject(final String key) { 115 | return redisTemplate.delete(key); 116 | } 117 | 118 | 119 | /** 120 | * 值 incr增加 121 | * 122 | * @param key key 123 | */ 124 | public Long incr(final String key) { 125 | RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory())); 126 | return entityIdCounter.incrementAndGet(); 127 | } 128 | 129 | /** 130 | * 值 decr减少 131 | * 132 | * @param key key 133 | */ 134 | public Long decr(final String key) { 135 | RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, Objects.requireNonNull(redisTemplate.getConnectionFactory())); 136 | return entityIdCounter.incrementAndGet(); 137 | } 138 | 139 | 140 | /** 141 | * 删除集合对象 142 | * 143 | * @param collection 多个对象 144 | * @return 145 | */ 146 | public long deleteObject(final Collection collection) { 147 | return redisTemplate.delete(collection); 148 | } 149 | 150 | /** 151 | * 普通缓存放入并设置时间 152 | * 153 | * @param key 键 154 | * @param value 值 155 | * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 156 | * @return true成功 false 失败 157 | */ 158 | public Boolean set(String key, Object value, Long time) { 159 | try { 160 | if (time > 0) { 161 | redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); 162 | } else { 163 | set(key, value); 164 | } 165 | return true; 166 | } catch (Exception e) { 167 | e.printStackTrace(); 168 | return false; 169 | } 170 | } 171 | 172 | /** 173 | * 设置有效时间 174 | * 175 | * @param key Redis键 176 | * @param timeout 超时时间 177 | * @return true=设置成功;false=设置失败 178 | */ 179 | public boolean expire(final String key, final long timeout) { 180 | return expire(key, timeout, TimeUnit.SECONDS); 181 | } 182 | 183 | /** 184 | * 设置有效时间 185 | * 186 | * @param key Redis键 187 | * @param timeout 超时时间 188 | * @param unit 时间单位 189 | * @return true=设置成功;false=设置失败 190 | */ 191 | public boolean expire(final String key, final long timeout, final TimeUnit unit) { 192 | return redisTemplate.expire(key, timeout, unit); 193 | } 194 | 195 | // ============================String============================= 196 | public void set(String key, Object value) { 197 | redisTemplate.opsForValue().set(key, value); 198 | } 199 | 200 | public void set(String key, Object value, long timeout, TimeUnit unit) { 201 | redisTemplate.opsForValue().set(key, value, timeout, unit); 202 | } 203 | 204 | public Object get(String key) { 205 | return redisTemplate.opsForValue().get(key); 206 | } 207 | 208 | // ================================List================================= 209 | public void lPush(String key, Object value) { 210 | redisTemplate.opsForList().rightPush(key, value); 211 | } 212 | 213 | public Object lPop(String key) { 214 | return redisTemplate.opsForList().leftPop(key); 215 | } 216 | 217 | public List lRange(String key, long start, long end) { 218 | return redisTemplate.opsForList().range(key, start, end); 219 | } 220 | 221 | // ===============================Set================================= 222 | public void sAdd(String key, Object... values) { 223 | redisTemplate.opsForSet().add(key, values); 224 | } 225 | 226 | public Set sMembers(String key) { 227 | return redisTemplate.opsForSet().members(key); 228 | } 229 | 230 | // ===============================Hash================================ 231 | public void hSet(String key, Map map) { 232 | redisTemplate.opsForHash().putAll(key, map); 233 | } 234 | 235 | public Map hGetAll(String key) { 236 | return redisTemplate.opsForHash().entries(key); 237 | } 238 | 239 | // ============================ZSet================================= 240 | public void zAdd(String key, Object value, double score) { 241 | redisTemplate.opsForZSet().add(key, value, score); 242 | } 243 | 244 | public Set zRange(String key, long start, long end) { 245 | return redisTemplate.opsForZSet().range(key, start, end); 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/config/RestApiProperties.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * @author 公众号 程序员三时 13 | * @version 1.0 14 | * @date 2023/6/27 22:55 15 | * @webSite https://github.com/coder-amiao 16 | */ 17 | @Configuration 18 | @ConfigurationProperties(prefix = "rest-api") 19 | @Data 20 | public class RestApiProperties { 21 | 22 | 23 | private boolean enabled = Boolean.FALSE; 24 | 25 | 26 | private String success = "success"; 27 | 28 | private String code = "code"; 29 | 30 | private String codeSuccessValue = "OK"; 31 | 32 | private String msg = "msg"; 33 | 34 | private String timestamp = "timestamp"; 35 | 36 | private String data = "data"; 37 | 38 | /** 39 | * 当前页 40 | */ 41 | private String previousPage = "previousPage"; 42 | /** 43 | * 下一页 44 | */ 45 | private String nextPage = "nextPage"; 46 | 47 | /** 48 | * 总页数 49 | */ 50 | private String pageSize = "pageSize"; 51 | 52 | private String totalPageSize = "totalPageSize"; 53 | 54 | /** 55 | * 是否有下一页 56 | */ 57 | private String hasNext = "hasNext"; 58 | 59 | /** 60 | * 是否包装分页结果到data 61 | */ 62 | private Boolean pageWrap = Boolean.TRUE; 63 | 64 | private String pageData = "pageData"; 65 | 66 | /** 67 | * 排除不需要统一返回的restFull 68 | */ 69 | private String[] excludePackages; 70 | /** 71 | * 添加需要统一返回的restFull 72 | */ 73 | private String[] includePackages; 74 | 75 | 76 | @Configuration 77 | @ConfigurationProperties(prefix = "rest-api.ip2region") 78 | @Data 79 | public class Ip2regionProperties { 80 | /** 81 | * 是否使用外部的IP数据文件. 82 | */ 83 | private boolean external = false; 84 | /** 85 | * ip2region.db 文件路径,默认: classpath:ip2region/ip2region.db 86 | */ 87 | private String location = "classpath:ip2region/ip2region.xdb"; 88 | 89 | } 90 | 91 | 92 | @Configuration 93 | @ConfigurationProperties(prefix = "rest-api.jwt") 94 | @Data 95 | public class JwtProperties { 96 | 97 | /** 98 | * 过期时间秒1天后过期=86400 (单位秒) 99 | */ 100 | private Long expiration = 86400l; 101 | 102 | /** 103 | * 记住我过期时间 7天后过期=604800(单位秒) 104 | */ 105 | private Long rememberMeExpiration = 604800l; 106 | 107 | /** 108 | * 配置用户自定义签名 109 | */ 110 | private Boolean userSign = Boolean.FALSE; 111 | /** 112 | * Header Key 113 | */ 114 | private String tokenHeader = "Token"; 115 | 116 | /** 117 | * # 密匙KEY 118 | */ 119 | private String secret = "2af57b969bac152d"; 120 | 121 | 122 | private Authorization authorization = new Authorization(); 123 | } 124 | 125 | @Data 126 | public static class Authorization { 127 | 128 | private Boolean hasAuthorization = Boolean.FALSE; 129 | 130 | /** 131 | * 需要认证的url 132 | */ 133 | private String includesUrl; 134 | 135 | /** 136 | * 不需要认证的url 137 | */ 138 | private String excludesUrl; 139 | } 140 | 141 | 142 | @Configuration 143 | @ConfigurationProperties(prefix = "rest-api.logging") 144 | @Data 145 | public class LoggingProperties { 146 | private String path; 147 | private String maxHistory; 148 | private String maxFileSize; 149 | private String maxTotalSizeCap; 150 | private String levelRoot; 151 | private String logDataSourceClass = "cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource"; 152 | } 153 | 154 | @Configuration 155 | @ConfigurationProperties(prefix = "rest-api.redis") 156 | @Data 157 | public class RedisProperties { 158 | /** 159 | * 全局注册key 160 | */ 161 | private String keyPrefix; 162 | /** 163 | * redis 缓存的默认超时时间(s) 1天超时 164 | */ 165 | private Long expireTime; 166 | } 167 | 168 | @Configuration 169 | @ConfigurationProperties(prefix = "rest-api.openapi") 170 | @Data 171 | public class OpenApiProperties { 172 | /** 173 | * 是否开启swagger 174 | */ 175 | private Boolean enabled = true; 176 | 177 | /** 178 | * 分组名称 179 | */ 180 | private String groupName; 181 | 182 | /** 183 | * 文档版本,默认使用 2.0 184 | */ 185 | private String documentationType = "v2.0"; 186 | 187 | /** 188 | * swagger会解析的包路径 189 | **/ 190 | private String basePackage = ""; 191 | 192 | /** 193 | * swagger会解析的url规则 194 | **/ 195 | private List basePath = new ArrayList<>(); 196 | 197 | /** 198 | * 在basePath基础上需要排除的url规则 199 | **/ 200 | private List excludePath = new ArrayList<>(); 201 | 202 | /** 203 | * 标题 204 | **/ 205 | private String title = "REST FULL"; 206 | 207 | /** 208 | * 描述 209 | **/ 210 | private String description = "SpringBoot Web Easy RestFull API"; 211 | 212 | /** 213 | * 版本 214 | **/ 215 | private String version = "1.5.0"; 216 | 217 | /** 218 | * 许可证 219 | **/ 220 | private String license = ""; 221 | 222 | /** 223 | * 许可证URL 224 | **/ 225 | private String licenseUrl = ""; 226 | 227 | /** 228 | * 服务条款URL 229 | **/ 230 | private String termsOfServiceUrl = ""; 231 | 232 | /** 233 | * host信息 234 | **/ 235 | private String host = "https://rest-api-boot.soboys.cn/doc-rest-api-springboot-starter/"; 236 | 237 | /** 238 | * 联系人信息 239 | */ 240 | private Contact contact = new Contact(); 241 | } 242 | 243 | @Data 244 | public static class Contact { 245 | 246 | /** 247 | * 联系人 248 | **/ 249 | private String name = "公众号程序员三时"; 250 | 251 | /** 252 | * 联系人url 253 | **/ 254 | private String url = "https://github.com/coder-amiao/rest-api-spring-boot-starter"; 255 | 256 | /** 257 | * 联系人email 258 | **/ 259 | private String email = "xymarcus@163.com"; 260 | 261 | } 262 | 263 | @Configuration 264 | @ConfigurationProperties(prefix = "rest-api.json") 265 | @Data 266 | public static class JsonSerializeProperties { 267 | /** 268 | * 序列化类型 269 | */ 270 | private List serializableType = new ArrayList<>(); 271 | /** 272 | * 时间类型序列化返回 默认时间戳格式 273 | * yyyy-MM-dd HH:mm:ss.SSS 274 | */ 275 | private String dateForm = "timestamp"; 276 | 277 | /** 278 | * 浮点数序列化 BigDecimal 保留完整精度 科学计数法 279 | * 四舍五入 280 | */ 281 | private String numberForm = ",###.##"; 282 | 283 | 284 | /** 285 | * 对空 返回处理 286 | */ 287 | private NullAble nullAble = new NullAble(); 288 | 289 | } 290 | 291 | /** 292 | * JSON 序列化对空返回处理 293 | * 空集合 返回[],Double 返回 0.00 Number 返回0 字符串返回"" 294 | */ 295 | @Data 296 | public static class NullAble{ 297 | /** 298 | * 是否开启对空值处理 299 | */ 300 | private Boolean hasNullAble = Boolean.FALSE; 301 | 302 | /** 303 | * 当 int和long 类型为空默认处理返回0 304 | *original 不处理| number 0|string ""| 305 | */ 306 | private String NumberType="number"; 307 | 308 | /** 309 | * 当 集合类型 空处理默认返会 [] 310 | * original 不处理 |array [] 311 | */ 312 | private String ArrayType="array"; 313 | 314 | /** 315 | * 浮点类型 空处理 默认返回 0.00 316 | * original 不处理 |double 0.00 317 | */ 318 | private String DoubleType="double"; 319 | 320 | /** 321 | * 对象 空处理 包括null空字符 322 | * original 不处理 |string "" 323 | */ 324 | private String ObjectType="string"; 325 | 326 | 327 | 328 | } 329 | 330 | } 331 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter; 2 | 3 | 4 | import cn.soboys.restapispringbootstarter.exception.BusinessException; 5 | import cn.soboys.restapispringbootstarter.exception.CacheException; 6 | import cn.soboys.restapispringbootstarter.exception.LimitAccessException; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.dromara.hutool.core.collection.CollUtil; 9 | import org.dromara.hutool.core.collection.ListUtil; 10 | import org.dromara.hutool.core.exception.ExceptionUtil; 11 | import org.dromara.hutool.core.stream.CollectorUtil; 12 | import org.dromara.hutool.core.text.StrUtil; 13 | import org.hibernate.validator.internal.engine.path.PathImpl; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 15 | import org.springframework.http.converter.HttpMessageNotReadableException; 16 | import org.springframework.validation.BindException; 17 | import org.springframework.validation.BindingResult; 18 | import org.springframework.validation.FieldError; 19 | import org.springframework.web.HttpMediaTypeNotSupportedException; 20 | import org.springframework.web.HttpRequestMethodNotSupportedException; 21 | import org.springframework.web.bind.MethodArgumentNotValidException; 22 | import org.springframework.web.bind.annotation.RestControllerAdvice; 23 | import org.springframework.web.servlet.NoHandlerFoundException; 24 | 25 | import javax.servlet.http.HttpServletRequest; 26 | import javax.validation.ConstraintViolation; 27 | import javax.validation.ConstraintViolationException; 28 | import javax.validation.Path; 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | import java.util.List; 32 | import java.util.Set; 33 | import java.util.stream.Collectors; 34 | 35 | 36 | /** 37 | * @author 公众号 程序员三时 38 | * @version 1.0 39 | * @date 2023/6/26 16:44 40 | * @webSite https://github.com/coder-amiao 41 | */ 42 | @RestControllerAdvice 43 | @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 44 | @Slf4j 45 | public class ExceptionHandler { 46 | 47 | 48 | /** 49 | * 未知异常全局捕获 50 | * 51 | * @param e 52 | * @return 53 | */ 54 | @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class) 55 | public Result error(Exception e) { 56 | log.error("未知异常{}", ExceptionUtil.stacktraceToString(e)); 57 | return Result.buildFailure(HttpStatus.INTERNAL_SERVER_ERROR, ExceptionUtil.stacktraceToString(e)); 58 | } 59 | 60 | 61 | /** 62 | * 统一业务异常处理 63 | * 64 | * @param e 65 | * @return 66 | */ 67 | @org.springframework.web.bind.annotation.ExceptionHandler(BusinessException.class) 68 | public Result error(BusinessException e) { 69 | return Result.buildFailure(e.getCode(), e.getMessage()); 70 | } 71 | 72 | 73 | /** 74 | * get 请求是没有参数体,post请求有参数体,支持表单,json,同时支持url参数 75 | */ 76 | 77 | /** 78 | * 验证 对象类型参数 79 | */ 80 | @org.springframework.web.bind.annotation.ExceptionHandler(BindException.class) 81 | public Result BindExceptionHandler(BindException e, HttpServletRequest request) { 82 | List fieldErrors = e.getBindingResult().getFieldErrors(); 83 | List collect = fieldErrors.stream() 84 | .map(o -> o.getField() + o.getDefaultMessage()) 85 | .collect(Collectors.toList()); 86 | request.setAttribute("argument_error", CollUtil.join(collect, ";")); 87 | return Result.buildFailure(HttpStatus.INVALID_ARGUMENT.getCode(), 88 | StrUtil.format(HttpStatus.INVALID_ARGUMENT.getMessage(), CollUtil.join(collect, ";"))); 89 | } 90 | 91 | /** 92 | * 验证 单个参数类型 93 | */ 94 | @org.springframework.web.bind.annotation.ExceptionHandler(ConstraintViolationException.class) 95 | public Result ConstraintViolationExceptionHandler(ConstraintViolationException e, HttpServletRequest request) { 96 | List errorList = new ArrayList<>(); 97 | Set> violations = e.getConstraintViolations(); 98 | for (ConstraintViolation violation : violations) { 99 | StringBuilder message = new StringBuilder(); 100 | Path path = violation.getPropertyPath(); 101 | String msg = message.append(((PathImpl) path).getLeafNode()).append(violation.getMessage()).toString(); 102 | errorList.add(msg); 103 | } 104 | request.setAttribute("argument_error", CollUtil.join(errorList, ";")); 105 | return Result.buildFailure(HttpStatus.INVALID_ARGUMENT.getCode(), 106 | StrUtil.format(HttpStatus.INVALID_ARGUMENT.getMessage(), CollUtil.join(errorList, ";"))); 107 | } 108 | 109 | 110 | /** 111 | * 验证 对象类型参数 JSON body 参数 112 | */ 113 | @org.springframework.web.bind.annotation.ExceptionHandler(MethodArgumentNotValidException.class) 114 | public Result jsonParamsException(MethodArgumentNotValidException e, HttpServletRequest request) { 115 | BindingResult bindingResult = e.getBindingResult(); 116 | List errorList = new ArrayList<>(); 117 | 118 | for (FieldError fieldError : bindingResult.getFieldErrors()) { 119 | String msg = String.format("%s%s;", fieldError.getField(), fieldError.getDefaultMessage()); 120 | errorList.add(msg); 121 | } 122 | request.setAttribute("argument_error", CollUtil.join(errorList, ";")); 123 | return Result.buildFailure(HttpStatus.INVALID_ARGUMENT.getCode(), 124 | StrUtil.format(HttpStatus.INVALID_ARGUMENT.getMessage(), CollUtil.join(errorList, ";"))); 125 | } 126 | 127 | 128 | /** 129 | * 接口不存在 130 | * 131 | * @param e 132 | * @return 133 | */ 134 | @org.springframework.web.bind.annotation.ExceptionHandler(NoHandlerFoundException.class) 135 | public Result error(NoHandlerFoundException e) { 136 | return Result.buildFailure(HttpStatus.NOT_FOUND, e.getRequestURL()); 137 | } 138 | 139 | /** 140 | * 请求方法不被允许 141 | */ 142 | @org.springframework.web.bind.annotation.ExceptionHandler(HttpRequestMethodNotSupportedException.class) 143 | public Result httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { 144 | return Result.buildFailure(HttpStatus.METHOD_NOT_ALLOWED, ExceptionUtil.stacktraceToString(e)); 145 | } 146 | 147 | /** 148 | * 请求与响应媒体类型不一致 异常 149 | * 150 | * @param e 151 | * @return 152 | */ 153 | @org.springframework.web.bind.annotation.ExceptionHandler(HttpMediaTypeNotSupportedException.class) 154 | public Result httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) { 155 | return Result.buildFailure(HttpStatus.BAD_GATEWAY, ExceptionUtil.stacktraceToString(e)); 156 | } 157 | 158 | /** 159 | * body json参数解析异常 160 | * 161 | * @param e 162 | * @return 163 | */ 164 | @org.springframework.web.bind.annotation.ExceptionHandler(HttpMessageNotReadableException.class) 165 | public Result HttpMessageNotReadableException(HttpMessageNotReadableException e,HttpServletRequest request) { 166 | request.setAttribute("argument_error", e.getMessage()); 167 | return Result.buildFailure(HttpStatus.INVALID_ARGUMENT.getCode(), 168 | StrUtil.format(HttpStatus.INVALID_ARGUMENT.getMessage(),e.getMessage()), ExceptionUtil.stacktraceToString(e)); 169 | } 170 | 171 | @org.springframework.web.bind.annotation.ExceptionHandler(LimitAccessException.class) 172 | public Result LimitAccessExceptionException(LimitAccessException e,HttpServletRequest request) { 173 | request.setAttribute("argument_error", e.getMessage()); 174 | return Result.buildFailure(HttpStatus.REQUEST_TIMEOUT.getCode(), 175 | StrUtil.format(HttpStatus.REQUEST_TIMEOUT.getMessage() , e.getMessage()),ExceptionUtil.stacktraceToString(e)); 176 | } 177 | 178 | 179 | /** 180 | * 统一业务异常处理 181 | * 182 | * @param e 183 | * @return 184 | */ 185 | @org.springframework.web.bind.annotation.ExceptionHandler(CacheException.class) 186 | public Result CacheException(CacheException e,HttpServletRequest request) { 187 | request.setAttribute("argument_error", e.getMessage()); 188 | return Result.buildFailure(HttpStatus.CACHE_EXCEPTION.getCode(), 189 | StrUtil.format(HttpStatus.CACHE_EXCEPTION.getMessage() , e.getMessage()), ExceptionUtil.stacktraceToString(e)); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/cn/soboys/restapispringbootstarter/utils/RequestUtil.java: -------------------------------------------------------------------------------- 1 | package cn.soboys.restapispringbootstarter.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.dromara.hutool.core.map.MapUtil; 6 | import org.dromara.hutool.json.JSON; 7 | import org.dromara.hutool.json.JSONObject; 8 | import org.dromara.hutool.json.JSONUtil; 9 | import org.springframework.web.context.request.RequestContextHolder; 10 | import org.springframework.web.context.request.ServletRequestAttributes; 11 | 12 | import javax.servlet.http.Cookie; 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.io.BufferedReader; 15 | import java.io.IOException; 16 | import java.io.InputStreamReader; 17 | import java.io.UnsupportedEncodingException; 18 | import java.lang.reflect.Method; 19 | import java.net.URLDecoder; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * @author 公众号 程序员三时 25 | * @version 1.0 26 | * @date 2023/7/11 17:58 27 | * @webSite https://github.com/coder-amiao 28 | */ 29 | @Slf4j 30 | public class RequestUtil { 31 | 32 | /** 33 | * 获取HttpServletRequest 34 | * 35 | * @return 36 | */ 37 | public static HttpServletRequest getReq() { 38 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 39 | HttpServletRequest httpServletRequest = null; 40 | if (attributes != null) { 41 | httpServletRequest = attributes.getRequest(); 42 | } 43 | return httpServletRequest; 44 | } 45 | 46 | /** 47 | * 获取post请求的body中的内容 48 | * 49 | * @param request 50 | * @return 51 | * @throws IOException 52 | */ 53 | public static JSON getParam(HttpServletRequest request) throws IOException { 54 | BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); 55 | String str = ""; 56 | StringBuffer wholeStr = new StringBuffer(); 57 | //读取body体里面的内容; 58 | while ((str = reader.readLine()) != null) { 59 | wholeStr.append(str); 60 | } 61 | //判断是否是数组 62 | String text = wholeStr.toString(); 63 | if (StringUtils.isBlank(text)) { 64 | return new JSONObject(); 65 | } 66 | if (StringUtils.startsWith(text, "[")) { 67 | return JSONUtil.parseArray(text); 68 | } 69 | return JSONUtil.parseObj(text); 70 | } 71 | 72 | public static JSON getRequestParams(HttpServletRequest request) throws IOException { 73 | JSONObject jsonObject = new JSONObject(); 74 | //post或get 普通提交 75 | Map parameterMap = request.getParameterMap(); 76 | if (MapUtil.isNotEmpty(parameterMap)) { 77 | parameterMap.forEach((key, value) -> { 78 | jsonObject.put(key, value[0]); 79 | }); 80 | return jsonObject; 81 | } 82 | //JSON提交 83 | return getParam(request); 84 | } 85 | 86 | /** 87 | * 根据key获取value,会从header,paramter,cookie中查找。 88 | */ 89 | public static String getValue(HttpServletRequest request, String name) { 90 | String value = request.getHeader(name); 91 | if (value == null) { 92 | value = request.getParameter(name); 93 | } 94 | if (value == null) { 95 | Cookie[] cookies = request.getCookies(); 96 | if (cookies != null && cookies.length > 0) { 97 | for (Cookie cookie : cookies) { 98 | if (cookie.getName().equals(name)) { 99 | value = cookie.getValue(); 100 | try { 101 | value = URLDecoder.decode(value, "UTF-8"); 102 | } catch (UnsupportedEncodingException e) { 103 | log.error("Cookie 解码失败,编码=UTF-8,值=" + value); 104 | } 105 | break; 106 | } 107 | } 108 | } 109 | } 110 | return value; 111 | } 112 | 113 | public static boolean hasAjaxRequest() { 114 | return hasAjaxRequest(getReq()); 115 | } 116 | 117 | public static boolean hasLocalRequest() { 118 | return "127.0.0.1".endsWith(HttpUserAgent.getIpAddr()) ? true : false; 119 | } 120 | 121 | public static String getHost() { 122 | HttpServletRequest request = RequestUtil.getReq(); 123 | return request.getServerName() + (request.getServerPort() == 80 ? "" : (":" + request.getServerPort())); 124 | } 125 | 126 | /** 127 | * 将name转化为可访问的url地址,前端自动加上http头和域名端口信息,name自动用/连接 128 | * 129 | * @param names 130 | * @return 131 | */ 132 | public static String getHttpUrl(String... names) { 133 | return getHttpUrl(getReq(), names); 134 | } 135 | 136 | private static String getHttpUrl(HttpServletRequest request, String... names) { 137 | String httpUrl = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() == 80 ? "" : (":" + request.getServerPort())); 138 | if (names != null) { 139 | for (String name : names) { 140 | httpUrl = httpUrl + "/" + (name.startsWith("/") ? name.substring(1) : name); 141 | } 142 | } 143 | return httpUrl; 144 | } 145 | 146 | public static boolean hasAjaxRequest(HttpServletRequest request) { 147 | String requestedWith = request.getHeader("x-requested-with"); 148 | if (requestedWith != null && requestedWith.equalsIgnoreCase("XMLHttpRequest")) { 149 | return true; 150 | } else { 151 | return false; 152 | } 153 | } 154 | 155 | 156 | 157 | 158 | /** 159 | * 获取请求地址中的某个参数 160 | * @param url 161 | * @param name 162 | * @return 163 | */ 164 | public static String getParam(String url, String name) { 165 | return urlSplit(url).get(name); 166 | } 167 | 168 | /** 169 | * 去掉url中的路径,留下请求参数部分 170 | * @param url url地址 171 | * @return url请求参数部分 172 | */ 173 | private static String truncateUrlPage(String url) { 174 | String strAllParam = null; 175 | String[] arrSplit = null; 176 | url = url.trim().toLowerCase(); 177 | arrSplit = url.split("[?]"); 178 | if (url.length() > 1) { 179 | if (arrSplit.length > 1) { 180 | for (int i = 1; i < arrSplit.length; i++) { 181 | strAllParam = arrSplit[i]; 182 | } 183 | } 184 | } 185 | return strAllParam; 186 | } 187 | 188 | /** 189 | * 将参数存入map集合 190 | * @param url url地址 191 | * @return url请求参数部分存入map集合 192 | */ 193 | public static Map urlSplit(String url) { 194 | Map mapRequest = new HashMap(); 195 | String[] arrSplit = null; 196 | String strUrlParam = truncateUrlPage(url); 197 | if (strUrlParam == null) { 198 | return mapRequest; 199 | } 200 | arrSplit = strUrlParam.split("[&]"); 201 | for (String strSplit : arrSplit) { 202 | String[] arrSplitEqual = null; 203 | arrSplitEqual = strSplit.split("[=]"); 204 | //解析出键值 205 | if (arrSplitEqual.length > 1) { 206 | //正确解析 207 | mapRequest.put(arrSplitEqual[0], arrSplitEqual[1]); 208 | } else { 209 | if (arrSplitEqual[0] != "") { 210 | //只有参数没有值,不加入 211 | mapRequest.put(arrSplitEqual[0], ""); 212 | } 213 | } 214 | } 215 | return mapRequest; 216 | } 217 | 218 | public static class WebRequest { 219 | 220 | private T request; 221 | 222 | public WebRequest(T request) { 223 | this.request = request; 224 | } 225 | 226 | public String getParameter(String paramName) { 227 | try { 228 | Method method = request.getClass().getMethod("getParameter", String.class); 229 | Object value = method.invoke(request, paramName); 230 | return (String) value; 231 | } catch (Exception e) { 232 | return null; 233 | } 234 | } 235 | 236 | public Map getParameterMap() { 237 | try { 238 | Method method = request.getClass().getMethod("getParameterMap"); 239 | Object value = method.invoke(request); 240 | return (Map) value; 241 | } catch (Exception e) { 242 | return null; 243 | } 244 | } 245 | 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.8 9 | 10 | 11 | cn.soboys 12 | simplest-api-spring-boot-starter 13 | 2.0.0 14 | jar 15 | simplest-api 16 | 前后端分离项目基于simplest-api 可以快速构建基于Web Json API格式的统一通讯交互 17 | https://github.com/coder-amiao/simplest-api 18 | 19 | 20 | 21 | 1.8 22 | ${java.version} 23 | ${java.version} 24 | UTF-8 25 | 26 | 1234qwer 27 | E933FBC878FB2EC0900A2ABAF79C3CD9E9E6A8EF 28 | /Users/xiangyong/.gnupg 29 | 30 | 31 | 1.7.0 32 | 3.0.3 33 | 2.7.0 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | The Apache Software License, Version2.0 42 | https://www.apache.org/licenses/ 43 | repo 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 三时 52 | xymarcus@163.com 53 | 54 | 55 | 56 | 57 | 58 | scm:git:https://github.com/zw201913/jtile38.git 59 | https://github.com/coder-amiao/simplest-api 60 | https://github.com/coder-amiao/simplest-api.git 61 | v${project.version} 62 | 63 | 64 | 65 | 66 | 67 | ossrh 68 | https://s01.oss.sonatype.org/content/repositories/snapshots 69 | 70 | 71 | 72 | ossrh 73 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-starter-web 82 | true 83 | 84 | 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-logging 89 | 90 | 91 | 92 | org.springframework.boot 93 | spring-boot-configuration-processor 94 | true 95 | 96 | 97 | 98 | 99 | org.projectlombok 100 | lombok 101 | 102 | 103 | 104 | 105 | org.dromara.hutool 106 | hutool-all 107 | 6.0.0-M4 108 | 109 | 110 | 111 | org.lionsoul 112 | ip2region 113 | ${ip2region.version} 114 | 115 | 116 | 117 | 118 | io.jsonwebtoken 119 | jjwt 120 | 0.9.1 121 | 122 | 123 | 124 | 125 | 126 | org.apache.commons 127 | commons-lang3 128 | 3.8.1 129 | 130 | 131 | 132 | 133 | 134 | com.google.guava 135 | guava 136 | 32.0.1-jre 137 | 138 | 139 | 140 | 141 | 142 | 143 | org.springframework.boot 144 | spring-boot-starter-data-redis 145 | 146 | 147 | 148 | org.apache.commons 149 | commons-pool2 150 | 151 | 152 | 153 | 154 | 155 | 156 | com.baomidou 157 | mybatis-plus-generator 158 | 3.4.1 159 | true 160 | 161 | 162 | 163 | mysql 164 | mysql-connector-java 165 | 8.0.28 166 | true 167 | 168 | 169 | 170 | org.freemarker 171 | freemarker 172 | 2.3.31 173 | true 174 | 175 | 176 | 177 | 178 | org.springframework.boot 179 | spring-boot-starter-validation 180 | 181 | 182 | 183 | 184 | org.springframework.boot 185 | spring-boot-starter-aop 186 | 187 | 188 | 189 | 190 | org.springdoc 191 | springdoc-openapi-ui 192 | ${springdoc-openapi-ui.version} 193 | 194 | 195 | 196 | com.github.xiaoymin 197 | knife4j-springdoc-ui 198 | ${knife4j-springdoc-ui.version} 199 | 200 | 201 | 202 | 203 | org.springframework.boot 204 | spring-boot-starter-test 205 | test 206 | true 207 | 208 | 209 | 210 | 211 | org.springframework.boot 212 | spring-boot-starter-tomcat 213 | 214 | provided 215 | true 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | org.apache.maven.plugins 224 | maven-compiler-plugin 225 | 226 | 1.8 227 | 1.8 228 | 229 | 230 | 231 | 232 | org.apache.maven.plugins 233 | maven-source-plugin 234 | 2.2.1 235 | 236 | 237 | package 238 | 239 | jar-no-fork 240 | 241 | 242 | 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-javadoc-plugin 248 | 2.10.4 249 | 250 | 251 | -Xdoclint:none 252 | 253 | 254 | 255 | 256 | package 257 | 258 | jar 259 | 260 | 261 | 262 | 263 | 264 | 265 | org.apache.maven.plugins 266 | maven-gpg-plugin 267 | 1.6 268 | 269 | 270 | --pinentry-mode 271 | loopback 272 | 273 | 274 | 275 | 276 | sign-artifacts 277 | verify 278 | 279 | sign 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | --------------------------------------------------------------------------------