diff --git a/continew-starter-core/pom.xml b/continew-starter-core/pom.xml index cb5a153a..cd691d26 100644 --- a/continew-starter-core/pom.xml +++ b/continew-starter-core/pom.xml @@ -16,6 +16,12 @@ ContiNew Starter 核心模块 + + + org.springframework.boot + spring-boot-starter-validation + + org.springframework.boot diff --git a/continew-starter-dependencies/pom.xml b/continew-starter-dependencies/pom.xml index 399dcf56..efb72542 100644 --- a/continew-starter-dependencies/pom.xml +++ b/continew-starter-dependencies/pom.xml @@ -186,6 +186,13 @@ + + + top.charles7c.continew + continew-starter-extension-crud + ${revision} + + top.charles7c.continew diff --git a/continew-starter-extension/continew-starter-extension-crud/pom.xml b/continew-starter-extension/continew-starter-extension-crud/pom.xml new file mode 100644 index 00000000..1d0b99a0 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + top.charles7c.continew + continew-starter-extension + ${revision} + + + continew-starter-extension-crud + jar + + ${project.artifactId} + ContiNew Starter 扩展模块 - CRUD(增删改查) + + + + org.springframework.data + spring-data-commons + + + + + top.charles7c.continew + continew-starter-auth-satoken + + + + + top.charles7c.continew + continew-starter-data-mybatis-plus + + + + + top.charles7c.continew + continew-starter-file-excel + + + + + top.charles7c.continew + continew-starter-api-doc + + + \ No newline at end of file diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/CrudRequestMapping.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/CrudRequestMapping.java new file mode 100644 index 00000000..02e70ad0 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/CrudRequestMapping.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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}; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/NoResponseAdvice.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/NoResponseAdvice.java new file mode 100644 index 00000000..3f39c238 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/NoResponseAdvice.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 {} \ No newline at end of file diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/Query.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/Query.java new file mode 100644 index 00000000..4f15be77 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/Query.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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(ELADMIN) + * @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 类型属性 + *

+ * 例如:@Query(blurry = {"username", "email"}) 表示根据用户名和邮箱模糊查询 + *

