diff --git a/hsweb-authorization/hsweb-authorization-api/pom.xml b/hsweb-authorization/hsweb-authorization-api/pom.xml index 787d6abe2..21217cb71 100644 --- a/hsweb-authorization/hsweb-authorization-api/pom.xml +++ b/hsweb-authorization/hsweb-authorization-api/pom.xml @@ -5,7 +5,7 @@ hsweb-authorization org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java index 6144f6c5f..93f9bb261 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java @@ -2,11 +2,14 @@ package org.hswebframework.web.authorization.events; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.Setter; import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.event.DefaultAsyncEvent; @Getter +@Setter @AllArgsConstructor -public class AuthorizationInitializeEvent { +public class AuthorizationInitializeEvent extends DefaultAsyncEvent { private Authentication authentication; } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java index 7a1d5502e..c7a6dc643 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java @@ -1,6 +1,7 @@ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.hswebframework.web.authorization.*; import reactor.core.publisher.Flux; @@ -11,6 +12,7 @@ import java.util.function.Function; import java.util.stream.Collectors; @AllArgsConstructor +@Slf4j public class CompositeReactiveAuthenticationManager implements ReactiveAuthenticationManager { private final List providers; @@ -21,7 +23,10 @@ public class CompositeReactiveAuthenticationManager implements ReactiveAuthentic .stream() .map(manager -> manager .authenticate(request) - .onErrorResume((err) -> Mono.empty())) + .onErrorResume((err) -> { + log.warn("get user authenticate error", err); + return Mono.empty(); + })) .collect(Collectors.toList())) .take(1) .next(); @@ -34,7 +39,10 @@ public class CompositeReactiveAuthenticationManager implements ReactiveAuthentic .stream() .map(manager -> manager .getByUserId(userId) - .onErrorResume((err) -> Mono.empty()) + .onErrorResume((err) -> { + log.warn("get user [{}] authentication error", userId, err); + return Mono.empty(); + }) )) .flatMap(Function.identity()) .collectList() diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java index 8d8ef7e7d..f4aac6b40 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java @@ -58,10 +58,16 @@ public class SimpleAuthentication implements Authentication { } public SimpleAuthentication merge(Authentication authentication) { - Map mePermissionGroup = permissions.stream() + Map mePermissionGroup = permissions + .stream() .collect(Collectors.toMap(Permission::getId, Function.identity())); - user = authentication.getUser(); + + if (authentication.getUser() != null) { + user = authentication.getUser(); + } + attributes.putAll(authentication.getAttributes()); + for (Permission permission : authentication.getPermissions()) { Permission me = mePermissionGroup.get(permission.getId()); if (me == null) { @@ -72,7 +78,6 @@ public class SimpleAuthentication implements Authentication { me.getDataAccesses().addAll(permission.getDataAccesses()); } - for (Dimension dimension : authentication.getDimensions()) { if (!getDimension(dimension.getType(), dimension.getId()).isPresent()) { dimensions.add(dimension); @@ -85,14 +90,27 @@ public class SimpleAuthentication implements Authentication { public Authentication copy(BiPredicate permissionFilter, Predicate dimension) { SimpleAuthentication authentication = new SimpleAuthentication(); - authentication.setUser(user); authentication.setDimensions(dimensions.stream().filter(dimension).collect(Collectors.toList())); authentication.setPermissions(permissions - .stream() - .map(permission -> permission.copy(action -> permissionFilter.test(permission, action), conf -> true)) - .filter(per -> !per.getActions().isEmpty()) - .collect(Collectors.toList()) + .stream() + .map(permission -> permission.copy(action -> permissionFilter.test(permission, action), conf -> true)) + .filter(per -> !per.getActions().isEmpty()) + .collect(Collectors.toList()) ); + authentication.setUser(user); return authentication; } + + public void setUser(User user) { + this.user = user; + dimensions.add(user); + } + + public void setDimensions(List dimensions) { + this.dimensions.addAll(dimensions); + } + + public void addDimension(Dimension dimension) { + this.dimensions.add(dimension); + } } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java index a33b5fa3f..01e3932f5 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java @@ -53,9 +53,13 @@ public class RedisUserTokenManager implements UserTokenManager { .buffer(Flux.interval(Duration.ofSeconds(10)), HashSet::new) .flatMap(list -> Flux .fromIterable(list) - .flatMap(token -> operations - .expire(getTokenRedisKey(token.getToken()), Duration.ofMillis(token.getMaxInactiveInterval())) - .then()) + .flatMap(token -> { + String key = getTokenRedisKey(token.getToken()); + return Mono + .zip(this.userTokenStore.put(key, "lastRequestTime", token.getLastRequestTime()), + this.operations.expire(key, Duration.ofMillis(token.getMaxInactiveInterval()))) + .then(); + }) .onErrorResume(err -> Mono.empty())) .subscribe(); @@ -103,7 +107,7 @@ public class RedisUserTokenManager implements UserTokenManager { return userTokenStore .entries(getTokenRedisKey(token)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) - .filter(map -> !map.isEmpty()) + .filter(map -> !map.isEmpty() && map.containsKey("token") && map.containsKey("userId")) .map(SimpleUserToken::of) .doOnNext(userToken -> localCache.put(userToken.getToken(), userToken)) .cast(UserToken.class); diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_en.properties b/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_en.properties index 9e1bcb7d1..a65a77de1 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_en.properties +++ b/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_en.properties @@ -2,6 +2,7 @@ error.access_denied=Access Denied error.permission_denied=Permission Denied [{0}]:{1} error.logged_in_elsewhere=User logged in elsewhere error.illegal_password=Bad username or password +error.illegal_user_password=Bad Password error.user_disabled=User is disabled # message.token_state_normal=Normal diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_zh.properties b/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_zh.properties index 4c349297b..9cf971ee4 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_zh.properties +++ b/hsweb-authorization/hsweb-authorization-api/src/main/resources/i18n/authentication/messages_zh.properties @@ -2,6 +2,7 @@ error.access_denied=暂无权限,请联系管理员! error.permission_denied=当前用户无权限[{0}]:{1} error.logged_in_elsewhere=该用户已在其他地方登陆 error.illegal_password=用户名或密码错误 +error.illegal_user_password=密码错误 error.user_disabled=用户已被禁用 # message.token_state_normal=正常 diff --git a/hsweb-authorization/hsweb-authorization-basic/pom.xml b/hsweb-authorization/hsweb-authorization-basic/pom.xml index 615c07198..610b39c74 100644 --- a/hsweb-authorization/hsweb-authorization-basic/pom.xml +++ b/hsweb-authorization/hsweb-authorization-basic/pom.xml @@ -5,7 +5,7 @@ hsweb-authorization org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java index d1d59ba53..23171ebd2 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java @@ -64,7 +64,8 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor AuthorizingContext context, Supplier> invoker) { - return Authentication.currentReactive() + return Authentication + .currentReactive() .switchIfEmpty(Mono.error(UnAuthorizedException::new)) .flatMapMany(auth -> { context.setAuthentication(auth); @@ -133,8 +134,7 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor context.setAuthentication(authentication); isControl = true; - Phased dataAccessPhased = null; - dataAccessPhased = definition.getResources().getPhased(); + Phased dataAccessPhased = definition.getResources().getPhased(); if (definition.getPhased() == Phased.before) { //RDAC before authorizingHandler.handRBAC(context); diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java index 50e4510a9..3ba9465d1 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java @@ -145,6 +145,12 @@ public class AuthorizingHandlerAutoConfiguration { return new ReactiveUserTokenController(); } + @Bean + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + public BearerTokenParser bearerTokenParser() { + return new BearerTokenParser(); + } + @Configuration public static class DataAccessHandlerProcessor implements BeanPostProcessor { diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java index d5c556493..dd334acf8 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java @@ -38,7 +38,7 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition { private String message = "error.access_denied"; - private Phased phased; + private Phased phased = Phased.before; @Override public boolean isEmpty() { @@ -65,6 +65,7 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition { getResources().getResources().clear(); getDimensions().getDimensions().clear(); } + setPhased(ann.phased()); getResources().setPhased(ann.phased()); for (Resource resource : ann.resources()) { putAnnotation(resource); @@ -97,6 +98,8 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition { putAnnotation(resource, action); } resource.setGroup(new ArrayList<>(Arrays.asList(ann.group()))); + setPhased(ann.phased()); + getResources().setPhased(ann.phased()); resources.addResource(resource, ann.merge()); } @@ -132,8 +135,8 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition { return; } definition.getDataAccess() - .getDataAccessTypes() - .add(typeDefinition); + .getDataAccessTypes() + .add(typeDefinition); } public void putAnnotation(ResourceActionDefinition definition, DataAccessType dataAccessType) { @@ -147,8 +150,8 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition { typeDefinition.setConfiguration(dataAccessType.configuration()); typeDefinition.setDescription(String.join("\n", dataAccessType.description())); definition.getDataAccess() - .getDataAccessTypes() - .add(typeDefinition); + .getDataAccessTypes() + .add(typeDefinition); } } diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedAuthenticationProperties.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedAuthenticationProperties.java index e4a09e3c0..1b35e4b1a 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedAuthenticationProperties.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedAuthenticationProperties.java @@ -2,6 +2,7 @@ package org.hswebframework.web.authorization.basic.embed; import lombok.Getter; import lombok.Setter; +import org.apache.commons.collections4.MapUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationRequest; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; @@ -69,7 +70,10 @@ public class EmbedAuthenticationProperties implements InitializingBean { for (Map.Entry stringObjectEntry : objectMap.entrySet()) { if (stringObjectEntry.getValue() instanceof Map) { Map mapVal = ((Map) stringObjectEntry.getValue()); - boolean maybeIsList = mapVal.keySet().stream().allMatch(org.hswebframework.utils.StringUtils::isInt); + boolean maybeIsList = mapVal + .keySet() + .stream() + .allMatch(org.hswebframework.utils.StringUtils::isInt); if (maybeIsList) { stringObjectEntry.setValue(mapVal.values()); } @@ -82,20 +86,23 @@ public class EmbedAuthenticationProperties implements InitializingBean { } public Authentication authenticate(AuthenticationRequest request) { - if(request instanceof PlainTextUsernamePasswordAuthenticationRequest){ + if (MapUtils.isEmpty(users)) { + return null; + } + if (request instanceof PlainTextUsernamePasswordAuthenticationRequest) { PlainTextUsernamePasswordAuthenticationRequest pwdReq = ((PlainTextUsernamePasswordAuthenticationRequest) request); - return users.values() - .stream() - .filter(user -> - pwdReq.getUsername().equals(user.getUsername()) - && pwdReq.getPassword().equals(user.getPassword())) - .findFirst() - .map(EmbedAuthenticationInfo::getId) - .map(authentications::get) - .orElseThrow(() -> new ValidationException("用户不存在")); + for (EmbedAuthenticationInfo user : users.values()) { + if (pwdReq.getUsername().equals(user.getUsername())) { + if (pwdReq.getPassword().equals(user.getPassword())) { + return user.toAuthentication(dataAccessConfigBuilderFactory); + } + return null; + } + } + return null; } - throw new UnsupportedOperationException("不支持的授权请求:"+request); + throw new UnsupportedOperationException("不支持的授权请求:" + request); } public Optional getAuthentication(String userId) { diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedReactiveAuthenticationManager.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedReactiveAuthenticationManager.java index 6aabf28d9..94fbd6739 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedReactiveAuthenticationManager.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedReactiveAuthenticationManager.java @@ -1,6 +1,8 @@ package org.hswebframework.web.authorization.basic.embed; import lombok.AllArgsConstructor; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationRequest; import org.hswebframework.web.authorization.ReactiveAuthenticationManager; @@ -22,7 +24,16 @@ public class EmbedReactiveAuthenticationManager implements ReactiveAuthenticatio @Override public Mono authenticate(Mono request) { - return request.map(properties::authenticate); + if (MapUtils.isEmpty(properties.getUsers())) { + return Mono.empty(); + } + return request. + handle((req, sink) -> { + Authentication auth = properties.authenticate(req); + if (auth != null) { + sink.next(auth); + } + }); } diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/BearerTokenParser.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/BearerTokenParser.java new file mode 100644 index 000000000..d7c49d176 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/BearerTokenParser.java @@ -0,0 +1,22 @@ +package org.hswebframework.web.authorization.basic.web; + +import org.hswebframework.web.authorization.token.ParsedToken; +import org.springframework.http.HttpHeaders; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class BearerTokenParser implements ReactiveUserTokenParser { + @Override + public Mono parseToken(ServerWebExchange exchange) { + + String token = exchange + .getRequest() + .getHeaders() + .getFirst(HttpHeaders.AUTHORIZATION); + + if (token != null && token.startsWith("Bearer ")) { + return Mono.just(ParsedToken.of("bearer", token.substring(7))); + } + return Mono.empty(); + } +} diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/DefaultUserTokenGenPar.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/DefaultUserTokenGenPar.java index 0e4c1810f..b1948ef42 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/DefaultUserTokenGenPar.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/DefaultUserTokenGenPar.java @@ -62,16 +62,6 @@ public class DefaultUserTokenGenPar implements ReactiveUserTokenGenerator, React if (token == null) { return Mono.empty(); } - return Mono.just(new ParsedToken() { - @Override - public String getToken() { - return token; - } - - @Override - public String getType() { - return getTokenType(); - } - }); + return Mono.just(ParsedToken.of(getTokenType(),token)); } } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/pom.xml b/hsweb-authorization/hsweb-authorization-oauth2/pom.xml index f86d28db9..0c679b5fd 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/pom.xml +++ b/hsweb-authorization/hsweb-authorization-oauth2/pom.xml @@ -5,7 +5,7 @@ hsweb-authorization org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java index e90793c6c..7762b4e4c 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java @@ -40,4 +40,21 @@ public interface AccessTokenManager { */ Mono refreshAccessToken(String clientId, String refreshToken); + /** + * 移除token + * + * @param clientId clientId + * @param token token + * @return void + */ + Mono removeToken(String clientId, String token); + + /** + * 取消对用户的授权 + * + * @param clientId clientId + * @param userId 用户ID + * @return void + */ + Mono cancelGrant(String clientId, String userId); } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java index 6003a593f..42f1a575c 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java @@ -3,6 +3,8 @@ package org.hswebframework.web.oauth2.server; import org.hswebframework.web.authorization.ReactiveAuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationManager; import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser; +import org.hswebframework.web.authorization.token.UserToken; +import org.hswebframework.web.authorization.token.UserTokenManager; import org.hswebframework.web.oauth2.server.auth.ReactiveOAuth2AccessTokenParser; import org.hswebframework.web.oauth2.server.code.AuthorizationCodeGranter; import org.hswebframework.web.oauth2.server.code.DefaultAuthorizationCodeGranter; @@ -19,9 +21,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.core.ReactiveRedisOperations; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(OAuth2Properties.class) @@ -32,13 +36,13 @@ public class OAuth2ServerAutoConfiguration { @ConditionalOnClass(ReactiveUserTokenParser.class) static class ReactiveOAuth2AccessTokenParserConfiguration { - @Bean - @ConditionalOnBean(AccessTokenManager.class) - public ReactiveOAuth2AccessTokenParser reactiveOAuth2AccessTokenParser(AccessTokenManager accessTokenManager) { - ReactiveOAuth2AccessTokenParser parser = new ReactiveOAuth2AccessTokenParser(accessTokenManager); - ReactiveAuthenticationHolder.addSupplier(parser); - return parser; - } +// @Bean +// @ConditionalOnBean(AccessTokenManager.class) +// public ReactiveOAuth2AccessTokenParser reactiveOAuth2AccessTokenParser(AccessTokenManager accessTokenManager) { +// ReactiveOAuth2AccessTokenParser parser = new ReactiveOAuth2AccessTokenParser(accessTokenManager); +// ReactiveAuthenticationHolder.addSupplier(parser); +// return parser; +// } } @Configuration(proxyBeanMethods = false) @@ -48,9 +52,11 @@ public class OAuth2ServerAutoConfiguration { @Bean @ConditionalOnMissingBean - public AccessTokenManager accessTokenManager(ReactiveRedisConnectionFactory redisConnectionFactory, + public AccessTokenManager accessTokenManager(ReactiveRedisOperations redis, + UserTokenManager tokenManager, OAuth2Properties properties) { - RedisAccessTokenManager manager = new RedisAccessTokenManager(redisConnectionFactory); + @SuppressWarnings("all") + RedisAccessTokenManager manager = new RedisAccessTokenManager((ReactiveRedisOperations) redis, tokenManager); manager.setTokenExpireIn((int) properties.getTokenExpireIn().getSeconds()); manager.setRefreshExpireIn((int) properties.getRefreshTokenIn().getSeconds()); return manager; @@ -59,15 +65,17 @@ public class OAuth2ServerAutoConfiguration { @Bean @ConditionalOnMissingBean public ClientCredentialGranter clientCredentialGranter(ReactiveAuthenticationManager authenticationManager, - AccessTokenManager accessTokenManager) { - return new DefaultClientCredentialGranter(authenticationManager, accessTokenManager); + AccessTokenManager accessTokenManager, + ApplicationEventPublisher eventPublisher) { + return new DefaultClientCredentialGranter(authenticationManager, accessTokenManager,eventPublisher); } @Bean @ConditionalOnMissingBean public AuthorizationCodeGranter authorizationCodeGranter(AccessTokenManager tokenManager, + ApplicationEventPublisher eventPublisher, ReactiveRedisConnectionFactory redisConnectionFactory) { - return new DefaultAuthorizationCodeGranter(tokenManager, redisConnectionFactory); + return new DefaultAuthorizationCodeGranter(tokenManager,eventPublisher, redisConnectionFactory); } @Bean diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java index c3b53cabd..38e0341f6 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java @@ -4,13 +4,16 @@ import lombok.AllArgsConstructor; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.id.IDGenerator; import org.hswebframework.web.oauth2.ErrorType; +import org.hswebframework.web.oauth2.GrantType; import org.hswebframework.web.oauth2.OAuth2Constants; import org.hswebframework.web.oauth2.OAuth2Exception; import org.hswebframework.web.oauth2.server.AccessToken; import org.hswebframework.web.oauth2.server.AccessTokenManager; import org.hswebframework.web.oauth2.server.OAuth2Client; import org.hswebframework.web.oauth2.server.ScopePredicate; +import org.hswebframework.web.oauth2.server.event.OAuth2GrantedEvent; import org.hswebframework.web.oauth2.server.utils.OAuth2ScopeUtils; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisOperations; import org.springframework.data.redis.core.ReactiveRedisTemplate; @@ -25,11 +28,15 @@ public class DefaultAuthorizationCodeGranter implements AuthorizationCodeGranter private final AccessTokenManager accessTokenManager; + private final ApplicationEventPublisher eventPublisher; + private final ReactiveRedisOperations redis; @SuppressWarnings("all") - public DefaultAuthorizationCodeGranter(AccessTokenManager accessTokenManager, ReactiveRedisConnectionFactory connectionFactory) { - this(accessTokenManager, new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext + public DefaultAuthorizationCodeGranter(AccessTokenManager accessTokenManager, + ApplicationEventPublisher eventPublisher, + ReactiveRedisConnectionFactory connectionFactory) { + this(accessTokenManager, eventPublisher, new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext .newSerializationContext() .key((RedisSerializer) RedisSerializer.string()) .value(RedisSerializer.java()) @@ -48,9 +55,12 @@ public class DefaultAuthorizationCodeGranter implements AuthorizationCodeGranter request.getParameter(OAuth2Constants.scope).map(String::valueOf).ifPresent(codeCache::setScope); codeCache.setCode(code); codeCache.setClientId(client.getClientId()); + ScopePredicate permissionPredicate = OAuth2ScopeUtils.createScopePredicate(codeCache.getScope()); - codeCache.setAuthentication(authentication.copy((permission, action) -> permissionPredicate.test(permission.getId(), action), dimension -> true)); + codeCache.setAuthentication(authentication.copy( + (permission, action) -> permissionPredicate.test(permission.getId(), action), + dimension -> permissionPredicate.test(dimension.getType().getId(), dimension.getId()))); return redis @@ -72,16 +82,27 @@ public class DefaultAuthorizationCodeGranter implements AuthorizationCodeGranter .map(this::getRedisKey) .flatMap(redis.opsForValue()::get) .switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.ILLEGAL_CODE))) - .flatMap(cache -> redis - .opsForValue() - .delete(getRedisKey(cache.getCode())) - .thenReturn(cache)) + //移除code + .flatMap(cache -> redis.opsForValue().delete(getRedisKey(cache.getCode())).thenReturn(cache)) .flatMap(cache -> { if (!request.getClient().getClientId().equals(cache.getClientId())) { return Mono.error(new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID)); } - return accessTokenManager.createAccessToken(cache.getClientId(), cache.getAuthentication(), false); - }); + return accessTokenManager + .createAccessToken(cache.getClientId(), cache.getAuthentication(), false) + .flatMap(token -> new OAuth2GrantedEvent(request.getClient(), + token, + cache.getAuthentication(), + cache.getScope(), + GrantType.authorization_code, + request.getParameters()) + .publish(eventPublisher) + .onErrorResume(err -> accessTokenManager + .removeToken(cache.getClientId(), token.getAccessToken()) + .then(Mono.error(err))) + .thenReturn(token)); + }) + ; } } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/credential/DefaultClientCredentialGranter.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/credential/DefaultClientCredentialGranter.java index 74155bc44..08a78cf29 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/credential/DefaultClientCredentialGranter.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/credential/DefaultClientCredentialGranter.java @@ -2,9 +2,12 @@ package org.hswebframework.web.oauth2.server.credential; import lombok.AllArgsConstructor; import org.hswebframework.web.authorization.ReactiveAuthenticationManager; +import org.hswebframework.web.oauth2.GrantType; import org.hswebframework.web.oauth2.server.AccessToken; import org.hswebframework.web.oauth2.server.AccessTokenManager; import org.hswebframework.web.oauth2.server.OAuth2Client; +import org.hswebframework.web.oauth2.server.event.OAuth2GrantedEvent; +import org.springframework.context.ApplicationEventPublisher; import reactor.core.publisher.Mono; @AllArgsConstructor @@ -14,6 +17,8 @@ public class DefaultClientCredentialGranter implements ClientCredentialGranter { private final AccessTokenManager accessTokenManager; + private final ApplicationEventPublisher eventPublisher; + @Override public Mono requestToken(ClientCredentialRequest request) { @@ -21,6 +26,19 @@ public class DefaultClientCredentialGranter implements ClientCredentialGranter { return authenticationManager .getByUserId(client.getUserId()) - .flatMap(auth -> accessTokenManager.createAccessToken(client.getClientId(), auth, true)); + .flatMap(auth -> accessTokenManager + .createAccessToken(client.getClientId(), auth, true) + .flatMap(token -> new OAuth2GrantedEvent(client, + token, + auth, + "*", + GrantType.client_credentials, + request.getParameters()) + .publish(eventPublisher) + .onErrorResume(err -> accessTokenManager + .removeToken(client.getClientId(), token.getAccessToken()) + .then(Mono.error(err))) + .thenReturn(token)) + ); } } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/event/OAuth2GrantedEvent.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/event/OAuth2GrantedEvent.java new file mode 100644 index 000000000..38b341dcb --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/event/OAuth2GrantedEvent.java @@ -0,0 +1,32 @@ +package org.hswebframework.web.oauth2.server.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.event.DefaultAsyncEvent; +import org.hswebframework.web.oauth2.server.AccessToken; +import org.hswebframework.web.oauth2.server.OAuth2Client; + +import java.util.Map; + +/** + * OAuth2授权成功事件 + * + * @author zhouhao + * @since 4.0.15 + */ +@Getter +@AllArgsConstructor +public class OAuth2GrantedEvent extends DefaultAsyncEvent { + private final OAuth2Client client; + + private final AccessToken accessToken; + + private final Authentication authentication; + + private final String scope; + + private final String grantType; + + private final Map parameters; +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java index e6a0a0ac6..412cb00b8 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java @@ -4,6 +4,9 @@ import lombok.Getter; import lombok.Setter; import org.apache.commons.codec.digest.DigestUtils; import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.authorization.token.AuthenticationUserToken; +import org.hswebframework.web.authorization.token.UserTokenManager; +import org.hswebframework.web.authorization.token.redis.RedisUserTokenManager; import org.hswebframework.web.oauth2.ErrorType; import org.hswebframework.web.oauth2.OAuth2Exception; import org.hswebframework.web.oauth2.server.AccessToken; @@ -13,15 +16,20 @@ import org.springframework.data.redis.core.ReactiveRedisOperations; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.Duration; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; public class RedisAccessTokenManager implements AccessTokenManager { private final ReactiveRedisOperations tokenRedis; + private final UserTokenManager userTokenManager; + @Getter @Setter private int tokenExpireIn = 7200;//2小时 @@ -30,37 +38,49 @@ public class RedisAccessTokenManager implements AccessTokenManager { @Setter private int refreshExpireIn = 2592000; //30天 - public RedisAccessTokenManager(ReactiveRedisOperations tokenRedis) { + public RedisAccessTokenManager(ReactiveRedisOperations tokenRedis, + UserTokenManager userTokenManager) { this.tokenRedis = tokenRedis; + this.userTokenManager = userTokenManager; } @SuppressWarnings("all") public RedisAccessTokenManager(ReactiveRedisConnectionFactory connectionFactory) { - this(new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext + ReactiveRedisTemplate redis = new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext .newSerializationContext() .key((RedisSerializer) RedisSerializer.string()) .value(RedisSerializer.java()) .hashKey(RedisSerializer.string()) .hashValue(RedisSerializer.java()) - .build() - )); + .build()); + this.tokenRedis = redis; + this.userTokenManager = new RedisUserTokenManager(redis); } + @Override public Mono getAuthenticationByToken(String accessToken) { - - return tokenRedis - .opsForValue() - .get(createTokenRedisKey(accessToken)) - .map(RedisAccessToken::getAuthentication); + return userTokenManager + .getByToken(accessToken) + .filter(token -> token instanceof AuthenticationUserToken) + .map(t -> ((AuthenticationUserToken) t).getAuthentication()); } - private String createTokenRedisKey(String token) { - return "oauth2-token:" + token; + private String createTokenRedisKey(String clientId, String token) { + return "oauth2-token:" + clientId + ":" + token; } - private String createRefreshTokenRedisKey(String token) { - return "oauth2-refresh-token:" + token; + private String createUserTokenRedisKey(RedisAccessToken token) { + return createUserTokenRedisKey(token.getClientId(), token.getAuthentication().getUser().getId()); + } + + private String createUserTokenRedisKey(String clientId, String userId) { + return "oauth2-user-tokens:" + clientId + ":" + userId; + } + + + private String createRefreshTokenRedisKey(String clientId, String token) { + return "oauth2-refresh-token:" + clientId + ":" + token; } private String createSingletonTokenRedisKey(String clientId) { @@ -75,12 +95,41 @@ public class RedisAccessTokenManager implements AccessTokenManager { return storeToken(accessToken).thenReturn(accessToken); } + private Mono storeAuthToken(RedisAccessToken token) { + if (token.isSingleton()) { + return userTokenManager + .signIn(token.getAccessToken(), + createTokenType(token.getClientId()), + token.getAuthentication().getUser().getId(), + tokenExpireIn * 1000L) + .then(); + } else { + return userTokenManager + .signIn(token.getAccessToken(), + createTokenType(token.getClientId()), + token.getAuthentication().getUser().getId(), + tokenExpireIn * 1000L, + token.getAuthentication()) + .then(); + } + } + private Mono storeToken(RedisAccessToken token) { - return Mono - .zip( - tokenRedis.opsForValue().set(createTokenRedisKey(token.getAccessToken()), token, Duration.ofSeconds(tokenExpireIn)), - tokenRedis.opsForValue().set(createRefreshTokenRedisKey(token.getRefreshToken()), token, Duration.ofSeconds(refreshExpireIn)) - ).then(); + + return Flux + .merge(storeAuthToken(token), + tokenRedis + .opsForValue() + .set(createUserTokenRedisKey(token), token, Duration.ofSeconds(tokenExpireIn)), + tokenRedis + .opsForValue() + .set(createTokenRedisKey(token.getClientId(), + token.getAccessToken()), token, Duration.ofSeconds(tokenExpireIn)), + tokenRedis + .opsForValue() + .set(createRefreshTokenRedisKey(token.getClientId(), + token.getRefreshToken()), token, Duration.ofSeconds(refreshExpireIn))) + .then(); } private Mono doCreateSingletonAccessToken(String clientId, Authentication authentication) { @@ -111,7 +160,7 @@ public class RedisAccessTokenManager implements AccessTokenManager { @Override public Mono refreshAccessToken(String clientId, String refreshToken) { - String redisKey = createRefreshTokenRedisKey(refreshToken); + String redisKey = createRefreshTokenRedisKey(clientId, refreshToken); return tokenRedis .opsForValue() @@ -129,10 +178,15 @@ public class RedisAccessTokenManager implements AccessTokenManager { .as(result -> { // 单例token if (token.isSingleton()) { - return tokenRedis - .opsForValue() - .set(createSingletonTokenRedisKey(clientId), token, Duration.ofSeconds(tokenExpireIn)) - .then(result); + return userTokenManager + .signOutByToken(token.getAccessToken()) + .then( + tokenRedis + .opsForValue() + .set(createSingletonTokenRedisKey(clientId), token, Duration.ofSeconds(tokenExpireIn)) + .then(result) + ) + ; } return result; }) @@ -140,4 +194,58 @@ public class RedisAccessTokenManager implements AccessTokenManager { }); } + + @Override + public Mono removeToken(String clientId, String token) { + + return Flux + .merge(userTokenManager.signOutByToken(token), + tokenRedis.delete(createSingletonTokenRedisKey(clientId)), + tokenRedis.delete(createTokenRedisKey(clientId, token))) + .then(); + } + + @Override + public Mono cancelGrant(String clientId, String userId) { + //删除最新的refresh_token + Mono removeRefreshToken = tokenRedis + .opsForValue() + .get(createUserTokenRedisKey(clientId, userId)) + .flatMap(t -> tokenRedis + .opsForValue() + .delete(createRefreshTokenRedisKey(t.getClientId(), t.getRefreshToken()))) + .then(); + + //删除access_token + Mono removeAccessToken = userTokenManager + .getByUserId(userId) + .flatMap(token -> { + //其他类型的token 忽略 + if (!(createTokenType(clientId)).equals(token.getType())) { + return Mono.empty(); + } + return tokenRedis + .opsForValue() + .get(createTokenRedisKey(clientId, token.getToken())) + .flatMap(t -> { + //移除token + return tokenRedis + .delete(createTokenRedisKey(t.getClientId(), t.getAccessToken())) + //移除token对应的refresh_token + .then(tokenRedis + .opsForValue() + .delete(createRefreshTokenRedisKey(t.getClientId(), t.getRefreshToken()))); + }) + .then(userTokenManager.signOutByToken(token.getToken())); + }) + .then(); + + return Flux + .merge(removeRefreshToken, removeAccessToken) + .then(); + } + + private String createTokenType(String clientId) { + return "oauth2-" + clientId; + } } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtils.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtils.java index 4ac30fff0..40806f974 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtils.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtils.java @@ -6,6 +6,10 @@ import org.springframework.util.StringUtils; import java.util.*; /** + *
{@code
+ *   role:* user:* device-manager:*
+ * }
+ * * @author zhouhao * @since 4.0.8 */ @@ -23,10 +27,13 @@ public class OAuth2ScopeUtils { Set acts = actions.computeIfAbsent(per, k -> new HashSet<>()); acts.addAll(Arrays.asList(permissions).subList(1, permissions.length)); } - + //全部授权 + if (actions.containsKey("*")) { + return ((permission, action) -> true); + } return ((permission, action) -> Optional .ofNullable(actions.get(permission)) - .map(acts -> action.length == 0 || acts.containsAll(Arrays.asList(action))) + .map(acts -> action.length == 0 || acts.contains("*") || acts.containsAll(Arrays.asList(action))) .orElse(false)); } } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java index b71ee6468..123ab20f9 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java @@ -19,7 +19,9 @@ import org.hswebframework.web.oauth2.server.OAuth2GrantService; import org.hswebframework.web.oauth2.server.code.AuthorizationCodeRequest; import org.hswebframework.web.oauth2.server.code.AuthorizationCodeTokenRequest; import org.hswebframework.web.oauth2.server.credential.ClientCredentialRequest; +import org.hswebframework.web.oauth2.server.event.OAuth2GrantedEvent; import org.hswebframework.web.oauth2.server.refresh.RefreshTokenRequest; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java index 4f9fdaacc..6e0545441 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java @@ -7,6 +7,7 @@ import org.hswebframework.web.oauth2.server.OAuth2Client; import org.hswebframework.web.oauth2.server.RedisHelper; import org.hswebframework.web.oauth2.server.impl.RedisAccessTokenManager; import org.junit.Test; +import org.springframework.context.support.StaticApplicationContext; import reactor.test.StepVerifier; import java.util.Collections; @@ -20,7 +21,7 @@ public class DefaultAuthorizationCodeGranterTest { public void testRequestToken() { DefaultAuthorizationCodeGranter codeGranter = new DefaultAuthorizationCodeGranter( - new RedisAccessTokenManager(RedisHelper.factory), RedisHelper.factory + new RedisAccessTokenManager(RedisHelper.factory), new StaticApplicationContext(), RedisHelper.factory ); OAuth2Client client = new OAuth2Client(); diff --git a/hsweb-authorization/pom.xml b/hsweb-authorization/pom.xml index e5a24f830..28f2b9278 100644 --- a/hsweb-authorization/pom.xml +++ b/hsweb-authorization/pom.xml @@ -5,7 +5,7 @@ hsweb-framework org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-commons/hsweb-commons-api/pom.xml b/hsweb-commons/hsweb-commons-api/pom.xml index b4ac2168d..677a3f2df 100644 --- a/hsweb-commons/hsweb-commons-api/pom.xml +++ b/hsweb-commons/hsweb-commons-api/pom.xml @@ -5,7 +5,7 @@ hsweb-commons org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-commons/hsweb-commons-crud/pom.xml b/hsweb-commons/hsweb-commons-crud/pom.xml index e029e2a4e..7b16b1518 100644 --- a/hsweb-commons/hsweb-commons-crud/pom.xml +++ b/hsweb-commons/hsweb-commons-crud/pom.xml @@ -5,7 +5,7 @@ hsweb-commons org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java index 9042b6c9d..393ca3cb8 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java @@ -38,6 +38,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; @@ -112,22 +114,24 @@ public class EasyormConfiguration { }; } + @Bean - public EntityEventListener entityEventListener(ApplicationEventPublisher eventPublisher, - ObjectProvider customizers) { - DefaultEntityEventListenerConfigure configure = new DefaultEntityEventListenerConfigure(); - customizers.forEach(customizer -> customizer.customize(configure)); - return new EntityEventListener(eventPublisher, configure); + public CreatorEventListener creatorEventListener() { + return new CreatorEventListener(); } + @Bean public ValidateEventListener validateEventListener() { return new ValidateEventListener(); } @Bean - public CreatorEventListener creatorEventListener() { - return new CreatorEventListener(); + public EntityEventListener entityEventListener(ApplicationEventPublisher eventPublisher, + ObjectProvider customizers) { + DefaultEntityEventListenerConfigure configure = new DefaultEntityEventListenerConfigure(); + customizers.forEach(customizer -> customizer.customize(configure)); + return new EntityEventListener(eventPublisher, configure); } @Bean diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/entity/factory/MapperEntityFactory.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/entity/factory/MapperEntityFactory.java index c61c6f805..d1a201c41 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/entity/factory/MapperEntityFactory.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/entity/factory/MapperEntityFactory.java @@ -27,8 +27,10 @@ import org.hswebframework.web.bean.FastBeanCopier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; /** @@ -37,9 +39,11 @@ import java.util.function.Supplier; */ @SuppressWarnings("unchecked") public class MapperEntityFactory implements EntityFactory, BeanFactory { - private Map realTypeMapper = new HashMap<>(); - private Logger logger = LoggerFactory.getLogger(this.getClass()); - private Map copierCache = new HashMap<>(); + @SuppressWarnings("all") + private final Map, Mapper> realTypeMapper = new ConcurrentHashMap<>(); + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + @SuppressWarnings("all") + private final Map copierCache = new ConcurrentHashMap<>(); private static final DefaultMapperFactory DEFAULT_MAPPER_FACTORY = clazz -> { String simpleClassName = clazz.getPackage().getName().concat(".Simple").concat(clazz.getSimpleName()); @@ -68,6 +72,16 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory { this.realTypeMapper.putAll(realTypeMapper); } + public MapperEntityFactory addMapping(Class target, Supplier mapper) { + realTypeMapper.put(target, new Mapper(target, mapper)); + return this; + } + + public MapperEntityFactory addMappingIfAbsent(Class target, Supplier mapper) { + realTypeMapper.putIfAbsent(target, new Mapper(target, mapper)); + return this; + } + public MapperEntityFactory addMapping(Class target, Mapper mapper) { realTypeMapper.put(target, mapper); return this; @@ -216,8 +230,8 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory { } public static class Mapper { - Class target; - Supplier instanceGetter; + final Class target; + final Supplier instanceGetter; public Mapper(Class target, Supplier instanceGetter) { this.target = target; @@ -242,16 +256,17 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory { } static class DefaultInstanceGetter implements Supplier { - Class type; + final Constructor constructor; + @SneakyThrows public DefaultInstanceGetter(Class type) { - this.type = type; + this.constructor = type.getConstructor(); } @Override @SneakyThrows public T get() { - return type.newInstance(); + return constructor.newInstance(); } } } diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CompositeEventListener.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CompositeEventListener.java index 638850b9f..f07ece5db 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CompositeEventListener.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CompositeEventListener.java @@ -5,7 +5,9 @@ import lombok.Setter; import org.hswebframework.ezorm.rdb.events.EventContext; import org.hswebframework.ezorm.rdb.events.EventListener; import org.hswebframework.ezorm.rdb.events.EventType; +import org.springframework.core.Ordered; +import java.util.Comparator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; @@ -24,5 +26,6 @@ public class CompositeEventListener implements EventListener { public void addListener(EventListener eventListener) { eventListeners.add(eventListener); + eventListeners.sort(Comparator.comparingLong(e -> e instanceof Ordered ? ((Ordered) e).getOrder() : Ordered.LOWEST_PRECEDENCE)); } } diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CreatorEventListener.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CreatorEventListener.java index 9662899d3..54f1f14af 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CreatorEventListener.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/CreatorEventListener.java @@ -12,19 +12,21 @@ import org.hswebframework.web.api.crud.entity.RecordModifierEntity; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.validator.CreateGroup; import org.hswebframework.web.validator.UpdateGroup; +import org.springframework.core.Ordered; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; /** * 自动填充创建人和修改人信息 */ -public class CreatorEventListener implements EventListener { +public class CreatorEventListener implements EventListener, Ordered { @Override public String getId() { @@ -60,27 +62,44 @@ public class CreatorEventListener implements EventListener { } protected void doApplyCreator(EventType type, EventContext context, Authentication auth) { - context.get(MappingContextKeys.instance) - .ifPresent(obj -> { - if (obj instanceof Collection) { - applyCreator(auth, ((Collection) obj), type != MappingEventTypes.update_before); - } else { - applyCreator(auth, obj, type != MappingEventTypes.update_before); - } - }); + Object instance = context.get(MappingContextKeys.instance).orElse(null); + if (instance != null) { + if (instance instanceof Collection) { + applyCreator(auth, context, ((Collection) instance), type != MappingEventTypes.update_before); + } else { + applyCreator(auth, context, instance, type != MappingEventTypes.update_before); + } + } + + context + .get(MappingContextKeys.updateColumnInstance) + .ifPresent(map -> applyCreator(auth, context, map, type != MappingEventTypes.update_before)); + } - public void applyCreator(Authentication auth, Object entity, boolean updateCreator) { - if (updateCreator && entity instanceof RecordCreationEntity) { - RecordCreationEntity e = (RecordCreationEntity) entity; - if (ObjectUtils.isEmpty(e.getCreatorId())) { - e.setCreatorId(auth.getUser().getId()); - e.setCreatorName(auth.getUser().getName()); - } - if (e.getCreateTime() == null) { - e.setCreateTimeNow(); + public void applyCreator(Authentication auth, + EventContext context, + Object entity, + boolean updateCreator) { + long now = System.currentTimeMillis(); + if (updateCreator) { + if (entity instanceof RecordCreationEntity) { + RecordCreationEntity e = (RecordCreationEntity) entity; + if (ObjectUtils.isEmpty(e.getCreatorId())) { + e.setCreatorId(auth.getUser().getId()); + e.setCreatorName(auth.getUser().getName()); + } + if (e.getCreateTime() == null) { + e.setCreateTime(now); + } + } else if (entity instanceof Map) { + Map map = ((Map) entity); + map.putIfAbsent("creatorId", auth.getUser().getId()); + map.putIfAbsent("creatorName", auth.getUser().getName()); + map.putIfAbsent("createTime", now); } + } if (entity instanceof RecordModifierEntity) { RecordModifierEntity e = (RecordModifierEntity) entity; @@ -89,15 +108,26 @@ public class CreatorEventListener implements EventListener { e.setModifierName(auth.getUser().getName()); } if (e.getModifyTime() == null) { - e.setModifyTimeNow(); + e.setModifyTime(now); } + } else if (entity instanceof Map) { + Map map = ((Map) entity); + map.putIfAbsent("modifierId", auth.getUser().getId()); + map.putIfAbsent("modifierName", auth.getUser().getName()); + map.putIfAbsent("modifyTime", now); + } } - public void applyCreator(Authentication auth, Collection entities, boolean updateCreator) { + public void applyCreator(Authentication auth, EventContext context, Collection entities, boolean updateCreator) { for (Object entity : entities) { - applyCreator(auth, entity, updateCreator); + applyCreator(auth, context, entity, updateCreator); } } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } } diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityEventListener.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityEventListener.java index 94e82b682..80e31bf9a 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityEventListener.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/EntityEventListener.java @@ -22,6 +22,7 @@ import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.event.AsyncEvent; import org.hswebframework.web.event.GenericsPayloadApplicationEvent; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.Ordered; import reactor.core.publisher.Mono; import reactor.function.Function3; import reactor.util.function.Tuple2; @@ -36,7 +37,10 @@ import static org.hswebframework.web.crud.events.EntityEventHelper.*; @SuppressWarnings("all") @AllArgsConstructor -public class EntityEventListener implements EventListener { +public class EntityEventListener implements EventListener, Ordered { + + public static final ContextKey> readyToDeleteContextKey = ContextKey.of("readyToDelete"); + public static final ContextKey> readyToUpdateContextKey = ContextKey.of("readyToUpdate"); private final ApplicationEventPublisher eventPublisher; @@ -133,39 +137,37 @@ public class EntityEventListener implements EventListener { protected List createAfterData(List olds, EventContext context) { List newValues = new ArrayList<>(olds.size()); + EntityColumnMapping mapping = context .get(MappingContextKeys.columnMapping) .orElseThrow(UnsupportedOperationException::new); - TableOrViewMetadata table = context.get(ContextKeys.table).orElseThrow(UnsupportedOperationException::new); - RDBColumnMetadata idColumn = table - .getColumns() - .stream() - .filter(RDBColumnMetadata::isPrimaryKey) - .findFirst() - .orElse(null); - if (idColumn == null) { - return Collections.emptyList(); - } + + Map columns = context + .get(MappingContextKeys.updateColumnInstance) + .orElse(Collections.emptyMap()); + for (Object old : olds) { - Object newValue = context - .get(MappingContextKeys.updateColumnInstance) - .map(map -> { - Object data = FastBeanCopier.copy(map, FastBeanCopier.copy(old, mapping.newInstance())); - for (Map.Entry entry : map.entrySet()) { - //set null - if (entry.getValue() == null - || entry.getValue() instanceof NullValue) { - GlobalConfig - .getPropertyOperator() - .setProperty(data, entry.getKey(), null); - } - } - return data; - }) - .orElseThrow(() -> { - return new IllegalArgumentException("can not get update instance"); - }); - newValues.add(newValue); + Object data = FastBeanCopier.copy(old, mapping.newInstance()); + for (Map.Entry entry : columns.entrySet()) { + + RDBColumnMetadata column = mapping.getColumnByName(entry.getKey()).orElse(null); + if (column == null) { + continue; + } + + Object value = entry.getValue(); + + //set null + if (value instanceof NullValue) { + value = null; + } + + GlobalConfig + .getPropertyOperator() + .setProperty(data, column.getAlias(), value); + + } + newValues.add(data); } return newValues; } @@ -230,6 +232,7 @@ public class EntityEventListener implements EventListener { holder.invoke(this.doAsyncEvent(() -> { Tuple2, List> _tmp = updated.get(); if (_tmp != null) { + return sendUpdateEvent(_tmp.getT1(), _tmp.getT2(), entityType, @@ -297,6 +300,9 @@ public class EntityEventListener implements EventListener { .setParam(dslUpdate.toQueryParam()) .fetch() .collectList() + .doOnNext(list->{ + context.set(readyToDeleteContextKey, list); + }) .filter(CollectionUtils::isNotEmpty) .flatMap(list -> { deleted.set(list); @@ -468,4 +474,9 @@ public class EntityEventListener implements EventListener { return eventSupplier.get(); }); } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 100; + } } diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventListener.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventListener.java index be7e1fb00..4d52c1c14 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventListener.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventListener.java @@ -10,11 +10,12 @@ import org.hswebframework.web.api.crud.entity.Entity; import org.hswebframework.web.i18n.LocaleUtils; import org.hswebframework.web.validator.CreateGroup; import org.hswebframework.web.validator.UpdateGroup; +import org.springframework.core.Ordered; import java.util.List; import java.util.Optional; -public class ValidateEventListener implements EventListener { +public class ValidateEventListener implements EventListener, Ordered { @Override public String getId() { @@ -35,9 +36,10 @@ public class ValidateEventListener implements EventListener { resultHolder .ifPresent(holder -> holder .invoke(LocaleUtils - .currentReactive() - .doOnNext(locale -> LocaleUtils.doWith(locale, (l) -> tryValidate(type, context))) - .then() + .doInReactive(() -> { + tryValidate(type, context); + return null; + }) )); } else { tryValidate(type, context); @@ -72,4 +74,9 @@ public class ValidateEventListener implements EventListener { .ifPresent(entity -> entity.tryValidate(UpdateGroup.class)); } } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 1000; + } } diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java index f3b4617ad..c163b9fed 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java @@ -1,5 +1,6 @@ package org.hswebframework.web.crud.web; +import io.r2dbc.spi.R2dbcException; import lombok.extern.slf4j.Slf4j; import org.hswebframework.web.CodeConstants; import org.hswebframework.web.authorization.exception.AccessDenyException; @@ -15,6 +16,7 @@ import org.hswebframework.web.logger.ReactiveLogger; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; +import org.springframework.transaction.TransactionException; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; @@ -43,6 +45,25 @@ import java.util.stream.Collectors; @Order public class CommonErrorControllerAdvice { + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Mono> handleException(TransactionException e) { + log.error(e.getMessage(), e); + return LocaleUtils + .resolveMessageReactive("error.internal_server_error") + .map(msg -> ResponseMessage.error(500, "error." + e.getClass().getSimpleName(), msg)); + } + + @ExceptionHandler + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Mono> handleException(R2dbcException e) { + log.error(e.getMessage(), e); + return LocaleUtils + .resolveMessageReactive("error.internal_server_error") + .map(msg -> ResponseMessage.error(500, "error." + e.getClass().getSimpleName(), msg)); + } + + @ExceptionHandler @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Mono> handleException(BusinessException e) { diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonWebFluxConfiguration.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonWebFluxConfiguration.java index 9a2c8e7b6..dc3f338a4 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonWebFluxConfiguration.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonWebFluxConfiguration.java @@ -1,6 +1,5 @@ package org.hswebframework.web.crud.web; -import io.r2dbc.spi.R2dbcDataIntegrityViolationException; import org.hswebframework.web.i18n.WebFluxLocaleFilter; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -33,12 +32,6 @@ public class CommonWebFluxConfiguration { return new ResponseMessageWrapper(codecConfigurer.getWriters(), resolver, registry); } - @Bean - public R2dbcDataIntegrityViolationException r2dbcDataIntegrityViolationException(){ - return new R2dbcDataIntegrityViolationException(); - } - - @Bean public WebFilter localeWebFilter() { return new WebFluxLocaleFilter(); diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/ResponseMessageWrapperAdvice.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/ResponseMessageWrapperAdvice.java index b57bcc504..d4f32299c 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/ResponseMessageWrapperAdvice.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/ResponseMessageWrapperAdvice.java @@ -20,6 +20,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.annotation.Nonnull; +import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; @@ -82,17 +83,21 @@ public class ResponseMessageWrapperAdvice implements ResponseBodyAdvice if (body instanceof Mono) { return ((Mono) body) .map(ResponseMessage::ok) - .switchIfEmpty(Mono.just(ResponseMessage.ok())); + .switchIfEmpty(Mono.fromSupplier(ResponseMessage::ok)); } if (body instanceof Flux) { return ((Flux) body) .collectList() .map(ResponseMessage::ok) - .switchIfEmpty(Mono.just(ResponseMessage.ok())); + .switchIfEmpty(Mono.fromSupplier(ResponseMessage::ok)); } - if (body instanceof String) { + + Method method = returnType.getMethod(); + + if (method != null && returnType.getMethod().getReturnType() == String.class) { return JSON.toJSONString(ResponseMessage.ok(body)); } + return ResponseMessage.ok(body); } diff --git a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties index f35bfc889..8e5ffa046 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties +++ b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties @@ -2,4 +2,5 @@ error.unsupported_media_type=Unsupported media type error.not_acceptable_media_type=Not acceptable media type error.method_not_allowed=Method not allowed error.duplicate_data=Duplicate data -error.data_error=Data error \ No newline at end of file +error.data_error=Data error +error.internal_server_error = Internal server error \ No newline at end of file diff --git a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties index 9b9331846..946a0c0eb 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties +++ b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties @@ -2,4 +2,5 @@ error.unsupported_media_type=不支持的请求类型 error.not_acceptable_media_type=不支持的媒体类型 error.method_not_allowed=不支持的请求方法 error.duplicate_data=重复的数据 -error.data_error=数据错误 \ No newline at end of file +error.data_error=数据错误 +error.internal_server_error=服务器内部错误 \ No newline at end of file diff --git a/hsweb-commons/pom.xml b/hsweb-commons/pom.xml index a3fed49ca..730db0e40 100644 --- a/hsweb-commons/pom.xml +++ b/hsweb-commons/pom.xml @@ -23,7 +23,7 @@ hsweb-framework org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 ../pom.xml 4.0.0 diff --git a/hsweb-concurrent/hsweb-concurrent-cache/pom.xml b/hsweb-concurrent/hsweb-concurrent-cache/pom.xml index fe2f8afce..d972315bc 100644 --- a/hsweb-concurrent/hsweb-concurrent-cache/pom.xml +++ b/hsweb-concurrent/hsweb-concurrent-cache/pom.xml @@ -5,7 +5,7 @@ hsweb-concurrent org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/RedisReactiveCache.java b/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/RedisReactiveCache.java index df5b53a8c..c8d6f4387 100644 --- a/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/RedisReactiveCache.java +++ b/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/RedisReactiveCache.java @@ -64,9 +64,8 @@ public class RedisReactiveCache implements ReactiveCache { } protected Mono handleError(Throwable error) { - return Mono.fromRunnable(() -> { - log.error(error.getMessage(), error); - }); + log.error(error.getMessage(), error); + return Mono.empty(); } @Override diff --git a/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/UnSupportedReactiveCache.java b/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/UnSupportedReactiveCache.java index 4200e2de7..68a2b8ed2 100644 --- a/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/UnSupportedReactiveCache.java +++ b/hsweb-concurrent/hsweb-concurrent-cache/src/main/java/org/hswebframework/web/cache/supports/UnSupportedReactiveCache.java @@ -15,10 +15,11 @@ import java.util.function.Supplier; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class UnSupportedReactiveCache implements ReactiveCache { - private static final UnSupportedReactiveCache INSTANCE = new UnSupportedReactiveCache(); + private static final UnSupportedReactiveCache INSTANCE = new UnSupportedReactiveCache<>(); + @SuppressWarnings("all") public static ReactiveCache getInstance() { - return INSTANCE; + return (UnSupportedReactiveCache)INSTANCE; } @Override diff --git a/hsweb-concurrent/pom.xml b/hsweb-concurrent/pom.xml index 4b6c20efb..be658a0ac 100644 --- a/hsweb-concurrent/pom.xml +++ b/hsweb-concurrent/pom.xml @@ -5,7 +5,7 @@ hsweb-framework org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 4.0.0 diff --git a/hsweb-core/pom.xml b/hsweb-core/pom.xml index e432f92e6..664ec0dcd 100644 --- a/hsweb-core/pom.xml +++ b/hsweb-core/pom.xml @@ -5,7 +5,7 @@ hsweb-framework org.hswebframework.web - 4.0.15-SNAPSHOT + 4.0.15 ../pom.xml 4.0.0 @@ -118,7 +118,18 @@ jctools-core org.jctools - 2.1.2 + 4.0.1 + + + + io.netty + netty-common + + + + io.seruco.encoding + base62 + 0.1.3 diff --git a/hsweb-core/src/main/java/org/hswebframework/web/bean/FastBeanCopier.java b/hsweb-core/src/main/java/org/hswebframework/web/bean/FastBeanCopier.java index de0eea385..909c99343 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/bean/FastBeanCopier.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/bean/FastBeanCopier.java @@ -21,6 +21,7 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -513,6 +514,8 @@ public final class FastBeanCopier { if (targetClass == List.class) { return new ArrayList<>(); + } else if (targetClass == ConcurrentHashMap.KeySetView.class) { + return ConcurrentHashMap.newKeySet(); } else if (targetClass == Set.class) { return new HashSet<>(); } else if (targetClass == Queue.class) { @@ -568,13 +571,15 @@ public final class FastBeanCopier { return (T) new Date(((Date) source).getTime()); } } - if (Collection.class.isAssignableFrom(targetClass)) { + if (target.isCollectionType()) { Collection collection = newCollection(targetClass); Collection sourceCollection; if (source instanceof Collection) { sourceCollection = (Collection) source; } else if (source instanceof Object[]) { sourceCollection = Arrays.asList((Object[]) source); + } else if (source instanceof Map) { + sourceCollection = ((Map) source).values(); } else { if (source instanceof String) { String stringValue = ((String) source); @@ -619,7 +624,7 @@ public final class FastBeanCopier { Enum t = ((Enum) e); if ((t.name().equalsIgnoreCase(strSource) || Objects.equals(String.valueOf(t.ordinal()), strSource))) { - return (T)e; + return (T) e; } } @@ -642,7 +647,20 @@ public final class FastBeanCopier { //快速复制map if (targetClass == Map.class) { if (source instanceof Map) { - return (T) new HashMap(((Map) source)); + return (T) copyMap(((Map) source)); + } + if (source instanceof Collection) { + Map map = new LinkedHashMap<>(); + int i = 0; + for (Object o : ((Collection) source)) { + if (genericType.length >= 2) { + map.put(convert(i++, genericType[0], EMPTY_CLASS_ARRAY), convert(o, genericType[1], EMPTY_CLASS_ARRAY)); + } else { + map.put(i++, o); + } + } + return (T) map; + } ClassDescription sourType = ClassDescriptions.getDescription(source.getClass()); return (T) copy(source, Maps.newHashMapWithExpectedSize(sourType.getFieldSize())); @@ -656,6 +674,22 @@ public final class FastBeanCopier { // return null; } + private Map copyMap(Map map) { + if (map instanceof TreeMap) { + return new TreeMap<>(map); + } + + if (map instanceof LinkedHashMap) { + return new LinkedHashMap<>(map); + } + + if (map instanceof ConcurrentHashMap) { + return new ConcurrentHashMap<>(map); + } + + return new HashMap<>(map); + } + private Object converterByApache(Class targetClass, Object source) { org.apache.commons.beanutils.Converter converter = convertUtils.lookup(targetClass); if (null != converter) { diff --git a/hsweb-core/src/main/java/org/hswebframework/web/context/ContextUtils.java b/hsweb-core/src/main/java/org/hswebframework/web/context/ContextUtils.java index 256ee95ee..96d2d77c6 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/context/ContextUtils.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/context/ContextUtils.java @@ -17,6 +17,7 @@ public class ContextUtils { return contextThreadLocal.get(); } + @Deprecated public static Mono reactiveContext() { return Mono .subscriberContext() @@ -32,6 +33,7 @@ public class ContextUtils { })); } + @Deprecated public static Function acceptContext(Consumer contextConsumer) { return context -> { if (!context.hasKey(Context.class)) { diff --git a/hsweb-core/src/main/java/org/hswebframework/web/event/DefaultAsyncEvent.java b/hsweb-core/src/main/java/org/hswebframework/web/event/DefaultAsyncEvent.java index 5ee7d9020..9d79534db 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/event/DefaultAsyncEvent.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/event/DefaultAsyncEvent.java @@ -8,10 +8,10 @@ import java.util.function.Function; public class DefaultAsyncEvent implements AsyncEvent { - private Mono async = Mono.empty(); - private Mono first = Mono.empty(); + private transient Mono async = Mono.empty(); + private transient Mono first = Mono.empty(); - private boolean hasListener; + private transient boolean hasListener; public synchronized void async(Publisher publisher) { hasListener = true; diff --git a/hsweb-core/src/main/java/org/hswebframework/web/exception/I18nSupportException.java b/hsweb-core/src/main/java/org/hswebframework/web/exception/I18nSupportException.java index 6213a36c8..2478ab72b 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/exception/I18nSupportException.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/exception/I18nSupportException.java @@ -5,6 +5,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.i18n.LocaleUtils; +import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; import java.util.Locale; @@ -19,7 +20,7 @@ import java.util.Objects; */ @Getter @Setter(AccessLevel.PROTECTED) -public class I18nSupportException extends RuntimeException { +public class I18nSupportException extends TraceSourceException { /** * 消息code,在message.properties文件中定义的key @@ -70,4 +71,29 @@ public class I18nSupportException extends RuntimeException { .currentReactive() .map(this::getLocalizedMessage); } + + public static String tryGetLocalizedMessage(Throwable error, Locale locale) { + if (error instanceof I18nSupportException) { + return ((I18nSupportException) error).getLocalizedMessage(locale); + } + String msg = error.getMessage(); + + if (!StringUtils.hasText(msg)) { + msg = "error." + error.getClass().getSimpleName(); + } + if (msg.contains(".")) { + return LocaleUtils.resolveMessage(msg, locale, msg); + } + return msg; + } + + public static String tryGetLocalizedMessage(Throwable error) { + return tryGetLocalizedMessage(error, LocaleUtils.current()); + } + + public static Mono tryGetLocalizedMessageReactive(Throwable error) { + return LocaleUtils + .currentReactive() + .map(locale -> tryGetLocalizedMessage(error, locale)); + } } diff --git a/hsweb-core/src/main/java/org/hswebframework/web/exception/TraceSourceException.java b/hsweb-core/src/main/java/org/hswebframework/web/exception/TraceSourceException.java new file mode 100644 index 000000000..30ab58049 --- /dev/null +++ b/hsweb-core/src/main/java/org/hswebframework/web/exception/TraceSourceException.java @@ -0,0 +1,159 @@ +package org.hswebframework.web.exception; + +import org.hswebframework.web.i18n.LocaleUtils; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + +import javax.annotation.Nullable; +import java.util.Locale; +import java.util.Optional; +import java.util.function.Function; + +/** + * 支持溯源的异常,通过{@link TraceSourceException#withSource(Object) }来标识异常的源头. + * 在捕获异常的地方通过获取异常源来处理一些逻辑,比如判断是由哪条数据发生的错误等操作. + * + * @author zhouhao + * @since 4.0.15 + */ +public class TraceSourceException extends RuntimeException { + + private static final String deepTraceKey = TraceSourceException.class.getName() + "_deep"; + private static final Context deepTraceContext = Context.of(deepTraceKey, true); + + private String operation; + + private Object source; + + public TraceSourceException() { + + } + + public TraceSourceException(String message) { + super(message); + } + + public TraceSourceException(Throwable e) { + super(e.getMessage(),e); + } + + public TraceSourceException(String message, Throwable e) { + super(message, e); + } + + @Nullable + public Object getSource() { + return source; + } + + @Nullable + public String getOperation() { + return operation; + } + + public TraceSourceException withSource(Object source) { + this.source = source; + return self(); + } + + public TraceSourceException withSource(String operation, Object source) { + this.operation = operation; + this.source = source; + return self(); + } + + protected TraceSourceException self() { + return this; + } + + /** + * 深度溯源上下文,用来标识是否是深度溯源的异常.开启深度追踪后,会创建新的{@link TraceSourceException}对象. + * + * @return 上下文 + * @see reactor.core.publisher.Flux#subscriberContext(Context) + * @see Mono#subscriberContext(Context) + */ + public static Context deepTraceContext() { + return deepTraceContext; + } + + public static Function> transfer(Object source) { + return transfer(null, source); + } + + + /** + * 溯源异常转换器.通常配合{@link Mono#onErrorResume(Function)}使用. + *

+ * 转换逻辑: + *

+ * 1. 如果捕获的异常不是TraceSourceException,则直接创建新的TraceSourceException并返回. + *

+ * 2. 如果捕获的异常是TraceSourceException,并且上下文没有指定{@link TraceSourceException#deepTraceContext()}, + * 则修改捕获的TraceSourceException异常中的source.如果上下文中指定了{@link TraceSourceException#deepTraceContext()} + * 则创建新的TraceSourceException + * + *

{@code
+     *
+     *  doSomething()
+     *  .onErrorResume(TraceSourceException.transfer(data))
+     *
+     * }
+ * + * @param operation 操作名称 + * @param source 源 + * @param 泛型 + * @return 转换器 + * @see reactor.core.publisher.Flux#onErrorResume(Function) + * @see Mono#onErrorResume(Function) + */ + public static Function> transfer(String operation, Object source) { + if (source == null && operation == null) { + return Mono::error; + } + return err -> { + if (err instanceof TraceSourceException) { + return Mono + .deferWithContext(ctx -> { + if (ctx.hasKey(deepTraceKey)) { + return Mono.error(new TraceSourceException(err).withSource(operation,source)); + } else { + return Mono.error(((TraceSourceException) err).withSource(operation,source)); + } + }); + } + return Mono.error(new TraceSourceException(err).withSource(operation,source)); + }; + } + + public static Object tryGetSource(Throwable err) { + if (err instanceof TraceSourceException) { + return ((TraceSourceException) err).getSource(); + } + return null; + } + + public static String tryGetOperation(Throwable err) { + if (err instanceof TraceSourceException) { + return ((TraceSourceException) err).getOperation(); + } + return null; + } + + public static String tryGetOperationLocalized(Throwable err, Locale locale) { + String opt = tryGetOperation(err); + return StringUtils.hasText(opt) ? LocaleUtils.resolveMessage(opt, locale, opt) : opt; + } + + public static Mono tryGetOperationLocalizedReactive(Throwable err) { + return LocaleUtils + .currentReactive() + .handle((locale, sink) -> { + String opt = tryGetOperationLocalized(err, locale); + if (opt != null) { + sink.next(opt); + } + }); + } +} diff --git a/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java b/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java index d3c66ade3..fc01cef1a 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/i18n/LocaleUtils.java @@ -1,15 +1,18 @@ package org.hswebframework.web.i18n; +import io.netty.util.concurrent.FastThreadLocal; +import lombok.AllArgsConstructor; import org.hswebframework.web.exception.I18nSupportException; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; import org.springframework.context.MessageSource; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Signal; -import reactor.core.publisher.SignalType; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.*; import reactor.util.context.Context; +import javax.annotation.Nonnull; import java.util.Locale; +import java.util.concurrent.Callable; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -23,7 +26,6 @@ import java.util.function.Function; *
  • {@link LocaleUtils#current()}
  • *
  • {@link LocaleUtils#currentReactive()}
  • *
  • {@link LocaleUtils#resolveMessageReactive(String, Object...)}
  • - *
  • {@link LocaleUtils#doOnNext(BiConsumer)}
  • * * * @author zhouhao @@ -33,7 +35,7 @@ public final class LocaleUtils { public static final Locale DEFAULT_LOCALE = Locale.getDefault(); - private static final ThreadLocal CONTEXT_THREAD_LOCAL = new ThreadLocal<>(); + private static final FastThreadLocal CONTEXT_THREAD_LOCAL = new FastThreadLocal<>(); static MessageSource messageSource = UnsupportedMessageSource.instance(); @@ -63,11 +65,12 @@ public final class LocaleUtils { * @return 返回值 */ public static R doWith(T data, Locale locale, BiFunction mapper) { + Locale old = CONTEXT_THREAD_LOCAL.get(); try { CONTEXT_THREAD_LOCAL.set(locale); return mapper.apply(data, locale); } finally { - CONTEXT_THREAD_LOCAL.remove(); + CONTEXT_THREAD_LOCAL.set(old); } } @@ -78,11 +81,12 @@ public final class LocaleUtils { * @param consumer 任务 */ public static void doWith(Locale locale, Consumer consumer) { + Locale old = CONTEXT_THREAD_LOCAL.get(); try { CONTEXT_THREAD_LOCAL.set(locale); consumer.accept(locale); } finally { - CONTEXT_THREAD_LOCAL.remove(); + CONTEXT_THREAD_LOCAL.set(old); } } @@ -112,6 +116,23 @@ public final class LocaleUtils { .subscriberContext() .map(ctx -> ctx.getOrDefault(Locale.class, DEFAULT_LOCALE)); } + public static Mono doInReactive(Callable call) { + return currentReactive() + .handle((locale, sink) -> { + Locale old = CONTEXT_THREAD_LOCAL.get(); + try { + CONTEXT_THREAD_LOCAL.set(locale); + T data = call.call(); + if (data != null) { + sink.next(data); + } + } catch (Throwable e) { + sink.error(e); + } finally { + CONTEXT_THREAD_LOCAL.set(old); + } + }); + } /** * 响应式方式解析出异常的区域消息,并进行结果转换. @@ -450,4 +471,92 @@ public final class LocaleUtils { return doOn(SignalType.ON_ERROR, (s, l) -> operation.accept(s.getThrowable(), l)); } + public static Flux transform(Flux flux) { + return new LocaleFlux<>(flux); + } + + public static Mono transform(Mono mono) { + return new LocaleMono<>(mono); + } + + @AllArgsConstructor + static class LocaleMono extends Mono { + private final Mono source; + + @Override + public void subscribe(@Nonnull CoreSubscriber actual) { + doWith(actual, + actual.currentContext().getOrDefault(Locale.class, DEFAULT_LOCALE), + (a, l) -> { + source.subscribe( + new LocaleSwitchSubscriber<>(a) + ); + return null; + } + ); + } + } + + @AllArgsConstructor + static class LocaleFlux extends Flux { + private final Flux source; + + @Override + public void subscribe(@Nonnull CoreSubscriber actual) { + doWith(actual, + actual.currentContext().getOrDefault(Locale.class, DEFAULT_LOCALE), + (a, l) -> { + source.subscribe( + new LocaleSwitchSubscriber<>(a) + ); + return null; + } + ); + } + } + + @AllArgsConstructor + static class LocaleSwitchSubscriber extends BaseSubscriber { + private final CoreSubscriber actual; + + @Override + @Nonnull + public Context currentContext() { + return actual + .currentContext(); + } + + @Override + protected void hookOnSubscribe(@Nonnull Subscription subscription) { + actual.onSubscribe(this); + } + + private Locale current() { + return currentContext() + .getOrDefault(Locale.class, DEFAULT_LOCALE); + } + + @Override + protected void hookOnComplete() { + doWith(current(), (l) -> actual.onComplete()); + } + + @Override + protected void hookOnError(@Nonnull Throwable error) { + + doWith(error, current(), (v, l) -> { + actual.onError(v); + return null; + }); + } + + @Override + protected void hookOnNext(@Nonnull T value) { + + doWith(value, current(), (v, l) -> { + actual.onNext(v); + return null; + }); + } + } } diff --git a/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java b/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java index 748d7faee..61a34e1d6 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/i18n/WebFluxLocaleFilter.java @@ -15,6 +15,7 @@ public class WebFluxLocaleFilter implements WebFilter { public Mono filter(@NonNull ServerWebExchange exchange, WebFilterChain chain) { return chain .filter(exchange) + .as(LocaleUtils::transform) .subscriberContext(LocaleUtils.useLocale(getLocaleContext(exchange))); } diff --git a/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java b/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java index 0b8dca66c..844039d8d 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/id/IDGenerator.java @@ -18,13 +18,8 @@ package org.hswebframework.web.id; -import org.hswebframework.utils.RandomUtil; import org.hswebframework.web.utils.DigestUtils; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - /** * ID生成器,用于生成ID * @@ -53,7 +48,7 @@ public interface IDGenerator { /** * 随机字符 */ - IDGenerator RANDOM = RandomUtil::randomChar; + IDGenerator RANDOM = RandomIdGenerator.GLOBAL; /** * md5(uuid()) diff --git a/hsweb-core/src/main/java/org/hswebframework/web/id/RandomIdGenerator.java b/hsweb-core/src/main/java/org/hswebframework/web/id/RandomIdGenerator.java new file mode 100644 index 000000000..93a87046a --- /dev/null +++ b/hsweb-core/src/main/java/org/hswebframework/web/id/RandomIdGenerator.java @@ -0,0 +1,64 @@ +package org.hswebframework.web.id; + +import io.netty.util.concurrent.FastThreadLocal; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + +import java.util.Base64; +import java.util.concurrent.ThreadLocalRandom; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class RandomIdGenerator implements IDGenerator { + + // java -Dgenerator.random.instance-id=8 + static final RandomIdGenerator GLOBAL = new RandomIdGenerator( + Integer.getInteger("generator.random.instance-id", ThreadLocalRandom.current().nextInt(1,127)).byteValue() + ); + + static final Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding(); + + private final static FastThreadLocal HOLDER = new FastThreadLocal() { + @Override + protected byte[] initialValue() { + return new byte[24]; + } + }; + + private final byte instanceId; + + public static RandomIdGenerator create(byte instanceId) { + return new RandomIdGenerator(instanceId); + } + + public String generate() { + long now = System.currentTimeMillis(); + byte[] value = HOLDER.get(); + value[0] = instanceId; + + value[1] = (byte) (now >>> 32); + value[2] = (byte) (now >>> 24); + value[3] = (byte) (now >>> 16); + value[4] = (byte) (now >>> 8); + value[5] = (byte) (now); + + nextBytes(value, 6, 8); + nextBytes(value, 8, 16); + nextBytes(value, 16, 24); + return encoder.encodeToString(value); + } + + + private static void nextBytes(byte[] bytes, int offset, int len) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + + for (int i = offset; i < len; ) { + for (int rnd = random.nextInt(), + n = Math.min(len - i, Integer.SIZE / Byte.SIZE); + n-- > 0; rnd >>= Byte.SIZE) { + bytes[i++] = (byte) rnd; + } + } + + } + +} diff --git a/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java b/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java index 373ac01a5..18328f602 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/id/SnowflakeIdGenerator.java @@ -5,26 +5,27 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; @Slf4j public class SnowflakeIdGenerator { - private long workerId; - private long dataCenterId; + private final long workerId; + private final long dataCenterId; private long sequence = 0L; - private long twepoch = 1288834974657L; + private final long twepoch = 1288834974657L; - private long workerIdBits = 5L; - private long datacenterIdBits = 5L; - private long maxWorkerId = -1L ^ (-1L << workerIdBits); - private long maxDataCenterId = -1L ^ (-1L << datacenterIdBits); - private long sequenceBits = 12L; + private final long workerIdBits = 5L; + private final long datacenterIdBits = 5L; + private final long maxWorkerId = ~(-1L << workerIdBits); + private final long maxDataCenterId = ~(-1L << datacenterIdBits); + private final long sequenceBits = 12L; - private long workerIdShift = sequenceBits; - private long datacenterIdShift = sequenceBits + workerIdBits; - private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; - private long sequenceMask = -1L ^ (-1L << sequenceBits); + private final long workerIdShift = sequenceBits; + private final long datacenterIdShift = sequenceBits + workerIdBits; + private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + private final long sequenceMask = ~(-1L << sequenceBits); private long lastTimestamp = -1L; @@ -41,7 +42,15 @@ public class SnowflakeIdGenerator { return generator; } - public SnowflakeIdGenerator(long workerId, long dataCenterId) { + public static SnowflakeIdGenerator create(int workerId, int dataCenterId) { + return new SnowflakeIdGenerator(workerId, dataCenterId); + } + + public static SnowflakeIdGenerator create() { + return create(ThreadLocalRandom.current().nextInt(31), ThreadLocalRandom.current().nextInt(31)); + } + + private SnowflakeIdGenerator(long workerId, long dataCenterId) { // sanity check for workerId if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); diff --git a/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java b/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java index ac58189f4..4c863a9c8 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/utils/DigestUtils.java @@ -1,6 +1,9 @@ package org.hswebframework.web.utils; +import io.netty.util.concurrent.FastThreadLocal; +import io.seruco.encoding.base62.Base62; import org.apache.commons.codec.binary.Hex; +import org.hswebframework.web.id.RandomIdGenerator; import java.security.MessageDigest; import java.util.function.Consumer; @@ -8,10 +11,33 @@ import java.util.function.Supplier; public class DigestUtils { - public static final ThreadLocal md5 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getMd5Digest); - public static final ThreadLocal sha256 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getSha256Digest); - public static final ThreadLocal sha1 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getSha1Digest); + public static final FastThreadLocal md5 = new FastThreadLocal() { + @Override + protected MessageDigest initialValue() { + return org.apache.commons.codec.digest.DigestUtils.getMd5Digest(); + } + }; + public static final FastThreadLocal sha256 = new FastThreadLocal() { + @Override + protected MessageDigest initialValue() { + return org.apache.commons.codec.digest.DigestUtils.getSha256Digest(); + } + }; + + public static final FastThreadLocal sha1 = new FastThreadLocal() { + @Override + protected MessageDigest initialValue() { + return org.apache.commons.codec.digest.DigestUtils.getSha1Digest(); + } + }; + + private static final Base62 base62 = Base62.createInstance(); + + + public static Base62 base62(){ + return base62; + } public static byte[] md5(Consumer digestHandler) { return digest(md5::get, digestHandler); } @@ -47,6 +73,9 @@ public class DigestUtils { public static String md5Hex(String str) { return Hex.encodeHexString(md5(str.getBytes())); } + public static String md5Base62(String str) { + return new String(base62.encode(md5(str.getBytes()))); + } public static byte[] sha256(byte[] data) { return org.apache.commons.codec.digest.DigestUtils.digest(sha256.get(), data); diff --git a/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java b/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java index 3db2fad56..4c5a1a748 100644 --- a/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java +++ b/hsweb-core/src/main/java/org/hswebframework/web/validator/ValidatorUtils.java @@ -38,7 +38,7 @@ public final class ValidatorUtils { public static T tryValidate(T bean, Class... group) { Set> violations = getValidator().validate(bean, group); if (!violations.isEmpty()) { - throw new ValidationException(violations); + throw new ValidationException(violations).withSource(bean); } return bean; @@ -47,7 +47,7 @@ public final class ValidatorUtils { public static T tryValidate(T bean, String property, Class... group) { Set> violations = getValidator().validateProperty(bean, property, group); if (!violations.isEmpty()) { - throw new ValidationException(violations); + throw new ValidationException(violations).withSource(bean); } return bean; @@ -56,7 +56,7 @@ public final class ValidatorUtils { public static void tryValidate(Class bean, String property, Object value, Class... group) { Set> violations = getValidator().validateValue(bean, property, value, group); if (!violations.isEmpty()) { - throw new ValidationException(violations); + throw new ValidationException(violations).withSource(value); } } diff --git a/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java b/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java index fe7e177e4..0c73a2b45 100644 --- a/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java +++ b/hsweb-core/src/test/java/org/hswebframework/web/bean/FastBeanCopierTest.java @@ -1,16 +1,14 @@ package org.hswebframework.web.bean; import com.google.common.collect.ImmutableMap; -import jdk.nashorn.internal.objects.annotations.Getter; +import lombok.Getter; +import lombok.Setter; import org.junit.Assert; import org.junit.Test; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicReference; /** @@ -61,6 +59,43 @@ public class FastBeanCopierTest { } + @Test + public void testMapList() { + Map data = new HashMap<>(); + data.put("templates", new HashMap() { + { + put("0", Collections.singletonMap("name", "test")); + put("1", Collections.singletonMap("name", "test")); + } + }); + + Config config = FastBeanCopier.copy(data, new Config()); + + Assert.assertNotNull(config); + Assert.assertNotNull(config.templates); + System.out.println(config.templates); + Assert.assertEquals(2,config.templates.size()); + + + } + + @Getter + @Setter + public static class Config { + private List