zhouhao 3 éve
szülő
commit
4de4861d15
24 módosított fájl, 767 hozzáadás és 45 törlés
  1. 4 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/FieldDataAccess.java
  2. 73 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Resource.java
  3. 42 1
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/ResourceAction.java
  4. 8 1
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/SaveAction.java
  5. 3 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/UserOwnData.java
  6. 30 2
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java
  7. 30 0
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/Entity.java
  8. 36 0
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/PagerResult.java
  9. 18 0
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/QueryNoPagingOperation.java
  10. 17 1
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/QueryOperation.java
  11. 13 3
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/QueryParamEntity.java
  12. 32 0
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/RecordCreationEntity.java
  13. 30 0
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/RecordModifierEntity.java
  14. 17 5
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/SortSupportEntity.java
  15. 57 0
      hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/TreeSupportEntity.java
  16. 16 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/annotation/EnableEasyormRepository.java
  17. 4 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/annotation/Reactive.java
  18. 13 10
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/ReactiveCrudService.java
  19. 48 3
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/ReactiveTreeSortEntityService.java
  20. 7 2
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java
  21. 9 0
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveCrudController.java
  22. 98 6
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveSaveController.java
  23. 69 4
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveServiceQueryController.java
  24. 93 7
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveServiceSaveController.java

+ 4 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/FieldDataAccess.java

@@ -4,10 +4,14 @@ import org.springframework.core.annotation.AliasFor;
 
 import java.lang.annotation.*;
 