+ */ + String[] blurry() default {}; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/TreeField.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/TreeField.java new file mode 100644 index 00000000..2f81d6ac --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/annotation/TreeField.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/autoconfigure/CrudAutoConfiguration.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/autoconfigure/CrudAutoConfiguration.java new file mode 100644 index 00000000..f3bacd35 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/autoconfigure/CrudAutoConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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."); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseController.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseController.java new file mode 100644 index 00000000..9d99e9af --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseController.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * 业务接口 + * @param + * 列表信息 + * @param + * 详情信息 + * @param + * 查询条件 + * @param + * 创建或修改信息 + * @author Charles7c + * @since 1.0.0 + */ +@NoArgsConstructor +public abstract class BaseController, L, D, Q, C extends BaseReq> { + + @Autowired + protected S baseService; + + /** + * 分页查询列表 + * + * @param query + * 查询条件 + * @param pageQuery + * 分页查询条件 + * @return 分页信息 + */ + @Operation(summary = "分页查询列表", description = "分页查询列表") + @ResponseBody + @GetMapping + public PageDataResp 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(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 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 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 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())); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseDO.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseDO.java new file mode 100644 index 00000000..2b5cd45f --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseDO.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseDetailResp.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseDetailResp.java new file mode 100644 index 00000000..5f6d7263 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseDetailResp.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseMapper.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseMapper.java new file mode 100644 index 00000000..b451f082 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseMapper.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * 实体类 + * @author Charles7c + * @since 1.0.0 + */ +public interface BaseMapper extends com.baomidou.mybatisplus.core.mapper.BaseMapper { + + /** + * 批量插入记录 + * + * @param entityList + * 实体列表 + * @return 是否成功 + */ + default boolean insertBatch(Collection entityList) { + return Db.saveBatch(entityList); + } + + /** + * 批量更新记录 + * + * @param entityList + * 实体列表 + * @return 是否成功 + */ + default boolean updateBatchById(Collection entityList) { + return Db.updateBatchById(entityList); + } + + /** + * 链式查询 + * + * @return QueryWrapper 的包装类 + */ + default QueryChainWrapper query() { + return ChainWrappers.queryChain(this); + } + + /** + * 链式查询(lambda 式) + * + * @return LambdaQueryWrapper 的包装类 + */ + default LambdaQueryChainWrapper lambdaQuery() { + return ChainWrappers.lambdaQueryChain(this, this.currentEntityClass()); + } + + /** + * 链式查询(lambda 式) + * + * @param entity + * 实体对象 + * @return LambdaQueryWrapper 的包装类 + */ + default LambdaQueryChainWrapper lambdaQuery(T entity) { + return ChainWrappers.lambdaQueryChain(this, entity); + } + + /** + * 链式更改 + * + * @return UpdateWrapper 的包装类 + */ + default UpdateChainWrapper update() { + return ChainWrappers.updateChain(this); + } + + /** + * 链式更改(lambda 式) + * + * @return LambdaUpdateWrapper 的包装类 + */ + default LambdaUpdateChainWrapper lambdaUpdate() { + return ChainWrappers.lambdaUpdateChain(this); + } + + /** + * 获取实体类 Class 对象 + * + * @return 实体类 Class 对象 + */ + default Class currentEntityClass() { + return (Class)ClassUtil.getTypeArgument(this.getClass(), 0); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseReq.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseReq.java new file mode 100644 index 00000000..d59b807a --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseReq.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseResp.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseResp.java new file mode 100644 index 00000000..ed6304d1 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseResp.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseService.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseService.java new file mode 100644 index 00000000..4e45db6f --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseService.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * 列表信息 + * @param + * 详情信息 + * @param + * 查询条件 + * @param + * 创建或修改信息 + * @author Charles7c + * @since 1.0.0 + */ +public interface BaseService { + + /** + * 分页查询列表 + * + * @param query + * 查询条件 + * @param pageQuery + * 分页查询条件 + * @return 分页列表信息 + */ + PageDataResp page(Q query, PageQuery pageQuery); + + /** + * 查询树列表 + * + * @param query + * 查询条件 + * @param sortQuery + * 排序查询条件 + * @param isSimple + * 是否为简单树结构(不包含基本树结构之外的扩展字段) + * @return 树列表信息 + */ + List> tree(Q query, SortQuery sortQuery, boolean isSimple); + + /** + * 查询列表 + * + * @param query + * 查询条件 + * @param sortQuery + * 排序查询条件 + * @return 列表信息 + */ + List 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 ids); + + /** + * 导出 + * + * @param query + * 查询条件 + * @param sortQuery + * 排序查询条件 + * @param response + * 响应对象 + */ + void export(Q query, SortQuery sortQuery, HttpServletResponse response); +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseServiceImpl.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseServiceImpl.java new file mode 100644 index 00000000..69da2259 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/BaseServiceImpl.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * Mapper 接口 + * @param + * 实体类 + * @param + * 列表信息 + * @param + * 详情信息 + * @param + * 查询条件 + * @param + * 创建或修改信息 + * @author Charles7c + * @since 1.0.0 + */ +public abstract class BaseServiceImpl, T extends BaseDO, L, D, Q, C extends BaseReq> + implements BaseService { + + @Autowired + protected M baseMapper; + + private final Class entityClass; + private final Class listClass; + private final Class detailClass; + + protected BaseServiceImpl() { + this.entityClass = (Class)ClassUtil.getTypeArgument(this.getClass(), 1); + this.listClass = (Class)ClassUtil.getTypeArgument(this.getClass(), 2); + this.detailClass = (Class)ClassUtil.getTypeArgument(this.getClass(), 3); + } + + @Override + public PageDataResp page(Q query, PageQuery pageQuery) { + QueryWrapper queryWrapper = QueryHelper.build(query); + IPage page = baseMapper.selectPage(pageQuery.toPage(), queryWrapper); + PageDataResp pageDataResp = PageDataResp.build(page, listClass); + pageDataResp.getList().forEach(this::fill); + return pageDataResp; + } + + @Override + public List> tree(Q query, SortQuery sortQuery, boolean isSimple) { + List 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 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 list(Q query, SortQuery sortQuery) { + List list = this.list(query, sortQuery, listClass); + list.forEach(this::fill); + return list; + } + + /** + * 查询列表 + * + * @param query + * 查询条件 + * @param sortQuery + * 排序查询条件 + * @param targetClass + * 指定类型 + * @return 列表信息 + */ + protected List list(Q query, SortQuery sortQuery, Class targetClass) { + QueryWrapper queryWrapper = QueryHelper.build(query); + // 设置排序 + this.sort(queryWrapper, sortQuery); + List entityList = baseMapper.selectList(queryWrapper); + return BeanUtil.copyToList(entityList, targetClass); + } + + /** + * 设置排序 + * + * @param queryWrapper + * 查询 Wrapper + * @param sortQuery + * 排序查询条件 + */ + protected void sort(QueryWrapper 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 ids) { + baseMapper.deleteBatchIds(ids); + } + + @Override + public void export(Q query, SortQuery sortQuery, HttpServletResponse response) { + List 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))); + } + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/CommonUserService.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/CommonUserService.java new file mode 100644 index 00000000..09fa8eeb --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/CommonUserService.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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); +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/IBaseEnum.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/IBaseEnum.java new file mode 100644 index 00000000..ae38f3d2 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/IBaseEnum.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * value 类型 + * @author Charles7c + * @since 1.0.0 + */ +public interface IBaseEnum extends IEnum { + + /** + * 枚举描述 + * + * @return 枚举描述 + */ + String getDescription(); + + /** + * 颜色 + * + * @return 颜色 + */ + default String getColor() { + return null; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/ValidateGroup.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/ValidateGroup.java new file mode 100644 index 00000000..1b06fa70 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/base/ValidateGroup.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 {} + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/enums/Api.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/enums/Api.java new file mode 100644 index 00000000..69dc8c84 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/enums/Api.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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, +} \ No newline at end of file diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/enums/QueryTypeEnum.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/enums/QueryTypeEnum.java new file mode 100644 index 00000000..e86eea84 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/enums/QueryTypeEnum.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 { + + /** + * 等值查询,例如: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; +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/exception/BadRequestException.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/exception/BadRequestException.java new file mode 100644 index 00000000..32aacde1 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/exception/BadRequestException.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/exception/BusinessException.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/exception/BusinessException.java new file mode 100644 index 00000000..e24ea14b --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/exception/BusinessException.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/handler/CrudRequestMappingHandlerMapping.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/handler/CrudRequestMappingHandlerMapping.java new file mode 100644 index 00000000..114efee2 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/handler/CrudRequestMappingHandlerMapping.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/query/PageQuery.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/query/PageQuery.java new file mode 100644 index 00000000..603f5ed4 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/query/PageQuery.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * 列表数据类型 + * @return MyBatis Plus 分页条件 + */ + public IPage toPage() { + Page 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; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/query/SortQuery.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/query/SortQuery.java new file mode 100644 index 00000000..770309ad --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/query/SortQuery.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 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 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); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/resp/PageDataResp.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/resp/PageDataResp.java new file mode 100644 index 00000000..4d8b9ef0 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/resp/PageDataResp.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * 列表数据类型 + * @author Charles7c + * @since 1.0.0 + */ +@Data +@Schema(description = "分页信息") +public class PageDataResp implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 列表数据 + */ + @Schema(description = "列表数据") + private List list; + + /** + * 总记录数 + */ + @Schema(description = "总记录数", example = "10") + private long total; + + /** + * 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据 + * + * @param page + * MyBatis Plus 分页数据 + * @param targetClass + * 目标类型 Class 对象 + * @param + * 源列表数据类型 + * @param + * 目标列表数据类型 + * @return 分页信息 + */ + public static PageDataResp build(IPage page, Class targetClass) { + if (null == page) { + return empty(); + } + PageDataResp pageDataResp = new PageDataResp<>(); + pageDataResp.setList(BeanUtil.copyToList(page.getRecords(), targetClass)); + pageDataResp.setTotal(page.getTotal()); + return pageDataResp; + } + + /** + * 基于 MyBatis Plus 分页数据构建分页信息 + * + * @param page + * MyBatis Plus 分页数据 + * @param + * 列表数据类型 + * @return 分页信息 + */ + public static PageDataResp build(IPage page) { + if (null == page) { + return empty(); + } + PageDataResp pageDataResp = new PageDataResp<>(); + pageDataResp.setList(page.getRecords()); + pageDataResp.setTotal(page.getTotal()); + return pageDataResp; + } + + /** + * 基于列表数据构建分页信息 + * + * @param page + * 页码 + * @param size + * 每页条数 + * @param list + * 列表数据 + * @param + * 列表数据类型 + * @return 分页信息 + */ + public static PageDataResp build(int page, int size, List list) { + if (CollUtil.isEmpty(list)) { + return empty(); + } + PageDataResp 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 + * 列表数据类型 + * @return 分页信息 + */ + private static PageDataResp empty() { + PageDataResp pageDataResp = new PageDataResp<>(); + pageDataResp.setList(new ArrayList<>(0)); + return pageDataResp; + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/resp/R.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/resp/R.java new file mode 100644 index 00000000..f010ce66 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/model/resp/R.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 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 R ok() { + return new R<>(true, SUCCESS_CODE, "操作成功", null); + } + + public static R ok(T data) { + return new R<>(true, SUCCESS_CODE, "操作成功", data); + } + + public static R ok(String msg) { + return new R<>(true, SUCCESS_CODE, msg, null); + } + + public static R ok(String msg, T data) { + return new R<>(true, SUCCESS_CODE, msg, data); + } + + public static R fail() { + return new R<>(false, FAIL_CODE, "操作失败", null); + } + + public static R fail(String msg) { + return new R<>(false, FAIL_CODE, msg, null); + } + + public static R fail(T data) { + return new R<>(false, FAIL_CODE, "操作失败", data); + } + + public static R fail(String msg, T data) { + return new R<>(false, FAIL_CODE, msg, data); + } + + public static R fail(int code, String msg) { + return new R<>(false, code, msg, null); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/QueryHelper.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/QueryHelper.java new file mode 100644 index 00000000..4347e805 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/QueryHelper.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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(ELADMIN) + * @author Charles7c + * @since 1.0.0 + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class QueryHelper { + + /** + * 根据查询条件构建 MyBatis Plus 查询条件封装对象 + * + * @param query + * 查询条件 + * @param + * 查询条件数据类型 + * @param + * 查询数据类型 + * @return MyBatis Plus 查询条件封装对象 + */ + public static QueryWrapper build(Q query) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + // 没有查询条件,直接返回 + if (null == query) { + return queryWrapper; + } + // 获取查询条件中所有的字段 + List 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 + * 查询条件数据类型 + * @param + * 查询数据类型 + */ + private static void buildQuery(Q query, Field field, QueryWrapper 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 + * 查询数据类型 + */ + private static void parse(Query queryAnnotation, String fieldName, Object fieldValue, + QueryWrapper 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 between = new ArrayList<>((List)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)fieldValue); + } + case NOT_IN -> { + ValidationUtils.throwIfEmpty(fieldValue, "[{}] 不能为空", fieldName); + queryWrapper.notIn(columnName, (List)fieldValue); + } + case IS_NULL -> queryWrapper.isNull(columnName); + case IS_NOT_NULL -> queryWrapper.isNotNull(columnName); + default -> throw new IllegalArgumentException(String.format("暂不支持 [%s] 查询类型", queryType)); + } + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/ReflectUtils.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/ReflectUtils.java new file mode 100644 index 00000000..ba1994b9 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/ReflectUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 { + + /** + * 获得一个类中所有非静态字段名列表,包括其父类中的字段
+ * 如果子类与父类中存在同名字段,则这两个字段同时存在,子类字段在前,父类字段在后。 + * + * @param beanClass + * 类 + * @return 非静态字段名列表 + * @throws SecurityException + * 安全检查异常 + */ + public static List getNonStaticFieldsName(Class beanClass) throws SecurityException { + List nonStaticFields = getNonStaticFields(beanClass); + return nonStaticFields.stream().map(Field::getName).collect(Collectors.toList()); + } + + /** + * 获得一个类中所有非静态字段列表,包括其父类中的字段
+ * 如果子类与父类中存在同名字段,则这两个字段同时存在,子类字段在前,父类字段在后。 + * + * @param beanClass + * 类 + * @return 非静态字段列表 + * @throws SecurityException + * 安全检查异常 + */ + public static List getNonStaticFields(Class beanClass) throws SecurityException { + Field[] fields = ReflectUtil.getFields(beanClass); + return Arrays.stream(fields).filter(f -> !Modifier.isStatic(f.getModifiers())).collect(Collectors.toList()); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/TreeUtils.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/TreeUtils.java new file mode 100644 index 00000000..c70e0d97 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/TreeUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 + * 转换的实体 为数据源里的对象类型 + * @param + * ID类型 + * @param list + * 源数据集合 + * @param nodeParser + * 转换器 + * @return List 树列表 + */ + public static List> build(List list, NodeParser nodeParser) { + return build(list, DEFAULT_CONFIG, nodeParser); + } + + /** + * 树构建 + * + * @param + * 转换的实体 为数据源里的对象类型 + * @param + * ID类型 + * @param list + * 源数据集合 + * @param treeNodeConfig + * 配置 + * @param nodeParser + * 转换器 + * @return List 树列表 + */ + public static List> build(List list, TreeNodeConfig treeNodeConfig, NodeParser 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()); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/CheckUtils.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/CheckUtils.java new file mode 100644 index 00000000..ab986ab7 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/CheckUtils.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 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); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/ValidationUtils.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/ValidationUtils.java new file mode 100644 index 00000000..b0312465 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/ValidationUtils.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 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); + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/Validator.java b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/Validator.java new file mode 100644 index 00000000..99f1280a --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/java/top/charles7c/continew/starter/extension/crud/util/validate/Validator.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *

