feat: 适配spring-boot3

This commit is contained in:
zhouhao
2023-12-25 11:51:41 +08:00
parent 92a0cb5f6e
commit 6a00714151
114 changed files with 519 additions and 2638 deletions

View File

@@ -1 +1 @@
distributionUrl=https://downloads.apache.org/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip
distributionUrl=https://archive.apache.org/dist/maven/maven-3/3.9.3/binaries/apache-maven-3.9.3-bin.zip

View File

@@ -47,8 +47,8 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

View File

@@ -9,6 +9,7 @@ import org.hswebframework.web.authorization.define.AuthorizingContext;
* @author zhouhao
* @since 3.0
*/
@Deprecated
public interface DataAccessController {
/**
* 执行权限控制

View File

@@ -3,6 +3,7 @@ package org.hswebframework.web.authorization.simple;
import lombok.*;
import org.hswebframework.web.authorization.User;
import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
@@ -16,6 +17,7 @@ import java.util.Map;
@Builder
public class SimpleUser implements User {
@Serial
private static final long serialVersionUID = 2194541828191869091L;
private String id;

View File

@@ -1,12 +1,12 @@
package org.hswebframework.web.authorization.simple.builder;
import com.alibaba.fastjson.JSON;
import jakarta.annotation.PostConstruct;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilder;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.simple.*;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

View File

@@ -0,0 +1,37 @@
module hsweb.authorization.api {
requires spring.core;
requires hsweb.core;
requires spring.beans;
requires spring.boot.autoconfigure;
requires spring.context;
requires spring.boot;
requires static spring.data.redis;
requires reactor.core;
requires static lombok;
requires fastjson;
requires commons.collections;
requires com.fasterxml.jackson.annotation;
requires jakarta.annotation;
requires org.slf4j;
exports org.hswebframework.web.authorization;
exports org.hswebframework.web.authorization.access;
exports org.hswebframework.web.authorization.annotation;
exports org.hswebframework.web.authorization.token.redis;
exports org.hswebframework.web.authorization.token.event;
exports org.hswebframework.web.authorization.builder;
exports org.hswebframework.web.authorization.define;
exports org.hswebframework.web.authorization.dimension;
exports org.hswebframework.web.authorization.events;
exports org.hswebframework.web.authorization.exception;
exports org.hswebframework.web.authorization.setting;
exports org.hswebframework.web.authorization.simple;
exports org.hswebframework.web.authorization.simple.builder;
exports org.hswebframework.web.authorization.twofactor.defaults;
exports org.hswebframework.web.authorization.twofactor;
opens org.hswebframework.web.authorization.simple;
exports org.hswebframework.web.authorization.token;
}

View File

@@ -20,11 +20,6 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework</groupId>
<artifactId>hsweb-expands-script</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
@@ -69,8 +64,8 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<optional>true</optional>
</dependency>
@@ -103,6 +98,25 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-client-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -3,33 +3,20 @@ package org.hswebframework.web.authorization.basic.configuration;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.ReactiveAuthenticationManagerProvider;
import org.hswebframework.web.authorization.access.DataAccessController;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.basic.aop.AopMethodAuthorizeDefinitionParser;
import org.hswebframework.web.authorization.basic.embed.EmbedAuthenticationProperties;
import org.hswebframework.web.authorization.basic.embed.EmbedReactiveAuthenticationManager;
import org.hswebframework.web.authorization.basic.handler.DefaultAuthorizingHandler;
import org.hswebframework.web.authorization.basic.handler.UserAllowPermissionHandler;
import org.hswebframework.web.authorization.basic.handler.access.DefaultDataAccessController;
import org.hswebframework.web.authorization.basic.twofactor.TwoFactorHandlerInterceptorAdapter;
import org.hswebframework.web.authorization.basic.web.*;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Nonnull;
import java.util.List;
/**
* 权限控制自动配置类
@@ -41,14 +28,10 @@ import java.util.List;
@EnableConfigurationProperties(EmbedAuthenticationProperties.class)
public class AuthorizingHandlerAutoConfiguration {
@Bean
public DefaultDataAccessController dataAccessController() {
return new DefaultDataAccessController();
}
@Bean
public DefaultAuthorizingHandler authorizingHandler(DataAccessController dataAccessController) {
return new DefaultAuthorizingHandler(dataAccessController);
public DefaultAuthorizingHandler authorizingHandler() {
return new DefaultAuthorizingHandler(null);
}
@@ -93,27 +76,6 @@ public class AuthorizingHandlerAutoConfiguration {
return new BearerTokenParser();
}
@Configuration
public static class DataAccessHandlerProcessor implements BeanPostProcessor {
@Autowired
private DefaultDataAccessController defaultDataAccessController;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof DataAccessHandler) {
defaultDataAccessController.addHandler(((DataAccessHandler) bean));
}
return bean;
}
}
@Configuration
@ConditionalOnProperty(prefix = "hsweb.authorize", name = "basic-authorization", havingValue = "true")
@ConditionalOnClass(UserTokenForTypeParser.class)

View File

@@ -11,7 +11,7 @@ import org.hswebframework.web.authorization.token.UserToken;
import org.hswebframework.web.authorization.token.UserTokenManager;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
public class BasicAuthorizationTokenParser implements UserTokenForTypeParser {

View File

@@ -1,7 +1,6 @@
package org.hswebframework.web.authorization.basic.configuration;
import org.hswebframework.web.authorization.basic.aop.AopMethodAuthorizeDefinitionParser;
import org.hswebframework.web.authorization.basic.twofactor.TwoFactorHandlerInterceptorAdapter;
import org.hswebframework.web.authorization.basic.web.*;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
@@ -15,7 +14,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.util.List;
@AutoConfiguration
@@ -64,16 +63,4 @@ public class WebMvcAuthorizingConfiguration {
return new SessionIdUserTokenGenerator();
}
@Bean
@ConditionalOnProperty(prefix = "hsweb.authorize.two-factor", name = "enable", havingValue = "true")
@Order(100)
public WebMvcConfigurer twoFactorHandlerConfigurer(TwoFactorValidatorManager manager) {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(@Nonnull InterceptorRegistry registry) {
registry.addInterceptor(new TwoFactorHandlerInterceptorAdapter(manager));
}
};
}
}

View File

@@ -11,10 +11,8 @@ import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfi
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import org.springframework.util.ObjectUtils;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -36,10 +34,6 @@ import java.util.Optional;
* permissions:
* - id: user-manager
* actions: *
* dataAccesses:
* - action: query
* type: DENY_FIELDS
* fields: password,salt
* </pre>
*
* @author zhouhao
@@ -62,25 +56,9 @@ public class EmbedAuthenticationProperties implements InitializingBean {
@Override
public void afterPropertiesSet() {
users.forEach((id, properties) -> {
if (StringUtils.isEmpty(properties.getId())) {
if (ObjectUtils.isEmpty(properties.getId())) {
properties.setId(id);
}
for (EmbedAuthenticationInfo.PermissionInfo permissionInfo : properties.getPermissions()) {
for (Map<String, Object> objectMap : permissionInfo.getDataAccesses()) {
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);
if (maybeIsList) {
stringObjectEntry.setValue(mapVal.values());
}
}
}
}
}
authentications.put(id, properties.toAuthentication(dataAccessConfigBuilderFactory));
});
}

View File

@@ -69,7 +69,6 @@ public class DefaultAuthorizingHandler implements AuthorizingHandler {
public void handleDataAccess(AuthorizingContext context) {
if (dataAccessController == null) {
log.warn("dataAccessController is null,skip result access control!");
return;
}
if (context.getDefinition().getResources() == null) {

View File

@@ -1,63 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.utils.ClassUtils;
import org.hswebframework.web.aop.MethodInterceptorContext;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Dimension;
import org.hswebframework.web.authorization.DimensionType;
import org.hswebframework.web.authorization.define.AuthorizeDefinition;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import org.hswebframework.web.crud.web.reactive.*;
import java.util.List;
@Getter
@Setter
public class DataAccessHandlerContext {
private Class<?> entityType;
private ReactiveRepository<?, Object> repository;
private Authentication authentication;
private List<Dimension> dimensions;
private MethodInterceptorContext paramContext;
private AuthorizeDefinition definition;
public static DataAccessHandlerContext of(AuthorizingContext context, String type) {
DataAccessHandlerContext requestContext = new DataAccessHandlerContext();
Authentication authentication = context.getAuthentication();
requestContext.setDimensions(authentication.getDimensions(type));
requestContext.setAuthentication(context.getAuthentication());
requestContext.setParamContext(context.getParamContext());
requestContext.setDefinition(context.getDefinition());
Object target = context.getParamContext().getTarget();
Class entityType = ClassUtils.getGenericType(org.springframework.util.ClassUtils.getUserClass(target));
if (entityType != Object.class) {
requestContext.setEntityType(entityType);
}
if (target instanceof ReactiveQueryController) {
requestContext.setRepository(((ReactiveQueryController) target).getRepository());
} else if (target instanceof ReactiveSaveController) {
requestContext.setRepository(((ReactiveSaveController) target).getRepository());
} else if (target instanceof ReactiveDeleteController) {
requestContext.setRepository(((ReactiveDeleteController) target).getRepository());
} else if (target instanceof ReactiveServiceQueryController) {
requestContext.setRepository(((ReactiveServiceQueryController) target).getService().getRepository());
} else if (target instanceof ReactiveServiceSaveController) {
requestContext.setRepository(((ReactiveServiceSaveController) target).getService().getRepository());
} else if (target instanceof ReactiveServiceDeleteController) {
requestContext.setRepository(((ReactiveServiceDeleteController) target).getService().getRepository());
}
// TODO: 2019-11-18 not reactive implements
return requestContext;
}
}

View File

@@ -1,59 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.access.DataAccessController;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import java.util.LinkedList;
import java.util.List;
/**
* 默认的行级权限控制.通过获取DataAccessHandler进行实际处理
*
* @author zhouhao
* @see DataAccessHandler
* @since 3.0
*/
public final class DefaultDataAccessController implements DataAccessController {
private DataAccessController parent;
private List<DataAccessHandler> handlers = new LinkedList<>();
public DefaultDataAccessController() {
this(null);
}
public DefaultDataAccessController(DataAccessController parent) {
if (parent == this) {
throw new UnsupportedOperationException();
}
this.parent = parent;
addHandler(new FieldFilterDataAccessHandler())
.addHandler(new DimensionDataAccessHandler());
}
@Override
public boolean doAccess(DataAccessConfig access, AuthorizingContext context) {
if (parent != null) {
parent.doAccess(access, context);
}
return handlers.stream()
.filter(handler -> handler.isSupport(access))
.allMatch(handler -> handler.handle(access, context));
}
public DefaultDataAccessController addHandler(DataAccessHandler handler) {
handlers.add(handler);
return this;
}
public void setHandlers(List<DataAccessHandler> handlers) {
this.handlers = handlers;
}
public List<DataAccessHandler> getHandlers() {
return handlers;
}
}

View File

@@ -1,440 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.hswebframework.ezorm.core.param.Param;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.web.api.crud.entity.Entity;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Dimension;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.annotation.DimensionDataAccess;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import org.hswebframework.web.authorization.define.Phased;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.hswebframework.web.authorization.simple.DimensionDataAccessConfig;
import org.hswebframework.web.bean.FastBeanCopier;
import org.reactivestreams.Publisher;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
public class DimensionDataAccessHandler implements DataAccessHandler {
@Override
public boolean isSupport(DataAccessConfig access) {
return access instanceof DimensionDataAccessConfig;
}
@Override
public boolean handle(DataAccessConfig access, AuthorizingContext context) {
DimensionDataAccessConfig config = ((DimensionDataAccessConfig) access);
DataAccessHandlerContext requestContext = DataAccessHandlerContext.of(context, config.getScopeType());
if (!checkSupported(config, requestContext)) {
return false;
}
switch (access.getAction()) {
case Permission.ACTION_QUERY:
case Permission.ACTION_GET:
return doHandleQuery(config, requestContext);
case Permission.ACTION_ADD:
case Permission.ACTION_SAVE:
case Permission.ACTION_UPDATE:
return doHandleUpdate(config, requestContext);
case Permission.ACTION_DELETE:
return doHandleDelete(config, requestContext);
default:
if (log.isDebugEnabled()) {
log.debug("data access [{}] not support for {}", config.getType().getId(), access.getAction());
}
return true;
}
}
@SneakyThrows
protected String getProperty(DimensionDataAccessConfig cfg,
DataAccessHandlerContext ct) {
return Optional.ofNullable(
getMappingInfo(ct).get(cfg.getScopeType()))
.map(MappingInfo::getProperty)
.orElseGet(() -> {
log.warn("{} not supported dimension data access", ct.getParamContext().getMethod());
return null;
});
}
protected boolean checkSupported(DimensionDataAccessConfig cfg, DataAccessHandlerContext ctx) {
Authentication authentication = ctx.getAuthentication();
/*
DataAccessHelper.assert()
*/
if (CollectionUtils.isEmpty(ctx.getDimensions())) {
log.warn("user:[{}] dimension not setup", authentication.getUser().getId());
return false;
}
if (!getMappingInfo(ctx).containsKey(cfg.getScopeType())) {
log.warn("{} not supported dimension data access.see annotation: @DimensionDataAccess", ctx.getParamContext().getMethod());
return false;
}
return true;
}
protected boolean doHandleDelete(DimensionDataAccessConfig cfg,
DataAccessHandlerContext context) {
// TODO: 2019-11-18
return doHandleUpdate(cfg, context);
}
@SuppressWarnings("all")
protected Object handleById(DimensionDataAccessConfig config,
DataAccessHandlerContext context,
MappingInfo mappingInfo,
Object id) {
if (id instanceof Param || id instanceof Entity) {
applyQueryParam(config, context, id);
return id;
}
List<Dimension> dimensions = context.getDimensions();
Set<Object> scope = CollectionUtils.isNotEmpty(config.getScope()) ?
config.getScope() :
dimensions
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
Function<Collection<Object>, Mono<Void>> reactiveCheck = obj -> context
.getRepository()
.findById(obj)
.doOnNext(r -> {
Object val = FastBeanCopier.copy(r, new HashMap<>(), FastBeanCopier.include(mappingInfo.getProperty()))
.get(mappingInfo.getProperty());
if (!StringUtils.isEmpty(val)
&& !scope.contains(val)) {
throw new AccessDenyException();
}
})
.then();
if (id instanceof Publisher) {
if (id instanceof Mono) {
return ((Mono) id)
.flatMap(r -> {
if (r instanceof Param) {
applyQueryParam(config, context, r);
return Mono.just(r);
}
return reactiveCheck.apply(r instanceof Collection ? ((Collection) r) : Collections.singleton(r));
})
.then((Mono) id);
}
if (id instanceof Flux) {
return ((Flux) id)
.filter(v -> {
if (v instanceof Param) {
applyQueryParam(config, context, v);
return false;
}
return true;
})
.collectList()
.flatMap(reactiveCheck)
.thenMany((Flux) id);
}
}
Collection<Object> idVal = id instanceof Collection ? ((Collection) id) : Collections.singleton(id);
Object result = context.getParamContext().getInvokeResult();
if (result instanceof Mono) {
context.getParamContext()
.setInvokeResult(reactiveCheck.apply(idVal).then(((Mono) result)));
} else if (result instanceof Flux) {
context.getParamContext()
.setInvokeResult(reactiveCheck.apply(idVal).thenMany(((Flux) result)));
} else {
// TODO: 2019-11-19 非响应式处理
log.warn("unsupported handle data access by id :{}", context.getParamContext().getMethod());
}
return id;
}
protected boolean doHandleUpdate(DimensionDataAccessConfig cfg,
DataAccessHandlerContext context) {
MappingInfo info = getMappingInfo(context).get(cfg.getScopeType());
if (info != null) {
if (info.idParamIndex != -1) {
Object param = context.getParamContext().getArguments()[info.idParamIndex];
context.getParamContext().getArguments()[info.idParamIndex] = handleById(cfg, context, info, param);
return true;
}
} else {
return true;
}
boolean reactive = context.getParamContext()
.handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono.from(publisher)
.flatMap(payload -> applyReactiveUpdatePayload(cfg, info, Collections.singleton(payload), context)
.thenReturn(payload));
}
if (publisher instanceof Flux) {
return Flux.from(publisher)
.collectList()
.flatMapMany(list ->
applyReactiveUpdatePayload(cfg, info, list, context)
.flatMapIterable(v -> list));
}
return publisher;
});
if (!reactive) {
applyUpdatePayload(cfg, info, Arrays
.stream(context.getParamContext().getArguments())
.flatMap(obj -> {
if (obj instanceof Collection) {
return ((Collection<?>) obj).stream();
}
return Stream.of(obj);
})
.filter(Entity.class::isInstance)
.collect(Collectors.toSet()), context);
return true;
}
return true;
}
protected void applyUpdatePayload(DimensionDataAccessConfig config,
MappingInfo mappingInfo,
Collection<?> payloads,
DataAccessHandlerContext context) {
List<Dimension> dimensions = context.getDimensions();
Set<Object> scope = CollectionUtils.isNotEmpty(config.getScope()) ?
config.getScope() :
dimensions
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
for (Object payload : payloads) {
if (!(payload instanceof Entity)) {
continue;
}
if (payload instanceof Param) {
applyQueryParam(config, context, ((Param) payload));
continue;
}
String property = mappingInfo.getProperty();
Map<String, Object> map = FastBeanCopier.copy(payload, new HashMap<>(), FastBeanCopier.include(property));
Object value = map.get(property);
if (StringUtils.isEmpty(value)) {
if (dimensions.size() == 1) {
map.put(property, dimensions.get(0).getId());
FastBeanCopier.copy(map, payload, property);
}
continue;
}
if (CollectionUtils.isNotEmpty(scope)) {
if (!scope.contains(value)) {
throw new AccessDenyException();
}
}
}
}
protected Mono<Void> applyReactiveUpdatePayload(DimensionDataAccessConfig config,
MappingInfo info,
Collection<?> payloads,
DataAccessHandlerContext context) {
return Mono.fromRunnable(() -> applyUpdatePayload(config, info, payloads, context));
}
protected boolean hasAccessByProperty(Set<Object> scope, String property, Object payload) {
Map<String, Object> values = FastBeanCopier.copy(payload, new HashMap<>(), FastBeanCopier.include(property));
Object val = values.get(property);
return val == null || scope.contains(val);
}
@SuppressWarnings("all")
protected boolean doHandleQuery(DimensionDataAccessConfig cfg, DataAccessHandlerContext context) {
MappingInfo mappingInfo = getMappingInfo(context).get(cfg.getScopeType());
//根据结果控制
if (context.getDefinition().getResources().getPhased() == Phased.after) {
Object result = context.getParamContext().getInvokeResult();
Set<Object> scope = CollectionUtils.isNotEmpty(cfg.getScope()) ?
cfg.getScope() :
context.getDimensions()
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
String property = mappingInfo.getProperty();
if (result instanceof Mono) {
context.getParamContext()
.setInvokeResult(((Mono) result).
filter(data -> hasAccessByProperty(scope, property, data)));
return true;
} else if (result instanceof Flux) {
context.getParamContext()
.setInvokeResult(((Flux) result).
filter(data -> hasAccessByProperty(scope, property, data)));
return true;
}
return hasAccessByProperty(scope, property, result);
}
//根据id控制
if (mappingInfo.getIdParamIndex() >= 0) {
Object param = context.getParamContext().getArguments()[mappingInfo.idParamIndex];
context.getParamContext().getArguments()[mappingInfo.idParamIndex] = handleById(cfg, context, mappingInfo, param);
return true;
}
//根据查询条件控制
boolean reactive = context.getParamContext().handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono
.from(publisher)
.flatMap(param -> this
.applyReactiveQueryParam(cfg, context, param)
.thenReturn(param));
}
return publisher;
});
if (!reactive) {
Object[] args = context.getParamContext().getArguments();
this.applyQueryParam(cfg, context, args);
}
return true;
}
protected String getTermType(DimensionDataAccessConfig cfg) {
return "in";
}
protected void applyQueryParam(DimensionDataAccessConfig cfg,
DataAccessHandlerContext requestContext,
Param param) {
Set<Object> scope = CollectionUtils.isNotEmpty(cfg.getScope()) ?
cfg.getScope() :
requestContext.getDimensions()
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
QueryParamEntity entity = new QueryParamEntity();
entity.setTerms(new ArrayList<>(param.getTerms()));
entity.toNestQuery(query ->
query.where(
getProperty(cfg, requestContext),
getTermType(cfg),
scope));
param.setTerms(entity.getTerms());
}
protected void applyQueryParam(DimensionDataAccessConfig cfg,
DataAccessHandlerContext requestContext,
Object... params) {
for (Object param : params) {
if (param instanceof QueryParam) {
applyQueryParam(cfg, requestContext, (QueryParam) param);
}
}
}
protected Mono<Void> applyReactiveQueryParam(DimensionDataAccessConfig cfg,
DataAccessHandlerContext requestContext,
Object... param) {
return Mono.fromRunnable(() -> applyQueryParam(cfg, requestContext, param));
}
private Map<Method, Map<String, MappingInfo>> cache = new ConcurrentHashMap<>();
public Map<String, MappingInfo> getMappingInfo(DataAccessHandlerContext context) {
return getMappingInfo(ClassUtils.getUserClass(context.getParamContext().getTarget()), context.getParamContext().getMethod());
}
private Set<Class<? extends Annotation>> ann = new HashSet<>(Arrays.asList(DimensionDataAccess.class, DimensionDataAccess.Mapping.class));
private Map<String, MappingInfo> getMappingInfo(Class target, Method method) {
return cache.computeIfAbsent(method, m -> {
Set<Annotation> methodAnnotation = AnnotatedElementUtils.findAllMergedAnnotations(method, ann);
Set<Annotation> classAnnotation = AnnotatedElementUtils.findAllMergedAnnotations(target, ann);
List<Annotation> all = new ArrayList<>(classAnnotation);
all.addAll(methodAnnotation);
if (CollectionUtils.isEmpty(all)) {
return Collections.emptyMap();
}
Map<String, MappingInfo> mappingInfoMap = new HashMap<>();
for (Annotation annotation : all) {
if (annotation instanceof DimensionDataAccess) {
for (DimensionDataAccess.Mapping mapping : ((DimensionDataAccess) annotation).mapping()) {
mappingInfoMap.put(mapping.dimensionType(), MappingInfo.of(mapping));
}
}
if (annotation instanceof DimensionDataAccess.Mapping) {
mappingInfoMap.put(((DimensionDataAccess.Mapping) annotation).dimensionType(), MappingInfo.of(((DimensionDataAccess.Mapping) annotation)));
}
}
return mappingInfoMap;
});
}
@Getter
@Setter
@AllArgsConstructor
static class MappingInfo {
String dimension;
String property;
int idParamIndex;
static MappingInfo of(DimensionDataAccess.Mapping mapping) {
return new MappingInfo(mapping.dimensionType(), mapping.property(), mapping.idParamIndex());
}
}
}

