zhou-hao 5 سال پیش
والد
کامیت
c5cf95828f
22فایلهای تغییر یافته به همراه601 افزوده شده و 22 حذف شده
  1. 1 1
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java
  2. 4 2
      hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java
  3. 2 0
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/GenericEntity.java
  4. 3 1
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/RecordCreationEntity.java
  5. 6 0
      hsweb-commons/hsweb-commons-crud/pom.xml
  6. 18 5
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyOrmConfiguration.java
  7. 33 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventHandler.java
  8. 39 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/CurrentTimeGenerator.java
  9. 45 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/DefaultIdGenerator.java
  10. 27 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/Generators.java
  11. 4 3
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/MD5Generator.java
  12. 4 3
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/SnowFlakeStringIdGenerator.java
  13. 171 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java
  14. 59 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/ResponseMessage.java
  15. 7 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveCrudController.java
  16. 21 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveDeleteController.java
  17. 82 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveQueryController.java
  18. 68 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveSaveController.java
  19. 0 2
      hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/CrudTests.java
  20. 2 1
      hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/entity/TestEntity.java
  21. 1 1
      hsweb-core/src/main/java/org/hswebframework/web/exception/NotFoundException.java
  22. 4 3
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-api/src/main/java/org/hswebframework/web/system/authorization/api/entity/UserEntity.java

+ 1 - 1
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java

