mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-06-21 19:02:47 +08:00
Merge branch 'master' of github.com:hs-web/hsweb-framework
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ReactiveAuthenticationManagerProvider> 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()
|
||||
|
||||
@@ -58,10 +58,16 @@ public class SimpleAuthentication implements Authentication {
|
||||
}
|
||||
|
||||
public SimpleAuthentication merge(Authentication authentication) {
|
||||
Map<String, Permission> mePermissionGroup = permissions.stream()
|
||||
Map<String, Permission> 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<Permission, String> permissionFilter,
|
||||
Predicate<Dimension> 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<Dimension> dimensions) {
|
||||
this.dimensions.addAll(dimensions);
|
||||
}
|
||||
|
||||
public void addDimension(Dimension dimension) {
|
||||
this.dimensions.add(dimension);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=正常
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -64,7 +64,8 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
|
||||
AuthorizingContext context,
|
||||
Supplier<? extends Publisher<?>> 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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Object> 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<Authentication> getAuthentication(String userId) {
|
||||
|
||||
@@ -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<Authentication> authenticate(Mono<AuthenticationRequest> 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);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ParsedToken> 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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -40,4 +40,21 @@ public interface AccessTokenManager {
|
||||
*/
|
||||
Mono<AccessToken> refreshAccessToken(String clientId, String refreshToken);
|
||||
|
||||
/**
|
||||
* 移除token
|
||||
*
|
||||
* @param clientId clientId
|
||||
* @param token token
|
||||
* @return void
|
||||
*/
|
||||
Mono<Void> removeToken(String clientId, String token);
|
||||
|
||||
/**
|
||||
* 取消对用户的授权
|
||||
*
|
||||
* @param clientId clientId
|
||||
* @param userId 用户ID
|
||||
* @return void
|
||||
*/
|
||||
Mono<Void> cancelGrant(String clientId, String userId);
|
||||
}
|
||||
|
||||
@@ -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<Object, Object> 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
|
||||
|
||||
@@ -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<String, AuthorizationCodeCache> 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));
|
||||
})
|
||||
;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AccessToken> 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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, String> parameters;
|
||||
}
|
||||
@@ -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<String, RedisAccessToken> 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<String, RedisAccessToken> tokenRedis) {
|
||||
public RedisAccessTokenManager(ReactiveRedisOperations<String, RedisAccessToken> 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<Authentication> 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<Void> 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<Void> 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<AccessToken> doCreateSingletonAccessToken(String clientId, Authentication authentication) {
|
||||
@@ -111,7 +160,7 @@ public class RedisAccessTokenManager implements AccessTokenManager {
|
||||
|
||||
@Override
|
||||
public Mono<AccessToken> 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<Void> removeToken(String clientId, String token) {
|
||||
|
||||
return Flux
|
||||
.merge(userTokenManager.signOutByToken(token),
|
||||
tokenRedis.delete(createSingletonTokenRedisKey(clientId)),
|
||||
tokenRedis.delete(createTokenRedisKey(clientId, token)))
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> cancelGrant(String clientId, String userId) {
|
||||
//删除最新的refresh_token
|
||||
Mono<Void> removeRefreshToken = tokenRedis
|
||||
.opsForValue()
|
||||
.get(createUserTokenRedisKey(clientId, userId))
|
||||
.flatMap(t -> tokenRedis
|
||||
.opsForValue()
|
||||
.delete(createRefreshTokenRedisKey(t.getClientId(), t.getRefreshToken())))
|
||||
.then();
|
||||
|
||||
//删除access_token
|
||||
Mono<Void> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ import org.springframework.util.StringUtils;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* <pre>{@code
|
||||
* role:* user:* device-manager:*
|
||||
* }</pre>
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 4.0.8
|
||||
*/
|
||||
@@ -23,10 +27,13 @@ public class OAuth2ScopeUtils {
|
||||
Set<String> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-commons</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-commons</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -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<EntityEventListenerCustomizer> 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<EntityEventListenerCustomizer> customizers) {
|
||||
DefaultEntityEventListenerConfigure configure = new DefaultEntityEventListenerConfigure();
|
||||
customizers.forEach(customizer -> customizer.customize(configure));
|
||||
return new EntityEventListener(eventPublisher, configure);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -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<Class, Mapper> realTypeMapper = new HashMap<>();
|
||||
private Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private Map<String, PropertyCopier> copierCache = new HashMap<>();
|
||||
@SuppressWarnings("all")
|
||||
private final Map<Class<?>, Mapper> realTypeMapper = new ConcurrentHashMap<>();
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
@SuppressWarnings("all")
|
||||
private final Map<String, PropertyCopier> 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 <T> MapperEntityFactory addMapping(Class<T> target, Supplier<? extends T> mapper) {
|
||||
realTypeMapper.put(target, new Mapper(target, mapper));
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> MapperEntityFactory addMappingIfAbsent(Class<T> target, Supplier<? extends T> mapper) {
|
||||
realTypeMapper.putIfAbsent(target, new Mapper(target, mapper));
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> MapperEntityFactory addMapping(Class<T> target, Mapper<? extends T> mapper) {
|
||||
realTypeMapper.put(target, mapper);
|
||||
return this;
|
||||
@@ -216,8 +230,8 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
|
||||
}
|
||||
|
||||
public static class Mapper<T> {
|
||||
Class<T> target;
|
||||
Supplier<T> instanceGetter;
|
||||
final Class<T> target;
|
||||
final Supplier<T> instanceGetter;
|
||||
|
||||
public Mapper(Class<T> target, Supplier<T> instanceGetter) {
|
||||
this.target = target;
|
||||
@@ -242,16 +256,17 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
|
||||
}
|
||||
|
||||
static class DefaultInstanceGetter<T> implements Supplier<T> {
|
||||
Class<T> type;
|
||||
final Constructor<T> constructor;
|
||||
|
||||
@SneakyThrows
|
||||
public DefaultInstanceGetter(Class<T> type) {
|
||||
this.type = type;
|
||||
this.constructor = type.getConstructor();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public T get() {
|
||||
return type.newInstance();
|
||||
return constructor.newInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Object, Object> 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<Object, Object> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<Object>> readyToDeleteContextKey = ContextKey.of("readyToDelete");
|
||||
public static final ContextKey<List<Object>> readyToUpdateContextKey = ContextKey.of("readyToUpdate");
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@@ -133,39 +137,37 @@ public class EntityEventListener implements EventListener {
|
||||
protected List<Object> createAfterData(List<Object> olds,
|
||||
EventContext context) {
|
||||
List<Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<Object>, List<Object>> _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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ResponseMessage<Object>> 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<ResponseMessage<Object>> 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<ResponseMessage<Object>> handleException(BusinessException e) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Object>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
error.data_error=Data error
|
||||
error.internal_server_error = Internal server error
|
||||
@@ -2,4 +2,5 @@ error.unsupported_media_type=不支持的请求类型
|
||||
error.not_acceptable_media_type=不支持的媒体类型
|
||||
error.method_not_allowed=不支持的请求方法
|
||||
error.duplicate_data=重复的数据
|
||||
error.data_error=数据错误
|
||||
error.data_error=数据错误
|
||||
error.internal_server_error=服务器内部错误
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-concurrent</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -64,9 +64,8 @@ public class RedisReactiveCache<E> implements ReactiveCache<E> {
|
||||
}
|
||||
|
||||
protected <T> Mono<T> handleError(Throwable error) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
log.error(error.getMessage(), error);
|
||||
});
|
||||
log.error(error.getMessage(), error);
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,10 +15,11 @@ import java.util.function.Supplier;
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class UnSupportedReactiveCache<E> implements ReactiveCache<E> {
|
||||
|
||||
private static final UnSupportedReactiveCache INSTANCE = new UnSupportedReactiveCache();
|
||||
private static final UnSupportedReactiveCache<?> INSTANCE = new UnSupportedReactiveCache<>();
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public static <E> ReactiveCache<E> getInstance() {
|
||||
return INSTANCE;
|
||||
return (UnSupportedReactiveCache)INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -118,7 +118,18 @@
|
||||
<dependency>
|
||||
<artifactId>jctools-core</artifactId>
|
||||
<groupId>org.jctools</groupId>
|
||||
<version>2.1.2</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.seruco.encoding</groupId>
|
||||
<artifactId>base62</artifactId>
|
||||
<version>0.1.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -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<Object, Object> 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) {
|
||||
|
||||
@@ -17,6 +17,7 @@ public class ContextUtils {
|
||||
return contextThreadLocal.get();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static Mono<Context> reactiveContext() {
|
||||
return Mono
|
||||
.subscriberContext()
|
||||
@@ -32,6 +33,7 @@ public class ContextUtils {
|
||||
}));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static Function<reactor.util.context.Context, reactor.util.context.Context> acceptContext(Consumer<Context> contextConsumer) {
|
||||
return context -> {
|
||||
if (!context.hasKey(Context.class)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String> tryGetLocalizedMessageReactive(Throwable error) {
|
||||
return LocaleUtils
|
||||
.currentReactive()
|
||||
.map(locale -> tryGetLocalizedMessage(error, locale));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <T> Function<Throwable, Mono<T>> transfer(Object source) {
|
||||
return transfer(null, source);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 溯源异常转换器.通常配合{@link Mono#onErrorResume(Function)}使用.
|
||||
* <p>
|
||||
* 转换逻辑:
|
||||
* <p>
|
||||
* 1. 如果捕获的异常不是TraceSourceException,则直接创建新的TraceSourceException并返回.
|
||||
* <p>
|
||||
* 2. 如果捕获的异常是TraceSourceException,并且上下文没有指定{@link TraceSourceException#deepTraceContext()},
|
||||
* 则修改捕获的TraceSourceException异常中的source.如果上下文中指定了{@link TraceSourceException#deepTraceContext()}
|
||||
* 则创建新的TraceSourceException
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* doSomething()
|
||||
* .onErrorResume(TraceSourceException.transfer(data))
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param operation 操作名称
|
||||
* @param source 源
|
||||
* @param <T> 泛型
|
||||
* @return 转换器
|
||||
* @see reactor.core.publisher.Flux#onErrorResume(Function)
|
||||
* @see Mono#onErrorResume(Function)
|
||||
*/
|
||||
public static <T> Function<Throwable, Mono<T>> 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<String> tryGetOperationLocalizedReactive(Throwable err) {
|
||||
return LocaleUtils
|
||||
.currentReactive()
|
||||
.handle((locale, sink) -> {
|
||||
String opt = tryGetOperationLocalized(err, locale);
|
||||
if (opt != null) {
|
||||
sink.next(opt);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
* <li>{@link LocaleUtils#current()} </li>
|
||||
* <li>{@link LocaleUtils#currentReactive()}</li>
|
||||
* <li>{@link LocaleUtils#resolveMessageReactive(String, Object...)}</li>
|
||||
* <li>{@link LocaleUtils#doOnNext(BiConsumer)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author zhouhao
|
||||
@@ -33,7 +35,7 @@ public final class LocaleUtils {
|
||||
|
||||
public static final Locale DEFAULT_LOCALE = Locale.getDefault();
|
||||
|
||||
private static final ThreadLocal<Locale> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
|
||||
private static final FastThreadLocal<Locale> CONTEXT_THREAD_LOCAL = new FastThreadLocal<>();
|
||||
|
||||
static MessageSource messageSource = UnsupportedMessageSource.instance();
|
||||
|
||||
@@ -63,11 +65,12 @@ public final class LocaleUtils {
|
||||
* @return 返回值
|
||||
*/
|
||||
public static <T, R> R doWith(T data, Locale locale, BiFunction<T, Locale, R> 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<Locale> 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 <T> Mono<T> doInReactive(Callable<T> 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 <T> Flux<T> transform(Flux<T> flux) {
|
||||
return new LocaleFlux<>(flux);
|
||||
}
|
||||
|
||||
public static <T> Mono<T> transform(Mono<T> mono) {
|
||||
return new LocaleMono<>(mono);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static class LocaleMono<T> extends Mono<T> {
|
||||
private final Mono<T> source;
|
||||
|
||||
@Override
|
||||
public void subscribe(@Nonnull CoreSubscriber<? super T> actual) {
|
||||
doWith(actual,
|
||||
actual.currentContext().getOrDefault(Locale.class, DEFAULT_LOCALE),
|
||||
(a, l) -> {
|
||||
source.subscribe(
|
||||
new LocaleSwitchSubscriber<>(a)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static class LocaleFlux<T> extends Flux<T> {
|
||||
private final Flux<T> source;
|
||||
|
||||
@Override
|
||||
public void subscribe(@Nonnull CoreSubscriber<? super T> actual) {
|
||||
doWith(actual,
|
||||
actual.currentContext().getOrDefault(Locale.class, DEFAULT_LOCALE),
|
||||
(a, l) -> {
|
||||
source.subscribe(
|
||||
new LocaleSwitchSubscriber<>(a)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
static class LocaleSwitchSubscriber<T> extends BaseSubscriber<T> {
|
||||
private final CoreSubscriber<T> 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class WebFluxLocaleFilter implements WebFilter {
|
||||
public Mono<Void> filter(@NonNull ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return chain
|
||||
.filter(exchange)
|
||||
.as(LocaleUtils::transform)
|
||||
.subscriberContext(LocaleUtils.useLocale(getLocaleContext(exchange)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T> {
|
||||
/**
|
||||
* 随机字符
|
||||
*/
|
||||
IDGenerator<String> RANDOM = RandomUtil::randomChar;
|
||||
IDGenerator<String> RANDOM = RandomIdGenerator.GLOBAL;
|
||||
|
||||
/**
|
||||
* md5(uuid())
|
||||
|
||||
@@ -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<String> {
|
||||
|
||||
// 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<byte[]> HOLDER = new FastThreadLocal<byte[]>() {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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<MessageDigest> md5 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getMd5Digest);
|
||||
public static final ThreadLocal<MessageDigest> sha256 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getSha256Digest);
|
||||
public static final ThreadLocal<MessageDigest> sha1 = ThreadLocal.withInitial(org.apache.commons.codec.digest.DigestUtils::getSha1Digest);
|
||||
public static final FastThreadLocal<MessageDigest> md5 = new FastThreadLocal<MessageDigest>() {
|
||||
@Override
|
||||
protected MessageDigest initialValue() {
|
||||
return org.apache.commons.codec.digest.DigestUtils.getMd5Digest();
|
||||
}
|
||||
};
|
||||
|
||||
public static final FastThreadLocal<MessageDigest> sha256 = new FastThreadLocal<MessageDigest>() {
|
||||
@Override
|
||||
protected MessageDigest initialValue() {
|
||||
return org.apache.commons.codec.digest.DigestUtils.getSha256Digest();
|
||||
}
|
||||
};
|
||||
|
||||
public static final FastThreadLocal<MessageDigest> sha1 = new FastThreadLocal<MessageDigest>() {
|
||||
@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<MessageDigest> 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);
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class ValidatorUtils {
|
||||
public static <T> T tryValidate(T bean, Class<?>... group) {
|
||||
Set<ConstraintViolation<T>> 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> T tryValidate(T bean, String property, Class<?>... group) {
|
||||
Set<ConstraintViolation<T>> 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 <T> void tryValidate(Class<T> bean, String property, Object value, Class<?>... group) {
|
||||
Set<ConstraintViolation<T>> violations = getValidator().validateValue(bean, property, value, group);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ValidationException(violations);
|
||||
throw new ValidationException(violations).withSource(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, Object> 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<Template> templates;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Template {
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "name:"+name;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyMap() {
|
||||
|
||||
@@ -87,10 +122,10 @@ public class FastBeanCopierTest {
|
||||
|
||||
@Test
|
||||
public void testProxy() {
|
||||
AtomicReference<Object> reference=new AtomicReference<>();
|
||||
AtomicReference<Object> reference = new AtomicReference<>();
|
||||
|
||||
ProxyTest test = (ProxyTest) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
|
||||
new Class[]{ProxyTest.class}, (proxy, method, args) -> {
|
||||
new Class[]{ProxyTest.class}, (proxy, method, args) -> {
|
||||
if (method.getName().equals("getName")) {
|
||||
return "test";
|
||||
}
|
||||
@@ -105,20 +140,20 @@ public class FastBeanCopierTest {
|
||||
|
||||
Target source = new Target();
|
||||
|
||||
FastBeanCopier.copy(test,source);
|
||||
Assert.assertEquals(source.getName(),test.getName());
|
||||
FastBeanCopier.copy(test, source);
|
||||
Assert.assertEquals(source.getName(), test.getName());
|
||||
|
||||
|
||||
source.setName("test2");
|
||||
FastBeanCopier.copy(source,test);
|
||||
FastBeanCopier.copy(source, test);
|
||||
|
||||
Assert.assertEquals(reference.get(),source.getName());
|
||||
Assert.assertEquals(reference.get(), source.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProperty(){
|
||||
public void testGetProperty() {
|
||||
|
||||
Assert.assertEquals(1,FastBeanCopier.getProperty(ImmutableMap.of("a",1,"b",2),"a"));
|
||||
Assert.assertEquals(1, FastBeanCopier.getProperty(ImmutableMap.of("a", 1, "b", 2), "a"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.hswebframework.web.i18n;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -11,15 +13,35 @@ public class LocaleUtilsTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testOnNext() {
|
||||
public void testFlux() {
|
||||
Flux.just(1)
|
||||
.as(LocaleUtils.doOnNext((i, l) -> {
|
||||
.as(LocaleUtils::transform)
|
||||
.doOnNext(i -> {
|
||||
assertEquals(i.intValue(), 1);
|
||||
assertEquals(l, Locale.CHINA);
|
||||
}))
|
||||
.subscriberContext(LocaleUtils.useLocale(Locale.CHINA))
|
||||
assertEquals(LocaleUtils.current(), Locale.ENGLISH);
|
||||
})
|
||||
.subscriberContext(LocaleUtils.useLocale(Locale.ENGLISH))
|
||||
.blockLast();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMono() {
|
||||
Mono.just(1)
|
||||
.doOnNext(i -> {
|
||||
assertEquals(i.intValue(), 1);
|
||||
assertEquals(LocaleUtils.current(), Locale.ENGLISH);
|
||||
})
|
||||
.as(LocaleUtils::transform)
|
||||
.subscriberContext(LocaleUtils.useLocale(Locale.ENGLISH))
|
||||
.block();
|
||||
|
||||
LocaleUtils
|
||||
.doInReactive(()->{
|
||||
assertEquals(LocaleUtils.current(), Locale.ENGLISH);
|
||||
return null;
|
||||
})
|
||||
.subscriberContext(LocaleUtils.useLocale(Locale.ENGLISH))
|
||||
.block();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package org.hswebframework.web.id;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 3.0
|
||||
@@ -19,9 +22,19 @@ public class IDGeneratorTests {
|
||||
Assert.assertNotNull(IDGenerator.RANDOM.generate());
|
||||
Assert.assertNotNull(IDGenerator.SNOW_FLAKE.generate());
|
||||
Assert.assertNotNull(IDGenerator.SNOW_FLAKE_HEX.generate());
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
System.out.println(IDGenerator.RANDOM.generate());
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
System.out.println(IDGenerator.SNOW_FLAKE.generate());
|
||||
}
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
Set<String> set =new HashSet<>(100_0000);
|
||||
for (int i = 0; i < 100_0000; i++) {
|
||||
set.add(IDGenerator.RANDOM.generate());
|
||||
}
|
||||
System.out.println(set.size());
|
||||
System.out.println((System.currentTimeMillis()-time));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-datasource</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-datasource</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-datasource</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-logging</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
* @author zhouhao
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ReactiveAopAccessLoggerSupport extends StaticMethodMatcherPointcutAdvisor implements WebFilter {
|
||||
public class ReactiveAopAccessLoggerSupport extends StaticMethodMatcherPointcutAdvisor implements WebFilter{
|
||||
|
||||
@Autowired(required = false)
|
||||
private final List<AccessLoggerParser> loggerParsers = new ArrayList<>();
|
||||
@@ -58,9 +58,11 @@ public class ReactiveAopAccessLoggerSupport extends StaticMethodMatcherPointcutA
|
||||
AccessLoggerInfo info = createLogger(methodInterceptorHolder);
|
||||
Object response = methodInvocation.proceed();
|
||||
if (response instanceof Mono) {
|
||||
return wrapMonoResponse(((Mono<?>) response), info);
|
||||
return wrapMonoResponse(((Mono<?>) response), info)
|
||||
.subscriberContext(Context.of(AccessLoggerInfo.class, info));
|
||||
} else if (response instanceof Flux) {
|
||||
return wrapFluxResponse(((Flux<?>) response), info);
|
||||
return wrapFluxResponse(((Flux<?>) response), info)
|
||||
.subscriberContext(Context.of(AccessLoggerInfo.class, info));
|
||||
}
|
||||
return response;
|
||||
});
|
||||
@@ -183,7 +185,7 @@ public class ReactiveAopAccessLoggerSupport extends StaticMethodMatcherPointcutA
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,10 +219,12 @@ public class ReactiveAopAccessLoggerSupport extends StaticMethodMatcherPointcutA
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
private static class CacheKey{
|
||||
private static class CacheKey {
|
||||
private Class<?> type;
|
||||
private Method method;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-logging</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -28,12 +28,10 @@ import org.springframework.util.MimeType;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -70,23 +68,19 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
|
||||
|
||||
ObjectReader reader = getObjectReader(elementType, hints);
|
||||
|
||||
return LocaleUtils
|
||||
.currentReactive()
|
||||
.flatMapMany(locale -> tokens
|
||||
.handle((tokenBuffer, sink) -> {
|
||||
LocaleUtils.doWith(locale, l -> {
|
||||
try {
|
||||
Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()));
|
||||
logValue(value, hints);
|
||||
if (value != null) {
|
||||
sink.next(value);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
sink.error(processException(ex));
|
||||
}
|
||||
});
|
||||
|
||||
}));
|
||||
return tokens
|
||||
.as(LocaleUtils::transform)
|
||||
.handle((tokenBuffer, sink) -> {
|
||||
try {
|
||||
Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper()));
|
||||
logValue(value, hints);
|
||||
if (value != null) {
|
||||
sink.next(value);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
sink.error(processException(ex));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,15 +88,10 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
|
||||
public Mono<Object> decodeToMono(@NonNull Publisher<DataBuffer> input, @NonNull ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return LocaleUtils
|
||||
.currentReactive()
|
||||
.flatMap(locale -> DataBufferUtils
|
||||
.join(input)
|
||||
.map(dataBuffer -> LocaleUtils
|
||||
.doWith(dataBuffer,
|
||||
locale,
|
||||
(buf, l) -> decode(buf, elementType, mimeType, hints)))
|
||||
);
|
||||
return DataBufferUtils
|
||||
.join(input)
|
||||
.as(LocaleUtils::transform)
|
||||
.map(dataBuffer -> decode(dataBuffer, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -116,72 +116,55 @@ public class CustomJackson2jsonEncoder extends Jackson2CodecSupport implements H
|
||||
Assert.notNull(bufferFactory, "'bufferFactory' must not be null");
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
|
||||
return LocaleUtils
|
||||
.currentReactive()
|
||||
.flatMapMany(locale -> {
|
||||
if (inputStream instanceof Mono) {
|
||||
return Mono.from(inputStream)
|
||||
.map(value -> LocaleUtils
|
||||
.doWith(value, locale,
|
||||
((val, loc) ->
|
||||
encodeValue(val, bufferFactory, elementType, mimeType, hints)
|
||||
)
|
||||
))
|
||||
.flux();
|
||||
} else {
|
||||
byte[] separator = streamSeparator(mimeType);
|
||||
if (separator != null) { // streaming
|
||||
try {
|
||||
ObjectWriter writer = createObjectWriter(elementType, mimeType, hints);
|
||||
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer
|
||||
.getFactory()
|
||||
._getBufferRecycler());
|
||||
JsonEncoding encoding = getJsonEncoding(mimeType);
|
||||
JsonGenerator generator = getObjectMapper()
|
||||
.getFactory()
|
||||
.createGenerator(byteBuilder, encoding);
|
||||
SequenceWriter sequenceWriter = writer.writeValues(generator);
|
||||
if (inputStream instanceof Mono) {
|
||||
return Mono.from(inputStream)
|
||||
.as(LocaleUtils::transform)
|
||||
.map(value -> encodeValue(value, bufferFactory, elementType, mimeType, hints))
|
||||
.flux();
|
||||
} else {
|
||||
byte[] separator = streamSeparator(mimeType);
|
||||
if (separator != null) { // streaming
|
||||
try {
|
||||
ObjectWriter writer = createObjectWriter(elementType, mimeType, hints);
|
||||
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer
|
||||
.getFactory()
|
||||
._getBufferRecycler());
|
||||
JsonEncoding encoding = getJsonEncoding(mimeType);
|
||||
JsonGenerator generator = getObjectMapper()
|
||||
.getFactory()
|
||||
.createGenerator(byteBuilder, encoding);
|
||||
SequenceWriter sequenceWriter = writer.writeValues(generator);
|
||||
|
||||
return Flux
|
||||
.from(inputStream)
|
||||
.map(value -> LocaleUtils
|
||||
.doWith(value,
|
||||
locale,
|
||||
((val, loc) -> this
|
||||
.encodeStreamingValue(val,
|
||||
bufferFactory,
|
||||
hints,
|
||||
sequenceWriter,
|
||||
byteBuilder,
|
||||
separator)
|
||||
)
|
||||
))
|
||||
.doAfterTerminate(() -> {
|
||||
try {
|
||||
byteBuilder.release();
|
||||
generator.close();
|
||||
} catch (IOException ex) {
|
||||
logger.error("Could not close Encoder resources", ex);
|
||||
}
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
return Flux.error(ex);
|
||||
}
|
||||
} else { // non-streaming
|
||||
ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType);
|
||||
return Flux.from(inputStream)
|
||||
.collectList()
|
||||
.map(value -> LocaleUtils
|
||||
.doWith(value, locale,
|
||||
((val, loc) ->
|
||||
encodeValue(val, bufferFactory, listType, mimeType, hints)
|
||||
)
|
||||
))
|
||||
.flux();
|
||||
}
|
||||
return Flux
|
||||
.from(inputStream)
|
||||
.as(LocaleUtils::transform)
|
||||
.map(value -> this.encodeStreamingValue(value,
|
||||
bufferFactory,
|
||||
hints,
|
||||
sequenceWriter,
|
||||
byteBuilder,
|
||||
separator))
|
||||
.doAfterTerminate(() -> {
|
||||
try {
|
||||
byteBuilder.release();
|
||||
generator.close();
|
||||
} catch (IOException ex) {
|
||||
logger.error("Could not close Encoder resources", ex);
|
||||
}
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
return Flux.error(ex);
|
||||
}
|
||||
} else { // non-streaming
|
||||
ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType);
|
||||
return Flux.from(inputStream)
|
||||
.collectList()
|
||||
.as(LocaleUtils::transform)
|
||||
.map(value -> encodeValue(value, bufferFactory, listType, mimeType, hints))
|
||||
.flux();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.hswebframework.web.system.authorization.api.entity;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -9,15 +11,18 @@ import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.Comment;
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.DefaultValue;
|
||||
import org.hswebframework.web.api.crud.entity.GenericEntity;
|
||||
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
|
||||
import org.hswebframework.web.api.crud.entity.RecordCreationEntity;
|
||||
import org.hswebframework.web.bean.ToString;
|
||||
import org.hswebframework.web.validator.CreateGroup;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 系统用户实体
|
||||
@@ -85,7 +90,10 @@ public class UserEntity extends GenericEntity<String> implements RecordCreationE
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
public void generateId(){
|
||||
setId(DigestUtils.md5Hex(username));
|
||||
public void generateId() {
|
||||
if (StringUtils.hasText(getId())) {
|
||||
return;
|
||||
}
|
||||
setId(DigestUtils.md5Hex(username));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import org.hswebframework.web.system.authorization.api.entity.UserEntity;
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class UserModifiedEvent extends DefaultAsyncEvent {
|
||||
private UserEntity before;
|
||||
|
||||
private UserEntity userEntity;
|
||||
|
||||
private boolean passwordModified;
|
||||
|
||||
@@ -29,6 +29,8 @@ public interface ReactiveUserService {
|
||||
*/
|
||||
Mono<Boolean> saveUser(Mono<UserEntity> userEntity);
|
||||
|
||||
Mono<UserEntity> addUser(UserEntity userEntity);
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户实体,如果用户不存在则返回{@link Mono#empty()}
|
||||
*
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -55,10 +55,10 @@ public class AuthorizationServiceAutoConfiguration {
|
||||
return new DefaultDimensionService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDimensionProvider userPermissionDimensionProvider() {
|
||||
return new UserDimensionProvider();
|
||||
}
|
||||
// @Bean
|
||||
// public UserDimensionProvider userPermissionDimensionProvider() {
|
||||
// return new UserDimensionProvider();
|
||||
// }
|
||||
|
||||
@Bean
|
||||
public DefaultDimensionUserService defaultDimensionUserService() {
|
||||
|
||||
@@ -56,9 +56,6 @@ public class DefaultDimensionUserService extends GenericReactiveCrudService<Dime
|
||||
public void dispatchDimensionBind(EntitySavedEvent<DimensionUserEntity> event) {
|
||||
event.async(
|
||||
this.publishEvent(Flux.fromIterable(event.getEntity()), DimensionBindEvent::new)
|
||||
.then(
|
||||
this.clearUserCache(event.getEntity())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,9 +64,6 @@ public class DefaultDimensionUserService extends GenericReactiveCrudService<Dime
|
||||
public void dispatchDimensionBind(EntityCreatedEvent<DimensionUserEntity> event) {
|
||||
event.async(
|
||||
this.publishEvent(Flux.fromIterable(event.getEntity()), DimensionBindEvent::new)
|
||||
.then(
|
||||
this.clearUserCache(event.getEntity())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,9 +72,6 @@ public class DefaultDimensionUserService extends GenericReactiveCrudService<Dime
|
||||
public void dispatchDimensionUnbind(EntityDeletedEvent<DimensionUserEntity> event) {
|
||||
event.async(
|
||||
this.publishEvent(Flux.fromIterable(event.getEntity()), DimensionUnbindEvent::new)
|
||||
.then(
|
||||
this.clearUserCache(event.getEntity())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.hswebframework.web.authorization.ReactiveAuthenticationInitializeServ
|
||||
import org.hswebframework.web.authorization.access.DataAccessConfig;
|
||||
import org.hswebframework.web.authorization.access.DataAccessType;
|
||||
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory;
|
||||
import org.hswebframework.web.authorization.events.AuthorizationInitializeEvent;
|
||||
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
|
||||
import org.hswebframework.web.authorization.simple.SimplePermission;
|
||||
import org.hswebframework.web.authorization.simple.SimpleUser;
|
||||
@@ -21,6 +22,7 @@ import org.hswebframework.web.system.authorization.api.entity.PermissionEntity;
|
||||
import org.hswebframework.web.system.authorization.api.entity.UserEntity;
|
||||
import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -48,6 +50,9 @@ public class DefaultReactiveAuthenticationInitializeService
|
||||
@Autowired(required = false)
|
||||
private List<DimensionProvider> dimensionProviders = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> initUserAuthorization(String userId) {
|
||||
return doInit(userService.findById(userId));
|
||||
@@ -58,54 +63,57 @@ public class DefaultReactiveAuthenticationInitializeService
|
||||
return userEntityMono.flatMap(user -> {
|
||||
SimpleAuthentication authentication = new SimpleAuthentication();
|
||||
authentication.setUser(SimpleUser
|
||||
.builder()
|
||||
.id(user.getId())
|
||||
.name(user.getName())
|
||||
.username(user.getUsername())
|
||||
.userType(user.getType())
|
||||
.build());
|
||||
.builder()
|
||||
.id(user.getId())
|
||||
.name(user.getName())
|
||||
.username(user.getUsername())
|
||||
.userType(user.getType())
|
||||
.build());
|
||||
|
||||
return initPermission(authentication)
|
||||
.switchIfEmpty(Mono.just(authentication))
|
||||
.defaultIfEmpty(authentication)
|
||||
.onErrorResume(err -> {
|
||||
log.warn(err.getMessage(), err);
|
||||
return Mono.just(authentication);
|
||||
})
|
||||
.flatMap(auth -> {
|
||||
AuthorizationInitializeEvent event = new AuthorizationInitializeEvent(auth);
|
||||
return event
|
||||
.publish(eventPublisher)
|
||||
.then(Mono.fromSupplier(event::getAuthentication));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected Flux<AuthorizationSettingEntity> getSettings(List<Dimension> dimensions) {
|
||||
return Flux.fromIterable(dimensions)
|
||||
.filter(dimension -> dimension.getType() != null)
|
||||
.groupBy(d -> d.getType().getId(), (Function<Dimension, Object>) Dimension::getId)
|
||||
.flatMap(group ->
|
||||
group.collectList()
|
||||
.flatMapMany(list -> settingRepository
|
||||
.createQuery()
|
||||
.where(AuthorizationSettingEntity::getState, 1)
|
||||
.and(AuthorizationSettingEntity::getDimensionType, group.key())
|
||||
.in(AuthorizationSettingEntity::getDimensionTarget, list)
|
||||
.fetch()));
|
||||
.filter(dimension -> dimension.getType() != null)
|
||||
.groupBy(d -> d.getType().getId(), (Function<Dimension, Object>) Dimension::getId)
|
||||
.flatMap(group ->
|
||||
group.collectList()
|
||||
.flatMapMany(list -> settingRepository
|
||||
.createQuery()
|
||||
.where(AuthorizationSettingEntity::getState, 1)
|
||||
.and(AuthorizationSettingEntity::getDimensionType, group.key())
|
||||
.in(AuthorizationSettingEntity::getDimensionTarget, list)
|
||||
.fetch()));
|
||||
}
|
||||
|
||||
protected Mono<Authentication> initPermission(SimpleAuthentication authentication) {
|
||||
return Flux.fromIterable(dimensionProviders)
|
||||
.flatMap(provider -> provider.getDimensionByUserId(authentication.getUser().getId()))
|
||||
.cast(Dimension.class)
|
||||
.collectList()
|
||||
.doOnNext(authentication::setDimensions)
|
||||
.flatMap(allDimension ->
|
||||
Mono.zip(
|
||||
getAllPermission()
|
||||
, getSettings(allDimension)
|
||||
.collect(Collectors.groupingBy(AuthorizationSettingEntity::getPermission))
|
||||
, (_p, _s) -> handlePermission(authentication, allDimension, _p, _s)
|
||||
));
|
||||
.flatMap(provider -> provider.getDimensionByUserId(authentication.getUser().getId()))
|
||||
.cast(Dimension.class)
|
||||
.doOnNext(authentication::addDimension)
|
||||
.collectList()
|
||||
.then(Mono.defer(() -> Mono
|
||||
.zip(getAllPermission(),
|
||||
getSettings(authentication.getDimensions()).collect(Collectors.groupingBy(AuthorizationSettingEntity::getPermission)),
|
||||
(_p, _s) -> handlePermission(authentication, _p, _s)
|
||||
)));
|
||||
|
||||
}
|
||||
|
||||
protected SimpleAuthentication handlePermission(SimpleAuthentication authentication,
|
||||
List<Dimension> dimensionList,
|
||||
Map<String, PermissionEntity> permissions,
|
||||
Map<String, List<AuthorizationSettingEntity>> settings) {
|
||||
Map<String, PermissionEntity> permissionMap = new HashMap<>();
|
||||
@@ -134,16 +142,19 @@ public class DefaultReactiveAuthenticationInitializeService
|
||||
|
||||
if (permissionSetting.getDataAccesses() != null) {
|
||||
permissionSetting.getDataAccesses()
|
||||
.stream()
|
||||
.map(conf -> {
|
||||
DataAccessConfig config = builderFactory.create().fromMap(conf.toMap()).build();
|
||||
if (config == null) {
|
||||
log.warn("unsupported data access:{}", conf.toMap());
|
||||
}
|
||||
return config;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(configs::add);
|
||||
.stream()
|
||||
.map(conf -> {
|
||||
DataAccessConfig config = builderFactory
|
||||
.create()
|
||||
.fromMap(conf.toMap())
|
||||
.build();
|
||||
if (config == null) {
|
||||
log.warn("unsupported data access:{}", conf.toMap());
|
||||
}
|
||||
return config;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(configs::add);
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(permissionSetting.getActions())) {
|
||||
permission.getActions().addAll(permissionSetting.getActions());
|
||||
|
||||
@@ -24,6 +24,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.ValidationException;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class DefaultReactiveUserService extends GenericReactiveCrudService<UserEntity, String> implements ReactiveUserService {
|
||||
@@ -60,11 +61,20 @@ public class DefaultReactiveUserService extends GenericReactiveCrudService<UserE
|
||||
return doAdd(userEntity);
|
||||
}
|
||||
return findById(userEntity.getId())
|
||||
.flatMap(ignore -> doUpdate(userEntity))
|
||||
.switchIfEmpty(Mono.error(NotFoundException::new));
|
||||
.flatMap(old -> doUpdate(old, userEntity))
|
||||
.switchIfEmpty(
|
||||
Objects.equals(userEntity.getId(), userEntity.getUsername()) ?
|
||||
doAdd(userEntity) :
|
||||
Mono.error(NotFoundException::new)
|
||||
);
|
||||
}).thenReturn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserEntity> addUser(UserEntity userEntity) {
|
||||
return doAdd(userEntity);
|
||||
}
|
||||
|
||||
protected Mono<UserEntity> doAdd(UserEntity userEntity) {
|
||||
|
||||
return Mono
|
||||
@@ -94,22 +104,29 @@ public class DefaultReactiveUserService extends GenericReactiveCrudService<UserE
|
||||
}
|
||||
|
||||
|
||||
protected Mono<UserEntity> doUpdate(UserEntity userEntity) {
|
||||
protected Mono<UserEntity> doUpdate(UserEntity old, UserEntity newer) {
|
||||
return Mono
|
||||
.defer(() -> {
|
||||
boolean passwordChanged = StringUtils.hasText(userEntity.getPassword());
|
||||
if (passwordChanged) {
|
||||
userEntity.setSalt(IDGenerator.RANDOM.generate());
|
||||
passwordValidator.validate(userEntity.getPassword());
|
||||
userEntity.setPassword(passwordEncoder.encode(userEntity.getPassword(), userEntity.getSalt()));
|
||||
boolean updatePassword = StringUtils.hasText(newer.getPassword());
|
||||
|
||||
boolean passwordChanged = updatePassword &&
|
||||
!Objects.equals(
|
||||
passwordEncoder.encode(newer.getPassword(), old.getSalt()),
|
||||
old.getPassword()
|
||||
);
|
||||
|
||||
if (updatePassword) {
|
||||
newer.setSalt(IDGenerator.RANDOM.generate());
|
||||
passwordValidator.validate(newer.getPassword());
|
||||
newer.setPassword(passwordEncoder.encode(newer.getPassword(), newer.getSalt()));
|
||||
}
|
||||
return getRepository()
|
||||
.createUpdate()
|
||||
.set(userEntity)
|
||||
.where(userEntity::getId)
|
||||
.set(newer)
|
||||
.where(newer::getId)
|
||||
.execute()
|
||||
.flatMap(__ -> new UserModifiedEvent(userEntity, passwordChanged).publish(eventPublisher))
|
||||
.thenReturn(userEntity)
|
||||
.flatMap(__ -> new UserModifiedEvent(old, newer, passwordChanged).publish(eventPublisher))
|
||||
.thenReturn(newer)
|
||||
.flatMap(e -> ClearUserAuthorizationCacheEvent
|
||||
.of(e.getId())
|
||||
.publish(eventPublisher)
|
||||
@@ -175,12 +192,22 @@ public class DefaultReactiveUserService extends GenericReactiveCrudService<UserE
|
||||
return findById(userId)
|
||||
.switchIfEmpty(Mono.error(NotFoundException::new))
|
||||
.filter(user -> passwordEncoder.encode(oldPassword, user.getSalt()).equals(user.getPassword()))
|
||||
.switchIfEmpty(Mono.error(() -> new ValidationException("密码错误")))
|
||||
.flatMap(user -> repository
|
||||
.createUpdate()
|
||||
.set(UserEntity::getPassword, passwordEncoder.encode(newPassword, user.getSalt()))
|
||||
.where(user::getId)
|
||||
.execute())
|
||||
.switchIfEmpty(Mono.error(() -> new ValidationException("error.illegal_user_password")))
|
||||
.flatMap(old -> {
|
||||
String encodePwd = passwordEncoder.encode(newPassword, old.getSalt());
|
||||
|
||||
boolean passwordChanged = !Objects.equals(encodePwd, old.getPassword());
|
||||
UserEntity newer = old.copyTo(new UserEntity());
|
||||
newer.setPassword(encodePwd);
|
||||
return repository
|
||||
.createUpdate()
|
||||
.set(newer::getPassword)
|
||||
.where(newer::getId)
|
||||
.execute()
|
||||
.flatMap(e -> new UserModifiedEvent(old, newer, passwordChanged)
|
||||
.publish(eventPublisher)
|
||||
.thenReturn(e));
|
||||
})
|
||||
.map(i -> i > 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,19 +28,19 @@ public class DimensionTerm extends AbstractTermFragmentBuilder {
|
||||
}
|
||||
|
||||
public static <T extends Conditional<?>> T inject(T query,
|
||||
String column,
|
||||
String dimensionType,
|
||||
List<String> userId) {
|
||||
String column,
|
||||
String dimensionType,
|
||||
List<String> userId) {
|
||||
return inject(query, column, dimensionType, false, false, userId);
|
||||
}
|
||||
|
||||
public static <T extends Conditional<?>> T inject(T query,
|
||||
String column,
|
||||
String dimensionType,
|
||||
boolean not,
|
||||
boolean any,
|
||||
List<String> userId) {
|
||||
return (T)query.accept(column, createTermType(dimensionType, not, any), userId);
|
||||
String column,
|
||||
String dimensionType,
|
||||
boolean not,
|
||||
boolean any,
|
||||
List<String> userId) {
|
||||
return (T) query.accept(column, createTermType(dimensionType, not, any), userId);
|
||||
}
|
||||
|
||||
public static String createTermType(String dimensionType, boolean not, boolean any) {
|
||||
@@ -73,7 +73,7 @@ public class DimensionTerm extends AbstractTermFragmentBuilder {
|
||||
fragments.addSql("not ");
|
||||
}
|
||||
fragments
|
||||
.addSql("exists(select 1 from s_dimension_user d where d.dimension_type_id = ? and d.dimension_id =", columnFullName)
|
||||
.addSql("exists(select 1 from", getTableName("s_dimension_user", column), "d where d.dimension_type_id = ? and d.dimension_id =", columnFullName)
|
||||
.addParameter(options.get(0));
|
||||
|
||||
if (!options.contains("any")) {
|
||||
|
||||
@@ -37,7 +37,7 @@ public class UserDimensionTerm extends AbstractTermFragmentBuilder {
|
||||
fragments.addSql("not");
|
||||
}
|
||||
|
||||
fragments.addSql("exists(select 1 from s_dimension_user d where d.user_id =", columnFullName);
|
||||
fragments.addSql("exists(select 1 from ",getTableName("s_dimension_user",column)," d where d.user_id =", columnFullName);
|
||||
|
||||
if (options.size() > 0) {
|
||||
String typeId = options.get(0);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -24,7 +24,7 @@
|
||||
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<version>4.0.15-SNAPSHOT</version>
|
||||
<version>4.0.15</version>
|
||||
<modules>
|
||||
<module>hsweb-starter</module>
|
||||
<module>hsweb-core</module>
|
||||
@@ -90,7 +90,7 @@
|
||||
<cglib.version>3.2.2</cglib.version>
|
||||
<aspectj.version>1.6.12</aspectj.version>
|
||||
|
||||
<hsweb.ezorm.version>4.0.14</hsweb.ezorm.version>
|
||||
<hsweb.ezorm.version>4.1.0-SNAPSHOT</hsweb.ezorm.version>
|
||||
<hsweb.utils.version>3.0.2</hsweb.utils.version>
|
||||
<hsweb.expands.version>3.0.2</hsweb.expands.version>
|
||||
<swagger.version>2.7.0</swagger.version>
|
||||
|
||||
Reference in New Issue
Block a user