View File

@@ -1,167 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.access.FieldFilterDataAccessConfig;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import org.hswebframework.web.authorization.define.Phased;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Set;
/**
* 数据权限字段过滤处理,目前仅支持deny. {@link DataAccessConfig.DefaultType#DENY_FIELDS}
*
* @author zhouhao
*/
public class FieldFilterDataAccessHandler implements DataAccessHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean isSupport(DataAccessConfig access) {
return access instanceof FieldFilterDataAccessConfig;
}
@Override
public boolean handle(DataAccessConfig access, AuthorizingContext context) {
FieldFilterDataAccessConfig filterDataAccessConfig = ((FieldFilterDataAccessConfig) access);
switch (access.getAction()) {
case Permission.ACTION_QUERY:
case Permission.ACTION_GET:
return doQueryAccess(filterDataAccessConfig, context);
case Permission.ACTION_ADD:
case Permission.ACTION_SAVE:
case Permission.ACTION_UPDATE:
return doUpdateAccess(filterDataAccessConfig, context);
default:
if (logger.isDebugEnabled()) {
logger.debug("field filter not support for {}", access.getAction());
}
return true;
}
}
protected void applyUpdateParam(FieldFilterDataAccessConfig config, Object... parameter) {
for (Object data : parameter) {
for (String field : config.getFields()) {
try {
//设置值为null,跳过修改
BeanUtilsBean.getInstance()
.getPropertyUtils()
.setProperty(data, field, null);
} catch (Exception e) {
logger.warn("can't set {} null", field, e);
}
}
}
}
/**
* @param accesses 不可操作的字段
* @param params 参数上下文
* @return true
* @see BeanUtilsBean
* @see org.apache.commons.beanutils.PropertyUtilsBean
*/
protected boolean doUpdateAccess(FieldFilterDataAccessConfig accesses, AuthorizingContext params) {
boolean reactive = params.getParamContext().handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono.from(publisher)
.doOnNext(data -> applyUpdateParam(accesses, data));
}
if (publisher instanceof Flux) {
return Flux.from(publisher)
.doOnNext(data -> applyUpdateParam(accesses, data));
}
return publisher;
});
if (reactive) {
return true;
}
applyUpdateParam(accesses, params.getParamContext().getArguments());
return true;
}
@SuppressWarnings("all")
protected void applyQueryParam(FieldFilterDataAccessConfig config, Object param) {
if (param instanceof QueryParam) {
Set<String> denyFields = config.getFields();
((QueryParam) param).excludes(denyFields.toArray(new String[0]));
return;
}
Object r = InvokeResultUtils.convertRealResult(param);
if (r instanceof Collection) {
((Collection) r).forEach(o -> setObjectPropertyNull(o, config.getFields()));
} else {
setObjectPropertyNull(r, config.getFields());
}
}
@SuppressWarnings("all")
protected boolean doQueryAccess(FieldFilterDataAccessConfig access, AuthorizingContext context) {
if (context.getDefinition().getResources().getPhased() == Phased.before) {
boolean reactive = context
.getParamContext()
.handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono.from(publisher)
.doOnNext(param -> {
applyQueryParam(access, param);
});
}
return publisher;
});
if (reactive) {
return true;
}
for (Object argument : context.getParamContext().getArguments()) {
applyQueryParam(access, argument);
}
} else {
if (context.getParamContext().getInvokeResult() instanceof Publisher) {
context.getParamContext().setInvokeResult(
Flux.from((Publisher<?>) context.getParamContext().getInvokeResult())
.doOnNext(result -> {
applyQueryParam(access, result);
})
);
return true;
}
applyQueryParam(access, context.getParamContext().getInvokeResult());
}
return true;
}
protected void setObjectPropertyNull(Object obj, Set<String> fields) {
if (null == obj) {
return;
}
for (String field : fields) {
try {
BeanUtilsBean.getInstance().getPropertyUtils().setProperty(obj, field, null);
} catch (Exception ignore) {
}
}
}
}