+/**
+ * @deprecated 已弃用
+ */
 @DataAccessType(id = "FIELD_DENY", name = "字段权限")
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
+@Deprecated
 public @interface FieldDataAccess {
 
     @AliasFor(annotation = DataAccessType.class)

+ 73 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Resource.java

@@ -1,28 +1,101 @@
 package org.hswebframework.web.authorization.annotation;
 
 
+import org.hswebframework.web.authorization.Permission;
 import org.hswebframework.web.authorization.define.Phased;
 
 import java.lang.annotation.*;
 
+/**
+ * 接口资源声明注解,声明Controller的资源相关信息,用于进行权限控制。
+ * <br>
+ * 在Controller进行注解,表示此接口需要有对应的权限{@link Permission#getId()}才能进行访问.
+ * 具体的操作权限控制,需要在方法上注解{@link ResourceAction}.
+ * <br>
+ *
+ *
+ * <pre>{@code
+ * @RestController
+ * //声明资源
+ * @Resource(id = "test", name = "测试功能")
+ * public class TestController implements ReactiveCrudController<TestEntity, String> {
+ *
+ *     //声明操作,需要有 test:query 权限才能访问此接口
+ *     @QueryAction
+ *     public Mono<User> getUser() {
+ *         return Authentication.currentReactive()
+ *                 .switchIfEmpty(Mono.error(new UnAuthorizedException()))
+ *                 .map(Authentication::getUser);
+ *     }
+ *
+ * }
+ * }
+ * </pre>
+ * 如果接口不需要进行权限控制,可注解{@link Authorize#ignore()}来标识此接口不需要权限控制.
+ * 或者通过监听 {@link org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent}来进行自定义处理
+ * <pre>{@code
+ *   @EventListener
+ *   public void handleAuthEvent(AuthorizingHandleBeforeEvent e) {
+ *      //admin用户可以访问全部操作
+ *      if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) {
+ *         e.setAllow(true);
+ *       }
+ *    }
+ * }</pre>
+ *
+ * @author zhouhao
+ * @see ResourceAction
+ * @see Authorize
+ * @see org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent
+ * @since 4.0
+ */
 @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited
 @Documented
 public @interface Resource {
+
+    /**
+     * 资源ID
+     *
+     * @return 资源ID
+     */
     String id();
 
+    /**
+     * @return 资源名称
+     */
     String name();
 
+    /**
+     * @return 资源操作定义
+     */
     ResourceAction[] actions() default {};
 
+    /**
+     * @return 多个操作控制逻辑
+     */
     Logical logical() default Logical.DEFAULT;
 
+    /**
+     * @return 权限控制阶段
+     */
     Phased phased() default Phased.before;
 
+    /**
+     * @return 资源描述
+     */
     String[] description() default {};
 
+    /**
+     * @return 资源分组
+     */
     String[] group() default {};
 
+    /**
+     * 如果在方法上设置此属性,表示是否合并类上注解的属性
+     *
+     * @return 是否合并
+     */
     boolean merge() default true;
 }

+ 42 - 1
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/ResourceAction.java

@@ -1,24 +1,65 @@
 package org.hswebframework.web.authorization.annotation;
 
-import org.hswebframework.web.authorization.define.Phased;
+
+import org.hswebframework.web.authorization.Permission;
 
 import java.lang.annotation.*;
 
 /**
+ * 对资源操作的描述,通常用来进行权限控制.
+ * <p>
+ * 在Controller方法上添加此注解,来声明根据权限操作{@link Permission#getActions()}进行权限控制.
+ * <p>
+ * 可以使用注解继承的方式来统一定义操作:
+ * <pre>{@code
+ * @Target(ElementType.METHOD)
+ * @Retention(RetentionPolicy.RUNTIME)
+ * @Inherited
+ * @Documented
+ * @ResourceAction(id = "create", name = "新增")
+ * public @interface CreateAction {
+ *
+ * }
+ * }
+ * </pre>
+ *
  * @see CreateAction
+ * @see DeleteAction
+ * @see SaveAction
+ * @see org.hswebframework.web.authorization.Authentication
+ * @see Permission#getActions()
  */
 @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited
 @Documented
 public @interface ResourceAction {
+    /**
+     * 操作标识
+     *
+     * @return 操作标识
+     * @see Permission#getActions()
+     */
     String id();
 
+    /**
+     * @return 操作名称
+     */
     String name();
 
+    /**
+     * @return 操作说明
+     */
     String[] description() default {};
 
+    /**
+     * @return 多个操作时的判断逻辑
+     */
     Logical logical() default Logical.DEFAULT;
 
+    /**
+     * @deprecated 已弃用, 4.1中移除
+     */
+    @Deprecated
     DataAccess[] dataAccess() default @DataAccess(ignore = true);
 }

+ 8 - 1
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/SaveAction.java

@@ -5,6 +5,12 @@ import org.springframework.core.annotation.AliasFor;
 
 import java.lang.annotation.*;
 
+/**
+ * 继承{@link ResourceAction},提供统一的id定义
+ *
+ * @author zhouhao
+ * @since 4.0
+ */
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited
@@ -12,6 +18,7 @@ import java.lang.annotation.*;
 @ResourceAction(id = Permission.ACTION_SAVE, name = "保存")
 public @interface SaveAction {
 
-    @AliasFor(annotation = ResourceAction.class,attribute = "dataAccess")
+    @Deprecated
+    @AliasFor(annotation = ResourceAction.class, attribute = "dataAccess")
     DataAccess dataAccess() default @DataAccess(ignore = true);
 }

+ 3 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/UserOwnData.java

@@ -4,12 +4,15 @@ import java.lang.annotation.*;
 
 /**
  * 声明某个操作支持用户查看自己的数据
+ *
+ * @deprecated 已弃用
  */
 @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited
 @Documented
 @DataAccessType(id = "user_own_data", name = "用户自己的数据")
+@Deprecated
 public @interface UserOwnData {
 
 }

+ 30 - 2
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java

@@ -4,6 +4,22 @@ import org.hswebframework.web.authorization.define.AuthorizingContext;
 import org.hswebframework.web.authorization.define.HandleType;
 import org.springframework.context.ApplicationEvent;
 
+/**
+ * 权限控制事件,在进行权限控制之前会推送此事件,用于自定义权限控制结果:
+ * <pre>{@code
+ *   @EventListener
+ *   public void handleAuthEvent(AuthorizingHandleBeforeEvent e) {
+ *      //admin用户可以访问全部操作
+ *      if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) {
+ *         e.setAllow(true);
+ *       }
+ *    }
+ * }</pre>
+ *
+ * @author zhouhao
+ * @since 4.0
+ */
+// TODO: 2021/12/21 Reactive支持
 public class AuthorizingHandleBeforeEvent extends ApplicationEvent implements AuthorizationEvent {
 
     private static final long serialVersionUID = -1095765748533721998L;
@@ -14,7 +30,7 @@ public class AuthorizingHandleBeforeEvent extends ApplicationEvent implements Au
 
     private String message;
 
-    private HandleType handleType;
+    private final HandleType handleType;
 
     public AuthorizingHandleBeforeEvent(AuthorizingContext context, HandleType handleType) {
         super(context);
@@ -33,6 +49,11 @@ public class AuthorizingHandleBeforeEvent extends ApplicationEvent implements Au
         return allow;
     }
 
+    /**
+     * 设置通过当前请求
+     *
+     * @param allow allow
+     */
     public void setAllow(boolean allow) {
         execute = false;
         this.allow = allow;
@@ -42,11 +63,18 @@ public class AuthorizingHandleBeforeEvent extends ApplicationEvent implements Au
         return message;
     }
 
+    /**
+     * 设置错误提示消息
+     *
+     * @param message 消息
+     */
     public void setMessage(String message) {
         this.message = message;
     }
 
-
+    /**
+     * @return 权限控制类型
+     */
     public HandleType getHandleType() {
         return handleType;
     }

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

@@ -32,18 +32,48 @@ import java.io.Serializable;
  */
 public interface Entity extends Serializable {
 
+    /**
+     * 使用jsr303对当前实体类进行验证,如果未通过验证则会抛出{@link org.hswebframework.web.exception.ValidationException}异常
+     *
+     * @param groups 分组
+     * @see org.hswebframework.web.exception.ValidationException
+     */
     default void tryValidate(Class<?>... groups) {
         ValidatorUtils.tryValidate(this, groups);
     }
 
+    /**
+     * 将当前实体类复制到指定其他类型中,类型将会被自动实例化,在类型明确时,建议使用{@link Entity#copyFrom(Object, String...)}.
+     *
+     * @param target           目标类型
+     * @param ignoreProperties 忽略复制的属性
+     * @param <T>类型
+     * @return 复制结果
+     */
     default <T> T copyTo(Class<T> target, String... ignoreProperties) {
         return FastBeanCopier.copy(this, target, ignoreProperties);
     }
 
+    /**
+     * 将当前实体类复制到其他对象中
+     *
+     * @param target           目标实体
+     * @param ignoreProperties 忽略复制的属性
+     * @param <T>类型
+     * @return 复制结果
+     */
     default <T> T copyTo(T target, String... ignoreProperties) {
         return FastBeanCopier.copy(this, target, ignoreProperties);
     }
 
+    /**
+     * 从其他对象复制属性到当前对象
+     *
+     * @param target           其他对象
+     * @param ignoreProperties 忽略复制的属性
+     * @param <T>              类型
+     * @return 当前对象
+     */
     @SuppressWarnings("all")
     default <T> T copyFrom(Object target, String... ignoreProperties) {
         return (T) FastBeanCopier.copy(target, this, ignoreProperties);

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

@@ -28,15 +28,42 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * 分页查询结果,用于在分页查询时,定义查询结果.如果需要拓展此类,例如自定义json序列化,请使用spi方式定义拓展实现类型:
+ * <pre>
+ * ---resources
+ * -----|--META-INF
+ * -----|----services
+ * -----|------org.hswebframework.web.api.crud.entity.PagerResult
+ * </pre>
+ *
+ * @param <E> 结果类型
+ * @author zhouhao
+ * @since 4.0.0
+ */
 @Getter
 @Setter
 public class PagerResult<E> {
     private static final long serialVersionUID = -6171751136953308027L;
 
+    /**
+     * 创建一个空结果
+     *
+     * @param <E> 结果类型
+     * @return PagerResult
+     */
     public static <E> PagerResult<E> empty() {
         return of(0, new ArrayList<>());
     }
 
+    /**
+     * 创建一个分页结果
+     *
+     * @param total 总数据量
+     * @param list  当前页数据列表
+     * @param <E>   结果类型
+     * @return PagerResult
+     */
     @SuppressWarnings("all")
     public static <E> PagerResult<E> of(int total, List<E> list) {
         PagerResult<E> result;
@@ -46,6 +73,15 @@ public class PagerResult<E> {
         return result;
     }
 
+    /**
+     * 创建一个分页结果,并将查询参数中的分页索引等信息填充到分页结果中
+     *
+     * @param total  总数据量
+     * @param list   当前页数据列表
+     * @param entity 查询参数
+     * @param <E>    结果类型
+     * @return PagerResult
+     */
     public static <E> PagerResult<E> of(int total, List<E> list, QueryParam entity) {
         PagerResult<E> pagerResult = of(total, list);
         pagerResult.setPageIndex(entity.getThinkPageIndex());

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

@@ -22,6 +22,24 @@ import java.lang.annotation.Target;
 import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.METHOD;
 
+/**
+ * 使用注解继承来对swagger接口文档注解的拓展,用来标识接口不支持分页查询参数.
+ *
+ *
+ * <pre>{@code
+ * @GetMapping
+ * @QueryNoPagingOperation(summary="接口说明")
+ * public Flux<MyEntity> handleRequest(@Parameter(hidden = true) QueryParamEntity query){
+ *  return service.query(query);
+ * }
+ *
+ * }</pre>
+ *
+ * 注意在参数上注解 {@code @Parameter(hidden=true)}
+ * @author zhouhao
+ * @since 4.0.5
+ * @see QueryNoPagingOperation#parameters()
+ */
 @Target({METHOD, ANNOTATION_TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited

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

@@ -22,6 +22,22 @@ import java.lang.annotation.Target;
 import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.METHOD;
 
+/**
+ * 使用注解继承来对swagger接口文档注解的拓展,用来标识接口支持分页查询参数.
+ *
+ * <pre>{@code
+ * @GetMapping
+ * @QueryOperation(summary="接口说明")
+ * public Flux<MyEntity> handleRequest(@Parameter(hidden = true) QueryParamEntity query){
+ *  return service.query(query);
+ * }
+ *
+ * }</pre>
+ *
+ * @author zhouhao
+ * @see QueryOperation#parameters()
+ * @since 4.0.5
+ */
 @Target({METHOD, ANNOTATION_TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited
@@ -97,7 +113,7 @@ public @interface QueryOperation {
             @Parameter(name = "where", description = "条件表达式,和terms参数冲突", example = "id = 1", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
             @Parameter(name = "orderBy", description = "排序表达式,和sorts参数冲突", example = "id desc", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
             @Parameter(name = "includes", description = "指定要查询的列,多列使用逗号分隔", example = "id", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
-            @Parameter(name = "excludes", description = "指定不查询的列,多列使用逗号分隔",  schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
+            @Parameter(name = "excludes", description = "指定不查询的列,多列使用逗号分隔", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
             @Parameter(name = "terms[0].column", description = "指定条件字段", schema = @Schema(implementation = String.class), in = ParameterIn.QUERY),
             @Parameter(name = "terms[0].termType", description = "条件类型", schema = @Schema(implementation = String.class), example = "like", in = ParameterIn.QUERY),
             @Parameter(name = "terms[0].type", description = "多个条件组合方式", schema = @Schema(implementation = Term.Type.class), in = ParameterIn.QUERY),

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

@@ -25,9 +25,19 @@ import java.util.function.Consumer;
  * 查询参数实体,使用<a href="https://github.com/hs-web/hsweb-easy-orm">easyorm</a>进行动态查询参数构建<br>
  * 可通过静态方法创建:<br>
  * 如:
- * <code>
- * QueryParamEntity.of("id",id);
- * </code>
+ * <pre>
+ *{@code
+ *      QueryParamEntity.of("id",id);
+ *}
+ * </pre>
+ *
+ * 或者使用DSL方式来构造:
+ * <pre>{@code
+ *  QueryParamEntity
+ *  .newQuery()
+ *  .where("id",1)
+ *  .execute(service::query)
+ * }</pre>
  *
  * @author zhouhao
  * @see QueryParam

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

@@ -12,23 +12,55 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
  */
 public interface RecordCreationEntity extends Entity {
 
+    /**
+     * @return 创建者ID
+     */
     String getCreatorId();
 
+    /**
+     * 设置创建者ID
+     *
+     * @param creatorId 创建者ID
+     */
     void setCreatorId(String creatorId);
 
+    /**
+     * 创建时间,UTC时间戳
+     *
+     * @return 创建时间
+     * @see System#currentTimeMillis()
+     */
     Long getCreateTime();
 
+    /**
+     * 设置创建时间 ,UTC时间戳
+     *
+     * @param createTime 创建时间
+     * @see System#currentTimeMillis()
+     */
     void setCreateTime(Long createTime);
 
+    /**
+     * 设置创建者名字,为了兼容,默认不支持记录创建者名字,由具体的实现类进行实现
+     *
+     * @param name 创建者名字
+     */
     default void setCreatorName(String name) {
 
     }
 
+    /**
+     * 设置创建时间为当前时间
+     */
     default void setCreateTimeNow() {
         setCreateTime(System.currentTimeMillis());
     }
 
+    /**
+     * @deprecated 已弃用, 在4.1版本中移除
+     */
     @JsonIgnore
+    @Deprecated
     default String getCreatorIdProperty() {
         return "creatorId";
     }

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

@@ -13,22 +13,52 @@ public interface RecordModifierEntity extends Entity {
     String modifierId = "modifierId";
     String modifyTime = "modifyTime";
 
+    /**
+     * 修改人ID
+     *
+     * @return 修改人ID
+     */
     String getModifierId();
 
+    /**
+     * 设置修改人ID
+     *
+     * @param modifierId 修改人ID
+     */
     void setModifierId(String modifierId);
 
+    /**
+     * 设置修改人名字,为了兼容,默认不支持记录修改人名字,由具体的实现类进行实现
+     *
+     * @param modifierName 修改人名字
+     */
     default void setModifierName(String modifierName) {
 
     }
 
+    /**
+     * @return 修改时间
+     */
     Long getModifyTime();
 
+    /**
+     * 设置修改时间,UTC时间戳
+     *
+     * @param modifyTime 修改时间
+     * @see System#currentTimeMillis()
+     */
     void setModifyTime(Long modifyTime);
 
+    /**
+     * 设置修改时间为当前时间
+     */
     default void setModifyTimeNow() {
         setModifyTime(System.currentTimeMillis());
     }
 
+    /**
+     * @deprecated 已弃用, 4.1版本中移除
+     */
     @JsonIgnore
     default String getModifierIdProperty() {
         return modifierId;

+ 17 - 5
hsweb-commons/hsweb-commons-api/src/main/java/org/hswebframework/web/api/crud/entity/SortSupportEntity.java

@@ -18,18 +18,30 @@
 
 package org.hswebframework.web.api.crud.entity;
 
+import javax.annotation.Nonnull;
+
+/**
+ * 支持排序的实体
+ *
+ * @author zhouhao
+ * @since 4.0.0
+ */
 public interface SortSupportEntity extends Comparable<SortSupportEntity>, Entity {
 
+    /**
+     * @return 排序序号
+     */
     Long getSortIndex();
 
+    /**
+     * 设置排序序号
+     *
+     * @param sortIndex 排序序号
+     */
     void setSortIndex(Long sortIndex);
 
     @Override
-    default int compareTo(SortSupportEntity support) {
-        if (support == null) {
-            return -1;
-        }
-        
+    default int compareTo(@Nonnull SortSupportEntity support) {
         return Long.compare(getSortIndex() == null ? 0 : getSortIndex(), support.getSortIndex() == null ? 0 : support.getSortIndex());
     }
 }

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

@@ -29,25 +29,82 @@ import java.util.function.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+/**
+ * 支持树结构的实体类
+ *
+ * @param <PK> 主键类型
+ * @author zhouhao
+ * @since 4.0
+ */
 @SuppressWarnings("all")
 public interface TreeSupportEntity<PK> extends Entity {
 
+    /**
+     * 获取主键
+     *
+     * @return ID
+     */
     PK getId();
 
+    /**
+     * 设置主键
+     *
+     * @param id ID
+     */
     void setId(PK id);
 
+    /**
+     * 获取树路径,树路径表示当前节点所在位置
+     * 格式通常为: aBcD-EfgH-iJkl,以-分割,一个分割表示一级.
+     * 比如: aBcD-EfgH-iJkl表示 当前节点在第三级,上一个节点为EfgH.
+     *
+     * @return 树路径
+     */
     String getPath();
 
+    /**
+     * 设置路径,此值通常不需要手动设置,在进行保存时,由service自动进行分配.
+     *
+     * @param path 路径
+     * @see TreeSupportEntity#expandTree2List(TreeSupportEntity, IDGenerator)
+     */
     void setPath(String path);
 
+    /**
+     * 获取上级ID
+     *
+     * @return 上级ID
+     */
     PK getParentId();
 
+    /**
+     * 设置上级节点ID
+     *
+     * @param parentId
+     */
     void setParentId(PK parentId);
 
+    /**
+     * 获取节点层级
+     *
+     * @return 节点层级
+     */
     Integer getLevel();
 
+    /**
+     * 设置节点层级
+     *
+     * @return 节点层级
+     */
     void setLevel(Integer level);
 
+    /**
+     * 获取所有子节点,默认情况下此字段只会返回null.可以使用{@link TreeSupportEntity#list2tree(Collection, BiConsumer)}将
+     * 列表结构转为树形结构
+     *
+     * @param <T> 当前实体类型
+     * @return 自己节点
+     */
     <T extends TreeSupportEntity<PK>> List<T> getChildren();
 
     @Override

+ 16 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/annotation/EnableEasyormRepository.java

@@ -7,6 +7,13 @@ import javax.persistence.Table;
 import java.lang.annotation.*;
 
 /**
+ * 在启动类上注解,标识开启自动注册实体通用增删改查接口到spring上下文中.
+ * 在spring中,可直接进行泛型注入使用:
+ * <pre>{@code
+ *   @Autowire
+ *   ReactiveRepository<String, MyEntity> repository;
+ * }</pre>
+ *
  * @see org.hswebframework.ezorm.rdb.mapping.ReactiveRepository
  * @see org.hswebframework.ezorm.rdb.mapping.SyncRepository
  * @since 4.0.0
@@ -30,8 +37,17 @@ public @interface EnableEasyormRepository {
      */
     Class<? extends Annotation>[] annotation() default Table.class;
 
+    /**
+     * @return 是否开启响应式, 默认开启
+     */
     boolean reactive() default true;
 
+    /**
+     * 是否开启非响应式操作,在使用WebFlux时,不建议开启
+     *
+     * @return 开启非响应式
+     * @see org.hswebframework.ezorm.rdb.mapping.SyncRepository
+     */
     boolean nonReactive() default false;
 
 }

+ 4 - 0
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/annotation/Reactive.java

@@ -3,7 +3,11 @@ package org.hswebframework.web.crud.annotation;
 import java.lang.annotation.*;
 
 /**
+ * 在实体类上注解,标记是否开启响应式仓库
+ *
+ * @author zhouhao
  * @see org.hswebframework.ezorm.rdb.mapping.ReactiveRepository
+ * @since 4.0.0
  */
 @Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)

+ 13 - 10
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/ReactiveCrudService.java

@@ -37,13 +37,13 @@ public interface ReactiveCrudService<E, K> {
 
     /**
      * 创建一个DSL的动态查询接口,可使用DSL方式进行链式调用来构造动态查询条件.例如:
-     * <pre>
-     * Flux&lt;MyEntity&gt; flux=
-     *     service
+     * <pre>{@code
+     * Flux<MyEntity> flux = service
      *     .createQuery()
      *     .where(MyEntity::getName,name)
      *     .in(MyEntity::getState,state1,state2)
      *     .fetch()
+     * }
      * </pre>
      *
      * @return 动态查询接口
@@ -54,14 +54,14 @@ public interface ReactiveCrudService<E, K> {
 
     /**
      * 创建一个DSL动态更新接口,可使用DSL方式进行链式调用来构造动态更新条件.例如:
-     * <pre>
-     * Mono&lt;Integer&gt; flux=
-     *     service
+     * <pre>{@code
+     * Mono<Integer> result = service
      *     .createUpdate()
      *     .set(entity::getState)
      *     .where(MyEntity::getName,name)
      *     .in(MyEntity::getState,state1,state2)
      *     .execute()
+     *     }
      * </pre>
      *
      * @return 动态更新接口
@@ -72,13 +72,13 @@ public interface ReactiveCrudService<E, K> {
 
     /**
      * 创建一个DSL动态删除接口,可使用DSL方式进行链式调用来构造动态删除条件.例如:
-     * <pre>
-     * Mono&lt;Integer&gt; flux=
-     *     service
+     * <pre>{@code
+     * Mono<Integer> result = service
      *     .createDelete()
      *     .where(MyEntity::getName,name)
      *     .in(MyEntity::getState,state1,state2)
      *     .execute()
+     * }
      * </pre>
      *
      * @return 动态更新接口
@@ -194,6 +194,8 @@ public interface ReactiveCrudService<E, K> {
 
     @Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager)
     default <T> Mono<PagerResult<T>> queryPager(QueryParamEntity query, Function<E, T> mapper) {
+        //如果查询参数指定了总数,表示不需要再进行count操作.
+        //建议前端在使用分页查询时,切换下一页时,将第一次查询到total结果传入查询参数,可以提升查询性能.
         if (query.getTotal() != null) {
             return getRepository()
                     .createQuery()
@@ -203,7 +205,7 @@ public interface ReactiveCrudService<E, K> {
                     .collectList()
                     .map(list -> PagerResult.of(query.getTotal(), list, query));
         }
-        //并行分页
+        //并行分页,更快,所在页码无数据时,会返回空list.
         if (query.isParallelPager()) {
             return Mono
                     .zip(
@@ -220,6 +222,7 @@ public interface ReactiveCrudService<E, K> {
                     if (total == 0) {
                         return Mono.just(PagerResult.of(0, new ArrayList<>(), query));
                     }
+                    //查询前根据数据总数进行重新分页:要跳转的页码没有数据则跳转到最后一页
                     return query(query.clone().rePaging(total))
                             .map(mapper)
                             .collectList()

+ 48 - 3
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/ReactiveTreeSortEntityService.java

@@ -20,6 +20,8 @@ import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /**
+ * 树形结构的通用增删改查服务
+ *
  * @param <E> TreeSortSupportEntity
  * @param <K> ID
  * @see GenericReactiveTreeSupportCrudService
@@ -27,10 +29,22 @@ import java.util.stream.Collectors;
 public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K>, K>
         extends ReactiveCrudService<E, K> {
 
+    /**
+     * 动态查询并将查询结构转为树形结构
+     *
+     * @param paramEntity 查询参数
+     * @return 树形结构
+     */
     default Mono<List<E>> queryResultToTree(Mono<? extends QueryParamEntity> paramEntity) {
         return paramEntity.flatMap(this::queryResultToTree);
     }
 
+    /**
+     * 动态查询并将查询结构转为树形结构
+     *
+     * @param paramEntity 查询参数
+     * @return 树形结构
+     */
     default Mono<List<E>> queryResultToTree(QueryParamEntity paramEntity) {
         return query(paramEntity)
                 .collectList()
@@ -39,6 +53,12 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
                                                          this::createRootNodePredicate));
     }
 
+    /**
+     * 动态查询并将查询结构转为树形结构,包含所有子节点
+     *
+     * @param paramEntity 查询参数
+     * @return 树形结构
+     */
     default Mono<List<E>> queryIncludeChildrenTree(QueryParamEntity paramEntity) {
         return queryIncludeChildren(paramEntity)
                 .collectList()
@@ -47,29 +67,43 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
                                                          this::createRootNodePredicate));
     }
 
+    /**
+     * 查询指定ID的实体以及对应的全部子节点
+     *
+     * @param idList ID集合
+     * @return 树形结构
+     */
     default Flux<E> queryIncludeChildren(Collection<K> idList) {
         Set<String> duplicateCheck = new HashSet<>();
 
         return findById(idList)
                 .concatMap(e -> StringUtils
-                        .isEmpty(e.getPath())|| !duplicateCheck.add(e.getPath())
+                        .isEmpty(e.getPath()) || !duplicateCheck.add(e.getPath())
                         ? Mono.just(e)
                         : createQuery()
                         .where()
+                        //使用path快速查询
                         .like$("path", e.getPath())
                         .fetch())
                 .distinct(TreeSupportEntity::getId);
     }
 
+    /**
+     * 查询指定ID的实体以及对应的全部父节点
+     *
+     * @param idList ID集合
+     * @return 树形结构
+     */
     default Flux<E> queryIncludeParent(Collection<K> idList) {
         Set<String> duplicateCheck = new HashSet<>();
 
         return findById(idList)
                 .concatMap(e -> StringUtils
-                        .isEmpty(e.getPath())|| !duplicateCheck.add(e.getPath())
+                        .isEmpty(e.getPath()) || !duplicateCheck.add(e.getPath())
                         ? Mono.just(e)
                         : createQuery()
                         .where()
+                        //where ? like path and path !='' and path not null
                         .accept(Terms.Like.reversal("path", e.getPath(), false, true))
                         .notEmpty("path")
                         .notNull("path")
@@ -77,6 +111,12 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
                 .distinct(TreeSupportEntity::getId);
     }
 
+    /**
+     * 动态查询并将查询结构转为树形结构
+     *
+     * @param queryParam 查询参数
+     * @return 树形结构
+     */
     default Flux<E> queryIncludeChildren(QueryParamEntity queryParam) {
         Set<String> duplicateCheck = new HashSet<>();
 
@@ -134,6 +174,7 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
                 .then(Mono.just(ele));
     }
 
+    //重构子节点的path
     default Mono<Void> refactorChildPath(K id, String path, Consumer<E> pathAccepter) {
         return this
                 .createQuery()
@@ -161,6 +202,7 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
     default Mono<SaveResult> save(Publisher<E> entityPublisher) {
         return Flux
                 .from(entityPublisher)
+                //1.先平铺
                 .flatMapIterable(e -> TreeSupportEntity.expandTree2List(e, getIDGenerator()))
                 .collectList()
                 .flatMapIterable(list -> {
@@ -168,14 +210,17 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
                             .stream()
                             .filter(e -> e.getId() != null)
                             .collect(Collectors.toMap(TreeSupportEntity::getId, Function.identity()));
-
+                    //2. 重新组装树结构
                     return TreeSupportEntity.list2tree(list,
                                                        this::setChildren,
                                                        (Predicate<E>) e -> this.isRootNode(e) || map.get(e.getParentId()) == null);
 
                 })
+                //执行验证
                 .doOnNext(e -> e.tryValidate(CreateGroup.class))
+                //再次平铺为
                 .flatMapIterable(e -> TreeSupportEntity.expandTree2List(e, getIDGenerator()))
+                //重构path
                 .as(this::tryRefactorPath)
                 .as(this.getRepository()::save);
 

+ 7 - 2
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/CommonErrorControllerAdvice.java

@@ -32,8 +32,13 @@ import java.util.List;
 import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
 
+/**
+ * 统一错误处理
+ *
+ * @author zhouhao
+ * @since 4.0
+ */
 @RestControllerAdvice
-//@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
 @Slf4j
 @Order
 public class CommonErrorControllerAdvice {
@@ -134,7 +139,7 @@ public class CommonErrorControllerAdvice {
                 .stream()
                 .map(err -> new ValidationException.Detail(err.getField(), err.getDefaultMessage(), null))
                 .collect(Collectors.toList());
-       return handleException(new ValidationException(message, details));
+        return handleException(new ValidationException(message, details));
     }
 
     @ExceptionHandler

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

@@ -1,5 +1,14 @@
 package org.hswebframework.web.crud.web.reactive;
 
+/**
+ * 通用响应式增删该查Controller,实现本接口来默认支持增删改查相关操作.
+ *
+ * @param <E> 实体类型
+ * @param <K> 主键类型
+ * @see ReactiveSaveController
+ * @see ReactiveQueryController
+ * @see ReactiveDeleteController
+ */
 public interface ReactiveCrudController<E, K> extends
         ReactiveSaveController<E, K>,
         ReactiveQueryController<E, K>,

+ 98 - 6
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveSaveController.java

@@ -15,6 +15,12 @@ import reactor.core.publisher.Mono;
 
 import javax.validation.Valid;
 
+/**
+ * 响应式保存接口,基于{@link  ReactiveRepository}提供默认的新增,保存,修改接口.
+ *
+ * @param <E> 实体类型
+ * @param <K> 主键类型
+ */
 public interface ReactiveSaveController<E, K> {
 
     @Authorize(ignore = true)
@@ -38,6 +44,14 @@ public interface ReactiveSaveController<E, K> {
         return entity;
     }
 
+    /**
+     * 尝试设置登陆用户信息到实体中
+     *
+     * @param entity         实体
+     * @param authentication 权限信息
+     * @see RecordCreationEntity
+     * @see RecordModifierEntity
+     */
     @Authorize(ignore = true)
     default E applyAuthentication(E entity, Authentication authentication) {
         if (entity instanceof RecordCreationEntity) {
@@ -49,45 +63,123 @@ public interface ReactiveSaveController<E, K> {
         return entity;
     }
 
+    /**
+     * 保存数据,如果传入了id,并且对应数据存在,则尝试覆盖,不存在则新增.
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * PATCH /api/test
+     * Content-Type: application/json
+     *
+     * [
+     *  {
+     *   "name":"value"
+     *  }
+     * ]
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 保存结果
+     */
     @PatchMapping
     @SaveAction
     @Operation(summary = "保存数据", description = "如果传入了id,并且对应数据存在,则尝试覆盖,不存在则新增.")
     default Mono<SaveResult> save(@RequestBody Flux<E> payload) {
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMapMany(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .as(getRepository()::save);
     }
 
+    /**
+     * 批量新增
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * POST /api/test/_batch
+     * Content-Type: application/json
+     *
+     * [
+     *  {
+     *   "name":"value"
+     *  }
+     * ]
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 保存结果
+     */
     @PostMapping("/_batch")
     @SaveAction
     @Operation(summary = "批量新增数据")
     default Mono<Integer> add(@RequestBody Flux<E> payload) {
-
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMapMany(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .collectList()
                 .as(getRepository()::insertBatch);
     }
 
+    /**
+     * 新增单个数据,并返回新增后的数据.
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * POST /api/test
+     * Content-Type: application/json
+     *
+     *  {
+     *   "name":"value"
+     *  }
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 新增后的数据
+     */
     @PostMapping
     @SaveAction
     @Operation(summary = "新增单个数据,并返回新增后的数据.")
     default Mono<E> add(@RequestBody Mono<E> payload) {
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMap(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .flatMap(entity -> getRepository().insert(Mono.just(entity)).thenReturn(entity));
     }
 
 
+    /**
+     * 根据ID修改数据
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * PUT /api/test/{id}
+     * Content-Type: application/json
+     *
+     *  {
+     *   "name":"value"
+     *  }
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 是否成功
+     */
     @PutMapping("/{id}")
     @SaveAction
     @Operation(summary = "根据ID修改数据")
     default Mono<Boolean> update(@PathVariable K id, @RequestBody Mono<E> payload) {
-
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMap(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .flatMap(entity -> getRepository().updateById(id, Mono.just(entity)))

+ 69 - 4
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveServiceQueryController.java

@@ -74,7 +74,7 @@ public interface ReactiveServiceQueryController<E, K> {
     @QueryAction
     @QueryNoPagingOperation(summary = "使用POST方式分页动态查询(不返回总数)",
             description = "此操作不返回分页总数,如果需要获取全部数据,请设置参数paging=false")
-    default Flux<E> query(@Parameter(hidden = true)@RequestBody Mono<QueryParamEntity> query) {
+    default Flux<E> query(@Parameter(hidden = true) @RequestBody Mono<QueryParamEntity> query) {
         return query.flatMapMany(this::query);
     }
 
@@ -105,6 +105,31 @@ public interface ReactiveServiceQueryController<E, K> {
 
     }
 
+    /**
+     * POST方式动态查询.
+     *
+     * <pre>
+     *     POST /_query
+     *
+     *     {
+     *         "pageIndex":0,
+     *         "pageSize":20,
+     *         "where":"name like 张%", //放心使用,没有SQL注入
+     *         "orderBy":"id desc",
+     *         "terms":[ //高级条件
+     *             {
+     *                 "column":"name",
+     *                 "termType":"like",
+     *                 "value":"张%"
+     *             }
+     *         ]
+     *     }
+     * </pre>
+     *
+     * @param query 查询条件
+     * @return 结果流
+     * @see QueryParamEntity
+     */
     @PostMapping("/_query")
     @QueryAction
     @SuppressWarnings("all")
@@ -113,6 +138,31 @@ public interface ReactiveServiceQueryController<E, K> {
         return query.flatMap(q -> queryPager(q));
     }
 
+    /**
+     * POST方式动态查询数量.
+     *
+     * <pre>
+     *     POST /_count
+     *
+     *     {
+     *         "pageIndex":0,
+     *         "pageSize":20,
+     *         "where":"name like 张%", //放心使用,没有SQL注入
+     *         "orderBy":"id desc",
+     *         "terms":[ //高级条件
+     *             {
+     *                 "column":"name",
+     *                 "termType":"like",
+     *                 "value":"张%"
+     *             }
+     *         ]
+     *     }
+     * </pre>
+     *
+     * @param query 查询条件
+     * @return 查询结果
+     * @see QueryParamEntity
+     */
     @PostMapping("/_count")
     @QueryAction
     @QueryNoPagingOperation(summary = "使用POST方式查询总数")
@@ -121,14 +171,17 @@ public interface ReactiveServiceQueryController<E, K> {
     }
 
     /**
-     * 统计查询
+     * GET方式动态查询数量.
      *
      * <pre>
-     *     GET /_count
+     *
+     *    GET /_query/_count?pageIndex=0&pageSize=20&where=name is 张三&orderBy=id desc
+     *
      * </pre>
      *
      * @param query 查询条件
-     * @return 统计结果
+     * @return 查询结果
+     * @see QueryParamEntity
      */
     @GetMapping("/_count")
     @QueryAction
@@ -140,6 +193,18 @@ public interface ReactiveServiceQueryController<E, K> {
                 .count();
     }
 
+    /**
+     * 根据ID查询.
+     * <pre>
+     * {@code
+     *     GET /{id}
+     * }
+     * </pre>
+     *
+     * @param id ID
+     * @return 结果流
+     * @see QueryParamEntity
+     */
     @GetMapping("/{id:.+}")
     @QueryAction
     @Operation(summary = "根据ID查询")

+ 93 - 7
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/web/reactive/ReactiveServiceSaveController.java

@@ -1,6 +1,7 @@
 package org.hswebframework.web.crud.web.reactive;
 
 import io.swagger.v3.oas.annotations.Operation;
+import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
 import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
 import org.hswebframework.web.api.crud.entity.RecordCreationEntity;
 import org.hswebframework.web.api.crud.entity.RecordModifierEntity;
@@ -12,10 +13,16 @@ import org.springframework.web.bind.annotation.*;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
-public interface ReactiveServiceSaveController<E,K>  {
+/**
+ * 响应式保存接口,基于{@link  ReactiveCrudService}提供默认的新增,保存,修改接口.
+ *
+ * @param <E> 实体类型
+ * @param <K> 主键类型
+ */
+public interface ReactiveServiceSaveController<E, K> {
 
     @Authorize(ignore = true)
-    ReactiveCrudService<E,K> getService();
+    ReactiveCrudService<E, K> getService();
 
     @Authorize(ignore = true)
     default E applyCreationEntity(Authentication authentication, E entity) {
@@ -46,45 +53,124 @@ public interface ReactiveServiceSaveController<E,K>  {
         return entity;
     }
 
+    /**
+     * 保存数据,如果传入了id,并且对应数据存在,则尝试覆盖,不存在则新增.
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * PATCH /api/test
+     * Content-Type: application/json
+     *
+     * [
+     *  {
+     *   "name":"value"
+     *  }
+     * ]
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 保存结果
+     */
     @PatchMapping
     @SaveAction
     @Operation(summary = "保存数据", description = "如果传入了id,并且对应数据存在,则尝试覆盖,不存在则新增.")
     default Mono<SaveResult> save(@RequestBody Flux<E> payload) {
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMapMany(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .as(getService()::save);
     }
 
+    /**
+     * 批量新增
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * POST /api/test/_batch
+     * Content-Type: application/json
+     *
+     * [
+     *  {
+     *   "name":"value"
+     *  }
+     * ]
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 保存结果
+     */
     @PostMapping("/_batch")
     @SaveAction
     @Operation(summary = "批量新增数据")
     default Mono<Integer> add(@RequestBody Flux<E> payload) {
 
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMapMany(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .collectList()
                 .as(getService()::insertBatch);
     }
 
+    /**
+     * 新增单个数据,并返回新增后的数据.
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * POST /api/test
+     * Content-Type: application/json
+     *
+     *  {
+     *   "name":"value"
+     *  }
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 新增后的数据
+     */
     @PostMapping
     @SaveAction
     @Operation(summary = "新增单个数据,并返回新增后的数据.")
     default Mono<E> add(@RequestBody Mono<E> payload) {
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMap(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .flatMap(entity -> getService().insert(Mono.just(entity)).thenReturn(entity));
     }
 
-
+    /**
+     * 根据ID修改数据
+     * <br><br>
+     * 以类注解{@code @RequestMapping("/api/test")}为例:
+     * <pre>{@code
+     *
+     * PUT /api/test/{id}
+     * Content-Type: application/json
+     *
+     *  {
+     *   "name":"value"
+     *  }
+     * }
+     * </pre>
+     *
+     * @param payload payload
+     * @return 是否成功
+     */
     @PutMapping("/{id}")
     @SaveAction
     @Operation(summary = "根据ID修改数据")
     default Mono<Boolean> update(@PathVariable K id, @RequestBody Mono<E> payload) {
 
-        return Authentication.currentReactive()
+        return Authentication
+                .currentReactive()
                 .flatMap(auth -> payload.map(entity -> applyAuthentication(entity, auth)))
                 .switchIfEmpty(payload)
                 .flatMap(entity -> getService().updateById(id, Mono.just(entity)))