+ * 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 + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 exceptionType) { + throwIf(null == obj, message, exceptionType); + } + + /** + * 如果不为空,抛出异常 + * + * @param obj + * 被检测的对象 + * @param message + * 错误信息 + * @param exceptionType + * 异常类型 + */ + protected static void throwIfNotNull(Object obj, String message, Class exceptionType) { + throwIf(null != obj, message, exceptionType); + } + + /** + * 如果为空,抛出异常 + * + * @param obj + * 被检测的对象 + * @param message + * 错误信息 + * @param exceptionType + * 异常类型 + */ + protected static void throwIfEmpty(Object obj, String message, Class exceptionType) { + throwIf(ObjectUtil.isEmpty(obj), message, exceptionType); + } + + /** + * 如果不为空,抛出异常 + * + * @param obj + * 被检测的对象 + * @param message + * 错误信息 + * @param exceptionType + * 异常类型 + */ + protected static void throwIfNotEmpty(Object obj, String message, Class exceptionType) { + throwIf(ObjectUtil.isNotEmpty(obj), message, exceptionType); + } + + /** + * 如果为空,抛出异常 + * + * @param str + * 被检测的字符串 + * @param message + * 错误信息 + * @param exceptionType + * 异常类型 + */ + protected static void throwIfBlank(CharSequence str, String message, + Class exceptionType) { + throwIf(StrUtil.isBlank(str), message, exceptionType); + } + + /** + * 如果不为空,抛出异常 + * + * @param str + * 被检测的字符串 + * @param message + * 错误信息 + * @param exceptionType + * 异常类型 + */ + protected static void throwIfNotBlank(CharSequence str, String message, + Class 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 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 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 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 exceptionType) { + throwIf(!StrUtil.equalsIgnoreCase(str1, str2), message, exceptionType); + } + + /** + * 如果条件成立,抛出异常 + * + * @param condition + * 条件 + * @param message + * 错误信息 + * @param exceptionType + * 异常类型 + */ + protected static void throwIf(boolean condition, String message, Class 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 exceptionType) { + if (null != conditionSupplier && conditionSupplier.getAsBoolean()) { + log.error(message); + throw ReflectUtil.newInstance(exceptionType, message); + } + } +} diff --git a/continew-starter-extension/continew-starter-extension-crud/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-extension/continew-starter-extension-crud/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..c0849a43 --- /dev/null +++ b/continew-starter-extension/continew-starter-extension-crud/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +top.charles7c.continew.starter.extension.crud.autoconfigure.CrudAutoConfiguration \ No newline at end of file diff --git a/continew-starter-extension/pom.xml b/continew-starter-extension/pom.xml new file mode 100644 index 00000000..ded90f1b --- /dev/null +++ b/continew-starter-extension/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + top.charles7c.continew + continew-starter + ${revision} + + + continew-starter-extension + pom + + ${project.artifactId} + ContiNew Starter 扩展模块 + + + continew-starter-extension-crud + + + + + + top.charles7c.continew + continew-starter-core + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index c326597f..f3172518 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,7 @@ @ ${java.version} ${java.version} + false UTF-8 UTF-8 @@ -77,6 +78,7 @@ continew-starter-data continew-starter-auth continew-starter-messaging + continew-starter-extension