feat(extension/crud-mf): 支持Mybatis Flex

This commit is contained in:
hellokaton
2024-05-23 18:31:11 +08:00
committed by Charles7c
parent c562e47f4a
commit 774db91d19
46 changed files with 3352 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.annotation;
import top.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};
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.annotation;
import org.springframework.context.annotation.Import;
import top.continew.starter.extension.crud.autoconfigure.CrudRestControllerAutoConfiguration;
import java.lang.annotation.*;
/**
* CRUD REST Controller 启用注解
*
* @author Charles7c
* @since 1.2.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CrudRestControllerAutoConfiguration.class})
public @interface EnableCrudRestController {
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.annotation;
import java.lang.annotation.*;
/**
* 树结构字段
*
* @author Charles7c
* @see cn.hutool.core.lang.tree.TreeNodeConfig
* @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;
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.autoconfigure;
import cn.hutool.core.util.ArrayUtil;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
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.continew.starter.core.util.ExceptionUtils;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.enums.Api;
import java.lang.reflect.AnnotatedElement;
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 = handlerType.getDeclaredAnnotation(CrudRequestMapping.class);
// 过滤 API如果非本类中定义且 API 列表中不包含,则忽略
Api[] apiArr = crudRequestMapping.api();
Api api = ExceptionUtils.exToNull(() -> Api.valueOf(method.getName().toUpperCase()));
if (method.getDeclaringClass() != handlerType && !ArrayUtil.containsAny(apiArr, Api.ALL, api)) {
return null;
}
// 拼接路径(合并了 @RequestMapping 的部分能力)
return this.getMappingForMethodWrapper(method, handlerType, crudRequestMapping);
}
private RequestMappingInfo getMappingForMethodWrapper(@NonNull Method method,
@NonNull Class<?> handlerType,
CrudRequestMapping crudRequestMapping) {
RequestMappingInfo info = this.buildRequestMappingInfo(method);
if (null != info) {
RequestMappingInfo typeInfo = this.buildRequestMappingInfo(handlerType);
if (null != typeInfo) {
info = typeInfo.combine(info);
}
String prefix = crudRequestMapping.value();
if (null != prefix) {
RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration();
options.setPatternParser(PathPatternParser.defaultInstance);
info = RequestMappingInfo.paths(prefix).options(options).build().combine(info);
}
}
return info;
}
private RequestMappingInfo buildRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class<?> clazz
? getCustomTypeCondition(clazz)
: getCustomMethodCondition((Method)element));
return (requestMapping != null ? super.createRequestMappingInfo(requestMapping, condition) : null);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.autoconfigure;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
/**
* CRUD REST Controller 自动配置
*
* @author Charles7c
* @since 1.0.0
*/
@Configuration
public class CrudRestControllerAutoConfiguration extends DelegatingWebMvcConfiguration {
private static final Logger log = LoggerFactory.getLogger(CrudRestControllerAutoConfiguration.class);
/**
* 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.debug("[ContiNew Starter] - Auto Configuration 'Extension-CRUD REST Controller' completed initialization.");
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.constant;
/**
* 数据源容器相关常量Crane4j 数据填充组件使用)
*
* @author Charles7c
* @since 1.2.0
*/
public class ContainerPool {
/**
* 用户昵称
*/
public static final String USER_NICKNAME = "UserNickname";
protected ContainerPool() {
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.text.CharSequenceUtil;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.util.ValidateGroup;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.req.BaseReq;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.web.model.R;
import java.util.List;
/**
* 控制器基类
*
* @param <S> 业务接口
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件
* @param <C> 创建或修改类型
* @author Charles7c
* @since 1.0.0
*/
public abstract class BaseController<S extends BaseService<L, D, Q, C>, L, D, Q, C extends BaseReq> {
@Autowired
protected S baseService;
/**
* 分页查询列表
*
* @param query 查询条件
* @param pageQuery 分页查询条件
* @return 分页信息
*/
@Operation(summary = "分页查询列表", description = "分页查询列表")
@ResponseBody
@GetMapping
public R<PageResp<L>> page(Q query, @Validated PageQuery pageQuery) {
this.checkPermission(Api.LIST);
return R.ok(baseService.page(query, pageQuery));
}
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 树列表信息
*/
@Operation(summary = "查询树列表", description = "查询树列表")
@ResponseBody
@GetMapping("/tree")
public R<List<Tree<Long>>> tree(Q query, SortQuery sortQuery) {
this.checkPermission(Api.LIST);
return R.ok(baseService.tree(query, sortQuery, false));
}
/**
* 查询列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 列表信息
*/
@Operation(summary = "查询列表", description = "查询列表")
@ResponseBody
@GetMapping("/list")
public R<List<L>> list(Q query, SortQuery sortQuery) {
this.checkPermission(Api.LIST);
return R.ok(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 R<D> get(@PathVariable Long id) {
this.checkPermission(Api.LIST);
return R.ok(baseService.get(id));
}
/**
* 新增
*
* @param req 创建信息
* @return 自增 ID
*/
@Operation(summary = "新增数据", description = "新增数据")
@ResponseBody
@PostMapping
public R<Long> add(@Validated(ValidateGroup.Crud.Add.class) @RequestBody C req) {
this.checkPermission(Api.ADD);
return R.ok("新增成功", baseService.add(req));
}
/**
* 修改
*
* @param req 修改信息
* @param id ID
* @return /
*/
@Operation(summary = "修改数据", description = "修改数据")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody
@PutMapping("/{id}")
public R<Void> 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<Void> delete(@PathVariable List<Long> ids) {
this.checkPermission(Api.DELETE);
baseService.delete(ids);
return R.ok("删除成功");
}
/**
* 导出
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param response 响应对象
*/
@Operation(summary = "导出数据", description = "导出数据")
@GetMapping("/export")
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
this.checkPermission(Api.EXPORT);
baseService.export(query, sortQuery, response);
}
/**
* 根据 API 类型进行权限验证
*
* @param api API 类型
*/
protected void checkPermission(Api api) {
CrudRequestMapping crudRequestMapping = this.getClass().getDeclaredAnnotation(CrudRequestMapping.class);
String path = crudRequestMapping.value();
String permissionPrefix = String.join(StringConstants.COLON, CharSequenceUtil
.splitTrim(path, StringConstants.SLASH));
StpUtil.checkPermission("%s:%s".formatted(permissionPrefix, api.name().toLowerCase()));
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.converter;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.data.mybatis.flex.base.IBaseEnum;
/**
* Easy Excel 枚举接口转换器
*
* @see IBaseEnum
* @author Charles7c
* @since 1.2.0
*/
public class ExcelBaseEnumConverter implements Converter<IBaseEnum<Integer>> {
@Override
public Class<IBaseEnum> supportJavaTypeKey() {
return IBaseEnum.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 转换为 Java 数据(读取 Excel
*/
@Override
public IBaseEnum convertToJavaData(ReadCellData<?> cellData,
ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return this.getEnum(IBaseEnum.class, Convert.toStr(cellData.getData()));
}
/**
* 转换为 Excel 数据(写入 Excel
*/
@Override
public WriteCellData<String> convertToExcelData(IBaseEnum<Integer> value,
ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
if (null == value) {
return new WriteCellData<>(StringConstants.EMPTY);
}
return new WriteCellData<>(value.getDescription());
}
/**
* 通过 value 获取枚举对象,获取不到时为 {@code null}
*
* @param enumType 枚举类型
* @param description 描述
* @return 对应枚举 ,获取不到时为 {@code null}
*/
private IBaseEnum<Integer> getEnum(Class<?> enumType, String description) {
Object[] enumConstants = enumType.getEnumConstants();
for (Object enumConstant : enumConstants) {
if (ClassUtil.isAssignable(IBaseEnum.class, enumType)) {
IBaseEnum<Integer> baseEnum = (IBaseEnum<Integer>)enumConstant;
if (baseEnum.getDescription().equals(description)) {
return baseEnum;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.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,
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.entity;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 实体类基类
*
* <p>
* 通用字段:创建人、创建时间
* </p>
*
* @author Charles7c
* @since 2.0.1
*/
public class BaseCreateDO extends BaseIdDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 创建人
*/
private Long createUser;
/**
* 创建时间
*/
private LocalDateTime createTime;
public Long getCreateUser() {
return createUser;
}
public void setCreateUser(Long createUser) {
this.createUser = createUser;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.entity;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 实体类基类
*
* @author Charles7c
* @since 1.0.0
*/
public class BaseDO extends BaseIdDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 创建人
*/
private Long createUser;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 修改人
*/
private Long updateUser;
/**
* 修改时间
*/
private LocalDateTime updateTime;
public Long getCreateUser() {
return createUser;
}
public void setCreateUser(Long createUser) {
this.createUser = createUser;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public Long getUpdateUser() {
return updateUser;
}
public void setUpdateUser(Long updateUser) {
this.updateUser = updateUser;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.entity;
import com.mybatisflex.annotation.Id;
import java.io.Serial;
import java.io.Serializable;
/**
* 实体类基类
*
* <p>
* 通用字段ID 主键
* </p>
*
* @author Charles7c
* @since 2.0.1
*/
public class BaseIdDO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Id
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.entity;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 实体类基类
*
* <p>
* 通用字段:创建人、创建时间
* </p>
*
* @author Charles7c
* @since 2.0.1
*/
public class BaseUpdateDO extends BaseIdDO {
@Serial
private static final long serialVersionUID = 1L;
/**
* 修改人
*/
private Long updateUser;
/**
* 修改时间
*/
private LocalDateTime updateTime;
public Long getUpdateUser() {
return updateUser;
}
public void setUpdateUser(Long updateUser) {
this.updateUser = updateUser;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.query;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Range;
import org.springdoc.core.annotations.ParameterObject;
import java.io.Serial;
/**
* 分页查询条件
*
* @author Charles7c
* @since 1.0.0
*/
@ParameterObject
@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;
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.query;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.text.CharSequenceUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.data.domain.Sort;
import top.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
*/
@Schema(description = "排序查询条件")
public class SortQuery implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 排序条件
*/
@Schema(description = "排序条件", example = "createTime,desc")
private String[] sort;
/**
* 解析排序条件为 Spring 分页排序实体
*
* @return Spring 分页排序实体
*/
public Sort getSort() {
if (ArrayUtil.isEmpty(sort)) {
return Sort.unsorted();
}
List<Sort.Order> orders = new ArrayList<>(sort.length);
if (CharSequenceUtil.contains(sort[0], StringConstants.COMMA)) {
// e.g "sort=createTime,desc&sort=name,asc"
for (String s : sort) {
List<String> sortList = CharSequenceUtil.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);
}
public void setSort(String[] sort) {
this.sort = sort;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.req;
import java.io.Serial;
import java.io.Serializable;
/**
* 请求基类
*
* @author Charles7c
* @since 1.0.0
*/
public class BaseReq implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.resp;
import cn.crane4j.annotation.Assemble;
import cn.crane4j.annotation.Mapping;
import cn.crane4j.annotation.condition.ConditionOnPropertyNotNull;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import top.continew.starter.extension.crud.constant.ContainerPool;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 详情响应基类
*
* @author Charles7c
* @since 1.0.0
*/
public class BaseDetailResp extends BaseResp {
@Serial
private static final long serialVersionUID = 1L;
/**
* 修改人
*/
@JsonIgnore
@ConditionOnPropertyNotNull
@Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "updateUserString"))
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;
public Long getUpdateUser() {
return updateUser;
}
public void setUpdateUser(Long updateUser) {
this.updateUser = updateUser;
}
public String getUpdateUserString() {
return updateUserString;
}
public void setUpdateUserString(String updateUserString) {
this.updateUserString = updateUserString;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.resp;
import cn.crane4j.annotation.Assemble;
import cn.crane4j.annotation.Mapping;
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 top.continew.starter.extension.crud.constant.ContainerPool;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 响应基类
*
* @author Charles7c
* @since 1.0.0
*/
public class BaseResp implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Schema(description = "ID", example = "1")
@ExcelProperty(value = "ID", order = 1)
private Long id;
/**
* 创建人
*/
@JsonIgnore
@Assemble(container = ContainerPool.USER_NICKNAME, props = @Mapping(ref = "createUserString"))
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;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCreateUser() {
return createUser;
}
public void setCreateUser(Long createUser) {
this.createUser = createUser;
}
public String getCreateUserString() {
return createUserString;
}
public void setCreateUserString(String createUserString) {
this.createUserString = createUserString;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public Boolean getDisabled() {
return disabled;
}
public void setDisabled(Boolean disabled) {
this.disabled = disabled;
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.model.resp;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.mybatisflex.core.paginate.Page;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 分页信息
*
* @param <L> 列表数据类型
* @author Charles7c
* @since 1.0.0
*/
@Schema(description = "分页信息")
public class PageResp<L> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 列表数据
*/
@Schema(description = "列表数据")
private List<L> list;
/**
* 总记录数
*/
@Schema(description = "总记录数", example = "10")
private long total;
/**
* 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据
*
* @param page MyBatis Plus 分页数据
* @param targetClass 目标类型 Class 对象
* @param <T> 源列表数据类型
* @param <L> 目标列表数据类型
* @return 分页信息
*/
public static <T, L> PageResp<L> build(Page<T> page, Class<L> targetClass) {
if (null == page) {
return empty();
}
PageResp<L> pageResp = new PageResp<>();
pageResp.setList(BeanUtil.copyToList(page.getRecords(), targetClass));
pageResp.setTotal(page.getTotalRow());
return pageResp;
}
/**
* 基于 MyBatis Plus 分页数据构建分页信息
*
* @param page MyBatis Plus 分页数据
* @param <L> 列表数据类型
* @return 分页信息
*/
public static <L> PageResp<L> build(Page<L> page) {
if (null == page) {
return empty();
}
PageResp<L> pageResp = new PageResp<>();
pageResp.setList(page.getRecords());
pageResp.setTotal(page.getTotalRow());
return pageResp;
}
/**
* 基于列表数据构建分页信息
*
* @param page 页码
* @param size 每页条数
* @param list 列表数据
* @param <L> 列表数据类型
* @return 分页信息
*/
public static <L> PageResp<L> build(int page, int size, List<L> list) {
if (CollUtil.isEmpty(list)) {
return empty();
}
PageResp<L> pageResp = new PageResp<>();
pageResp.setTotal(list.size());
// 对列表数据进行分页
int fromIndex = (page - 1) * size;
int toIndex = page * size + fromIndex;
if (fromIndex > list.size()) {
pageResp.setList(new ArrayList<>(0));
} else if (toIndex >= list.size()) {
pageResp.setList(list.subList(fromIndex, list.size()));
} else {
pageResp.setList(list.subList(fromIndex, toIndex));
}
return pageResp;
}
/**
* 空分页信息
*
* @param <L> 列表数据类型
* @return 分页信息
*/
private static <L> PageResp<L> empty() {
PageResp<L> pageResp = new PageResp<>();
pageResp.setList(Collections.emptyList());
return pageResp;
}
public List<L> getList() {
return list;
}
public void setList(List<L> list) {
this.list = list;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.service;
import cn.hutool.core.lang.tree.Tree;
import jakarta.servlet.http.HttpServletResponse;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import java.util.List;
/**
* 业务接口基类
*
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件
* @param <C> 创建或修改类型
* @author Charles7c
* @since 1.0.0
*/
public interface BaseService<L, D, Q, C> {
/**
* 分页查询列表
*
* @param query 查询条件
* @param pageQuery 分页查询条件
* @return 分页列表信息
*/
PageResp<L> page(Q query, PageQuery pageQuery);
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段)
* @return 树列表信息
*/
List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple);
/**
* 查询列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 列表信息
*/
List<L> list(Q query, SortQuery sortQuery);
/**
* 查看详情
*
* @param id ID
* @return 详情信息
*/
D get(Long id);
/**
* 新增
*
* @param req 创建信息
* @return 自增 ID
*/
Long add(C req);
/**
* 修改
*
* @param req 修改信息
* @param id ID
*/
void update(C req, Long id);
/**
* 删除
*
* @param ids ID 列表
*/
void delete(List<Long> ids);
/**
* 导出
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param response 响应对象
*/
void export(Q query, SortQuery sortQuery, HttpServletResponse response);
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.service;
import cn.crane4j.annotation.ContainerMethod;
import cn.crane4j.annotation.MappingType;
import top.continew.starter.extension.crud.constant.ContainerPool;
/**
* 公共用户业务接口
*
* @author Charles7c
* @since 1.0.0
*/
public interface CommonUserService {
/**
* 根据 ID 查询昵称
*
* @param id ID
* @return 昵称
*/
@ContainerMethod(namespace = ContainerPool.USER_NICKNAME, type = MappingType.ORDER_OF_KEYS)
String getNicknameById(Long id);
}

View File

@@ -0,0 +1,319 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.service.impl;
import cn.crane4j.core.support.OperateTemplate;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.ReflectUtils;
import top.continew.starter.core.util.validate.ValidationUtils;
import top.continew.starter.data.mybatis.flex.base.BaseMapper;
import top.continew.starter.data.mybatis.flex.query.QueryWrapperHelper;
import top.continew.starter.data.mybatis.flex.service.impl.ServiceImpl;
import top.continew.starter.extension.crud.annotation.TreeField;
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.crud.model.query.PageQuery;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseService;
import top.continew.starter.extension.crud.util.TreeUtils;
import top.continew.starter.file.excel.util.ExcelUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 业务实现基类
*
* @param <M> Mapper 接口
* @param <T> 实体类型
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件
* @param <C> 创建或修改类型
* @author Charles7c
* @since 1.0.0
*/
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl<M, T> implements BaseService<L, D, Q, C> {
protected final Class<L> listClass = this.currentListClass();
protected final Class<D> detailClass = this.currentDetailClass();
protected final Class<Q> queryClass = this.currentQueryClass();
private final List<Field> queryFields = ReflectUtils.getNonStaticFields(this.queryClass);
@Override
public PageResp<L> page(Q query, PageQuery pageQuery) {
QueryWrapper queryWrapper = this.buildQueryWrapper(query);
Page<T> page = mapper.paginate(pageQuery.getPage(), pageQuery.getSize(), queryWrapper);
PageResp<L> pageResp = PageResp.build(page, listClass);
pageResp.getList().forEach(this::fill);
return pageResp;
}
@Override
public List<Tree<Long>> tree(Q query, SortQuery sortQuery, boolean isSimple) {
List<L> list = this.list(query, sortQuery);
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
// 如果构建简单树结构,则不包含基本树结构之外的扩展字段
TreeNodeConfig treeNodeConfig = TreeUtils.DEFAULT_CONFIG;
TreeField treeField = listClass.getDeclaredAnnotation(TreeField.class);
if (!isSimple) {
// 根据 @TreeField 配置生成树结构配置
treeNodeConfig = TreeUtils.genTreeNodeConfig(treeField);
}
// 构建树
return TreeUtils.build(list, treeNodeConfig, (node, tree) -> {
// 转换器
tree.setId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.value())));
tree.setParentId(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.parentIdKey())));
tree.setName(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.nameKey())));
tree.setWeight(ReflectUtil.invoke(node, CharSequenceUtil.genGetter(treeField.weightKey())));
if (!isSimple) {
List<Field> fieldList = ReflectUtils.getNonStaticFields(listClass);
fieldList.removeIf(f -> CharSequenceUtil.equalsAnyIgnoreCase(f.getName(), treeField.value(), treeField
.parentIdKey(), treeField.nameKey(), treeField.weightKey(), treeField.childrenKey()));
fieldList.forEach(f -> tree.putExtra(f.getName(), ReflectUtil.invoke(node, CharSequenceUtil.genGetter(f
.getName()))));
}
});
}
@Override
public List<L> list(Q query, SortQuery sortQuery) {
List<L> list = this.list(query, sortQuery, listClass);
list.forEach(this::fill);
return list;
}
@Override
public D get(Long id) {
T entity = super.getById(id);
D detail = BeanUtil.toBean(entity, detailClass);
this.fill(detail);
return detail;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long add(C req) {
this.beforeAdd(req);
T entity = BeanUtil.copyProperties(req, this.entityClass);
mapper.insert(entity);
this.afterAdd(req, entity);
return entity.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(C req, Long id) {
this.beforeUpdate(req, id);
T entity = this.getById(id);
BeanUtil.copyProperties(req, entity, CopyOptions.create().ignoreNullValue());
mapper.update(entity);
this.afterUpdate(req, entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(List<Long> ids) {
this.beforeDelete(ids);
mapper.deleteBatchByIds(ids);
this.afterDelete(ids);
}
@Override
public void export(Q query, SortQuery sortQuery, HttpServletResponse response) {
List<D> list = this.list(query, sortQuery, detailClass);
list.forEach(this::fill);
ExcelUtils.export(list, "导出数据", detailClass, response);
}
/**
* 查询列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param targetClass 指定类型
* @return 列表信息
*/
protected <E> List<E> list(Q query, SortQuery sortQuery, Class<E> targetClass) {
QueryWrapper queryWrapper = this.buildQueryWrapper(query);
// 设置排序
this.sort(queryWrapper, sortQuery);
List<T> entityList = mapper.selectListByQuery(queryWrapper);
if (this.entityClass == targetClass) {
return (List<E>)entityList;
}
return BeanUtil.copyToList(entityList, targetClass);
}
/**
* 设置排序
*
* @param queryWrapper 查询条件封装对象
* @param sortQuery 排序查询条件
*/
protected void sort(QueryWrapper queryWrapper, SortQuery sortQuery) {
Sort sort = Opt.ofNullable(sortQuery).orElseGet(SortQuery::new).getSort();
List<Field> entityFields = ReflectUtils.getNonStaticFields(this.entityClass);
for (Sort.Order order : sort) {
if (null == order) {
continue;
}
String property = order.getProperty();
String checkProperty;
// 携带表别名则获取 . 后面的字段名
if (property.contains(StringConstants.DOT)) {
checkProperty = CollUtil.getLast(CharSequenceUtil.split(property, StringConstants.DOT));
} else {
checkProperty = property;
}
Optional<Field> optional = entityFields.stream()
.filter(field -> checkProperty.equals(field.getName()))
.findFirst();
ValidationUtils.throwIf(optional.isEmpty(), "无效的排序字段 [{}]", property);
queryWrapper.orderBy(CharSequenceUtil.toUnderlineCase(property), order.isAscending());
}
}
/**
* 填充数据
*
* @param obj 待填充信息
*/
protected void fill(Object obj) {
if (null == obj) {
return;
}
OperateTemplate operateTemplate = SpringUtil.getBean(OperateTemplate.class);
operateTemplate.execute(obj);
}
/**
* 构建 QueryWrapper
*
* @param query 查询条件
* @return QueryWrapper
*/
protected QueryWrapper buildQueryWrapper(Q query) {
QueryWrapper queryWrapper = QueryWrapper.create();
// 解析并拼接查询条件
return QueryWrapperHelper.build(query, queryFields, queryWrapper);
}
/**
* 新增前置处理
*
* @param req 创建信息
*/
protected void beforeAdd(C req) {
/* 新增前置处理 */
}
/**
* 修改前置处理
*
* @param req 修改信息
* @param id ID
*/
protected void beforeUpdate(C req, Long id) {
/* 修改前置处理 */
}
/**
* 删除前置处理
*
* @param ids ID 列表
*/
protected void beforeDelete(List<Long> ids) {
/* 删除前置处理 */
}
/**
* 新增后置处理
*
* @param req 创建信息
* @param entity 实体信息
*/
protected void afterAdd(C req, T entity) {
/* 新增后置处理 */
}
/**
* 修改后置处理
*
* @param req 修改信息
* @param entity 实体信息
*/
protected void afterUpdate(C req, T entity) {
/* 修改后置处理 */
}
/**
* 删除后置处理
*
* @param ids ID 列表
*/
protected void afterDelete(List<Long> ids) {
/* 删除后置处理 */
}
/**
* 获取当前列表信息类型
*
* @return 当前列表信息类型
*/
protected Class<L> currentListClass() {
return (Class<L>)this.typeArguments[2];
}
/**
* 获取当前详情信息类型
*
* @return 当前详情信息类型
*/
protected Class<D> currentDetailClass() {
return (Class<D>)this.typeArguments[3];
}
/**
* 获取当前查询条件类型
*
* @return 当前查询条件类型
*/
protected Class<Q> currentQueryClass() {
return (Class<Q>)this.typeArguments[4];
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.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 top.continew.starter.core.util.validate.CheckUtils;
import top.continew.starter.extension.crud.annotation.TreeField;
import java.util.ArrayList;
import java.util.List;
/**
* 树工具类
*
* @author Charles7c
* @since 1.0.0
*/
public class TreeUtils {
/**
* 默认字段配置对象(根据前端树结构灵活调整名称)
*/
public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("title")
.setIdKey("key")
.setWeightKey("sort");
private TreeUtils() {
}
/**
* 树构建
*
* @param <T> 转换的实体 为数据源里的对象类型
* @param <E> ID类型
* @param list 源数据集合
* @param nodeParser 转换器
* @return List 树列表
*/
public static <T, E> List<Tree<E>> build(List<T> list, NodeParser<T, E> nodeParser) {
return build(list, DEFAULT_CONFIG, nodeParser);
}
/**
* 树构建
*
* @param <T> 转换的实体 为数据源里的对象类型
* @param <E> ID类型
* @param list 源数据集合
* @param treeNodeConfig 配置
* @param nodeParser 转换器
* @return List 树列表
*/
public static <T, E> List<Tree<E>> build(List<T> list, TreeNodeConfig treeNodeConfig, NodeParser<T, E> nodeParser) {
if (CollUtil.isEmpty(list)) {
return new ArrayList<>(0);
}
E parentId = (E)ReflectUtil.getFieldValue(list.get(0), treeNodeConfig.getParentIdKey());
return TreeUtil.build(list, parentId, treeNodeConfig, nodeParser);
}
/**
* 根据 @TreeField 配置生成树结构配置
*
* @param treeField 树结构字段注解
* @return 树结构配置
*/
public static TreeNodeConfig genTreeNodeConfig(TreeField treeField) {
CheckUtils.throwIfNull(treeField, "请添加并配置 @TreeField 树结构信息");
return new TreeNodeConfig().setIdKey(treeField.value())
.setParentIdKey(treeField.parentIdKey())
.setNameKey(treeField.nameKey())
.setWeightKey(treeField.weightKey())
.setChildrenKey(treeField.childrenKey())
.setDeep(treeField.deep() < 0 ? null : treeField.deep());
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.extension.crud.util;
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 {
}
}
}