mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-30 23:00:11 +08:00 
			
		
		
		
	feat: 新增 CRUD 自动配置(扩展模块)
This commit is contained in:
		| @@ -0,0 +1,48 @@ | ||||
| <?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"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <parent> | ||||
|         <groupId>top.charles7c.continew</groupId> | ||||
|         <artifactId>continew-starter-extension</artifactId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
|  | ||||
|     <artifactId>continew-starter-extension-crud</artifactId> | ||||
|     <packaging>jar</packaging> | ||||
|  | ||||
|     <name>${project.artifactId}</name> | ||||
|     <description>ContiNew Starter 扩展模块 - CRUD(增删改查)</description> | ||||
|  | ||||
|     <dependencies> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.data</groupId> | ||||
|             <artifactId>spring-data-commons</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 认证模块 - SaToken --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c.continew</groupId> | ||||
|             <artifactId>continew-starter-auth-satoken</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 数据访问模块 - MyBatis Plus --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c.continew</groupId> | ||||
|             <artifactId>continew-starter-data-mybatis-plus</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 文件处理模块 - Excel --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c.continew</groupId> | ||||
|             <artifactId>continew-starter-file-excel</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- API 文档模块 --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c.continew</groupId> | ||||
|             <artifactId>continew-starter-api-doc</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| </project> | ||||
| @@ -0,0 +1,43 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.annotation; | ||||
|  | ||||
| import top.charles7c.continew.starter.extension.crud.enums.Api; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * CRUD(增删改查)请求映射器注解 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Target(ElementType.TYPE) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface CrudRequestMapping { | ||||
|  | ||||
|     /** | ||||
|      * 路径映射 URI(等同于:@RequestMapping("/foo1")) | ||||
|      */ | ||||
|     String value() default ""; | ||||
|  | ||||
|     /** | ||||
|      * API 列表 | ||||
|      */ | ||||
|     Api[] api() default {Api.PAGE, Api.GET, Api.ADD, Api.UPDATE, Api.DELETE, Api.EXPORT}; | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.annotation; | ||||
|  | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
|  | ||||
| /** | ||||
|  * 响应拦截忽略注解 | ||||
|  * | ||||
|  * @author BULL_BCLS | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Target({ElementType.METHOD}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| public @interface NoResponseAdvice {} | ||||
| @@ -0,0 +1,52 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.annotation; | ||||
|  | ||||
| import top.charles7c.continew.starter.extension.crud.enums.QueryTypeEnum; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 查询注解 | ||||
|  * | ||||
|  * @author Zheng Jie(<a href="https://gitee.com/elunez/eladmin">ELADMIN</a>) | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Target(ElementType.FIELD) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface Query { | ||||
|  | ||||
|     /** | ||||
|      * 属性名(默认和使用该注解的属性的名称一致) | ||||
|      */ | ||||
|     String property() default ""; | ||||
|  | ||||
|     /** | ||||
|      * 查询类型(等值查询、模糊查询、范围查询等) | ||||
|      */ | ||||
|     QueryTypeEnum type() default QueryTypeEnum.EQUAL; | ||||
|  | ||||
|     /** | ||||
|      * 多属性模糊查询,仅支持 String 类型属性 | ||||
|      * <p> | ||||
|      * 例如:@Query(blurry = {"username", "email"}) 表示根据用户名和邮箱模糊查询 | ||||
|      * </p> | ||||
|      */ | ||||
|     String[] blurry() default {}; | ||||
| } | ||||
| @@ -0,0 +1,74 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.annotation; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 树结构字段 | ||||
|  * | ||||
|  * @see cn.hutool.core.lang.tree.TreeNodeConfig | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Target(ElementType.TYPE) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface TreeField { | ||||
|  | ||||
|     /** | ||||
|      * ID 字段名 | ||||
|      * | ||||
|      * @return ID 字段名 | ||||
|      */ | ||||
|     String value() default "key"; | ||||
|  | ||||
|     /** | ||||
|      * 父 ID 字段名 | ||||
|      * | ||||
|      * @return 父 ID 字段名 | ||||
|      */ | ||||
|     String parentIdKey() default "parentId"; | ||||
|  | ||||
|     /** | ||||
|      * 名称字段名 | ||||
|      * | ||||
|      * @return 名称字段名 | ||||
|      */ | ||||
|     String nameKey() default "title"; | ||||
|  | ||||
|     /** | ||||
|      * 排序字段名 | ||||
|      * | ||||
|      * @return 排序字段名 | ||||
|      */ | ||||
|     String weightKey() default "sort"; | ||||
|  | ||||
|     /** | ||||
|      * 子列表字段名 | ||||
|      * | ||||
|      * @return 子列表字段名 | ||||
|      */ | ||||
|     String childrenKey() default "children"; | ||||
|  | ||||
|     /** | ||||
|      * 递归深度(< 0 不限制) | ||||
|      * | ||||
|      * @return 递归深度 | ||||
|      */ | ||||
|     int deep() default -1; | ||||
| } | ||||
| @@ -0,0 +1,64 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.autoconfigure; | ||||
|  | ||||
| import jakarta.annotation.PostConstruct; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.beans.factory.annotation.Qualifier; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Primary; | ||||
| import org.springframework.format.support.FormattingConversionService; | ||||
| import org.springframework.web.accept.ContentNegotiationManager; | ||||
| import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; | ||||
| import org.springframework.web.servlet.resource.ResourceUrlProvider; | ||||
| import top.charles7c.continew.starter.extension.crud.handler.CrudRequestMappingHandlerMapping; | ||||
|  | ||||
| /** | ||||
|  * CRUD 自动配置 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @AutoConfiguration | ||||
| public class CrudAutoConfiguration extends DelegatingWebMvcConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * CRUD 请求映射器处理器映射器(覆盖默认 RequestMappingHandlerMapping) | ||||
|      */ | ||||
|     @Override | ||||
|     public RequestMappingHandlerMapping createRequestMappingHandlerMapping() { | ||||
|         return new CrudRequestMappingHandlerMapping(); | ||||
|     } | ||||
|  | ||||
|     @Bean | ||||
|     @Primary | ||||
|     @Override | ||||
|     public RequestMappingHandlerMapping requestMappingHandlerMapping( | ||||
|             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, | ||||
|             @Qualifier("mvcConversionService") FormattingConversionService conversionService, | ||||
|             @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { | ||||
|         return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService, resourceUrlProvider); | ||||
|     } | ||||
|  | ||||
|     @PostConstruct | ||||
|     public void postConstruct() { | ||||
|         log.info("[ContiNew Starter] - Auto Configuration 'Extension-CRUD' completed initialization."); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,212 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import cn.dev33.satoken.stp.StpUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.enums.ParameterIn; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import top.charles7c.continew.starter.core.constant.StringConstants; | ||||
| import top.charles7c.continew.starter.extension.crud.annotation.CrudRequestMapping; | ||||
| import top.charles7c.continew.starter.extension.crud.annotation.NoResponseAdvice; | ||||
| import top.charles7c.continew.starter.extension.crud.enums.Api; | ||||
| import top.charles7c.continew.starter.extension.crud.model.query.PageQuery; | ||||
| import top.charles7c.continew.starter.extension.crud.model.query.SortQuery; | ||||
| import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp; | ||||
| import top.charles7c.continew.starter.extension.crud.model.resp.R; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 控制器基类 | ||||
|  * | ||||
|  * @param <S> | ||||
|  *            业务接口 | ||||
|  * @param <L> | ||||
|  *            列表信息 | ||||
|  * @param <D> | ||||
|  *            详情信息 | ||||
|  * @param <Q> | ||||
|  *            查询条件 | ||||
|  * @param <C> | ||||
|  *            创建或修改信息 | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @NoArgsConstructor | ||||
| public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, C extends BaseReq> { | ||||
|  | ||||
|     @Autowired | ||||
|     protected S baseService; | ||||
|  | ||||
|     /** | ||||
|      * 分页查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param pageQuery | ||||
|      *            分页查询条件 | ||||
|      * @return 分页信息 | ||||
|      */ | ||||
|     @Operation(summary = "分页查询列表", description = "分页查询列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping | ||||
|     public PageDataResp<L> page(Q query, @Validated PageQuery pageQuery) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.page(query, pageQuery); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     @Operation(summary = "查询树列表", description = "查询树列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping("/tree") | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.tree(query, sortQuery, false); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     @Operation(summary = "查询列表", description = "查询列表") | ||||
|     @ResponseBody | ||||
|     @GetMapping("/list") | ||||
|     public List<L> list(Q query, SortQuery sortQuery) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.list(query, sortQuery); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查看详情 | ||||
|      * | ||||
|      * @param id | ||||
|      *            ID | ||||
|      * @return 详情信息 | ||||
|      */ | ||||
|     @Operation(summary = "查看详情", description = "查看详情") | ||||
|     @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) | ||||
|     @ResponseBody | ||||
|     @GetMapping("/{id}") | ||||
|     public D get(@PathVariable Long id) { | ||||
|         this.checkPermission(Api.LIST); | ||||
|         return baseService.get(id); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 新增 | ||||
|      * | ||||
|      * @param req | ||||
|      *            创建信息 | ||||
|      * @return 自增 ID | ||||
|      */ | ||||
|     @Operation(summary = "新增数据", description = "新增数据") | ||||
|     @ResponseBody | ||||
|     @PostMapping | ||||
|     public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) { | ||||
|         this.checkPermission(Api.ADD); | ||||
|         Long id = baseService.add(req); | ||||
|         return R.ok("新增成功", id); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 修改 | ||||
|      * | ||||
|      * @param req | ||||
|      *            修改信息 | ||||
|      * @param id | ||||
|      *            ID | ||||
|      * @return / | ||||
|      */ | ||||
|     @Operation(summary = "修改数据", description = "修改数据") | ||||
|     @Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH) | ||||
|     @ResponseBody | ||||
|     @PutMapping("/{id}") | ||||
|     public R update(@Validated(ValidateGroup.Crud.Update.class) @RequestBody C req, @PathVariable Long id) { | ||||
|         this.checkPermission(Api.UPDATE); | ||||
|         baseService.update(req, id); | ||||
|         return R.ok("修改成功"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 删除 | ||||
|      * | ||||
|      * @param ids | ||||
|      *            ID 列表 | ||||
|      * @return / | ||||
|      */ | ||||
|     @Operation(summary = "删除数据", description = "删除数据") | ||||
|     @Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH) | ||||
|     @ResponseBody | ||||
|     @DeleteMapping("/{ids}") | ||||
|     public R delete(@PathVariable List<Long> ids) { | ||||
|         this.checkPermission(Api.DELETE); | ||||
|         baseService.delete(ids); | ||||
|         return R.ok("删除成功"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 导出 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      */ | ||||
|     @Operation(summary = "导出数据", description = "导出数据") | ||||
|     @NoResponseAdvice | ||||
|     @GetMapping("/export") | ||||
|     public void export(Q query, SortQuery sortQuery, HttpServletResponse response) { | ||||
|         this.checkPermission(Api.EXPORT); | ||||
|         baseService.export(query, sortQuery, response); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 API 类型进行权限验证 | ||||
|      * | ||||
|      * @param api | ||||
|      *            API 类型 | ||||
|      */ | ||||
|     private void checkPermission(Api api) { | ||||
|         CrudRequestMapping crudRequestMapping = this.getClass().getDeclaredAnnotation(CrudRequestMapping.class); | ||||
|         String path = crudRequestMapping.value(); | ||||
|         String permissionPrefix = String.join(StringConstants.COLON, StrUtil.splitTrim(path, StringConstants.SLASH)); | ||||
|         StpUtil.checkPermission(String.format("%s:%s", permissionPrefix, api.name().toLowerCase())); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,67 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.FieldFill; | ||||
| import com.baomidou.mybatisplus.annotation.TableField; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| /** | ||||
|  * 实体类基类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| public class BaseDO implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * ID | ||||
|      */ | ||||
|     @TableId | ||||
|     private Long id; | ||||
|  | ||||
|     /** | ||||
|      * 创建人 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private Long createUser; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private LocalDateTime createTime; | ||||
|  | ||||
|     /** | ||||
|      * 修改人 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.UPDATE) | ||||
|     private Long updateUser; | ||||
|  | ||||
|     /** | ||||
|      * 修改时间 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.UPDATE) | ||||
|     private LocalDateTime updateTime; | ||||
| } | ||||
| @@ -0,0 +1,59 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import com.alibaba.excel.annotation.ExcelProperty; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
|  | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| /** | ||||
|  * 详情响应基类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class BaseDetailResp extends BaseResp { | ||||
|  | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 修改人 | ||||
|      */ | ||||
|     @JsonIgnore | ||||
|     private Long updateUser; | ||||
|  | ||||
|     /** | ||||
|      * 修改人 | ||||
|      */ | ||||
|     @Schema(description = "修改人", example = "李四") | ||||
|     @ExcelProperty(value = "修改人", order = Integer.MAX_VALUE - 2) | ||||
|     private String updateUserString; | ||||
|  | ||||
|     /** | ||||
|      * 修改时间 | ||||
|      */ | ||||
|     @Schema(description = "修改时间", example = "2023-08-08 08:08:08", type = "string") | ||||
|     @ExcelProperty(value = "修改时间", order = Integer.MAX_VALUE - 1) | ||||
|     private LocalDateTime updateTime; | ||||
| } | ||||
| @@ -0,0 +1,116 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import cn.hutool.core.util.ClassUtil; | ||||
| import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; | ||||
| import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper; | ||||
| import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper; | ||||
| import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper; | ||||
| import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers; | ||||
| import com.baomidou.mybatisplus.extension.toolkit.Db; | ||||
|  | ||||
| import java.util.Collection; | ||||
|  | ||||
| /** | ||||
|  * Mapper 基类 | ||||
|  * | ||||
|  * @param <T> | ||||
|  *            实体类 | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public interface BaseMapper<T> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T> { | ||||
|  | ||||
|     /** | ||||
|      * 批量插入记录 | ||||
|      * | ||||
|      * @param entityList | ||||
|      *            实体列表 | ||||
|      * @return 是否成功 | ||||
|      */ | ||||
|     default boolean insertBatch(Collection<T> entityList) { | ||||
|         return Db.saveBatch(entityList); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 批量更新记录 | ||||
|      * | ||||
|      * @param entityList | ||||
|      *            实体列表 | ||||
|      * @return 是否成功 | ||||
|      */ | ||||
|     default boolean updateBatchById(Collection<T> entityList) { | ||||
|         return Db.updateBatchById(entityList); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 链式查询 | ||||
|      * | ||||
|      * @return QueryWrapper 的包装类 | ||||
|      */ | ||||
|     default QueryChainWrapper<T> query() { | ||||
|         return ChainWrappers.queryChain(this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 链式查询(lambda 式) | ||||
|      * | ||||
|      * @return LambdaQueryWrapper 的包装类 | ||||
|      */ | ||||
|     default LambdaQueryChainWrapper<T> lambdaQuery() { | ||||
|         return ChainWrappers.lambdaQueryChain(this, this.currentEntityClass()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 链式查询(lambda 式) | ||||
|      * | ||||
|      * @param entity | ||||
|      *            实体对象 | ||||
|      * @return LambdaQueryWrapper 的包装类 | ||||
|      */ | ||||
|     default LambdaQueryChainWrapper<T> lambdaQuery(T entity) { | ||||
|         return ChainWrappers.lambdaQueryChain(this, entity); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 链式更改 | ||||
|      * | ||||
|      * @return UpdateWrapper 的包装类 | ||||
|      */ | ||||
|     default UpdateChainWrapper<T> update() { | ||||
|         return ChainWrappers.updateChain(this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 链式更改(lambda 式) | ||||
|      * | ||||
|      * @return LambdaUpdateWrapper 的包装类 | ||||
|      */ | ||||
|     default LambdaUpdateChainWrapper<T> lambdaUpdate() { | ||||
|         return ChainWrappers.lambdaUpdateChain(this); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取实体类 Class 对象 | ||||
|      * | ||||
|      * @return 实体类 Class 对象 | ||||
|      */ | ||||
|     default Class<T> currentEntityClass() { | ||||
|         return (Class<T>)ClassUtil.getTypeArgument(this.getClass(), 0); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,33 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 请求基类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| public class BaseReq implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
| } | ||||
| @@ -0,0 +1,72 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import com.alibaba.excel.annotation.ExcelProperty; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| /** | ||||
|  * 响应基类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| public class BaseResp implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * ID | ||||
|      */ | ||||
|     @Schema(description = "ID", example = "1") | ||||
|     @ExcelProperty(value = "ID", order = 1) | ||||
|     private Long id; | ||||
|  | ||||
|     /** | ||||
|      * 创建人 | ||||
|      */ | ||||
|     @JsonIgnore | ||||
|     private Long createUser; | ||||
|  | ||||
|     /** | ||||
|      * 创建人 | ||||
|      */ | ||||
|     @Schema(description = "创建人", example = "超级管理员") | ||||
|     @ExcelProperty(value = "创建人", order = Integer.MAX_VALUE - 4) | ||||
|     private String createUserString; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") | ||||
|     @ExcelProperty(value = "创建时间", order = Integer.MAX_VALUE - 3) | ||||
|     private LocalDateTime createTime; | ||||
|  | ||||
|     /** | ||||
|      * 是否禁用修改 | ||||
|      */ | ||||
|     @Schema(description = "是否禁用修改", example = "true") | ||||
|     @JsonInclude(JsonInclude.Include.NON_NULL) | ||||
|     private Boolean disabled; | ||||
| } | ||||
| @@ -0,0 +1,125 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import top.charles7c.continew.starter.extension.crud.model.query.PageQuery; | ||||
| import top.charles7c.continew.starter.extension.crud.model.query.SortQuery; | ||||
| import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 业务接口基类 | ||||
|  * | ||||
|  * @param <L> | ||||
|  *            列表信息 | ||||
|  * @param <D> | ||||
|  *            详情信息 | ||||
|  * @param <Q> | ||||
|  *            查询条件 | ||||
|  * @param <C> | ||||
|  *            创建或修改信息 | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public interface BaseService<L, D, Q, C extends BaseReq> { | ||||
|  | ||||
|     /** | ||||
|      * 分页查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param pageQuery | ||||
|      *            分页查询条件 | ||||
|      * @return 分页列表信息 | ||||
|      */ | ||||
|     PageDataResp<L> page(Q query, PageQuery pageQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询树列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @param isSimple | ||||
|      *            是否为简单树结构(不包含基本树结构之外的扩展字段) | ||||
|      * @return 树列表信息 | ||||
|      */ | ||||
|     List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple); | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     List<L> list(Q query, SortQuery sortQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查看详情 | ||||
|      * | ||||
|      * @param id | ||||
|      *            ID | ||||
|      * @return 详情信息 | ||||
|      */ | ||||
|     D get(Long id); | ||||
|  | ||||
|     /** | ||||
|      * 新增 | ||||
|      * | ||||
|      * @param req | ||||
|      *            创建信息 | ||||
|      * @return 自增 ID | ||||
|      */ | ||||
|     Long add(C req); | ||||
|  | ||||
|     /** | ||||
|      * 修改 | ||||
|      * | ||||
|      * @param req | ||||
|      *            修改信息 | ||||
|      * @param id | ||||
|      *            ID | ||||
|      */ | ||||
|     void update(C req, Long id); | ||||
|  | ||||
|     /** | ||||
|      * 删除 | ||||
|      * | ||||
|      * @param ids | ||||
|      *            ID 列表 | ||||
|      */ | ||||
|     void delete(List<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 导出 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @param response | ||||
|      *            响应对象 | ||||
|      */ | ||||
|     void export(Q query, SortQuery sortQuery, HttpServletResponse response); | ||||
| } | ||||
| @@ -0,0 +1,254 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.bean.copier.CopyOptions; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.core.lang.Opt; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.util.ClassUtil; | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.data.domain.Sort; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import top.charles7c.continew.starter.core.util.ExceptionUtils; | ||||
| import top.charles7c.continew.starter.extension.crud.annotation.TreeField; | ||||
| import top.charles7c.continew.starter.extension.crud.model.query.PageQuery; | ||||
| import top.charles7c.continew.starter.extension.crud.model.query.SortQuery; | ||||
| import top.charles7c.continew.starter.extension.crud.model.resp.PageDataResp; | ||||
| import top.charles7c.continew.starter.extension.crud.util.QueryHelper; | ||||
| import top.charles7c.continew.starter.extension.crud.util.ReflectUtils; | ||||
| import top.charles7c.continew.starter.extension.crud.util.TreeUtils; | ||||
| import top.charles7c.continew.starter.extension.crud.util.validate.CheckUtils; | ||||
| import top.charles7c.continew.starter.file.excel.util.ExcelUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 业务实现基类 | ||||
|  * | ||||
|  * @param <M> | ||||
|  *            Mapper 接口 | ||||
|  * @param <T> | ||||
|  *            实体类 | ||||
|  * @param <L> | ||||
|  *            列表信息 | ||||
|  * @param <D> | ||||
|  *            详情信息 | ||||
|  * @param <Q> | ||||
|  *            查询条件 | ||||
|  * @param <C> | ||||
|  *            创建或修改信息 | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseDO, L, D, Q, C extends BaseReq> | ||||
|     implements BaseService<L, D, Q, C> { | ||||
|  | ||||
|     @Autowired | ||||
|     protected M baseMapper; | ||||
|  | ||||
|     private final Class<T> entityClass; | ||||
|     private final Class<L> listClass; | ||||
|     private final Class<D> detailClass; | ||||
|  | ||||
|     protected BaseServiceImpl() { | ||||
|         this.entityClass = (Class<T>)ClassUtil.getTypeArgument(this.getClass(), 1); | ||||
|         this.listClass = (Class<L>)ClassUtil.getTypeArgument(this.getClass(), 2); | ||||
|         this.detailClass = (Class<D>)ClassUtil.getTypeArgument(this.getClass(), 3); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public PageDataResp<L> page(Q query, PageQuery pageQuery) { | ||||
|         QueryWrapper<T> queryWrapper = QueryHelper.build(query); | ||||
|         IPage<T> page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper); | ||||
|         PageDataResp<L> pageDataResp = PageDataResp.build(page, listClass); | ||||
|         pageDataResp.getList().forEach(this::fill); | ||||
|         return pageDataResp; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) { | ||||
|         List<L> list = this.list(query, sortQuery); | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return new ArrayList<>(0); | ||||
|         } | ||||
|         // 如果构建简单树结构,则不包含基本树结构之外的扩展字段 | ||||
|         TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG; | ||||
|         TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class); | ||||
|         if (!isSimple) { | ||||
|             // 根据 @TreeField 配置生成树结构配置 | ||||
|             treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField); | ||||
|         } | ||||
|         // 构建树 | ||||
|         return TreeUtils.build(list, treeNodeConfig, (node, tree) -> { | ||||
|             // 转换器 | ||||
|             tree.setId(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.value()))); | ||||
|             tree.setParentId(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.parentIdKey()))); | ||||
|             tree.setName(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.nameKey()))); | ||||
|             tree.setWeight(ReflectUtil.invoke(node, StrUtil.genGetter(treeField.weightKey()))); | ||||
|             if (!isSimple) { | ||||
|                 List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass); | ||||
|                 fieldList.removeIf(f -> StrUtil.containsAnyIgnoreCase(f.getName(), treeField.value(), | ||||
|                     treeField.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey())); | ||||
|                 fieldList | ||||
|                     .forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, StrUtil.genGetter(f.getName())))); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<L> list(Q query, SortQuery sortQuery) { | ||||
|         List<L> list = this.list(query, sortQuery, listClass); | ||||
|         list.forEach(this::fill); | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      * @param targetClass | ||||
|      *            指定类型 | ||||
|      * @return 列表信息 | ||||
|      */ | ||||
|     protected <E> List<E> list(Q query, SortQuery sortQuery, Class<E> targetClass) { | ||||
|         QueryWrapper<T> queryWrapper = QueryHelper.build(query); | ||||
|         // 设置排序 | ||||
|         this.sort(queryWrapper, sortQuery); | ||||
|         List<T> entityList = baseMapper.selectList(queryWrapper); | ||||
|         return BeanUtil.copyToList(entityList, targetClass); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置排序 | ||||
|      * | ||||
|      * @param queryWrapper | ||||
|      *            查询 Wrapper | ||||
|      * @param sortQuery | ||||
|      *            排序查询条件 | ||||
|      */ | ||||
|     protected void sort(QueryWrapper<T> queryWrapper, SortQuery sortQuery) { | ||||
|         Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort(); | ||||
|         for (Sort.Order order : sort) { | ||||
|             if (null != order) { | ||||
|                 queryWrapper.orderBy(true, order.isAscending(), StrUtil.toUnderlineCase(order.getProperty())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public D get(Long id) { | ||||
|         T entity = this.getById(id); | ||||
|         D detail = BeanUtil.copyProperties(entity, detailClass); | ||||
|         this.fillDetail(detail); | ||||
|         return detail; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public Long add(C req) { | ||||
|         if (null == req) { | ||||
|             return 0L; | ||||
|         } | ||||
|         T entity = BeanUtil.copyProperties(req, entityClass); | ||||
|         baseMapper.insert(entity); | ||||
|         return entity.getId(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void update(C req, Long id) { | ||||
|         T entity = this.getById(id); | ||||
|         BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue()); | ||||
|         baseMapper.updateById(entity); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     @Transactional(rollbackFor = Exception.class) | ||||
|     public void delete(List<Long> ids) { | ||||
|         baseMapper.deleteBatchIds(ids); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void export(Q query, SortQuery sortQuery, HttpServletResponse response) { | ||||
|         List<D> list = this.list(query, sortQuery, detailClass); | ||||
|         list.forEach(this::fillDetail); | ||||
|         ExcelUtils.export(list, "导出数据", detailClass, response); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 ID 查询 | ||||
|      * | ||||
|      * @param id | ||||
|      *            ID | ||||
|      * @return 实体信息 | ||||
|      */ | ||||
|     protected T getById(Object id) { | ||||
|         T entity = baseMapper.selectById(Convert.toStr(id)); | ||||
|         CheckUtils.throwIfNotExists(entity, ClassUtil.getClassName(entityClass, true), "ID", id); | ||||
|         return entity; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充数据 | ||||
|      * | ||||
|      * @param baseObj | ||||
|      *            待填充列表信息 | ||||
|      */ | ||||
|     protected void fill(Object baseObj) { | ||||
|         if (baseObj instanceof BaseResp baseResp) { | ||||
|             Long createUser = baseResp.getCreateUser(); | ||||
|             if (null == createUser) { | ||||
|                 return; | ||||
|             } | ||||
|             CommonUserService userService = SpringUtil.getBean(CommonUserService.class); | ||||
|             baseResp.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getNicknameById(createUser))); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充详情数据 | ||||
|      * | ||||
|      * @param detailObj | ||||
|      *            待填充详情信息 | ||||
|      */ | ||||
|     public void fillDetail(Object detailObj) { | ||||
|         if (detailObj instanceof BaseDetailResp detail) { | ||||
|             this.fill(detail); | ||||
|             Long updateUser = detail.getUpdateUser(); | ||||
|             if (null == updateUser) { | ||||
|                 return; | ||||
|             } | ||||
|             CommonUserService userService = SpringUtil.getBean(CommonUserService.class); | ||||
|             detail.setUpdateUserString(ExceptionUtils.exToNull(() -> userService.getNicknameById(updateUser))); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,35 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| /** | ||||
|  * 公共用户业务接口 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public interface CommonUserService { | ||||
|  | ||||
|     /** | ||||
|      * 根据 ID 查询昵称 | ||||
|      * | ||||
|      * @param id | ||||
|      *            ID | ||||
|      * @return 昵称 | ||||
|      */ | ||||
|     String getNicknameById(Long id); | ||||
| } | ||||
| @@ -0,0 +1,48 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.IEnum; | ||||
|  | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 枚举接口 | ||||
|  * | ||||
|  * @param <T> | ||||
|  *            value 类型 | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public interface IBaseEnum<T extends Serializable> extends IEnum<T> { | ||||
|  | ||||
|     /** | ||||
|      * 枚举描述 | ||||
|      * | ||||
|      * @return 枚举描述 | ||||
|      */ | ||||
|     String getDescription(); | ||||
|  | ||||
|     /** | ||||
|      * 颜色 | ||||
|      * | ||||
|      * @return 颜色 | ||||
|      */ | ||||
|     default String getColor() { | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,43 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.base; | ||||
|  | ||||
| import jakarta.validation.groups.Default; | ||||
|  | ||||
| /** | ||||
|  * 分组校验 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public interface ValidateGroup extends Default { | ||||
|  | ||||
|     /** | ||||
|      * 分组校验-增删改查 | ||||
|      */ | ||||
|     interface Crud extends ValidateGroup { | ||||
|         /** | ||||
|          * 分组校验-创建 | ||||
|          */ | ||||
|         interface Add extends Crud {} | ||||
|  | ||||
|         /** | ||||
|          * 分组校验-修改 | ||||
|          */ | ||||
|         interface Update extends Crud {} | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,63 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.enums; | ||||
|  | ||||
| /** | ||||
|  * API 类型枚举 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public enum Api { | ||||
|  | ||||
|     /** | ||||
|      * 所有 API | ||||
|      */ | ||||
|     ALL, | ||||
|     /** | ||||
|      * 分页 | ||||
|      */ | ||||
|     PAGE, | ||||
|     /** | ||||
|      * 树列表 | ||||
|      */ | ||||
|     TREE, | ||||
|     /** | ||||
|      * 列表 | ||||
|      */ | ||||
|     LIST, | ||||
|     /** | ||||
|      * 详情 | ||||
|      */ | ||||
|     GET, | ||||
|     /** | ||||
|      * 新增 | ||||
|      */ | ||||
|     ADD, | ||||
|     /** | ||||
|      * 修改 | ||||
|      */ | ||||
|     UPDATE, | ||||
|     /** | ||||
|      * 删除 | ||||
|      */ | ||||
|     DELETE, | ||||
|     /** | ||||
|      * 导出 | ||||
|      */ | ||||
|     EXPORT, | ||||
| } | ||||
| @@ -0,0 +1,92 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.enums; | ||||
|  | ||||
| import lombok.Getter; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import top.charles7c.continew.starter.extension.crud.base.IBaseEnum; | ||||
|  | ||||
| /** | ||||
|  * 查询类型枚举 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Getter | ||||
| @RequiredArgsConstructor | ||||
| public enum QueryTypeEnum implements IBaseEnum<Integer> { | ||||
|  | ||||
|     /** | ||||
|      * 等值查询,例如:WHERE `age` = 18 | ||||
|      */ | ||||
|     EQUAL(1, "="), | ||||
|     /** | ||||
|      * 非等值查询,例如:WHERE `age` != 18 | ||||
|      */ | ||||
|     NOT_EQUAL(2, "!="), | ||||
|     /** | ||||
|      * 大于查询,例如:WHERE `age` > 18 | ||||
|      */ | ||||
|     GREATER_THAN(3, ">"), | ||||
|     /** | ||||
|      * 小于查询,例如:WHERE `age` < 18 | ||||
|      */ | ||||
|     LESS_THAN(4, "<"), | ||||
|     /** | ||||
|      * 大于等于查询,例如:WHERE `age` >= 18 | ||||
|      */ | ||||
|     GREATER_THAN_OR_EQUAL(5, ">="), | ||||
|     /** | ||||
|      * 小于等于查询,例如:WHERE `age` <= 18 | ||||
|      */ | ||||
|     LESS_THAN_OR_EQUAL(6, "<="), | ||||
|     /** | ||||
|      * 范围查询,例如:WHERE `age` BETWEEN 10 AND 18 | ||||
|      */ | ||||
|     BETWEEN(7, "BETWEEN"), | ||||
|     /** | ||||
|      * 左模糊查询,例如:WHERE `nickname` LIKE '%s' | ||||
|      */ | ||||
|     LEFT_LIKE(8, "LIKE '%s'"), | ||||
|     /** | ||||
|      * 中模糊查询,例如:WHERE `nickname` LIKE '%s%' | ||||
|      */ | ||||
|     INNER_LIKE(9, "LIKE '%s%'"), | ||||
|     /** | ||||
|      * 右模糊查询,例如:WHERE `nickname` LIKE 's%' | ||||
|      */ | ||||
|     RIGHT_LIKE(10, "LIKE 's%'"), | ||||
|     /** | ||||
|      * 包含查询,例如:WHERE `age` IN (10, 20, 30) | ||||
|      */ | ||||
|     IN(11, "IN"), | ||||
|     /** | ||||
|      * 不包含查询,例如:WHERE `age` NOT IN (20, 30) | ||||
|      */ | ||||
|     NOT_IN(12, "NOT IN"), | ||||
|     /** | ||||
|      * 空查询,例如:WHERE `email` IS NULL | ||||
|      */ | ||||
|     IS_NULL(13, "IS NULL"), | ||||
|     /** | ||||
|      * 非空查询,例如:WHERE `email` IS NOT NULL | ||||
|      */ | ||||
|     IS_NOT_NULL(14, "IS NOT NULL"),; | ||||
|  | ||||
|     private final Integer value; | ||||
|     private final String description; | ||||
| } | ||||
| @@ -0,0 +1,34 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.exception; | ||||
|  | ||||
| import lombok.NoArgsConstructor; | ||||
| import top.charles7c.continew.starter.core.exception.BaseException; | ||||
|  | ||||
| /** | ||||
|  * 自定义验证异常-错误请求 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @NoArgsConstructor | ||||
| public class BadRequestException extends BaseException { | ||||
|  | ||||
|     public BadRequestException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,34 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.exception; | ||||
|  | ||||
| import lombok.NoArgsConstructor; | ||||
| import top.charles7c.continew.starter.core.exception.BaseException; | ||||
|  | ||||
| /** | ||||
|  * 业务异常 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @NoArgsConstructor | ||||
| public class BusinessException extends BaseException { | ||||
|  | ||||
|     public BusinessException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,72 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.handler; | ||||
|  | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import org.springframework.lang.NonNull; | ||||
| import org.springframework.web.servlet.mvc.method.RequestMappingInfo; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; | ||||
| import org.springframework.web.util.pattern.PathPatternParser; | ||||
| import top.charles7c.continew.starter.core.util.ExceptionUtils; | ||||
| import top.charles7c.continew.starter.extension.crud.annotation.CrudRequestMapping; | ||||
| import top.charles7c.continew.starter.extension.crud.enums.Api; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| /** | ||||
|  * CRUD 请求映射器处理器映射器 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| public class CrudRequestMappingHandlerMapping extends RequestMappingHandlerMapping { | ||||
|  | ||||
|     @Override | ||||
|     protected RequestMappingInfo getMappingForMethod(@NonNull Method method, @NonNull Class<?> handlerType) { | ||||
|         RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method, handlerType); | ||||
|         if (null == requestMappingInfo) { | ||||
|             return null; | ||||
|         } | ||||
|         // 如果没有声明 @CrudRequestMapping 注解,直接返回 | ||||
|         if (!handlerType.isAnnotationPresent(CrudRequestMapping.class)) { | ||||
|             return requestMappingInfo; | ||||
|         } | ||||
|         // 获取 @CrudRequestMapping 注解信息 | ||||
|         CrudRequestMapping crudRequestMapping = handlerType.getDeclaredAnnotation(CrudRequestMapping.class); | ||||
|         // 拼接路径前缀(合并了 @RequestMapping 的部分能力) | ||||
|         String pathPrefix = crudRequestMapping.value(); | ||||
|         if (StrUtil.isNotBlank(pathPrefix)) { | ||||
|             /* | ||||
|              * 问题:RequestMappingInfo.paths(pathPrefix) 返回的 RequestMappingInfo 对象里 pathPatternsCondition = null | ||||
|              * 导致 combine() 方法抛出断言异常! 修复:创建 options 对象,并设置 PatternParser | ||||
|              */ | ||||
|             RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration(); | ||||
|             options.setPatternParser(PathPatternParser.defaultInstance); | ||||
|             requestMappingInfo = | ||||
|                 RequestMappingInfo.paths(pathPrefix).options(options).build().combine(requestMappingInfo); | ||||
|         } | ||||
|         // 过滤 API | ||||
|         Api[] apiArr = crudRequestMapping.api(); | ||||
|         // 如果非本类中定义,且 API 列表中不包含,则忽略 | ||||
|         Api api = ExceptionUtils.exToNull(() -> Api.valueOf(method.getName().toUpperCase())); | ||||
|         if (method.getDeclaringClass() != handlerType && !ArrayUtil.containsAny(apiArr, Api.ALL, api)) { | ||||
|             return null; | ||||
|         } | ||||
|         return requestMappingInfo; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,87 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.model.query; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import com.baomidou.mybatisplus.core.metadata.OrderItem; | ||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import jakarta.validation.constraints.Min; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.hibernate.validator.constraints.Range; | ||||
| import org.springdoc.core.annotations.ParameterObject; | ||||
| import org.springframework.data.domain.Sort; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 分页查询条件 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| @ParameterObject | ||||
| @NoArgsConstructor | ||||
| @Schema(description = "分页查询条件") | ||||
| public class PageQuery extends SortQuery { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|     /** 默认页码:1 */ | ||||
|     private static final int DEFAULT_PAGE = 1; | ||||
|     /** 默认每页条数:10 */ | ||||
|     private static final int DEFAULT_SIZE = 10; | ||||
|  | ||||
|     /** | ||||
|      * 页码 | ||||
|      */ | ||||
|     @Schema(description = "页码", example = "1") | ||||
|     @Min(value = 1, message = "页码最小值为 {value}") | ||||
|     private Integer page = DEFAULT_PAGE; | ||||
|  | ||||
|     /** | ||||
|      * 每页条数 | ||||
|      */ | ||||
|     @Schema(description = "每页条数", example = "10") | ||||
|     @Range(min = 1, max = 1000, message = "每页条数(取值范围 {min}-{max})") | ||||
|     private Integer size = DEFAULT_SIZE; | ||||
|  | ||||
|     /** | ||||
|      * 基于分页查询条件转换为 MyBatis Plus 分页条件 | ||||
|      * | ||||
|      * @param <T> | ||||
|      *            列表数据类型 | ||||
|      * @return MyBatis Plus 分页条件 | ||||
|      */ | ||||
|     public <T> IPage<T> toPage() { | ||||
|         Page<T> mybatisPage = new Page<>(this.getPage(), this.getSize()); | ||||
|         Sort pageSort = this.getSort(); | ||||
|         if (CollUtil.isNotEmpty(pageSort)) { | ||||
|             for (Sort.Order order : pageSort) { | ||||
|                 OrderItem orderItem = new OrderItem(); | ||||
|                 orderItem.setAsc(order.isAscending()); | ||||
|                 orderItem.setColumn(StrUtil.toUnderlineCase(order.getProperty())); | ||||
|                 mybatisPage.addOrder(orderItem); | ||||
|             } | ||||
|         } | ||||
|         return mybatisPage; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,76 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.model.query; | ||||
|  | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
| import org.springframework.data.domain.Sort; | ||||
| import top.charles7c.continew.starter.core.constant.StringConstants; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 排序查询条件 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "排序查询条件") | ||||
| public class SortQuery implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 排序条件 | ||||
|      */ | ||||
|     @Schema(description = "排序条件", example = "createTime,desc") | ||||
|     private String[] sort; | ||||
|  | ||||
|     /** | ||||
|      * 解析排序条件为 Spring 分页排序实体 | ||||
|      * | ||||
|      * @return Spring 分页排序实体 | ||||
|      */ | ||||
|     public Sort getSort() { | ||||
|         if (ArrayUtil.isEmpty(sort)) { | ||||
|             return Sort.unsorted(); | ||||
|         } | ||||
|  | ||||
|         List<Sort.Order> orders = new ArrayList<>(sort.length); | ||||
|         if (StrUtil.contains(sort[0], StringConstants.COMMA)) { | ||||
|             // e.g "sort=createTime,desc&sort=name,asc" | ||||
|             for (String s : sort) { | ||||
|                 List<String> sortList = StrUtil.splitTrim(s, StringConstants.COMMA); | ||||
|                 Sort.Order order = | ||||
|                     new Sort.Order(Sort.Direction.valueOf(sortList.get(1).toUpperCase()), sortList.get(0)); | ||||
|                 orders.add(order); | ||||
|             } | ||||
|         } else { | ||||
|             // e.g "sort=createTime,desc" | ||||
|             Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]); | ||||
|             orders.add(order); | ||||
|         } | ||||
|         return Sort.by(orders); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,143 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.model.resp; | ||||
|  | ||||
| import cn.hutool.core.bean.BeanUtil; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 分页信息 | ||||
|  * | ||||
|  * @param <L> | ||||
|  *            列表数据类型 | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "分页信息") | ||||
| public class PageDataResp<L> implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 列表数据 | ||||
|      */ | ||||
|     @Schema(description = "列表数据") | ||||
|     private List<L> list; | ||||
|  | ||||
|     /** | ||||
|      * 总记录数 | ||||
|      */ | ||||
|     @Schema(description = "总记录数", example = "10") | ||||
|     private long total; | ||||
|  | ||||
|     /** | ||||
|      * 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据 | ||||
|      * | ||||
|      * @param page | ||||
|      *            MyBatis Plus 分页数据 | ||||
|      * @param targetClass | ||||
|      *            目标类型 Class 对象 | ||||
|      * @param <T> | ||||
|      *            源列表数据类型 | ||||
|      * @param <L> | ||||
|      *            目标列表数据类型 | ||||
|      * @return 分页信息 | ||||
|      */ | ||||
|     public static <T, L> PageDataResp<L> build(IPage<T> page, Class<L> targetClass) { | ||||
|         if (null == page) { | ||||
|             return empty(); | ||||
|         } | ||||
|         PageDataResp<L> pageDataResp = new PageDataResp<>(); | ||||
|         pageDataResp.setList(BeanUtil.copyToList(page.getRecords(), targetClass)); | ||||
|         pageDataResp.setTotal(page.getTotal()); | ||||
|         return pageDataResp; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 基于 MyBatis Plus 分页数据构建分页信息 | ||||
|      * | ||||
|      * @param page | ||||
|      *            MyBatis Plus 分页数据 | ||||
|      * @param <L> | ||||
|      *            列表数据类型 | ||||
|      * @return 分页信息 | ||||
|      */ | ||||
|     public static <L> PageDataResp<L> build(IPage<L> page) { | ||||
|         if (null == page) { | ||||
|             return empty(); | ||||
|         } | ||||
|         PageDataResp<L> pageDataResp = new PageDataResp<>(); | ||||
|         pageDataResp.setList(page.getRecords()); | ||||
|         pageDataResp.setTotal(page.getTotal()); | ||||
|         return pageDataResp; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 基于列表数据构建分页信息 | ||||
|      * | ||||
|      * @param page | ||||
|      *            页码 | ||||
|      * @param size | ||||
|      *            每页条数 | ||||
|      * @param list | ||||
|      *            列表数据 | ||||
|      * @param <L> | ||||
|      *            列表数据类型 | ||||
|      * @return 分页信息 | ||||
|      */ | ||||
|     public static <L> PageDataResp<L> build(int page, int size, List<L> list) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return empty(); | ||||
|         } | ||||
|         PageDataResp<L> pageDataResp = new PageDataResp<>(); | ||||
|         pageDataResp.setTotal(list.size()); | ||||
|         // 对列表数据进行分页 | ||||
|         int fromIndex = (page - 1) * size; | ||||
|         int toIndex = page * size + size; | ||||
|         if (fromIndex > list.size()) { | ||||
|             pageDataResp.setList(new ArrayList<>(0)); | ||||
|         } else if (toIndex >= list.size()) { | ||||
|             pageDataResp.setList(list.subList(fromIndex, list.size())); | ||||
|         } else { | ||||
|             pageDataResp.setList(list.subList(fromIndex, toIndex)); | ||||
|         } | ||||
|         return pageDataResp; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 空分页信息 | ||||
|      * | ||||
|      * @param <L> | ||||
|      *            列表数据类型 | ||||
|      * @return 分页信息 | ||||
|      */ | ||||
|     private static <L> PageDataResp<L> empty() { | ||||
|         PageDataResp<L> pageDataResp = new PageDataResp<>(); | ||||
|         pageDataResp.setList(new ArrayList<>(0)); | ||||
|         return pageDataResp; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,110 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.model.resp; | ||||
|  | ||||
| import cn.hutool.core.date.DateUtil; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.springframework.http.HttpStatus; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 响应信息 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| @Schema(description = "响应信息") | ||||
| public class R<T> implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** 是否成功 */ | ||||
|     @Schema(description = "是否成功", example = "true") | ||||
|     private boolean success; | ||||
|  | ||||
|     /** 业务状态码 */ | ||||
|     @Schema(description = "业务状态码", example = "200") | ||||
|     private int code; | ||||
|  | ||||
|     /** 业务状态信息 */ | ||||
|     @Schema(description = "业务状态信息", example = "操作成功") | ||||
|     private String msg; | ||||
|  | ||||
|     /** 响应数据 */ | ||||
|     @Schema(description = "响应数据") | ||||
|     private T data; | ||||
|  | ||||
|     /** 时间戳 */ | ||||
|     @Schema(description = "时间戳", example = "1691453288") | ||||
|     private long timestamp = DateUtil.currentSeconds(); | ||||
|  | ||||
|     /** 成功状态码 */ | ||||
|     private static final int SUCCESS_CODE = HttpStatus.OK.value(); | ||||
|     /** 失败状态码 */ | ||||
|     private static final int FAIL_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value(); | ||||
|  | ||||
|     private R(boolean success, int code, String msg, T data) { | ||||
|         this.success = success; | ||||
|         this.code = code; | ||||
|         this.msg = msg; | ||||
|         this.data = data; | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> ok() { | ||||
|         return new R<>(true, SUCCESS_CODE, "操作成功", null); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> ok(T data) { | ||||
|         return new R<>(true, SUCCESS_CODE, "操作成功", data); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> ok(String msg) { | ||||
|         return new R<>(true, SUCCESS_CODE, msg, null); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> ok(String msg, T data) { | ||||
|         return new R<>(true, SUCCESS_CODE, msg, data); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail() { | ||||
|         return new R<>(false, FAIL_CODE, "操作失败", null); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(String msg) { | ||||
|         return new R<>(false, FAIL_CODE, msg, null); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(T data) { | ||||
|         return new R<>(false, FAIL_CODE, "操作失败", data); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(String msg, T data) { | ||||
|         return new R<>(false, FAIL_CODE, msg, data); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(int code, String msg) { | ||||
|         return new R<>(false, code, msg, null); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,174 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.util; | ||||
|  | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import top.charles7c.continew.starter.extension.crud.annotation.Query; | ||||
| import top.charles7c.continew.starter.extension.crud.enums.QueryTypeEnum; | ||||
| import top.charles7c.continew.starter.extension.crud.exception.BadRequestException; | ||||
| import top.charles7c.continew.starter.extension.crud.util.validate.ValidationUtils; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 查询助手 | ||||
|  * | ||||
|  * @author Zheng Jie(<a href="https://gitee.com/elunez/eladmin">ELADMIN</a>) | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class QueryHelper { | ||||
|  | ||||
|     /** | ||||
|      * 根据查询条件构建 MyBatis Plus 查询条件封装对象 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param <Q> | ||||
|      *            查询条件数据类型 | ||||
|      * @param <R> | ||||
|      *            查询数据类型 | ||||
|      * @return MyBatis Plus 查询条件封装对象 | ||||
|      */ | ||||
|     public static <Q, R> QueryWrapper<R> build(Q query) { | ||||
|         QueryWrapper<R> queryWrapper = new QueryWrapper<>(); | ||||
|         // 没有查询条件,直接返回 | ||||
|         if (null == query) { | ||||
|             return queryWrapper; | ||||
|         } | ||||
|         // 获取查询条件中所有的字段 | ||||
|         List<Field> fieldList = ReflectUtils.getNonStaticFields(query.getClass()); | ||||
|         fieldList.forEach(field -> buildQuery(query, field, queryWrapper)); | ||||
|         return queryWrapper; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建 MyBatis Plus 查询条件封装对象 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param field | ||||
|      *            字段 | ||||
|      * @param queryWrapper | ||||
|      *            MyBatis Plus 查询条件封装对象 | ||||
|      * @param <Q> | ||||
|      *            查询条件数据类型 | ||||
|      * @param <R> | ||||
|      *            查询数据类型 | ||||
|      */ | ||||
|     private static <Q, R> void buildQuery(Q query, Field field, QueryWrapper<R> queryWrapper) { | ||||
|         boolean accessible = field.canAccess(query); | ||||
|         try { | ||||
|             field.setAccessible(true); | ||||
|             // 没有 @Query,直接返回 | ||||
|             Query queryAnnotation = field.getAnnotation(Query.class); | ||||
|             if (null == queryAnnotation) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // 如果字段值为空,直接返回 | ||||
|             Object fieldValue = field.get(query); | ||||
|             if (ObjectUtil.isEmpty(fieldValue)) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // 解析查询条件 | ||||
|             parse(queryAnnotation, field.getName(), fieldValue, queryWrapper); | ||||
|         } catch (BadRequestException e) { | ||||
|             log.error("Build query occurred an validation error: {}. Query: {}, Field: {}.", e.getMessage(), query, | ||||
|                 field, e); | ||||
|             throw e; | ||||
|         } catch (Exception e) { | ||||
|             log.error("Build query occurred an error: {}. Query: {}, Field: {}.", e.getMessage(), query, field, e); | ||||
|         } finally { | ||||
|             field.setAccessible(accessible); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 解析查询条件 | ||||
|      * | ||||
|      * @param queryAnnotation | ||||
|      *            查询注解 | ||||
|      * @param fieldName | ||||
|      *            字段名 | ||||
|      * @param fieldValue | ||||
|      *            字段值 | ||||
|      * @param queryWrapper | ||||
|      *            MyBatis Plus 查询条件封装对象 | ||||
|      * @param <R> | ||||
|      *            查询数据类型 | ||||
|      */ | ||||
|     private static <R> void parse(Query queryAnnotation, String fieldName, Object fieldValue, | ||||
|         QueryWrapper<R> queryWrapper) { | ||||
|         // 解析多属性模糊查询 | ||||
|         // 如果设置了多属性模糊查询,分割属性进行条件拼接 | ||||
|         String[] blurryPropertyArr = queryAnnotation.blurry(); | ||||
|         if (ArrayUtil.isNotEmpty(blurryPropertyArr)) { | ||||
|             queryWrapper.and(wrapper -> { | ||||
|                 for (String blurryProperty : blurryPropertyArr) { | ||||
|                     wrapper.or().like(StrUtil.toUnderlineCase(blurryProperty), fieldValue); | ||||
|                 } | ||||
|             }); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 解析单个属性查询 | ||||
|         // 如果没有单独指定属性名,就和使用该注解的属性的名称一致 | ||||
|         // 注意:数据库规范中列采用下划线连接法命名,程序规范中变量采用驼峰法命名 | ||||
|         String property = queryAnnotation.property(); | ||||
|         String columnName = StrUtil.toUnderlineCase(StrUtil.blankToDefault(property, fieldName)); | ||||
|         QueryTypeEnum queryType = queryAnnotation.type(); | ||||
|         switch (queryType) { | ||||
|             case EQUAL -> queryWrapper.eq(columnName, fieldValue); | ||||
|             case NOT_EQUAL -> queryWrapper.ne(columnName, fieldValue); | ||||
|             case GREATER_THAN -> queryWrapper.gt(columnName, fieldValue); | ||||
|             case LESS_THAN -> queryWrapper.lt(columnName, fieldValue); | ||||
|             case GREATER_THAN_OR_EQUAL -> queryWrapper.ge(columnName, fieldValue); | ||||
|             case LESS_THAN_OR_EQUAL -> queryWrapper.le(columnName, fieldValue); | ||||
|             case BETWEEN -> { | ||||
|                 List<Object> between = new ArrayList<>((List<Object>)fieldValue); | ||||
|                 ValidationUtils.throwIf(between.size() != 2, "[{}] 必须是一个范围", fieldName); | ||||
|                 queryWrapper.between(columnName, between.get(0), between.get(1)); | ||||
|             } | ||||
|             case LEFT_LIKE -> queryWrapper.likeLeft(columnName, fieldValue); | ||||
|             case INNER_LIKE -> queryWrapper.like(columnName, fieldValue); | ||||
|             case RIGHT_LIKE -> queryWrapper.likeRight(columnName, fieldValue); | ||||
|             case IN -> { | ||||
|                 ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName); | ||||
|                 queryWrapper.in(columnName, (List<Object>)fieldValue); | ||||
|             } | ||||
|             case NOT_IN -> { | ||||
|                 ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName); | ||||
|                 queryWrapper.notIn(columnName, (List<Object>)fieldValue); | ||||
|             } | ||||
|             case IS_NULL -> queryWrapper.isNull(columnName); | ||||
|             case IS_NOT_NULL -> queryWrapper.isNotNull(columnName); | ||||
|             default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,67 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.util; | ||||
|  | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Modifier; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * 反射工具类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ReflectUtils { | ||||
|  | ||||
|     /** | ||||
|      * 获得一个类中所有非静态字段名列表,包括其父类中的字段<br> | ||||
|      * 如果子类与父类中存在同名字段,则这两个字段同时存在,子类字段在前,父类字段在后。 | ||||
|      * | ||||
|      * @param beanClass | ||||
|      *            类 | ||||
|      * @return 非静态字段名列表 | ||||
|      * @throws SecurityException | ||||
|      *             安全检查异常 | ||||
|      */ | ||||
|     public static List<String> getNonStaticFieldsName(Class<?> beanClass) throws SecurityException { | ||||
|         List<Field> nonStaticFields = getNonStaticFields(beanClass); | ||||
|         return nonStaticFields.stream().map(Field::getName).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得一个类中所有非静态字段列表,包括其父类中的字段<br> | ||||
|      * 如果子类与父类中存在同名字段,则这两个字段同时存在,子类字段在前,父类字段在后。 | ||||
|      * | ||||
|      * @param beanClass | ||||
|      *            类 | ||||
|      * @return 非静态字段列表 | ||||
|      * @throws SecurityException | ||||
|      *             安全检查异常 | ||||
|      */ | ||||
|     public static List<Field> getNonStaticFields(Class<?> beanClass) throws SecurityException { | ||||
|         Field[] fields = ReflectUtil.getFields(beanClass); | ||||
|         return Arrays.stream(fields).filter(f -> !Modifier.isStatic(f.getModifiers())).collect(Collectors.toList()); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,99 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.util; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.lang.tree.TreeUtil; | ||||
| import cn.hutool.core.lang.tree.parser.NodeParser; | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import top.charles7c.continew.starter.extension.crud.annotation.TreeField; | ||||
| import top.charles7c.continew.starter.extension.crud.util.validate.CheckUtils; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 树工具类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class TreeUtils { | ||||
|  | ||||
|     /** 默认字段配置对象(根据前端树结构灵活调整名称) */ | ||||
|     public static final TreeNodeConfig DEFAULT_CONFIG = | ||||
|         TreeNodeConfig.DEFAULT_CONFIG.setNameKey("title").setIdKey("key").setWeightKey("sort"); | ||||
|  | ||||
|     /** | ||||
|      * 树构建 | ||||
|      * | ||||
|      * @param <T> | ||||
|      *            转换的实体 为数据源里的对象类型 | ||||
|      * @param <E> | ||||
|      *            ID类型 | ||||
|      * @param list | ||||
|      *            源数据集合 | ||||
|      * @param nodeParser | ||||
|      *            转换器 | ||||
|      * @return List 树列表 | ||||
|      */ | ||||
|     public static <T, E> List<Tree<E>> build(List<T> list, NodeParser<T, E> nodeParser) { | ||||
|         return build(list, DEFAULT_CONFIG, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 树构建 | ||||
|      * | ||||
|      * @param <T> | ||||
|      *            转换的实体 为数据源里的对象类型 | ||||
|      * @param <E> | ||||
|      *            ID类型 | ||||
|      * @param list | ||||
|      *            源数据集合 | ||||
|      * @param treeNodeConfig | ||||
|      *            配置 | ||||
|      * @param nodeParser | ||||
|      *            转换器 | ||||
|      * @return List 树列表 | ||||
|      */ | ||||
|     public static <T, E> List<Tree<E>> build(List<T> list, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return new ArrayList<>(0); | ||||
|         } | ||||
|         E parentId = (E)ReflectUtil.getFieldValue(list.get(0), treeNodeConfig.getParentIdKey()); | ||||
|         return TreeUtil.build(list, parentId, treeNodeConfig, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 @TreeField 配置生成树结构配置 | ||||
|      * | ||||
|      * @param treeField | ||||
|      *            树结构字段注解 | ||||
|      * @return 树结构配置 | ||||
|      */ | ||||
|     public static TreeNodeConfig genTreeNodeConfig(TreeField treeField) { | ||||
|         CheckUtils.throwIfNull(treeField, "请添加并配置 @TreeField 树结构信息"); | ||||
|         return new TreeNodeConfig().setIdKey(treeField.value()).setParentIdKey(treeField.parentIdKey()) | ||||
|             .setNameKey(treeField.nameKey()).setWeightKey(treeField.weightKey()).setChildrenKey(treeField.childrenKey()) | ||||
|             .setDeep(treeField.deep() < 0 ? null : treeField.deep()); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,252 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.util.validate; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import top.charles7c.continew.starter.core.constant.StringConstants; | ||||
| import top.charles7c.continew.starter.extension.crud.exception.BusinessException; | ||||
|  | ||||
| import java.util.function.BooleanSupplier; | ||||
|  | ||||
| /** | ||||
|  * 业务参数校验工具类(抛出 500 ServiceException) | ||||
|  * | ||||
|  * @see BusinessException | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class CheckUtils extends Validator { | ||||
|  | ||||
|     private static final Class<BusinessException> EXCEPTION_TYPE = BusinessException.class; | ||||
|  | ||||
|     /** | ||||
|      * 如果不存在,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param entityName | ||||
|      *            实体名 | ||||
|      * @param fieldName | ||||
|      *            字段名 | ||||
|      * @param fieldValue | ||||
|      *            字段值 | ||||
|      */ | ||||
|     public static void throwIfNotExists(Object obj, String entityName, String fieldName, Object fieldValue) { | ||||
|         String message = String.format("%s 为 [%s] 的 %s 记录已不存在", fieldName, fieldValue, | ||||
|             StrUtil.replace(entityName, "DO", StringConstants.EMPTY)); | ||||
|         throwIfNull(obj, message, EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNull(Object obj, String template, Object... params) { | ||||
|         throwIfNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotNull(Object obj, String template, Object... params) { | ||||
|         throwIfNotNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果存在,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param entityName | ||||
|      *            实体名 | ||||
|      * @param fieldName | ||||
|      *            字段名 | ||||
|      * @param fieldValue | ||||
|      *            字段值 | ||||
|      */ | ||||
|     public static void throwIfExists(Object obj, String entityName, String fieldName, Object fieldValue) { | ||||
|         String message = String.format("%s 为 [%s] 的 %s 记录已存在", fieldName, fieldValue, entityName); | ||||
|         throwIfNotNull(obj, message, EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfEmpty(Object obj, String template, Object... params) { | ||||
|         throwIfEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotEmpty(Object obj, String template, Object... params) { | ||||
|         throwIfNotEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param str | ||||
|      *            被检测的字符串 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfBlank(CharSequence str, String template, Object... params) { | ||||
|         throwIfBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param str | ||||
|      *            被检测的字符串 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotBlank(CharSequence str, String template, Object... params) { | ||||
|         throwIfNotBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果相同,抛出异常 | ||||
|      * | ||||
|      * @param obj1 | ||||
|      *            要比较的对象1 | ||||
|      * @param obj2 | ||||
|      *            要比较的对象2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfEqual(Object obj1, Object obj2, String template, Object... params) { | ||||
|         throwIfEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不相同,抛出异常 | ||||
|      * | ||||
|      * @param obj1 | ||||
|      *            要比较的对象1 | ||||
|      * @param obj2 | ||||
|      *            要比较的对象2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotEqual(Object obj1, Object obj2, String template, Object... params) { | ||||
|         throwIfNotEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果相同,抛出异常(不区分大小写) | ||||
|      * | ||||
|      * @param str1 | ||||
|      *            要比较的字符串1 | ||||
|      * @param str2 | ||||
|      *            要比较的字符串2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfEqualIgnoreCase(CharSequence str1, CharSequence str2, String template, Object... params) { | ||||
|         throwIfEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不相同,抛出异常(不区分大小写) | ||||
|      * | ||||
|      * @param str1 | ||||
|      *            要比较的字符串1 | ||||
|      * @param str2 | ||||
|      *            要比较的字符串2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotEqualIgnoreCase(CharSequence str1, CharSequence str2, String template, | ||||
|         Object... params) { | ||||
|         throwIfNotEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果条件成立,抛出异常 | ||||
|      * | ||||
|      * @param condition | ||||
|      *            条件 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIf(boolean condition, String template, Object... params) { | ||||
|         throwIf(condition, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果条件成立,抛出异常 | ||||
|      * | ||||
|      * @param conditionSupplier | ||||
|      *            条件 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIf(BooleanSupplier conditionSupplier, String template, Object... params) { | ||||
|         throwIf(conditionSupplier, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,216 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.util.validate; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import top.charles7c.continew.starter.extension.crud.exception.BadRequestException; | ||||
|  | ||||
| import java.util.function.BooleanSupplier; | ||||
|  | ||||
| /** | ||||
|  * 基本参数校验工具类(抛出 400 BadRequestException) | ||||
|  * | ||||
|  * @see BadRequestException | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ValidationUtils extends Validator { | ||||
|  | ||||
|     private static final Class<BadRequestException> EXCEPTION_TYPE = BadRequestException.class; | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNull(Object obj, String template, Object... params) { | ||||
|         throwIfNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotNull(Object obj, String template, Object... params) { | ||||
|         throwIfNotNull(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfEmpty(Object obj, String template, Object... params) { | ||||
|         throwIfEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotEmpty(Object obj, String template, Object... params) { | ||||
|         throwIfNotEmpty(obj, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param str | ||||
|      *            被检测的字符串 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfBlank(CharSequence str, String template, Object... params) { | ||||
|         throwIfBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param str | ||||
|      *            被检测的字符串 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotBlank(CharSequence str, String template, Object... params) { | ||||
|         throwIfNotBlank(str, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果相同,抛出异常 | ||||
|      * | ||||
|      * @param obj1 | ||||
|      *            要比较的对象1 | ||||
|      * @param obj2 | ||||
|      *            要比较的对象2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfEqual(Object obj1, Object obj2, String template, Object... params) { | ||||
|         throwIfEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不相同,抛出异常 | ||||
|      * | ||||
|      * @param obj1 | ||||
|      *            要比较的对象1 | ||||
|      * @param obj2 | ||||
|      *            要比较的对象2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotEqual(Object obj1, Object obj2, String template, Object... params) { | ||||
|         throwIfNotEqual(obj1, obj2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果相同,抛出异常(不区分大小写) | ||||
|      * | ||||
|      * @param str1 | ||||
|      *            要比较的字符串1 | ||||
|      * @param str2 | ||||
|      *            要比较的字符串2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfEqualIgnoreCase(CharSequence str1, CharSequence str2, String template, Object... params) { | ||||
|         throwIfEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不相同,抛出异常(不区分大小写) | ||||
|      * | ||||
|      * @param str1 | ||||
|      *            要比较的字符串1 | ||||
|      * @param str2 | ||||
|      *            要比较的字符串2 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIfNotEqualIgnoreCase(CharSequence str1, CharSequence str2, String template, | ||||
|         Object... params) { | ||||
|         throwIfNotEqualIgnoreCase(str1, str2, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果条件成立,抛出异常 | ||||
|      * | ||||
|      * @param condition | ||||
|      *            条件 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIf(boolean condition, String template, Object... params) { | ||||
|         throwIf(condition, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果条件成立,抛出异常 | ||||
|      * | ||||
|      * @param conditionSupplier | ||||
|      *            条件 | ||||
|      * @param template | ||||
|      *            异常信息模板,被替换的部分用 {} 表示,如果模板为 null,返回 "null" | ||||
|      * @param params | ||||
|      *            参数值 | ||||
|      */ | ||||
|     public static void throwIf(BooleanSupplier conditionSupplier, String template, Object... params) { | ||||
|         throwIf(conditionSupplier, StrUtil.format(template, params), EXCEPTION_TYPE); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,226 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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 top.charles7c.continew.starter.extension.crud.util.validate; | ||||
|  | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import java.util.function.BooleanSupplier; | ||||
|  | ||||
| /** | ||||
|  * 校验器 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.0.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||||
| public class Validator { | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfNull(Object obj, String message, Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(null == obj, message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfNotNull(Object obj, String message, Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(null != obj, message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfEmpty(Object obj, String message, Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(ObjectUtil.isEmpty(obj), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param obj | ||||
|      *            被检测的对象 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfNotEmpty(Object obj, String message, Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(ObjectUtil.isNotEmpty(obj), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果为空,抛出异常 | ||||
|      * | ||||
|      * @param str | ||||
|      *            被检测的字符串 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfBlank(CharSequence str, String message, | ||||
|         Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(StrUtil.isBlank(str), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不为空,抛出异常 | ||||
|      * | ||||
|      * @param str | ||||
|      *            被检测的字符串 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfNotBlank(CharSequence str, String message, | ||||
|         Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(StrUtil.isNotBlank(str), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果相同,抛出异常 | ||||
|      * | ||||
|      * @param obj1 | ||||
|      *            要比较的对象1 | ||||
|      * @param obj2 | ||||
|      *            要比较的对象2 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfEqual(Object obj1, Object obj2, String message, | ||||
|         Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(ObjectUtil.equal(obj1, obj2), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不相同,抛出异常 | ||||
|      * | ||||
|      * @param obj1 | ||||
|      *            要比较的对象1 | ||||
|      * @param obj2 | ||||
|      *            要比较的对象2 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfNotEqual(Object obj1, Object obj2, String message, | ||||
|         Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(ObjectUtil.notEqual(obj1, obj2), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果相同,抛出异常(不区分大小写) | ||||
|      * | ||||
|      * @param str1 | ||||
|      *            要比较的字符串1 | ||||
|      * @param str2 | ||||
|      *            要比较的字符串2 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfEqualIgnoreCase(CharSequence str1, CharSequence str2, String message, | ||||
|         Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(StrUtil.equalsIgnoreCase(str1, str2), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果不相同,抛出异常(不区分大小写) | ||||
|      * | ||||
|      * @param str1 | ||||
|      *            要比较的字符串1 | ||||
|      * @param str2 | ||||
|      *            要比较的字符串2 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIfNotEqualIgnoreCase(CharSequence str1, CharSequence str2, String message, | ||||
|         Class<? extends RuntimeException> exceptionType) { | ||||
|         throwIf(!StrUtil.equalsIgnoreCase(str1, str2), message, exceptionType); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果条件成立,抛出异常 | ||||
|      * | ||||
|      * @param condition | ||||
|      *            条件 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIf(boolean condition, String message, Class<? extends RuntimeException> exceptionType) { | ||||
|         if (condition) { | ||||
|             log.error(message); | ||||
|             throw ReflectUtil.newInstance(exceptionType, message); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果条件成立,抛出异常 | ||||
|      * | ||||
|      * @param conditionSupplier | ||||
|      *            条件 | ||||
|      * @param message | ||||
|      *            错误信息 | ||||
|      * @param exceptionType | ||||
|      *            异常类型 | ||||
|      */ | ||||
|     protected static void throwIf(BooleanSupplier conditionSupplier, String message, | ||||
|         Class<? extends RuntimeException> exceptionType) { | ||||
|         if (null != conditionSupplier && conditionSupplier.getAsBoolean()) { | ||||
|             log.error(message); | ||||
|             throw ReflectUtil.newInstance(exceptionType, message); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1 @@ | ||||
| top.charles7c.continew.starter.extension.crud.autoconfigure.CrudAutoConfiguration | ||||
							
								
								
									
										29
									
								
								continew-starter-extension/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								continew-starter-extension/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| <?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"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <parent> | ||||
|         <groupId>top.charles7c.continew</groupId> | ||||
|         <artifactId>continew-starter</artifactId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
|  | ||||
|     <artifactId>continew-starter-extension</artifactId> | ||||
|     <packaging>pom</packaging> | ||||
|  | ||||
|     <name>${project.artifactId}</name> | ||||
|     <description>ContiNew Starter 扩展模块</description> | ||||
|  | ||||
|     <modules> | ||||
|         <module>continew-starter-extension-crud</module> | ||||
|     </modules> | ||||
|  | ||||
|     <dependencies> | ||||
|         <!-- 核心模块 --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c.continew</groupId> | ||||
|             <artifactId>continew-starter-core</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| </project> | ||||
		Reference in New Issue
	
	Block a user