@@ -24,7 +24,7 @@ public class AccessDenyException extends RuntimeException {
     }
 
     public AccessDenyException(String message, Throwable cause) {
-        super(message, cause);
+        this(message,"access_denied", cause);
     }
 
     public AccessDenyException(String message, String code, Throwable cause) {

+ 4 - 2
hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java

@@ -93,10 +93,12 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
         return Authentication.currentReactive()
                 .switchIfEmpty(Mono.error(new UnAuthorizedException()))
                 .flatMapMany(auth -> {
+                    DataAccessDefinition dataAccessDefinition = definition.getDataAccessDefinition();
+
                     context.setAuthentication(auth);
                     if (definition.getPhased() == Phased.before) {
                         authorizingHandler.handRBAC(context);
-                        if (definition.getDataAccessDefinition().getPhased() == Phased.before) {
+                        if (dataAccessDefinition != null && dataAccessDefinition.getPhased() == Phased.before) {
                             authorizingHandler.handleDataAccess(context);
                         } else {
                             return flux.doOnNext(res -> {
@@ -106,7 +108,7 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
                         }
                     } else {
 
-                        if (definition.getDataAccessDefinition().getPhased() == Phased.before) {
+                        if (dataAccessDefinition != null && dataAccessDefinition.getPhased() == Phased.before) {
                             authorizingHandler.handleDataAccess(context);
                             return flux.doOnNext(res -> {
                                 context.setParamContext(holder.createParamContext(res));

+ 2 - 0
hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/GenericEntity.java

@@ -23,6 +23,7 @@ import lombok.Setter;
 import org.hswebframework.web.bean.ToString;
 
 import javax.persistence.Column;
+import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
 
 
@@ -36,6 +37,7 @@ public class GenericEntity<PK> implements Entity {
 
     @Column(length = 32,updatable = false)
     @Id
+    @GeneratedValue(generator = "default_id")
     private PK id;
 
     public String toString(String... ignoreProperty) {

+ 3 - 1
hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/RecordCreationEntity.java

@@ -1,6 +1,7 @@
 package org.hswebframework.web.api.crud.entity;
 
-import org.hswebframework.web.api.crud.entity.Entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
 
 /**
  * 记录创建信息的实体类,包括创建人和创建时间。
@@ -23,6 +24,7 @@ public interface RecordCreationEntity extends Entity {
         setCreateTime(System.currentTimeMillis());
     }
 
+    @JsonIgnore
     default String getCreatorIdProperty() {
         return "creatorId";
     }

+ 6 - 0
hsweb-commons/hsweb-commons-crud/pom.xml

@@ -13,6 +13,12 @@
 
     <dependencies>
 
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-authorization-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>io.projectreactor</groupId>
             <artifactId>reactor-core</artifactId>

+ 18 - 5
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyOrmConfiguration.java

@@ -16,17 +16,18 @@ import org.hswebframework.ezorm.rdb.operator.DefaultDatabaseOperator;
 import org.hswebframework.web.api.crud.entity.EntityFactory;
 import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
 import org.hswebframework.web.crud.entity.factory.MapperEntityFactory;
+import org.hswebframework.web.crud.generator.CurrentTimeGenerator;
+import org.hswebframework.web.crud.generator.DefaultIdGenerator;
 import org.hswebframework.web.crud.generator.MD5Generator;
 import org.hswebframework.web.crud.generator.SnowFlakeStringIdGenerator;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+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.context.annotation.Import;
 
 import java.util.List;
 import java.util.Optional;
@@ -41,7 +42,7 @@ public class EasyOrmConfiguration {
 
     @Bean
     @ConditionalOnMissingBean
-    public EntityFactory entityFactory(){
+    public EntityFactory entityFactory() {
         return new MapperEntityFactory();
     }
 
@@ -121,13 +122,25 @@ public class EasyOrmConfiguration {
     }
 
     @Bean
-    public MD5Generator md5Generator(){
+    @ConfigurationProperties(prefix = "easyorm.default-value-generator")
+    public DefaultIdGenerator defaultIdGenerator() {
+
+        return new DefaultIdGenerator();
+    }
+
+    @Bean
+    public MD5Generator md5Generator() {
         return new MD5Generator();
     }
 
     @Bean
-    public SnowFlakeStringIdGenerator snowFlakeStringIdGenerator(){
+    public SnowFlakeStringIdGenerator snowFlakeStringIdGenerator() {
         return new SnowFlakeStringIdGenerator();
     }
 
+    @Bean
+    public CurrentTimeGenerator currentTimeGenerator() {
+        return new CurrentTimeGenerator();
+    }
+
 }

+ 33 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/events/ValidateEventHandler.java

@@ -0,0 +1,33 @@
+package org.hswebframework.web.crud.events;
+
+import org.hswebframework.ezorm.rdb.events.ContextKeys;
+import org.hswebframework.ezorm.rdb.events.EventContext;
+import org.hswebframework.ezorm.rdb.events.EventListener;
+import org.hswebframework.ezorm.rdb.events.EventType;
+import org.hswebframework.ezorm.rdb.mapping.events.MappingContextKeys;
+import org.hswebframework.ezorm.rdb.mapping.events.MappingEventTypes;
+import org.hswebframework.web.api.crud.entity.Entity;
+import org.hswebframework.web.validator.CreateGroup;
+import org.hswebframework.web.validator.UpdateGroup;
+
+public class ValidateEventHandler implements EventListener {
+
+
+    @Override
+    public void onEvent(EventType type, EventContext context) {
+        if (type == MappingEventTypes.insert_before) {
+
+            context.get(MappingContextKeys.instance)
+                    .filter(Entity.class::isInstance)
+                    .map(Entity.class::cast)
+                    .ifPresent(entity -> entity.tryValidate(CreateGroup.class));
+
+        } else if (type == MappingEventTypes.update_before) {
+            context.get(MappingContextKeys.instance)
+                    .filter(Entity.class::isInstance)
+                    .map(Entity.class::cast)
+                    .ifPresent(entity -> entity.tryValidate(UpdateGroup.class));
+        }
+
+    }
+}

+ 39 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/CurrentTimeGenerator.java

@@ -0,0 +1,39 @@
+package org.hswebframework.web.crud.generator;
+
+import org.hswebframework.ezorm.core.DefaultValue;
+import org.hswebframework.ezorm.core.DefaultValueGenerator;
+import org.hswebframework.ezorm.core.RuntimeDefaultValue;
+import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+public class CurrentTimeGenerator implements DefaultValueGenerator<RDBColumnMetadata> {
+    @Override
+    public String getSortId() {
+        return Generators.CURRENT_TIME;
+    }
+
+    @Override
+    public DefaultValue generate(RDBColumnMetadata metadata) {
+        return (RuntimeDefaultValue) () -> generic(metadata.getJavaType());
+    }
+
+    protected Object generic(Class type) {
+        if (type == Date.class) {
+            return new Date();
+        }
+        if (type == java.sql.Date.class) {
+            return new java.sql.Date(System.currentTimeMillis());
+        }
+        if (type == LocalDateTime.class) {
+            return LocalDateTime.now();
+        }
+        return System.currentTimeMillis();
+    }
+
+    @Override
+    public String getName() {
+        return "当前系统时间";
+    }
+}

+ 45 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/DefaultIdGenerator.java

@@ -0,0 +1,45 @@
+package org.hswebframework.web.crud.generator;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.ezorm.core.DefaultValue;
+import org.hswebframework.ezorm.core.DefaultValueGenerator;
+import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+public class DefaultIdGenerator implements DefaultValueGenerator<RDBColumnMetadata> {
+
+    @Getter
+    @Setter
+    private String defaultId = Generators.SNOW_FLAKE;
+
+    @Getter
+    @Setter
+    private Map<String, String> mappings = new HashMap<>();
+
+    @Override
+    public String getSortId() {
+        return Generators.DEFAULT_ID_GENERATOR;
+    }
+
+    @Override
+    public DefaultValue generate(RDBColumnMetadata metadata) {
+        return Mono.justOrEmpty(mappings.get(metadata.getOwner().getName()))
+                .switchIfEmpty(Mono.justOrEmpty(defaultId))
+                .flatMap(id->Mono.justOrEmpty(metadata.findFeature(DefaultValueGenerator.createId(id))))
+                .doOnNext(gen-> log.debug("use default id generator : {} for column : {}", gen.getSortId(), metadata.getFullName()))
+                .map(gen->gen.generate(metadata))
+                .blockOptional()
+                .orElseThrow(()->new UnsupportedOperationException("不支持的生成器:" + defaultId));
+    }
+
+    @Override
+    public String getName() {
+        return "默认ID生成器";
+    }
+}

+ 27 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/Generators.java

@@ -0,0 +1,27 @@
+package org.hswebframework.web.crud.generator;
+
+public interface Generators {
+
+    /**
+     * @see DefaultIdGenerator
+     */
+    String DEFAULT_ID_GENERATOR = "default_id";
+
+
+    /**
+     * @see MD5Generator
+     */
+    String MD5 = "md5";
+
+    /**
+     * @see SnowFlakeStringIdGenerator
+     */
+    String SNOW_FLAKE = "snow_flake";
+
+    /**
+     * @see CurrentTimeGenerator
+     */
+    String CURRENT_TIME = "current_time";
+
+
+}

+ 4 - 3
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/MD5Generator.java

@@ -2,16 +2,17 @@ package org.hswebframework.web.crud.generator;
 
 import org.hswebframework.ezorm.core.DefaultValueGenerator;
 import org.hswebframework.ezorm.core.RuntimeDefaultValue;
+import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
 import org.hswebframework.web.id.IDGenerator;
 
-public class MD5Generator implements DefaultValueGenerator {
+public class MD5Generator implements DefaultValueGenerator<RDBColumnMetadata> {
     @Override
     public String getSortId() {
-        return "md5";
+        return Generators.MD5;
     }
 
     @Override
-    public RuntimeDefaultValue generate() {
+    public RuntimeDefaultValue generate(RDBColumnMetadata metadata) {
         return IDGenerator.MD5::generate;
     }
 

+ 4 - 3
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/generator/SnowFlakeStringIdGenerator.java

@@ -2,16 +2,17 @@ package org.hswebframework.web.crud.generator;
 
 import org.hswebframework.ezorm.core.DefaultValueGenerator;
 import org.hswebframework.ezorm.core.RuntimeDefaultValue;
+import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
 import org.hswebframework.web.id.IDGenerator;
 
-public class SnowFlakeStringIdGenerator implements DefaultValueGenerator {
+public class SnowFlakeStringIdGenerator implements DefaultValueGenerator<RDBColumnMetadata> {
     @Override
     public String getSortId() {
-        return "snow_flake";
+        return Generators.SNOW_FLAKE;
     }
 
     @Override
-    public RuntimeDefaultValue generate() {
+    public RuntimeDefaultValue generate(RDBColumnMetadata metadata) {
         return IDGenerator.SNOW_FLAKE_STRING::generate;
     }
 

+ 171 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java

@@ -0,0 +1,171 @@
+package org.hswebframework.web.crud.web;
+
+import lombok.extern.slf4j.Slf4j;
+import org.hswebframework.web.authorization.exception.AccessDenyException;
+import org.hswebframework.web.authorization.exception.UnAuthorizedException;
+import org.hswebframework.web.exception.BusinessException;
+import org.hswebframework.web.exception.NotFoundException;
+import org.hswebframework.web.exception.ValidationException;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.http.HttpStatus;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.bind.support.WebExchangeBindException;
+import org.springframework.web.server.MediaTypeNotSupportedStatusException;
+import org.springframework.web.server.MethodNotAllowedException;
+import org.springframework.web.server.NotAcceptableStatusException;
+import reactor.core.publisher.Mono;
+
+import javax.validation.ConstraintViolationException;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+@RestControllerAdvice
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
+@Slf4j
+public class CommonErrorControllerAdvice {
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public Mono<ResponseMessage<?>> handleException(BusinessException e) {
+        log.error(e.getMessage(), e);
+        return Mono.just(ResponseMessage.error(e.getCode(), e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public Mono<ResponseMessage<?>> handleException(UnsupportedOperationException e) {
+        return Mono.just(ResponseMessage.error("unsupported", e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.UNAUTHORIZED)
+    public Mono<ResponseMessage<?>> handleException(UnAuthorizedException e) {
+        return Mono.just(ResponseMessage.error(401, "unauthorized", e.getMessage()).result(e.getState()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.FORBIDDEN)
+    public Mono<ResponseMessage<?>> handleException(AccessDenyException e) {
+        return Mono.just(ResponseMessage.error(403, e.getCode(), e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.NOT_FOUND)
+    public Mono<ResponseMessage<?>> handleException(NotFoundException e) {
+        return Mono.just(ResponseMessage.error(404, "not_found", e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Mono<ResponseMessage<?>> handleException(ValidationException e) {
+        return Mono.just(ResponseMessage.error(400, "illegal_argument", e.getMessage()).result(e.getDetails()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Mono<ResponseMessage<?>> handleException(ConstraintViolationException e) {
+        return handleException(new ValidationException(e.getMessage(), e.getConstraintViolations()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Mono<ResponseMessage<?>> handleException(BindException e) {
+        return handleException(new ValidationException(e.getMessage(), e.getBindingResult().getAllErrors()
+                .stream()
+                .filter(FieldError.class::isInstance)
+                .map(FieldError.class::cast)
+                .map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage()))
+                .collect(Collectors.toList())));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Mono<ResponseMessage<?>> handleException(WebExchangeBindException e) {
+        return handleException(new ValidationException(e.getMessage(), e.getBindingResult().getAllErrors()
+                .stream()
+                .filter(FieldError.class::isInstance)
+                .map(FieldError.class::cast)
+                .map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage()))
+                .collect(Collectors.toList())));
+    }
+
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Mono<ResponseMessage<?>> handleException(MethodArgumentNotValidException e) {
+        return handleException(new ValidationException(e.getMessage(), e.getBindingResult().getAllErrors()
+                .stream()
+                .filter(FieldError.class::isInstance)
+                .map(FieldError.class::cast)
+                .map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage()))
+                .collect(Collectors.toList())));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Mono<ResponseMessage<?>> handleException(javax.validation.ValidationException e) {
+        return Mono.just(ResponseMessage.error(400, "illegal_argument", e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.GATEWAY_TIMEOUT)
+    public Mono<ResponseMessage<?>> handleException(TimeoutException e) {
+        log.error(e.getMessage(),e);
+        return Mono.just(ResponseMessage.error(504, "timeout", e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public Mono<ResponseMessage<?>> handleException(RuntimeException e) {
+        log.error(e.getMessage());
+        return Mono.just(ResponseMessage.error(e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+    public Mono<ResponseMessage<?>> handleException(NullPointerException e) {
+        log.error(e.getMessage());
+        return Mono.just(ResponseMessage.error(e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
+    public Mono<ResponseMessage<?>> handleException(IllegalArgumentException e) {
+        log.error(e.getMessage());
+        return Mono.just(ResponseMessage.error("illegal_argument", e.getMessage()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
+    public Mono<ResponseMessage<?>> handleException(MediaTypeNotSupportedStatusException e) {
+        log.error(e.getMessage());
+        return Mono.just(ResponseMessage
+                .error(415, "unsupported_media_type", "不支持的请求类型")
+                .result(e.getSupportedMediaTypes()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
+    public Mono<ResponseMessage<?>> handleException(NotAcceptableStatusException e) {
+        log.error(e.getMessage(), e);
+        return Mono.just(ResponseMessage
+                .error(406, "not_acceptable_media_type", "不支持的响应类型")
+                .result(e.getSupportedMediaTypes()));
+    }
+
+    @ExceptionHandler
+    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
+    public Mono<ResponseMessage<?>> handleException(MethodNotAllowedException e) {
+        log.error(e.getMessage(), e);
+        return Mono.just(ResponseMessage
+                .error(405, "method_not_allowed", "不支持的请求方法:" + e.getHttpMethod())
+                .result(e.getSupportedMethods()));
+    }
+
+
+}

+ 59 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/ResponseMessage.java

@@ -0,0 +1,59 @@
+package org.hswebframework.web.crud.web;
+
+import lombok.*;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ResponseMessage<T> implements Serializable {
+
+    private static final long serialVersionUID = 8992436576262574064L;
+
+    protected String message;
+
+    protected T result;
+
+    private int status;
+
+    protected String code;
+
+    protected Long timestamp = System.currentTimeMillis();
+
+    public static <T> ResponseMessage<T> ok() {
+        return ok(null);
+    }
+
+    @SuppressWarnings("all")
+    public static <T> ResponseMessage<T> ok(T result) {
+        return (ResponseMessage) ResponseMessage.builder()
+                .result(result)
+                .status(200)
+                .code("success")
+                .build();
+    }
+
+    public static <T> ResponseMessage<T> error(String message) {
+        return error("error", message);
+    }
+
+    public static <T> ResponseMessage<T> error(String code, String message) {
+        return error(500, code, message);
+    }
+
+    public static <T> ResponseMessage<T> error(int status, String code, String message) {
+        ResponseMessage<T> msg = new ResponseMessage<>();
+        msg.message = message;
+        msg.code = code;
+        msg.status = status;
+        return msg;
+    }
+
+    public ResponseMessage<T> result(T result) {
+        this.result = result;
+        return this;
+    }
+}

+ 7 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveCrudController.java

@@ -0,0 +1,7 @@
+package org.hswebframework.web.crud.web.reactive;
+
+public interface ReactiveCrudController<E, K> extends
+        ReactiveSaveController<E, K>,
+        ReactiveQueryController<E, K>,
+        ReactiveDeleteController<E, K> {
+}

+ 21 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveDeleteController.java

@@ -0,0 +1,21 @@
+package org.hswebframework.web.crud.web.reactive;
+
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.hswebframework.web.exception.NotFoundException;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import reactor.core.publisher.Mono;
+
+public interface ReactiveDeleteController<E, K> {
+    ReactiveRepository<E, K> getRepository();
+
+    @DeleteMapping("/{id:.+}")
+    default Mono<E> delete(@PathVariable K id) {
+        return getRepository()
+                .findById(Mono.just(id))
+                .switchIfEmpty(Mono.error(NotFoundException::new))
+                .flatMap(e -> getRepository()
+                        .deleteById(Mono.just(id))
+                        .thenReturn(e));
+    }
+}

+ 82 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveQueryController.java

@@ -0,0 +1,82 @@
+package org.hswebframework.web.crud.web.reactive;
+
+import org.hswebframework.ezorm.core.param.QueryParam;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.hswebframework.web.api.crud.entity.PagerResult;
+import org.hswebframework.web.api.crud.entity.QueryParamEntity;
+import org.hswebframework.web.authorization.Permission;
+import org.hswebframework.web.authorization.annotation.Authorize;
+import org.hswebframework.web.exception.NotFoundException;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.Collections;
+
+public interface ReactiveQueryController<E, K> {
+
+    ReactiveRepository<E, K> getRepository();
+
+    @GetMapping("/_query/no-paging")
+    @Authorize(action = Permission.ACTION_QUERY)
+    default Flux<E> query(QueryParamEntity query) {
+        return getRepository()
+                .createQuery()
+                .setParam(query)
+                .fetch();
+    }
+
+    @PostMapping("/_query/no-paging")
+    @Authorize(action = Permission.ACTION_QUERY)
+    default Flux<E> query(Mono<QueryParamEntity> query) {
+        return query.flatMapMany(this::query);
+    }
+
+    @GetMapping("/_count")
+    @Authorize(action = Permission.ACTION_QUERY)
+    default Mono<Integer> count(QueryParamEntity query) {
+        return getRepository()
+                .createQuery()
+                .setParam(query)
+                .count();
+    }
+
+    @GetMapping("/_query")
+    @Authorize(action = Permission.ACTION_QUERY)
+    default Mono<PagerResult<E>> queryPager(QueryParamEntity query) {
+        return queryPager(Mono.just(query));
+    }
+
+    @PostMapping("/_query")
+    @Authorize(action = Permission.ACTION_QUERY)
+    @SuppressWarnings("all")
+    default Mono<PagerResult<E>> queryPager(Mono<QueryParamEntity> query) {
+        return count(query)
+                .zipWhen(total -> {
+                    if (total == 0) {
+                        return Mono.just(Collections.<E>emptyList());
+                    }
+                    return query
+                            .map(QueryParam::clone)
+                            .flatMap(q -> query(Mono.just(q.rePaging(total))).collectList());
+                }, PagerResult::of);
+    }
+
+    @PostMapping("/_count")
+    @Authorize(action = Permission.ACTION_QUERY)
+    default Mono<Integer> count(Mono<QueryParamEntity> query) {
+        return query.flatMap(this::count);
+    }
+
+    @GetMapping("/{id:.+}")
+    @Authorize(action = Permission.ACTION_GET)
+    default Mono<E> getById(@PathVariable K id) {
+        return getRepository()
+                .findById(Mono.just(id))
+                .switchIfEmpty(Mono.error(NotFoundException::new));
+    }
+
+
+}

+ 68 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveSaveController.java

@@ -0,0 +1,68 @@
+package org.hswebframework.web.crud.web.reactive;
+
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
+import org.hswebframework.web.api.crud.entity.RecordCreationEntity;
+import org.hswebframework.web.api.crud.entity.RecordModifierEntity;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.Permission;
+import org.hswebframework.web.authorization.annotation.Authorize;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Mono;
+
+public interface ReactiveSaveController<E, K> {
+
+    ReactiveRepository<E, K> getRepository();
+
+    default E applyCreationEntity(Authentication authentication, E entity) {
+        RecordCreationEntity creationEntity = ((RecordCreationEntity) entity);
+        creationEntity.setCreateTime(System.currentTimeMillis());
+        creationEntity.setCreatorId(authentication.getUser().getId());
+
+        return entity;
+    }
+
+    default E applyModifierEntity(Authentication authentication, E entity) {
+        RecordModifierEntity creationEntity = ((RecordModifierEntity) entity);
+        creationEntity.setModifyTimeNow();
+        creationEntity.setModifierId(authentication.getUser().getId());
+
+        return entity;
+    }
+
+    default E applyAuthentication(Authentication authentication, E entity) {
+        if (entity instanceof RecordCreationEntity) {
+            entity = applyCreationEntity(authentication, entity);
+        }
+        if (entity instanceof RecordModifierEntity) {
+            entity = applyModifierEntity(authentication, entity);
+        }
+        return entity;
+    }
+
+    @PatchMapping
+    @Authorize(action = Permission.ACTION_UPDATE)
+    default Mono<E> save(@RequestBody Mono<E> payload) {
+        return Authentication.currentReactive()
+                .zipWith(payload, this::applyAuthentication)
+                .switchIfEmpty(payload)
+                .flatMap(entity -> getRepository().save(Mono.just(entity)).thenReturn(entity));
+    }
+
+    @PostMapping
+    @Authorize(action = Permission.ACTION_UPDATE)
+    default Mono<E> add(@RequestBody Mono<E> payload) {
+        return  Authentication.currentReactive()
+                .zipWith(payload, this::applyAuthentication)
+                .switchIfEmpty(payload)
+                .flatMap(entity -> getRepository().insert(Mono.just(entity)).thenReturn(entity));
+    }
+
+    @PutMapping("/{id}")
+    @Authorize(action = Permission.ACTION_UPDATE)
+    default Mono<E> update(@PathVariable K id, @RequestBody Mono<E> payload) {
+        return  Authentication.currentReactive()
+                .zipWith(payload, this::applyAuthentication)
+                .switchIfEmpty(payload)
+                .flatMap(entity -> getRepository().updateById(id,Mono.just(entity)).thenReturn(entity));
+    }
+}

+ 0 - 2
hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/CrudTests.java

@@ -18,8 +18,6 @@ public class CrudTests  {
     @Autowired
     private TestEntityService service;
 
-    @Autowired
-    private TestEntityService service2;
 
     @Test
     public void test(){

+ 2 - 1
hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/entity/TestEntity.java

@@ -5,6 +5,7 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 import org.hswebframework.web.api.crud.entity.GenericEntity;
+import org.hswebframework.web.crud.generator.Generators;
 
 import javax.persistence.Column;
 import javax.persistence.GeneratedValue;
@@ -24,7 +25,7 @@ public class TestEntity extends GenericEntity<String> {
     private Integer age;
 
     @Override
-    @GeneratedValue(generator = "md5")
+    @GeneratedValue(generator = Generators.DEFAULT_ID_GENERATOR)
     public String getId() {
         return super.getId();
     }

+ 1 - 1
hsweb-core/src/main/java/org/hswebframework/web/exception/NotFoundException.java

@@ -27,6 +27,6 @@ public class NotFoundException extends BusinessException {
     }
 
     public NotFoundException() {
-        this("data not found");
+        this("记录不存在");
     }
 }

+ 4 - 3
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-api/src/main/java/org/hswebframework/web/system/authorization/api/entity/UserEntity.java

@@ -3,6 +3,7 @@ package org.hswebframework.web.system.authorization.api.entity;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Getter;
 import lombok.Setter;
+import org.hswebframework.ezorm.rdb.mapping.annotation.DefaultValue;
 import org.hswebframework.web.api.crud.entity.GenericEntity;
 import org.hswebframework.web.api.crud.entity.RecordCreationEntity;
 import org.hswebframework.web.bean.ToString;
@@ -31,13 +32,13 @@ public class UserEntity extends GenericEntity<String> implements RecordCreationE
 
     @Column(nullable = false)
     @ToString.Ignore(cover = false)
-    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
     @NotBlank(message = "密码不能为空", groups = CreateGroup.class)
     private String password;
 
     @Column(nullable = false)
     @ToString.Ignore(cover = false)
-    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
     private String salt;
 
     @Column
@@ -50,10 +51,10 @@ public class UserEntity extends GenericEntity<String> implements RecordCreationE
     private String creatorId;
 
     @Column(name = "create_time", updatable = false)
+    @DefaultValue(generator = "current_time")
     private Long createTime;
 
     @Override
-    @GeneratedValue(generator = "md5")
     public String getId() {
         return super.getId();
     }