View File

@@ -1,18 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import org.springframework.http.ResponseEntity;
public class InvokeResultUtils {
public static Object convertRealResult(Object result) {
if (result instanceof ResponseEntity) {
result = ((ResponseEntity) result).getBody();
}
// if (result instanceof ResponseMessage) {
// result = ((ResponseMessage) result).getResult();
// }
// if (result instanceof PagerResult) {
// result = ((PagerResult) result).getData();
// }
return result;
}
}

View File

@@ -1,54 +0,0 @@
package org.hswebframework.web.authorization.basic.twofactor;
import lombok.AllArgsConstructor;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.annotation.TwoFactor;
import org.hswebframework.web.authorization.exception.NeedTwoFactorException;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author zhouhao
* @since 3.0.4
*/
@AllArgsConstructor
public class TwoFactorHandlerInterceptorAdapter extends HandlerInterceptorAdapter {
private final TwoFactorValidatorManager validatorManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = ((HandlerMethod) handler);
TwoFactor factor = method.getMethodAnnotation(TwoFactor.class);
if (factor == null || factor.ignore()) {
return true;
}
String userId = Authentication.current()
.map(Authentication::getUser)
.map(User::getId)
.orElse(null);
TwoFactorValidator validator = validatorManager.getValidator(userId, factor.value(), factor.provider());
if (!validator.expired()) {
return true;
}
String code = request.getParameter(factor.parameter());
if (code == null) {
code = request.getHeader(factor.parameter());
}
if (StringUtils.isEmpty(code)) {
throw new NeedTwoFactorException("validation.need_two_factor_verify", factor.provider());
} else if (!validator.verify(code, factor.timeout())) {
throw new NeedTwoFactorException(factor.message(), factor.provider());
}
}
return super.preHandle(request, response, handler);
}
}

View File

@@ -7,7 +7,7 @@ import org.hswebframework.web.authorization.token.ParsedToken;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;

View File

@@ -3,7 +3,7 @@ package org.hswebframework.web.authorization.basic.web;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.utils.WebUtils;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;

View File

@@ -5,8 +5,8 @@ import org.hswebframework.web.authorization.token.UserToken;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import static org.hswebframework.web.authorization.basic.web.UserTokenGenerator.TOKEN_TYPE_SESSION_ID;

View File

@@ -2,7 +2,7 @@ package org.hswebframework.web.authorization.basic.web;
import org.hswebframework.web.authorization.token.ParsedToken;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
/**
* 令牌解析器,用于在接受到请求到时候,从请求中获取令牌

View File

@@ -7,10 +7,11 @@ import org.hswebframework.web.authorization.token.UserToken;
import org.hswebframework.web.authorization.token.UserTokenHolder;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -20,7 +21,7 @@ import java.util.stream.Collectors;
*
* @author zhouhao
*/
public class WebUserTokenInterceptor extends HandlerInterceptorAdapter {
public class WebUserTokenInterceptor implements HandlerInterceptor {
private final UserTokenManager userTokenManager;

View File

@@ -0,0 +1,34 @@
module hsweb.authorization.basic {
requires spring.core;
requires hsweb.core;
requires hsweb.authorization.api;
requires hsweb.access.logging.api;
requires spring.beans;
requires spring.boot.autoconfigure;
requires spring.context;
requires spring.boot;
requires reactor.core;
requires static lombok;
requires fastjson;
requires commons.collections;
requires com.fasterxml.jackson.annotation;
requires jakarta.annotation;
requires org.slf4j;
requires spring.aop;
requires org.reactivestreams;
requires spring.web;
requires org.apache.commons.collections4;
requires jakarta.validation;
requires io.swagger.v3.oas.annotations;
requires spring.webmvc;
requires jakarta.servlet;
requires org.apache.commons.codec;
exports org.hswebframework.web.authorization.basic.web;
exports org.hswebframework.web.authorization.basic.aop;
exports org.hswebframework.web.authorization.basic.configuration;
exports org.hswebframework.web.authorization.basic.define;
opens org.hswebframework.web.authorization.basic.aop;
opens org.hswebframework.web.authorization.basic.configuration;
}

View File

@@ -61,113 +61,4 @@ public class AopAuthorizingControllerTest {
.verifyComplete();
}
@Test
public void testFiledDeny() {
SimpleAuthentication authentication = new SimpleAuthentication();
SimpleFieldFilterDataAccessConfig config = new SimpleFieldFilterDataAccessConfig();
config.setAction("query");
config.setFields(new HashSet<>(Arrays.asList("name")));
authentication.setUser(SimpleUser.builder().id("test").username("test").build());
authentication.setPermissions(Arrays.asList(SimplePermission.builder()
.actions(Collections.singleton("query"))
.dataAccesses(Collections.singleton(config))
.id("test").build()));
ReactiveAuthenticationHolder.setSupplier(new ReactiveAuthenticationSupplier() {
@Override
public Mono<Authentication> get(String userId) {
return Mono.empty();
}
@Override
public Mono<Authentication> get() {
return Mono.just(authentication);
}
});
testController.queryUser(new QueryParam())
.map(Param::getExcludes)
.as(StepVerifier::create)
.expectNextMatches(f -> f.contains("name"))
.verifyComplete();
testController.queryUser(Mono.just(new QueryParam()))
.map(Param::getExcludes)
.as(StepVerifier::create)
.expectNextMatches(f -> f.contains("name"))
.verifyComplete();
}
@Test
public void testDimensionDataAccess() {
SimpleAuthentication authentication = new SimpleAuthentication();
DimensionDataAccessConfig config = new DimensionDataAccessConfig();
config.setAction("query");
config.setScopeType("role");
DimensionDataAccessConfig config2 = new DimensionDataAccessConfig();
config2.setAction("save");
config2.setScopeType("role");
ReactiveAuthenticationHolder.setSupplier(new ReactiveAuthenticationSupplier() {
@Override
public Mono<Authentication> get(String userId) {
return Mono.empty();
}
@Override
public Mono<Authentication> get() {
return Mono.just(authentication);
}
});
authentication.setUser(SimpleUser.builder().id("test").username("test").build());
authentication.setPermissions(Arrays.asList(SimplePermission.builder()
.actions(new HashSet<>(Arrays.asList("query", "save")))
.dataAccesses(new HashSet<>(Arrays.asList(config, config2)))
.id("test").build()));
authentication.setDimensions(Collections.singletonList(Dimension.of("test", "test", DefaultDimensionType.role)));
testController.queryUserByDimension(Mono.just(new QueryParam()))
.map(Param::getTerms)
.flatMapIterable(Function.identity())
.next()
.map(Term::getValue)
.map(CastUtil::<Collection<Object>>cast)
.flatMapIterable(Function.identity())
.next()
.as(StepVerifier::create)
.expectNextMatches("test"::equals)
.verifyComplete();
TestEntity testEntity = new TestEntity();
testEntity.setRoleId("123");
testController.save(Mono.just(testEntity))
.as(StepVerifier::create)
.expectError(AccessDenyException.class)
.verify();
testController.add(Mono.just(testEntity))
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
testController.update(testEntity.getId(),Mono.just(testEntity))
.as(StepVerifier::create)
.expectError(AccessDenyException.class)
.verify();
testEntity = new TestEntity();
testEntity.setRoleId("test");
testController.save(Mono.just(testEntity))
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
}
}

View File

@@ -6,7 +6,7 @@ import org.hswebframework.web.oauth2.ErrorType;
import org.hswebframework.web.oauth2.OAuth2Exception;
import org.springframework.util.StringUtils;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
@Getter
@Setter

View File

@@ -1,6 +1,7 @@
package org.hswebframework.web.oauth2.server.impl;
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
import org.hswebframework.web.authorization.simple.SimpleUser;
import org.hswebframework.web.oauth2.server.RedisHelper;
import org.junit.Test;
import reactor.core.publisher.Flux;
@@ -16,7 +17,7 @@ public class RedisAccessTokenManagerTest {
RedisAccessTokenManager tokenManager = new RedisAccessTokenManager(RedisHelper.factory);
SimpleAuthentication authentication = new SimpleAuthentication();
authentication.setUser(SimpleUser.builder().id("test").build());
tokenManager.createAccessToken("test", authentication, false)
.doOnNext(System.out::println)
.as(StepVerifier::create)
@@ -30,7 +31,7 @@ public class RedisAccessTokenManagerTest {
RedisAccessTokenManager tokenManager = new RedisAccessTokenManager(RedisHelper.factory);
SimpleAuthentication authentication = new SimpleAuthentication();
authentication.setUser(SimpleUser.builder().id("test").build());
tokenManager
.createAccessToken("test", authentication, false)
.zipWhen(token -> tokenManager.refreshAccessToken("test", token.getRefreshToken()))
@@ -47,7 +48,7 @@ public class RedisAccessTokenManagerTest {
RedisAccessTokenManager tokenManager = new RedisAccessTokenManager(RedisHelper.factory);
SimpleAuthentication authentication = new SimpleAuthentication();
authentication.setUser(SimpleUser.builder().id("test").build());
Flux
.concat(tokenManager
.createAccessToken("test", authentication, true),

View File

@@ -38,11 +38,11 @@
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>compile</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.google.code.findbugs</groupId>-->
<!-- <artifactId>jsr305</artifactId>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>commons-codec</groupId>

View File

@@ -16,7 +16,7 @@ import org.hswebframework.ezorm.core.param.TermType;
import org.hswebframework.web.bean.FastBeanCopier;
import org.springframework.util.StringUtils;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

View File

@@ -18,7 +18,7 @@
package org.hswebframework.web.api.crud.entity;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
/**
* 支持排序的实体

View File

@@ -149,6 +149,25 @@
<artifactId>jsqlparser</artifactId>
<version>4.6</version>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-client-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -66,18 +66,11 @@ public class EasyormRepositoryRegistrar implements ImportBeanDefinitionRegistrar
}
protected Set<String> scanEntities(String[] packageStr) {
CandidateComponentsIndex index = CandidateComponentsIndexLoader.loadIndex(org.springframework.util.ClassUtils.getDefaultClassLoader());
if (null == index) {
return Stream
.of(packageStr)
.flatMap(this::doGetResources)
.map(this::getResourceClassName)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
return Stream
.of(packageStr)
.flatMap(pkg -> index.getCandidateTypes(pkg, Table.class.getName()).stream())
.flatMap(this::doGetResources)
.map(this::getResourceClassName)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}

View File

@@ -1,6 +1,7 @@
package org.hswebframework.web.crud.events.expr;
import io.netty.util.concurrent.FastThreadLocal;
import jakarta.annotation.Nonnull;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.ezorm.rdb.mapping.EntityColumnMapping;
import org.hswebframework.web.crud.query.QueryHelperUtils;
@@ -13,7 +14,6 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert;
import reactor.function.Function3;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

View File

@@ -12,7 +12,7 @@ import org.slf4j.Logger;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

View File

@@ -10,7 +10,7 @@ import org.springframework.transaction.reactive.TransactionSynchronization;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.util.Collection;
import java.util.function.Function;

View File

@@ -12,7 +12,7 @@ import org.hswebframework.web.datasource.DataSourceHolder;
import org.hswebframework.web.datasource.R2dbcDataSource;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.r2dbc.connectionfactory.ConnectionFactoryUtils;
import org.springframework.r2dbc.connection.ConnectionFactoryUtils;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;

View File

@@ -28,7 +28,7 @@ import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.server.*;
import reactor.core.publisher.Mono;
import javax.validation.ConstraintViolationException;
import jakarta.validation.ConstraintViolationException;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@@ -161,7 +161,7 @@ public class CommonErrorControllerAdvice {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Mono<ResponseMessage<?>> handleException(javax.validation.ValidationException e) {
public Mono<ResponseMessage<?>> handleException(jakarta.validation.ValidationException e) {
return Mono.just(ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getMessage()));
}

View File

@@ -27,7 +27,7 @@ import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import reactor.core.publisher.Mono;
import javax.validation.ConstraintViolationException;
import jakarta.validation.ConstraintViolationException;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
@@ -135,7 +135,7 @@ public class CommonWebMvcErrorControllerAdvice {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseMessage<?> handleException(javax.validation.ValidationException e) {
public ResponseMessage<?> handleException(jakarta.validation.ValidationException e) {
return ResponseMessage.error(400, CodeConstants.Error.illegal_argument, e.getMessage());
}

View File

@@ -19,7 +19,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

View File

@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
import jakarta.validation.Valid;
/**
* 响应式保存接口,基于{@link ReactiveRepository}提供默认的新增,保存,修改接口.

View File

@@ -2,6 +2,7 @@ package org.hswebframework.web.crud;
import org.hswebframework.web.crud.entity.CustomTestEntity;
import org.hswebframework.web.crud.entity.TestEntity;
import org.hswebframework.web.crud.service.CustomTestCustom;
import org.hswebframework.web.crud.service.TestEntityService;
import org.junit.Assert;
import org.junit.Test;
@@ -12,7 +13,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@SpringBootTest
@SpringBootTest(classes = {TestApplication.class,TestEntityService.class, CustomTestCustom.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CrudTests {

View File

@@ -1,12 +1,15 @@
package org.hswebframework.web.crud;
import org.hswebframework.web.api.crud.entity.EntityFactory;
import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
import org.hswebframework.web.crud.entity.factory.EntityMappingCustomizer;
import org.hswebframework.web.crud.entity.factory.MapperEntityFactory;
import org.hswebframework.web.crud.events.TestEntityListener;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
@@ -20,4 +23,9 @@ public class TestApplication {
customizers.forEach(customizer -> customizer.custom(factory));
return factory;
}
@Bean
public TestEntityListener testEntityListener(){
return new TestEntityListener();
}
}

View File

@@ -9,7 +9,7 @@ import org.hswebframework.web.validator.CreateGroup;
import javax.persistence.Column;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
@Getter

View File

@@ -20,7 +20,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import javax.annotation.PostConstruct;
import jakarta.annotation.PostConstruct;
import static org.junit.Assert.*;

View File

@@ -5,10 +5,9 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import jakarta.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class TestEntityListener {
AtomicInteger beforeCreate = new AtomicInteger();

View File

@@ -8,9 +8,10 @@ import lombok.ToString;
import org.hswebframework.ezorm.core.param.Sort;
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.crud.TestApplication;
import org.hswebframework.web.crud.entity.EventTestEntity;
import org.hswebframework.web.crud.entity.TestEntity;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -19,16 +20,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import reactor.test.StepVerifier;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.*;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@SpringBootTest(classes = TestApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
class DefaultQueryHelperTest {
public class DefaultQueryHelperTest {
@Autowired
private DatabaseOperator database;

View File

@@ -4,7 +4,8 @@ import org.hswebframework.ezorm.rdb.executor.SqlRequest;
import org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrappers;
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.junit.jupiter.api.Test;
import org.hswebframework.web.crud.TestApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@@ -13,15 +14,15 @@ import reactor.test.StepVerifier;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@SpringBootTest(classes = TestApplication.class)
@RunWith(SpringJUnit4ClassRunner.class)
class QueryAnalyzerImplTest {
public class QueryAnalyzerImplTest {
@Autowired
private DatabaseOperator database;
@Test
void testInject() {
public void testInject() {
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(database,
"select count(distinct time) t2, \"name\" n from \"s_test\" t");
SqlRequest request = analyzer.refactor(
@@ -43,7 +44,7 @@ class QueryAnalyzerImplTest {
@Test
void testUnion() {
public void testUnion() {
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(database,
"select name n from s_test t " +
"union select name n from s_test t");
@@ -56,7 +57,7 @@ class QueryAnalyzerImplTest {
}
@Test
void test() {
public void test() {
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(database,
"select name n from s_test t");
@@ -69,7 +70,7 @@ class QueryAnalyzerImplTest {
}
@Test
void testSub() {
public void testSub() {
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(database,
"select * from ( select distinct(name) as n from s_test ) t");
@@ -94,7 +95,7 @@ class QueryAnalyzerImplTest {
}
@Test
void testJoin() {
public void testJoin() {
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
database,
"select *,t2.c from s_test t " +
@@ -110,7 +111,7 @@ class QueryAnalyzerImplTest {
}
@Test
void testPrepare(){
public void testPrepare(){
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
database,
"select * from (select substring(id,9) id from s_test where left(id,1) = ?) t");
@@ -122,7 +123,7 @@ class QueryAnalyzerImplTest {
}
@Test
void testWith(){
public void testWith(){
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
database,

View File

@@ -14,7 +14,7 @@ import reactor.test.StepVerifier;
import static org.junit.Assert.*;
@SpringBootTest(classes = TestApplication.class, args = "--hsweb.cache.type=guava")
@SpringBootTest(classes ={ TestApplication.class,TestCacheEntityService.class}, args = "--hsweb.cache.type=guava")
@RunWith(SpringRunner.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
public class GenericReactiveCacheSupportCrudServiceTest {

View File

@@ -3,6 +3,7 @@ package org.hswebframework.web.crud.service;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.crud.TestApplication;
import org.hswebframework.web.crud.entity.TestTreeSortEntity;
import org.hswebframework.web.exception.ValidationException;
import org.junit.Test;
@@ -19,7 +20,7 @@ import java.util.List;
import static org.junit.Assert.*;
@SpringBootTest
@SpringBootTest(classes = {TestApplication.class,TestTreeSortEntityService.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class ReactiveTreeSortEntityServiceTest {

View File

@@ -18,7 +18,7 @@
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.0-GA</version>
<version>${javassist.version}</version>
</dependency>
<dependency>
@@ -58,8 +58,20 @@
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
@@ -83,22 +95,19 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hswebframework</groupId>
<artifactId>hsweb-expands-script</artifactId>
<version>${hsweb.expands.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>jakarta.el</groupId>-->
<!-- <artifactId>jakarta.el-api</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.google.code.findbugs</groupId>-->
<!-- <artifactId>jsr305</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.hibernate.validator</groupId>
@@ -132,5 +141,10 @@
<version>0.1.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -11,7 +11,6 @@ import org.apache.commons.beanutils.PropertyUtilsBean;
import org.hswebframework.utils.time.DateFormatter;
import org.hswebframework.web.dict.EnumDict;
import org.hswebframework.web.proxy.Proxy;
import org.jctools.maps.NonBlockingHashMap;
import org.springframework.core.ResolvableType;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

View File

@@ -1,12 +1,12 @@
package org.hswebframework.web.exception;
import org.hswebframework.web.i18n.LocaleUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import javax.annotation.Nullable;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;

View File

@@ -1,6 +1,7 @@
package org.hswebframework.web.exception;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.ConstraintViolation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@@ -10,7 +11,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.ConstraintViolation;
import java.util.*;
import java.util.stream.Collectors;

View File

@@ -10,7 +10,7 @@ import reactor.core.CoreSubscriber;
import reactor.core.publisher.*;
import reactor.util.context.Context;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;

View File

@@ -7,7 +7,6 @@ import javassist.bytecode.annotation.*;
import lombok.Getter;
import lombok.SneakyThrows;
import org.springframework.util.ClassUtils;
import sun.misc.CompoundEnumeration;
import java.io.IOException;
import java.net.URI;
@@ -76,11 +75,28 @@ public class Proxy<I> extends URLClassLoader {
@SuppressWarnings("all")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[loaders.size()];
for (int i = 0; i < loaders.size(); i++) {
tmp[i] = loaders.get(i).getResources(name);
}
return new Enumeration<URL>() {
return new CompoundEnumeration<>(tmp);
@Override
public boolean hasMoreElements() {
for (Enumeration<URL> urlEnumeration : tmp) {
if (urlEnumeration.hasMoreElements()) {
return true;
}
}
return false;
}
@Override
public URL nextElement() {
for (Enumeration<URL> urlEnumeration : tmp) {
if (urlEnumeration.hasMoreElements()) {
return urlEnumeration.nextElement();
}
}
return null;
}
};
}
@SneakyThrows
@@ -212,13 +228,15 @@ public class Proxy<I> extends URLClassLoader {
@SneakyThrows
public I newInstance() {
return getTargetClass().newInstance();
return getTargetClass().getConstructor().newInstance();
}
@SneakyThrows
@SuppressWarnings("all")
public Class<I> getTargetClass() {
if (targetClass == null) {
targetClass = (Class) ctClass.toClass(this, null);
byte[] code = ctClass.toBytecode();
targetClass = (Class) defineClass(null, code, 0, code.length);
}
return targetClass;
}

View File

@@ -2,15 +2,10 @@ package org.hswebframework.web.utils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtilsBean2;
import org.hswebframework.expands.script.engine.DynamicScriptEngine;
import org.hswebframework.expands.script.engine.DynamicScriptEngineFactory;
import org.hswebframework.expands.script.engine.ExecuteResult;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@@ -20,6 +15,7 @@ import java.util.regex.Pattern;
* @since 3.0
*/
@Slf4j
@Deprecated
public class ExpressionUtils {
//表达式提取正则 ${.+?}
@@ -81,10 +77,7 @@ public class ExpressionUtils {
if (!expression.contains("${")) {
return expression;
}
DynamicScriptEngine engine = DynamicScriptEngineFactory.getEngine(language);
if (engine == null) {
return expression;
}
return TemplateParser.parse(expression, var -> {
if (StringUtils.isEmpty(var)) {
@@ -94,33 +87,7 @@ public class ExpressionUtils {
if (val != null) {
return String.valueOf(val);
}
if ("spel".equalsIgnoreCase(language) && !var.contains("#")) {
try {
Object fast = BeanUtilsBean2.getInstance().getPropertyUtils().getProperty(vars, var);
if (fast != null) {
return fast.toString();
}
} catch (Exception ignore) {
//ignore
return "";
}
}
String id = DigestUtils.md5Hex(var);
try {
if (!engine.compiled(id)) {
engine.compile(id, var);
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
return String.valueOf(engine.execute(id, vars).getIfSuccess());
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
return "";
}
return "";
});
}

View File

@@ -18,11 +18,11 @@
package org.hswebframework.web.utils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;

View File

@@ -4,7 +4,7 @@ import org.hibernate.validator.BaseHibernateValidatorConfiguration;
import org.hswebframework.web.exception.ValidationException;
import org.hswebframework.web.i18n.ContextLocaleResolver;
import javax.validation.*;
import jakarta.validation.*;
import java.util.Set;
public final class ValidatorUtils {
@@ -28,7 +28,6 @@ public final class ValidatorUtils {
configuration.messageInterpolator(configuration.getDefaultMessageInterpolator());
ValidatorFactory factory = configuration.buildValidatorFactory();
return validator = factory.getValidator();
}
}

View File

@@ -0,0 +1,54 @@
module hsweb.core{
requires org.hibernate.validator;
requires org.reactivestreams;
requires hsweb.utils;
requires com.google.common;
requires reactor.core;
requires fastjson;
requires spring.beans;
requires spring.core;
requires java.desktop;
requires commons.beanutils;
requires jctools.core;
requires org.aspectj.weaver;
requires reactor.extra;
requires io.netty.common;
requires io.seruco.encoding.base62;
requires spring.web;
requires jakarta.servlet;
requires jakarta.validation;
requires spring.context;
requires io.swagger.v3.oas.annotations;
requires org.slf4j;
requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.annotation;
requires com.fasterxml.jackson.databind;
requires spring.aop;
requires static lombok;
requires jsr305;
requires jakarta.annotation;
requires org.javassist;
requires java.base;
requires org.apache.commons.codec;
exports org.hswebframework.web;
exports org.hswebframework.web.aop;
exports org.hswebframework.web.bean;
exports org.hswebframework.web.context;
exports org.hswebframework.web.convert;
exports org.hswebframework.web.dict;
exports org.hswebframework.web.dict.defaults;
exports org.hswebframework.web.enums;
exports org.hswebframework.web.event;
exports org.hswebframework.web.exception;
exports org.hswebframework.web.i18n;
exports org.hswebframework.web.id;
exports org.hswebframework.web.logger;
exports org.hswebframework.web.proxy;
exports org.hswebframework.web.utils;
exports org.hswebframework.web.validator;
opens org.hswebframework.web.validator;
}

View File

@@ -6,6 +6,7 @@ import lombok.Setter;
import lombok.SneakyThrows;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.util.ClassUtils;
import java.io.File;
import java.io.IOException;
@@ -127,11 +128,12 @@ public class FastBeanCopierTest {
@Test
@SneakyThrows
public void testCrossClassLoader() {
URL clazz = Source.class.getResource("/");
URL clazz = new File("target/test-classes").getAbsoluteFile().toURI().toURL();
System.out.println(clazz);
URLClassLoader loader = new URLClassLoader(new URL[]{
clazz
}, ClassLoader.getSystemClassLoader()){
}, ClassUtils.getDefaultClassLoader()){
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
try {
@@ -143,7 +145,7 @@ public class FastBeanCopierTest {
return clazz;
}
} catch (Throwable ignore) {
// ignore.printStackTrace();
}
return super.loadClass(name, resolve);
}
@@ -189,7 +191,7 @@ public class FastBeanCopierTest {
public void testProxy() {
AtomicReference<Object> reference = new AtomicReference<>();
ProxyTest test = (ProxyTest) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
ProxyTest test = (ProxyTest) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(),
new Class[]{ProxyTest.class}, (proxy, method, args) -> {
if (method.getName().equals("getName")) {
return "test";

View File

@@ -1,75 +0,0 @@
package org.hswebframework.web.utils;
import com.alibaba.fastjson.JSON;
import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
public class ExpressionUtilsTest {
@Test
public void testArray() {
String expression = ExpressionUtils.analytical("test-${array}", Collections.singletonMap("array", Arrays.asList(1,2,3)), "spel");
Assert.assertEquals(expression,"test-[1, 2, 3]");
}
@Test
public void test() {
String expression = ExpressionUtils.analytical("test-${#name}", Collections.singletonMap("name", "test"), "spel");
Assert.assertEquals(expression,"test-test");
String res = ExpressionUtils.analytical("test-${3+2}", Collections.singletonMap("name", "test"), "spel");
Assert.assertEquals(res,"test-5");
}
@Test
public void testComplete(){
TemplateParser.parse("${#data[payload][a_name]} ${#data[payload][b_name]} 发生 ${#data[payload][alarm_type_name]}",e->{
System.out.println(e);
return e;
});
}
@Test
public void testJson(){
Assert.assertEquals("{\"name\":\"test\"}",TemplateParser.parse("{\"name\":\"${#name}\"}",e->{
System.out.println(e);
Assert.assertEquals(e,"#name");
return "test";
}));
}
@Test
public void testJson2(){
String js = ExpressionUtils.analytical("{\n" +
" \"msgtype\": \"markdown\",\n" +
" \"markdown\": {\n" +
" \"title\":\"消息类型:${messageType}\",\n" +
" \"text\": \" - 设备ID: `${deviceId}` 值:`${properties.a-r-str}` \\n - 设备型号: `${headers.productId}`\\n - 设备名称: `${headers.deviceName}`\"" +
" \n},\n" +
" \"at\": {\n" +
" \"isAtAll\": false\n" +
" }\n" +
"}", JSON.parseObject("{\n" +
" \"deviceId\": \"VIS-Mandrake-12289\",\n" +
" \"headers\": {\n" +
" \"productId\": \"VIS-Mandrake\",\n" +
" \"deviceName\": \"能见度仪-曼德克-01\"\n" +
" }, \"properties\": {\n" +
" \"a-r-str\": \"a2\"\n" +
" },\n" +
" \"messageType\": \"OFFLINE\",\n" +
" \"timestamp\": 1592098397277\n" +
"}"), "spel");
System.out.println(js);
}
}

View File

@@ -8,7 +8,7 @@ import org.hswebframework.web.i18n.MessageSourceInitializer;
import org.junit.Test;
import org.springframework.context.support.StaticMessageSource;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.util.Locale;

View File

@@ -1,49 +0,0 @@
# 动态数据源JTA实现 (atomikos)
使用atomikos实现动态数据源事务管理
# 数据源配置
默认数据源配置,使用spring的jta配置即可:
```yaml
spring:
jta:
status: true
atomikos:
datasource:
xa-data-source-class-name: com.alibaba.druid.pool.xa.DruidXADataSource
xa-properties:
url : jdbc:h2:mem:core;DB_CLOSE_ON_EXIT=FALSE
username : sa
password :
max-pool-size: 20
borrow-connection-timeout: 1000
connectionfactory:
max-pool-size: 20
local-transaction-mode: true
```
动态数据源配置,默认提供一个 ``InMemoryAtomikosDataSourceRepository``,在application.yml 中进行配置即可:
```yaml
hsweb:
datasource:
jta:
test_ds: # 数据源ID
xa-data-source-class-name: com.alibaba.druid.pool.xa.DruidXADataSource
xa-properties: # 数据源的配置属性
url: jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
max-pool-size: 20
borrow-connection-timeout: 1000
test_ds2: # 数据源ID
xa-data-source-class-name: com.alibaba.druid.pool.xa.DruidXADataSource
xa-properties: # 数据源的配置属性
url: jdbc:mysql://localhost:3306/hsweb?pinGlobalTxToPhysicalConnection=true&useSSL=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false
# url: jdbc:h2:mem:test2;DB_CLOSE_ON_EXIT=FALSE
username: root
password: "123456" # 纯数字密码要加上双引号不然启动会报Cannot initialize AtomikosDataSourceBean
max-pool-size: 20
borrow-connection-timeout: 1000
init-timeout: 20
```
自定义,将数据源配置放到数据库中,实现 ``DynamicDataSourceConfigRepository<AtomikosDataSourceConfig>`` 接口并注入到spring容器即可

View File

@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hsweb-datasource</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.17-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-datasource-jta</artifactId>
<description>基于atomikos的多数据源实现,支持事务中切换数据源</description>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.26</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hswebframework</groupId>
<artifactId>hsweb-easy-orm-rdb</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-datasource-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,39 +0,0 @@
package org.hswebframework.web.datasource.jta;
import org.hswebframework.web.datasource.DynamicDataSourceAutoConfiguration;
import org.hswebframework.web.datasource.DynamicDataSourceService;
import org.hswebframework.web.datasource.config.DynamicDataSourceConfigRepository;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.sql.SQLException;
/**
* @author zhouhao
*/
@AutoConfiguration
@AutoConfigureBefore(DynamicDataSourceAutoConfiguration.class)
public class AtomikosDataSourceAutoConfiguration {
//默认数据源
@Bean(initMethod = "init", destroyMethod = "destroy", value = "datasource")
@Primary
public AtomikosDataSourceBean datasource() {
return new AtomikosDataSourceBean();
}
@ConditionalOnMissingBean(DynamicDataSourceConfigRepository.class)
@Bean
public DynamicDataSourceConfigRepository inMemoryAtomikosDataSourceRepository() {
return new InMemoryAtomikosDataSourceRepository();
}
}

View File

@@ -1,69 +0,0 @@
package org.hswebframework.web.datasource.jta;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.datasource.config.DynamicDataSourceConfig;
import org.springframework.util.ClassUtils;
import javax.sql.XADataSource;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author zhouhao
*/
@EqualsAndHashCode(callSuper = true)
@Slf4j
@Data
public class AtomikosDataSourceConfig extends DynamicDataSourceConfig {
private static final long serialVersionUID = 5588085000663972571L;
private int minPoolSize = 5;
private int maxPoolSize = 200;
private int borrowConnectionTimeout = 60;
private int reapTimeout = 0;
private int maxIdleTime = 60;
private int maintenanceInterval = 60;
private int defaultIsolationLevel = -1;
private String xaDataSourceClassName = null;
private int loginTimeout = 0;
private String testQuery = null;
private int maxLifetime = 0;
private Properties xaProperties = null;
//初始化超时时间
private int initTimeout = 10;
@SneakyThrows
public void putProperties(AtomikosDataSourceBean atomikosDataSourceBean) {
if (null != xaProperties) {
xaProperties.entrySet().forEach(entry -> entry.setValue(String.valueOf(entry.getValue())));
}
//fix #87
XADataSource dataSource = (XADataSource) ClassUtils.forName(getXaDataSourceClassName(),null).newInstance();
FastBeanCopier.copy(xaProperties, dataSource);
atomikosDataSourceBean.setXaDataSource(dataSource);
atomikosDataSourceBean.setXaDataSourceClassName(getXaDataSourceClassName());
atomikosDataSourceBean.setBorrowConnectionTimeout(getBorrowConnectionTimeout());
if (loginTimeout != 0) {
try {
atomikosDataSourceBean.setLoginTimeout(getLoginTimeout());
} catch (SQLException e) {
log.warn(e.getMessage(), e);
}
}
atomikosDataSourceBean.setMaxIdleTime(getMaxIdleTime());
atomikosDataSourceBean.setMaxPoolSize(getMaxPoolSize());
atomikosDataSourceBean.setMinPoolSize(getMinPoolSize());
atomikosDataSourceBean.setDefaultIsolationLevel(getDefaultIsolationLevel());
atomikosDataSourceBean.setMaintenanceInterval(getMaintenanceInterval());
atomikosDataSourceBean.setReapTimeout(getReapTimeout());
atomikosDataSourceBean.setTestQuery(getTestQuery());
atomikosDataSourceBean.setXaProperties(getXaProperties());
atomikosDataSourceBean.setMaxLifetime(getMaxLifetime());
}
}

View File

@@ -1,54 +0,0 @@
package org.hswebframework.web.datasource.jta;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author zhouhao
*/
@ConfigurationProperties(prefix = "hsweb.datasource")
public class InMemoryAtomikosDataSourceRepository implements JtaDataSourceRepository {
@Getter
@Setter
private Map<String, AtomikosDataSourceConfig> jta = new HashMap<>();
@PostConstruct
public void init() {
jta.forEach((id, config) -> {
if (config.getId() == null) {
config.setId(id);
} else if (!config.getId().equals(id)) {
jta.put(config.getId(), config);
}
});
}
@Override
public List<AtomikosDataSourceConfig> findAll() {
return new ArrayList<>(jta.values());
}
@Override
public AtomikosDataSourceConfig findById(String dataSourceId) {
return jta.get(dataSourceId);
}
@Override
public AtomikosDataSourceConfig add(AtomikosDataSourceConfig config) {
return jta.put(config.getId(), config);
}
@Override
public AtomikosDataSourceConfig remove(String dataSourceId) {
return jta.remove(dataSourceId);
}
}

View File

@@ -1,11 +0,0 @@
package org.hswebframework.web.datasource.jta;
import org.hswebframework.web.datasource.config.DynamicDataSourceConfigRepository;
/**
* @author zhouhao
* @see org.hswebframework.web.datasource.config.DynamicDataSourceConfigRepository
*/
public interface JtaDataSourceRepository extends DynamicDataSourceConfigRepository<AtomikosDataSourceConfig> {
}

View File

@@ -1 +0,0 @@
org.hswebframework.web.datasource.jta.AtomikosDataSourceAutoConfiguration

View File

@@ -1,63 +0,0 @@
spring:
jta:
status: true
atomikos:
datasource:
xa-data-source-class-name: com.alibaba.druid.pool.xa.DruidXADataSource
xa-properties:
url : jdbc:h2:mem:core;DB_CLOSE_ON_EXIT=FALSE
username : sa
password :
maxActive: 1000
initialSize: 5
minIdle: 5
maxWait: 50000
max-pool-size: 20
borrow-connection-timeout: 1000
connectionfactory:
max-pool-size: 20
local-transaction-mode: true
activemq:
# broker-url: tcp://localhost:61616
in-memory: true
logging:
level:
com.atomikos: WARN
org.hswebframework: DEBUG
hsweb:
datasource:
jta:
test_ds:
xa-data-source-class-name: com.alibaba.druid.pool.xa.DruidXADataSource
xa-properties:
url: jdbc:h2:mem:test;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
filters: stat
maxActive: 1000
initialSize: 5
minIdle: 5
maxWait: 50000
max-pool-size: 20
borrow-connection-timeout: 1000
test_ds2:
xa-data-source-class-name: com.alibaba.druid.pool.xa.DruidXADataSource
xa-properties:
# url: jdbc:mysql://localhost:3306/hsweb?pinGlobalTxToPhysicalConnection=true&useSSL=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false
url: jdbc:h2:mem:test2;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
max-pool-size: 20
borrow-connection-timeout: 1000
init-timeout: 20
switcher:
test: # 只是一个标识
# 拦截表达式
expression: org.hswebframework.**.*DynDsTest.find*
# 使用数据源
data-source-id: test_ds
config:
test: 123

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hsweb-datasource</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.17-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-datasource-web</artifactId>
<description>暴露多数据源的web接口</description>
<dependencies>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-datasource-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-authorization-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,28 +0,0 @@
package org.hswebframework.web.datasource.web;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.authorization.annotation.Resource;
import org.hswebframework.web.datasource.config.DynamicDataSourceConfig;
import org.hswebframework.web.datasource.config.DynamicDataSourceConfigRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/datasource")
@Resource(id = "datasource", name = "数据源管理")
public class DatasourceController {
@Autowired
private DynamicDataSourceConfigRepository<? extends DynamicDataSourceConfig> repository;
@GetMapping
@QueryAction
public List<? extends DynamicDataSourceConfig> getAllConfig() {
return repository.findAll();
}
}

View File

@@ -1,14 +0,0 @@
package org.hswebframework.web.datasource.web;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AutoConfiguration
public class DatasourceWebApiAutoConfiguration {
@Bean
public DatasourceController datasourceController() {
return new DatasourceController();
}
}

View File

@@ -1 +0,0 @@
org.hswebframework.web.datasource.web.DatasourceWebApiAutoConfiguration

View File

@@ -17,8 +17,6 @@
<packaging>pom</packaging>
<modules>
<module>hsweb-datasource-api</module>
<module>hsweb-datasource-jta</module>
<module>hsweb-datasource-web</module>
</modules>

View File

@@ -61,8 +61,8 @@
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>

View File

@@ -15,7 +15,7 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.Ordered;
import org.springframework.util.ClassUtils;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

View File

@@ -28,7 +28,7 @@ import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import reactor.util.context.ContextView;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -212,7 +212,7 @@ public class ReactiveAopAccessLoggerSupport extends StaticMethodMatcherPointcutA
ServerHttpRequest request = exchange.getRequest();
info.setRequestId(request.getId());
info.setPath(request.getPath().value());
info.setRequestMethod(request.getMethodValue());
info.setRequestMethod(request.getMethod().name());
info.setHeaders(request.getHeaders().toSingleValueMap());
Optional.ofNullable(ReactiveWebUtils.getIpAddr(request))

View File

@@ -17,18 +17,6 @@
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework</groupId>
<artifactId>hsweb-expands-script</artifactId>
<version>${hsweb.expands.version}</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
@@ -83,9 +71,28 @@
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>compile</scope>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-client-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>5.0.0</version>
<scope>test</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.google.code.findbugs</groupId>-->
<!-- <artifactId>jsr305</artifactId>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@@ -67,7 +67,7 @@ import java.util.Optional;
@EnableConfigurationProperties(CorsProperties.class)
public class CorsAutoConfiguration {
@ConditionalOnClass(name = "javax.servlet.Filter")
@ConditionalOnClass(name = "jakarta.servlet.Filter")
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Configuration
static class WebMvcCorsConfiguration {

View File

@@ -1,83 +0,0 @@
package org.hswebframework.web.starter;
import org.hswebframework.expands.script.engine.DynamicScriptEngine;
import org.hswebframework.expands.script.engine.DynamicScriptEngineFactory;
import org.hswebframework.ezorm.rdb.executor.SyncSqlExecutor;
import org.hswebframework.ezorm.rdb.executor.reactive.ReactiveSqlExecutor;
import org.hswebframework.ezorm.rdb.executor.reactive.ReactiveSyncSqlExecutor;
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
import org.hswebframework.web.starter.initialize.AppProperties;
import org.hswebframework.web.starter.initialize.SystemInitialize;
import org.hswebframework.web.starter.initialize.SystemVersion;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@AutoConfiguration
@EnableConfigurationProperties(AppProperties.class)
public class HswebAutoConfiguration {
private List<DynamicScriptEngine> engines;
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
engines = Stream.of("js", "groovy")
.map(DynamicScriptEngineFactory::getEngine)
.filter(Objects::nonNull)
.collect(Collectors.toList());
addGlobalVariable("logger", LoggerFactory.getLogger("org.hswebframework.script"));
addGlobalVariable("spring", applicationContext);
}
private void addGlobalVariable(String var, Object val) {
engines.forEach(engine -> {
try {
engine.addGlobalVariable(Collections.singletonMap(var, val));
} catch (NullPointerException ignore) {
}
}
);
}
@Bean
public CommandLineRunner systemInit(DatabaseOperator database,
AppProperties properties) {
addGlobalVariable("database", database);
addGlobalVariable("sqlExecutor", database
.getMetadata()
.getFeature(SyncSqlExecutor.ID)
.orElseGet(() -> database
.getMetadata()
.getFeature(ReactiveSqlExecutor.ID)
.map(ReactiveSyncSqlExecutor::of)
.orElse(null)));
SystemVersion version = properties.build();
return args -> {
if (properties.isAutoInit()) {
SystemInitialize initialize = new SystemInitialize(database, version);
initialize.setExcludeTables(properties.getInitTableExcludes());
initialize.install();
}
};
}
}

View File

@@ -6,7 +6,7 @@ import org.springframework.context.NoSuchMessageException;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

View File

@@ -1,29 +0,0 @@
package org.hswebframework.web.starter.initialize;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@ConfigurationProperties(prefix = "hsweb.app")
@Getter
@Setter
public class AppProperties {
private boolean autoInit = true;
private List<String> initTableExcludes;
private String name = "default";
private String comment;
private String website;
private String version;
public SystemVersion build() {
SystemVersion systemVersion = new SystemVersion();
systemVersion.setName(name);
systemVersion.setComment(comment);
systemVersion.setWebsite(website);
systemVersion.setVersion(version);
return systemVersion;
}
}

View File

@@ -1,10 +0,0 @@
package org.hswebframework.web.starter.initialize;
import java.util.Map;
/**
* @author zhouhao
*/
public interface CallBack {
void execute(Map<String, Object> context);
}

View File

@@ -1,70 +0,0 @@
package org.hswebframework.web.starter.initialize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author zhouhao
*/
public class DefaultDependencyUpgrader implements DependencyUpgrader {
private Logger logger = LoggerFactory.getLogger(this.getClass());
Dependency installed;
Dependency dependency;
List<Map<String, Object>> shouldUpdateVersionList;
private Map<String, Object> context;
private boolean firstInstall;
public DefaultDependencyUpgrader(Dependency installed, Dependency dependency, Map<String, Object> context) {
this.firstInstall = installed == null;
if (firstInstall) {
this.installed = dependency;
} else {
this.installed = installed;
}
this.context = context;
this.dependency = dependency;
}
@Override
public DependencyUpgrader filter(List<Map<String, Object>> versions) {
shouldUpdateVersionList = versions.stream()
.filter(map -> {
String ver = (String) map.get("version");
if (null == ver) {
return false;
}
//首次安装
if (firstInstall) {
return true;
}
//相同版本
if (installed.compareTo(dependency) == 0) {
return false;
}
return installed.compareTo(new SystemVersion(ver)) < 0;
})
.sorted(Comparator.comparing(m -> new SystemVersion((String) m.get("version"))))
.collect(Collectors.toList());
return this;
}
@Override
public void upgrade(CallBack callBack) {
shouldUpdateVersionList.forEach(context -> {
if (this.context != null) {
context.putAll(this.context);
}
if (logger.isInfoEnabled()) {
logger.info("upgrade [{}/{}] to version:{} {}", dependency.getGroupId(), dependency.getArtifactId(), context.get("version"), dependency.getWebsite());
}
callBack.execute(context);
});
}
}

View File

@@ -1,60 +0,0 @@
package org.hswebframework.web.starter.initialize;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.Map;
public class Dependency extends Version {
protected String groupId;
protected String artifactId;
protected String author;
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getArtifactId() {
return artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public static Dependency fromMap(Map<String, Object> map) {
Dependency dependency = new Dependency();
dependency.setGroupId((String) map.get("groupId"));
dependency.setArtifactId((String) map.get("artifactId"));
dependency.setName((String) map.getOrDefault(SystemVersion.Property.name, dependency.getArtifactId()));
dependency.setVersion((String) map.get("version"));
dependency.setWebsite((String) map.get(SystemVersion.Property.website));
dependency.setAuthor((String) map.get("author"));
return dependency;
}
public boolean isSameDependency(Dependency dependency) {
return isSameDependency(dependency.getGroupId(), dependency.getArtifactId());
}
public boolean isSameDependency(String groupId, String artifactId) {
return groupId.equals(this.getGroupId()) && artifactId.equals(this.getArtifactId());
}
@Override
public String toString() {
return JSON.toJSONString(this, SerializerFeature.PrettyFormat);
}
}

View File

@@ -1,41 +0,0 @@
/*
*
* * Copyright 2020 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.starter.initialize;
import java.util.Map;
/**
* @author zhouhao
*/
public interface DependencyInstaller {
DependencyInstaller setup(Dependency dependency);
default DependencyInstaller setup(Map<String, Object> mapDependency) {
return setup(Dependency.fromMap(mapDependency));
}
DependencyInstaller onInstall(CallBack callBack);
DependencyInstaller onUpgrade(CallBack callBack);
DependencyInstaller onUninstall(CallBack callBack);
DependencyInstaller onInitialize(CallBack callBack);
}

View File

@@ -1,32 +0,0 @@
/*
*
* * Copyright 2020 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.starter.initialize;
import java.util.List;
import java.util.Map;
/**
* @author zhouhao
*/
public interface DependencyUpgrader {
DependencyUpgrader filter(List<Map<String, Object>> versions);
void upgrade(CallBack context);
}

View File

@@ -1,88 +0,0 @@
package org.hswebframework.web.starter.initialize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* @author zhouhao
*/
public class SimpleDependencyInstaller implements DependencyInstaller {
Dependency dependency;
CallBack installer;
CallBack upgrader;
CallBack unInstaller;
CallBack initializer;
private Logger logger = LoggerFactory.getLogger(this.getClass());
public SimpleDependencyInstaller() {
}
public Dependency getDependency() {
return dependency;
}
public void doInstall(Map<String, Object> context) {
if (installer != null) {
if (logger.isInfoEnabled()) {
logger.info("install [{}/{}]", dependency.getGroupId(), dependency.getArtifactId());
}
installer.execute(context);
}
}
public void doInitialize(Map<String, Object> context) {
if (initializer != null) {
if (logger.isInfoEnabled()) {
logger.info("initialize [{}/{}]", dependency.getGroupId(), dependency.getArtifactId());
}
initializer.execute(context);
}
}
public void doUnInstall(Map<String, Object> context) {
if (unInstaller != null) {
unInstaller.execute(context);
}
}
public void doUpgrade(Map<String, Object> context, Dependency installed) {
DefaultDependencyUpgrader defaultDependencyUpgrader =
new DefaultDependencyUpgrader(installed, dependency, context);
context.put("upgrader", defaultDependencyUpgrader);
if (upgrader != null) {
upgrader.execute(context);
}
}
@Override
public DependencyInstaller setup(Dependency dependency) {
this.dependency = dependency;
return this;
}
@Override
public DependencyInstaller onInstall(CallBack callBack) {
this.installer = callBack;
return this;
}
@Override
public DependencyInstaller onUpgrade(CallBack callBack) {
this.upgrader = callBack;
return this;
}
@Override
public DependencyInstaller onUninstall(CallBack callBack) {
this.unInstaller = callBack;
return this;
}
@Override
public DependencyInstaller onInitialize(CallBack initializeCallBack) {
this.initializer = initializeCallBack;
return this;
}
}

View File

@@ -1,214 +0,0 @@
package org.hswebframework.web.starter.initialize;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.expands.script.engine.DynamicScriptEngine;
import org.hswebframework.expands.script.engine.DynamicScriptEngineFactory;
import org.hswebframework.ezorm.rdb.codec.ClobValueCodec;
import org.hswebframework.ezorm.rdb.codec.CompositeValueCodec;
import org.hswebframework.ezorm.rdb.codec.JsonValueCodec;
import org.hswebframework.ezorm.rdb.mapping.SyncRepository;
import org.hswebframework.ezorm.rdb.mapping.defaults.record.Record;
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
import org.hswebframework.web.bean.FastBeanCopier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.StreamUtils;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author zhouhao
*/
public class SystemInitialize {
private final Logger logger = LoggerFactory.getLogger(SystemInitialize.class);
private final DatabaseOperator database;
//将要安装的信息
private final SystemVersion targetVersion;
//已安装的信息
private SystemVersion installed;
private List<SimpleDependencyInstaller> readyToInstall = new ArrayList<>();
@Setter
@Getter
private List<String> excludeTables;
private String installScriptPath = "classpath*:hsweb-starter.js";
private Map<String, Object> scriptContext = new HashMap<>();
private boolean initialized = false;
private SyncRepository<Record, String> system;
public SystemInitialize(DatabaseOperator database, SystemVersion targetVersion) {
this.database = database;
this.targetVersion = targetVersion;
}
public void init() {
if (initialized) {
return;
}
// if (!CollectionUtils.isEmpty(excludeTables)) {
// this.database = new SkipCreateOrAlterRDBDatabase(database, excludeTables, sqlExecutor);
// }
scriptContext.put("database", database);
scriptContext.put("logger", logger);
initialized = true;
}
public void addScriptContext(String var, Object val) {
scriptContext.put(var, val);
}
protected void syncSystemVersion() {
Map<String, Object> mapVersion = FastBeanCopier.copy(targetVersion, HashMap::new);
if (installed == null) {
system.insert(Record.newRecord(mapVersion));
} else {
//合并已安装的依赖
//修复如果删掉了依赖,再重启会丢失依赖信息的问题
for (Dependency dependency : installed.getDependencies()) {
Dependency target = targetVersion.getDependency(dependency.getGroupId(), dependency.getArtifactId());
if (target == null) {
targetVersion.getDependencies().add(dependency);
}
}
mapVersion = FastBeanCopier.copy(targetVersion, HashMap::new);
system.createUpdate().set(Record.newRecord(mapVersion))
.where(dsl -> dsl.is(targetVersion::getName))
.execute();
}
}
protected Map<String, Object> getScriptContext() {
return new HashMap<>(scriptContext);
}
protected void doInstall() {
List<SimpleDependencyInstaller> doInitializeDep = new ArrayList<>();
List<Dependency> installedDependencies =
readyToInstall.stream().map(installer -> {
Dependency dependency = installer.getDependency();
Dependency installed = getInstalledDependency(dependency.getGroupId(), dependency.getArtifactId());
//安装依赖
if (installed == null) {
doInitializeDep.add(installer);
installer.doInstall(getScriptContext());
}
//更新依赖
if (installed == null || installed.compareTo(dependency) < 0) {
installer.doUpgrade(getScriptContext(), installed);
}
return dependency;
}).collect(Collectors.toList());
for (SimpleDependencyInstaller installer : doInitializeDep) {
installer.doInitialize(getScriptContext());
}
targetVersion.setDependencies(installedDependencies);
}
private Dependency getInstalledDependency(String groupId, String artifactId) {
if (installed == null) {
return null;
}
return installed.getDependency(groupId, artifactId);
}
private SimpleDependencyInstaller getReadyToInstallDependency(String groupId, String artifactId) {
if (readyToInstall == null) {
return null;
}
return readyToInstall.stream()
.filter(installer -> installer.getDependency().isSameDependency(groupId, artifactId))
.findFirst().orElse(null);
}
private void initReadyToInstallDependencies() {
DynamicScriptEngine engine = DynamicScriptEngineFactory.getEngine("js");
try {
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(installScriptPath);
List<SimpleDependencyInstaller> installers = new ArrayList<>();
for (Resource resource : resources) {
String script = StreamUtils.copyToString(resource.getInputStream(), Charset.forName("utf-8"));
SimpleDependencyInstaller installer = new SimpleDependencyInstaller();
engine.compile("__tmp", script);
Map<String, Object> context = getScriptContext();
context.put("dependency", installer);
engine.execute("__tmp", context).getIfSuccess();
installers.add(installer);
}
readyToInstall = installers;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
engine.remove("__tmp");
}
}
protected void initInstallInfo() {
boolean tableInstall = database.getMetadata().getTable("s_system").isPresent();
database.ddl().createOrAlter("s_system")
.addColumn().name("name").varchar(128).comment("系统名称").commit()
.addColumn().name("major_version").alias("majorVersion").integer().comment("主版本号").commit()
.addColumn().name("minor_version").alias("minorVersion").integer().comment("次版本号").commit()
.addColumn().name("revision_version").alias("revisionVersion").integer().comment("修订版").commit()
.addColumn().name("comment").varchar(2000).comment("系统说明").commit()
.addColumn().name("website").varchar(2000).comment("系统网址").commit()
.addColumn().name("framework_version").notNull().alias("frameworkVersion").clob()
.custom(column ->
column.setValueCodec(new CompositeValueCodec()
.addEncoder(JsonValueCodec.of(SystemVersion.FrameworkVersion.class))
.addDecoder(ClobValueCodec.INSTANCE)
.addDecoder(JsonValueCodec.of(SystemVersion.FrameworkVersion.class)))).notNull().comment("框架版本").commit()
.addColumn().name("dependencies").notNull().alias("dependencies").clob()
.custom(column -> column.setValueCodec(new CompositeValueCodec()
.addEncoder(JsonValueCodec.ofCollection(List.class, Dependency.class))
.addDecoder(ClobValueCodec.INSTANCE)
.addDecoder(JsonValueCodec.ofCollection(List.class, Dependency.class)))).notNull().comment("依赖详情").commit()
.comment("系统信息")
.allowAlter(false)
.commit()
.sync();
system = database.dml().createRepository("s_system");
if (!tableInstall) {
installed = null;
return;
}
installed = system.createQuery()
.where(dsl -> dsl.is("name", targetVersion.getName()))
.paging(0, 1)
.fetchOne()
.map(r -> FastBeanCopier.copy(r, SystemVersion::new))
.orElse(null)
;
}
public void install() throws Exception {
init();
initInstallInfo();
initReadyToInstallDependencies();
doInstall();
syncSystemVersion();
}
}

View File

@@ -1,129 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.starter.initialize;
import org.hswebframework.utils.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SystemVersion extends Version {
public SystemVersion() {
}
public SystemVersion(String version) {
this.setVersion(version);
}
private FrameworkVersion frameworkVersion = new FrameworkVersion();
private List<Dependency> dependencies = new ArrayList<>();
public FrameworkVersion getFrameworkVersion() {
return frameworkVersion;
}
public void setFrameworkVersion(FrameworkVersion frameworkVersion) {
this.frameworkVersion = frameworkVersion;
}
public List<Dependency> getDependencies() {
return dependencies;
}
public void setDependencies(List<Dependency> dependencies) {
this.dependencies = dependencies;
initDepCache();
}
private Map<String, Dependency> depCache;
protected String getDepKey(String groupId, String artifactId) {
return StringUtils.concat(groupId, "/", artifactId);
}
protected void initDepCache() {
depCache = new HashMap<>();
dependencies.forEach(dependency -> depCache.put(getDepKey(dependency.groupId, dependency.artifactId), dependency));
}
public Dependency getDependency(String groupId, String artifactId) {
if (depCache == null) {
initDepCache();
}
return depCache.get(getDepKey(groupId, artifactId));
}
public static class FrameworkVersion extends Version {
public FrameworkVersion() {
setName("hsweb framework");
setComment("企业后台管理系统基础框架");
setWebsite("http://www.hsweb.io");
setComment("");
setVersion(4, 0, 0, true);
}
}
public interface Property {
/**
* @see SystemVersion#name
*/
String name = "name";
/**
* @see SystemVersion#comment
*/
String comment = "comment";
/**
* @see SystemVersion#website
*/
String website = "website";
/**
* @see SystemVersion#majorVersion
*/
String majorVersion = "majorVersion";
/**
* @see SystemVersion#minorVersion
*/
String minorVersion = "minorVersion";
/**
* @see SystemVersion#revisionVersion
*/
String revisionVersion = "revisionVersion";
/**
* @see SystemVersion#snapshot
*/
String snapshot = "snapshot";
/**
* @see SystemVersion#frameworkVersion
*/
String frameworkVersion = "frameworkVersion";
/**
* @see SystemVersion#dependencies
*/
String dependencies = "dependencies";
}
}

View File

@@ -1,147 +0,0 @@
package org.hswebframework.web.starter.initialize;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.utils.ListUtils;
import java.io.Serializable;
@Slf4j
public class Version implements Comparable<Version>, Serializable {
protected String name;
protected String comment;
protected String website;
protected int majorVersion = 1;
protected int minorVersion = 0;
protected int revisionVersion = 0;
protected boolean snapshot = false;
public void setVersion(int major, int minor, int revision, boolean snapshot) {
this.majorVersion = major;
this.minorVersion = minor;
this.revisionVersion = revision;
this.snapshot = snapshot;
}
public void setVersion(String version) {
if (null == version) {
return;
}
version = version.toLowerCase();
boolean snapshot = version.toLowerCase().contains("snapshot");
String[] ver = version.split("[-]")[0].split("[.]");
Integer[] numberVer = ListUtils.stringArr2intArr(ver);
if (numberVer.length == 0) {
numberVer = new Integer[]{1, 0, 0};
log.warn("解析版本号失败:{},将使用默认版本号:1.0.0,请检查hsweb-starter.js配置内容!", version);
}
for (int i = 0; i < numberVer.length; i++) {
if (numberVer[i] == null) {
numberVer[i] = 0;
}
}
setVersion(numberVer[0],
numberVer.length <= 1 ? 0 : numberVer[1],
numberVer.length <= 2 ? 0 : numberVer[2],
snapshot);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getWebsite() {
if (website == null) {
website = "";
}
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public int getMajorVersion() {
return majorVersion;
}
public void setMajorVersion(int majorVersion) {
this.majorVersion = majorVersion;
}
public int getMinorVersion() {
return minorVersion;
}
public void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
}
public int getRevisionVersion() {
return revisionVersion;
}
public void setRevisionVersion(int revisionVersion) {
this.revisionVersion = revisionVersion;
}
public boolean isSnapshot() {
return snapshot;
}
public void setSnapshot(boolean snapshot) {
this.snapshot = snapshot;
}
@Override
public int compareTo(Version o) {
if (null == o) {
return -1;
}
if (o.getMajorVersion() > this.getMajorVersion()) {
return -1;
}
if (o.getMajorVersion() == this.getMajorVersion()) {
if (o.getMinorVersion() > this.getMinorVersion()) {
return -1;
}
if (o.getMinorVersion() == this.getMinorVersion()) {
return Integer.compare(this.getRevisionVersion(), o.getRevisionVersion());
} else {
return 1;
}
} else {
return 1;
}
}
public String versionToString() {
return String.valueOf(majorVersion) + "." +
minorVersion + "." +
revisionVersion + (snapshot ? "-SNAPSHOT" : "");
}
@Override
public String toString() {
return name + " version " +
majorVersion + "." +
minorVersion + "." +
revisionVersion + (snapshot ? "-SNAPSHOT" : "");
}
}

View File

@@ -20,7 +20,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

View File

@@ -1,4 +1,3 @@
org.hswebframework.web.starter.jackson.CustomCodecsAutoConfiguration
org.hswebframework.web.starter.HswebAutoConfiguration
org.hswebframework.web.starter.CorsAutoConfiguration
org.hswebframework.web.starter.i18n.I18nConfiguration

View File

@@ -20,8 +20,7 @@ public class SystemInitializeTest {
@Test
public void test(){
Assert.assertTrue(databaseOperator.getMetadata().getTable("s_user").isPresent());
assertNotNull(databaseOperator);
}
}

View File

@@ -14,8 +14,8 @@ import org.hswebframework.web.validator.CreateGroup;
import org.springframework.util.CollectionUtils;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.sql.JDBCType;
import java.util.List;
import java.util.Set;

View File

@@ -14,8 +14,8 @@ import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import java.sql.JDBCType;
import java.util.List;
import java.util.Map;

View File

@@ -10,7 +10,7 @@ import org.hswebframework.web.validator.CreateGroup;
import javax.persistence.Column;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
@Getter
@Setter

View File

@@ -16,7 +16,7 @@ import org.springframework.util.StringUtils;
import javax.persistence.Column;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.sql.JDBCType;
@Getter

View File

@@ -16,8 +16,8 @@ import org.springframework.util.CollectionUtils;
import javax.persistence.Column;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import java.sql.JDBCType;
import java.util.List;
import java.util.Map;

Some files were not shown because too many files have changed in this diff Show More