From 599d13b1ac54968e4d26a3e8e811bbf6f67987aa Mon Sep 17 00:00:00 2001 From: youkehai <717407966@qq.com> Date: Tue, 10 Oct 2023 11:36:24 +0800 Subject: [PATCH 01/81] =?UTF-8?q?feat:=20=E9=9A=8F=E4=BE=BF=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/iocoder/yudao/framework/common/package-info.java | 1 + 1 file changed, 1 insertion(+) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java index f3f2574e5..09c43f748 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java @@ -1,6 +1,7 @@ /** * 基础的通用类,和框架无关 * + * sdadsdsd * 例如说,CommonResult 为通用返回 */ package cn.iocoder.yudao.framework.common; From 98aae5ed1a176283f937834b9074eb1f755a5b0f Mon Sep 17 00:00:00 2001 From: dongshanshan Date: Tue, 31 Oct 2023 16:51:56 +0800 Subject: [PATCH 02/81] =?UTF-8?q?feat:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/core/mapper/BaseMapperX.java | 7 ++ .../favorite/ProductFavoriteController.java | 104 ++++++++++++++++++ .../favorite/vo/ProductFavoriteBaseVO.java | 21 ++++ .../vo/ProductFavoriteBatchReqVO.java | 21 ++++ .../favorite/vo/ProductFavoritePageReqVO.java | 35 ++++++ .../favorite/vo/ProductFavoriteReqVO.java | 17 +++ .../favorite/vo/ProductFavoriteRespVO.java | 22 ++++ .../favorite/ProductFavoriteConvert.java | 19 ++++ .../favorite/ProductFavoriteDetailDO.java | 16 +++ .../mysql/favorite/ProductFavoriteMapper.java | 36 ++++++ .../favorite/ProductFavoriteService.java | 10 +- .../favorite/ProductFavoriteServiceImpl.java | 8 +- 12 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteReqVO.java create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java create mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index d70c21626..2654bec52 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.extension.toolkit.Db; import com.github.yulichang.base.MPJBaseMapper; +import com.github.yulichang.interfaces.MPJBaseJoin; import org.apache.ibatis.annotations.Param; import java.util.Collection; @@ -132,4 +133,10 @@ public interface BaseMapperX extends MPJBaseMapper { Db.saveOrUpdateBatch(collection); } + default PageResult selectJoinPage(PageParam pageParam, @Param("resultTypeClass_Eg1sG") Class var2, @Param("ew") MPJBaseJoin queryWrapper) { + IPage mpPage = MyBatisUtils.buildPage(pageParam); + selectJoinPage(mpPage, var2, queryWrapper); + // 转换返回 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + } } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java new file mode 100644 index 000000000..e59c10827 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.product.controller.admin.favorite; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteBatchReqVO; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteReqVO; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteRespVO; +import cn.iocoder.yudao.module.product.convert.favorite.ProductFavoriteConvert; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; +import cn.iocoder.yudao.module.product.service.favorite.ProductFavoriteService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 商品收藏") +@RestController +@RequestMapping("/product/favorite") +@Validated +public class ProductFavoriteController { + + @Resource + private ProductFavoriteService productFavoriteService; + + @PostMapping("/create") + @Operation(summary = "添加单个商品收藏") + @PreAuthorize("@ss.hasPermission('product:favorite:create')") + public CommonResult createFavorite(@Valid @RequestBody ProductFavoriteReqVO reqVO) { + return success(productFavoriteService.createFavorite(reqVO.getUserId(), reqVO.getSpuId())); + } + + @PostMapping("/create-list") + @Operation(summary = "添加多个商品收藏") + @PreAuthorize("@ss.hasPermission('product:favorite:create')") + public CommonResult createFavoriteList(@Valid @RequestBody ProductFavoriteBatchReqVO reqVO) { + // todo @jason:待实现;如果有已经收藏的,不用报错,忽略即可; + return success(Boolean.TRUE); + } + + @DeleteMapping("/delete") + @Operation(summary = "取消单个商品收藏") + @PreAuthorize("@ss.hasPermission('product:favorite:delete')") + public CommonResult deleteFavorite(@Valid @RequestBody ProductFavoriteReqVO reqVO) { + productFavoriteService.deleteFavorite(reqVO.getUserId(), reqVO.getSpuId()); + return success(Boolean.TRUE); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "取消单个商品收藏") + @PreAuthorize("@ss.hasPermission('product:favorite:delete')") + public CommonResult deleteFavoriteList(@Valid @RequestBody ProductFavoriteBatchReqVO reqVO) { + // todo @jason:待实现 +// productFavoriteService.deleteFavorite(getLoginUserId(), reqVO.getSpuId()); + return success(Boolean.TRUE); + } + + @GetMapping("/page") + @Operation(summary = "获得商品收藏分页") + @PreAuthorize("@ss.hasPermission('product:favorite:query')") + public CommonResult> getFavoritePage(@Valid ProductFavoritePageReqVO pageVO) { + PageResult favoritePage = productFavoriteService.getFavoritePageByFilter(pageVO); + if (CollUtil.isEmpty(favoritePage.getList())) { + return success(PageResult.empty()); + } + + // 得到商品 spu 信息 + List favorites = ProductFavoriteConvert.INSTANCE.convertList2admin(favoritePage.getList()); + + // 转换 VO 结果 + PageResult pageResult = new PageResult<>(favoritePage.getTotal()); + pageResult.setList(favorites); + + return success(pageResult); + } + + @PostMapping(value = "/exits") + @Operation(summary = "检查是否收藏过商品") + @PreAuthenticated + public CommonResult isFavoriteExists(@Valid @RequestBody ProductFavoriteReqVO reqVO) { + ProductFavoriteDO favorite = productFavoriteService.getFavorite(reqVO.getUserId(), reqVO.getSpuId()); + return success(favorite != null); + } + + @GetMapping(value = "/get-count") + @Operation(summary = "获得商品收藏数量") + @Parameter(name = "userId", description = "用户编号", required = true) + @PreAuthenticated + public CommonResult getFavoriteCount(@RequestParam("userId") Long userId) { + return success(productFavoriteService.getFavoriteCount(userId)); + } + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java new file mode 100644 index 000000000..72b2613d5 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.product.controller.admin.favorite.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.constraints.NotNull; + +/** + * 商品收藏 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class ProductFavoriteBaseVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5036") + @NotNull(message = "用户编号不能为空") + private Long userId; + + + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java new file mode 100644 index 000000000..d779ff3a8 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.product.controller.admin.favorite.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import javax.validation.constraints.NotNull; +import java.util.List; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +@Schema(description = "管理后台 - 商品收藏的批量 Request VO") +@Data +@ToString(callSuper = true) +public class ProductFavoriteBatchReqVO extends ProductFavoriteBaseVO{ + + @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502") + @NotNull(message = "商品 SPU 编号数组不能为空") + private List spuIds; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java new file mode 100644 index 000000000..37f8cecc3 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.product.controller.admin.favorite.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 商品收藏分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ProductFavoritePageReqVO extends PageParam { + + @Schema(description = "用户编号", example = "5036") + private Long userId; + + @Schema(description = "商品 SPU 编号", example = "32734") + private Long spuId; + + @Schema(description = "收藏时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + @Schema(description = "商品名称", example = "5036") + private String name; + + @Schema(description = "关键字", example = "5036") + private String keyword; +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteReqVO.java new file mode 100644 index 000000000..3c2222643 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.product.controller.admin.favorite.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商品收藏的单个 Response VO") +@Data +@ToString(callSuper = true) +public class ProductFavoriteReqVO extends ProductFavoriteBaseVO { + + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32734") + @NotNull(message = "商品 SPU 编号不能为空") + private Long spuId; +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java new file mode 100644 index 000000000..255fc631b --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.product.controller.admin.favorite.vo; + +import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +@Schema(description = "管理后台 - 商品收藏 Response VO") +@Data +@ToString(callSuper = true) +public class ProductFavoriteRespVO extends ProductSpuRespVO { + + @Schema(description = "userId", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") + private Long userId; + + @Schema(description = "spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") + private Long spuId; + + @Schema(description = "收藏状态", example = "1") + private Integer favoriteStatus; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java index b15afacb2..22ffb8577 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java @@ -1,7 +1,10 @@ package cn.iocoder.yudao.module.product.convert.favorite; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteRespVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoriteRespVO; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -34,4 +37,20 @@ public interface ProductFavoriteConvert { return resultList; } + @Mapping(target = "id", source = "favorite.id") + @Mapping(target = "userId", source = "favorite.userId") + @Mapping(target = "spuId", source = "favorite.spuId") + @Mapping(target = "createTime", source = "favorite.createTime") + @Mapping(target = "favoriteStatus", constant = "1") + ProductFavoriteRespVO convert2admin(ProductSpuDO spu, ProductFavoriteDO favorite); + + default List convertList2admin(List favorites) { + List resultList = new ArrayList<>(favorites.size()); + for (ProductFavoriteDetailDO favorite : favorites) { + resultList.add(convert2admin(favorite.getSpuDO(), favorite)); + } + return resultList; + } + + PageResult convertPage(PageResult page); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java new file mode 100644 index 000000000..ba4d5b557 --- /dev/null +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.product.dal.dataobject.favorite; + +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; +import lombok.Data; + +/** + * 商品收藏 DO + * + * @author 芋道源码 + */ +@Data +public class ProductFavoriteDetailDO extends ProductFavoriteDO { + + ProductSpuDO spuDO; + +} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java index 54d9d2dd6..08a5c3063 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java @@ -2,9 +2,15 @@ package cn.iocoder.yudao.module.product.dal.mysql.favorite; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.annotations.Mapper; @Mapper @@ -21,6 +27,36 @@ public interface ProductFavoriteMapper extends BaseMapperX { .orderByDesc(ProductFavoriteDO::getId)); } + default PageResult selectPageByUserAndFields(ProductFavoritePageReqVO reqVO) { + MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + .selectAll(ProductFavoriteDO.class) + .eq(ProductFavoriteDO::getUserId, reqVO.getUserId()) + .selectAssociation(ProductSpuDO.class, ProductFavoriteDetailDO::getSpuDO); + if(StringUtils.isNotEmpty(reqVO.getName())){ + wrapper.likeRight(ProductSpuDO::getName, reqVO.getName()); + } + if(StringUtils.isNotEmpty(reqVO.getName()) && StringUtils.isNotEmpty(reqVO.getKeyword())){ + wrapper.or(); + } + if(StringUtils.isNotEmpty(reqVO.getKeyword())){ + wrapper.likeRight(ProductSpuDO::getKeyword, reqVO.getKeyword()); + } + + if(reqVO.getCreateTime() != null){ + if (reqVO.getCreateTime()[0] != null && reqVO.getCreateTime()[1] != null) { + wrapper.between(ProductFavoriteDO::getCreateTime, reqVO.getCreateTime()[0], reqVO.getCreateTime()[1]); + } + if (reqVO.getCreateTime()[0] != null) { + wrapper.ge(ProductFavoriteDO::getCreateTime, reqVO.getCreateTime()[0]); + } + if (reqVO.getCreateTime()[1] != null) { + wrapper.le(ProductFavoriteDO::getCreateTime, reqVO.getCreateTime()[1]); + } + } + + return selectJoinPage(reqVO, ProductFavoriteDetailDO.class, wrapper); + } + default Long selectCountByUserId(Long userId) { return selectCount(ProductFavoriteDO::getUserId, userId); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java index 00aeddb8a..87c9854a9 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.product.service.favorite; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; import javax.validation.Valid; @@ -37,6 +39,13 @@ public interface ProductFavoriteService { */ PageResult getFavoritePage(Long userId, @Valid AppFavoritePageReqVO reqVO); + /** + * 分页查询用户收藏列表 + * + * @param reqVO 请求 vo + */ + PageResult getFavoritePageByFilter(@Valid ProductFavoritePageReqVO reqVO); + /** * 获取收藏过商品 * @@ -52,5 +61,4 @@ public interface ProductFavoriteService { * @return 数量 */ Long getFavoriteCount(Long userId); - } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java index 983cbf83c..0f8d30ec0 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java @@ -1,9 +1,11 @@ package cn.iocoder.yudao.module.product.service.favorite; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; import cn.iocoder.yudao.module.product.convert.favorite.ProductFavoriteConvert; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; +import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; import cn.iocoder.yudao.module.product.dal.mysql.favorite.ProductFavoriteMapper; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -33,7 +35,6 @@ public class ProductFavoriteServiceImpl implements ProductFavoriteService { if (favorite != null) { throw exception(FAVORITE_EXISTS); } - ProductFavoriteDO entity = ProductFavoriteConvert.INSTANCE.convert(userId, spuId); productFavoriteMapper.insert(entity); return entity.getId(); @@ -54,6 +55,11 @@ public class ProductFavoriteServiceImpl implements ProductFavoriteService { return productFavoriteMapper.selectPageByUserAndType(userId, reqVO); } + @Override + public PageResult getFavoritePageByFilter(@Valid ProductFavoritePageReqVO reqVO) { + return productFavoriteMapper.selectPageByUserAndFields(reqVO); + } + @Override public ProductFavoriteDO getFavorite(Long userId, Long spuId) { return productFavoriteMapper.selectByUserIdAndSpuId(userId, spuId); From 989d7c44d0b4c60e64bb49e59132b471f294fc99 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 4 Nov 2023 22:52:41 +0800 Subject: [PATCH 03/81] =?UTF-8?q?code=20review=20=E5=BA=97=E9=93=BA?= =?UTF-8?q?=E8=A3=85=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 +- .../diy/vo/template/DiyTemplateBaseVO.java | 4 +- .../dal/dataobject/diy/DiyPageDO.java | 2 + .../dal/dataobject/diy/DiyTemplateDO.java | 3 + .../service/diy/DiyPageServiceImpl.java | 8 ++- .../service/diy/DiyTemplateServiceImpl.java | 7 ++- yudao-server/pom.xml | 62 +++++++++---------- 7 files changed, 54 insertions(+), 38 deletions(-) diff --git a/pom.xml b/pom.xml index dae40f3dc..7f7c140bb 100644 --- a/pom.xml +++ b/pom.xml @@ -15,12 +15,12 @@ yudao-module-system yudao-module-infra - + yudao-module-member - - + yudao-module-pay + yudao-module-mall diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/vo/template/DiyTemplateBaseVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/vo/template/DiyTemplateBaseVO.java index 7959b6c26..b79c7231f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/vo/template/DiyTemplateBaseVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/vo/template/DiyTemplateBaseVO.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.template; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotEmpty; import java.util.List; /** @@ -14,7 +14,7 @@ import java.util.List; public class DiyTemplateBaseVO { @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "默认主题") - @NotNull(message = "模板名称不能为空") + @NotEmpty(message = "模板名称不能为空") private String name; @Schema(description = "备注", example = "默认主题") diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyPageDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyPageDO.java index e5e3f4208..7e1044104 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyPageDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyPageDO.java @@ -32,6 +32,8 @@ public class DiyPageDO extends BaseDO { private Long id; /** * 装修模板编号 + * + * 关联 {@link DiyTemplateDO#getId()} */ private Long templateId; /** diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyTemplateDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyTemplateDO.java index a50fd4dde..684a6f9cb 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyTemplateDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/diy/DiyTemplateDO.java @@ -14,6 +14,9 @@ import java.util.List; /** * 装修模板 DO * + * 1. 新建一个模版,下面可以包含多个 {@link DiyPageDO} 页面,例如说首页、我的 + * 2. 如果需要使用某个模版,则将 {@link #used} 设置为 true,表示已使用,有且仅有一个 + * * @author owen */ @TableName(value = "promotion_diy_template", autoResultMap = true) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImpl.java index d82b9b8ed..69f96ad8f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyPageServiceImpl.java @@ -42,7 +42,6 @@ public class DiyPageServiceImpl implements DiyPageService { DiyPageDO diyPage = DiyPageConvert.INSTANCE.convert(createReqVO); diyPage.setProperty("{}"); diyPageMapper.insert(diyPage); - // 返回 return diyPage.getId(); } @@ -57,6 +56,13 @@ public class DiyPageServiceImpl implements DiyPageService { diyPageMapper.updateById(updateObj); } + /** + * 校验 Page 页面,在一个 template 模版下的名字是唯一的 + * + * @param id Page 编号 + * @param templateId 模版编号 + * @param name Page 名字 + */ void validateNameUnique(Long id, Long templateId, String name) { if (templateId != null || StrUtil.isBlank(name)) { return; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java index 41025c5d6..530b31b94 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/diy/DiyTemplateServiceImpl.java @@ -32,9 +32,11 @@ public class DiyTemplateServiceImpl implements DiyTemplateService { @Resource private DiyTemplateMapper diyTemplateMapper; + @Resource private DiyPageService diyPageService; + // TODO @疯狂:事务; @Override public Long createDiyTemplate(DiyTemplateCreateReqVO createReqVO) { // 校验名称唯一 @@ -120,9 +122,11 @@ public class DiyTemplateServiceImpl implements DiyTemplateService { } @Override + // TODO @疯狂:事务; public void useDiyTemplate(Long id) { // 校验存在 validateDiyTemplateExists(id); + // TODO @疯狂:要不已使用的情况,抛个业务异常? // 已使用的更新为未使用 DiyTemplateDO used = diyTemplateMapper.selectByUsed(true); if (used != null) { @@ -136,8 +140,8 @@ public class DiyTemplateServiceImpl implements DiyTemplateService { this.updateUsed(id, true, LocalDateTime.now()); } - @Transactional(rollbackFor = Exception.class) @Override + @Transactional(rollbackFor = Exception.class) public void updateDiyTemplateProperty(DiyTemplatePropertyUpdateRequestVO updateReqVO) { // 校验存在 validateDiyTemplateExists(updateReqVO.getId()); @@ -151,6 +155,7 @@ public class DiyTemplateServiceImpl implements DiyTemplateService { return diyTemplateMapper.selectByUsed(true); } + // TODO @疯狂:挪到 useDiyTemplate 下面,改名 updateTemplateUsed 会不会好点哈; /** * 更新模板是否使用 * diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 87a961778..4463bd7fb 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -37,11 +37,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-member-biz + ${revision} + @@ -56,11 +56,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-pay-biz + ${revision} + @@ -69,27 +69,27 @@ - - - - - - - - - - - - - - - - - - - - - + + + cn.iocoder.boot + yudao-module-promotion-biz + ${revision} + + + cn.iocoder.boot + yudao-module-product-biz + ${revision} + + + cn.iocoder.boot + yudao-module-trade-biz + ${revision} + + + cn.iocoder.boot + yudao-module-statistics-biz + ${revision} + From ca184026cfcde8bb6a5e3cfeeeb49ddd4c00b851 Mon Sep 17 00:00:00 2001 From: kehaiyou <71740796@qq.com> Date: Sun, 5 Nov 2023 14:21:37 +0800 Subject: [PATCH 04/81] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E5=AE=9E=E4=BE=8B=E6=97=B6=EF=BC=8C=E6=8F=90=E5=89=8D?= =?UTF-8?q?=E6=8C=87=E6=B4=BE=E5=AE=A1=E6=89=B9=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/context/FlowableContextHolder.java | 39 +++++++++++++++++++ .../dto/BpmProcessInstanceCreateReqDTO.java | 7 ++++ .../BpmProcessInstanceCreateReqVO.java | 4 ++ .../task/BpmProcessInstanceExtDO.java | 6 +++ .../BpmTaskAssignRuleServiceImpl.java | 11 ++++++ .../task/BpmProcessInstanceService.java | 19 ++++++--- .../task/BpmProcessInstanceServiceImpl.java | 2 +- 7 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java diff --git a/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java new file mode 100644 index 000000000..e6021c0b7 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.framework.flowable.core.context; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.ttl.TransmittableThreadLocal; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * 工作流--用户用到的上下文相关信息 + */ +public class FlowableContextHolder { + + private static final ThreadLocal>> ASSIGNEE = new TransmittableThreadLocal<>(); + + /** + * 通过流程任务的定义 key ,拿到提前选好的审批人 + * 此方法目的:首次创建流程实例时,数据库中还查询不到 assignee 字段,所以存入上下文中获取 + * + * @param taskDefinitionKey 流程任务 key + * @return 审批人 ID 集合 + */ + public static List getAssigneeByTaskDefinitionKey(String taskDefinitionKey) { + if (CollUtil.isNotEmpty(ASSIGNEE.get())) { + return ASSIGNEE.get().get(taskDefinitionKey); + } + return Collections.emptyList(); + } + + /** + * 存入提前选好的审批人到上下文线程变量中 + * + * @param assignee 流程任务 key -> 审批人 ID 炅和 + */ + public static void setAssignee(Map> assignee) { + ASSIGNEE.set(assignee); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java index 5d7edbe80..6db999c1e 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.api.task.dto; import lombok.Data; import javax.validation.constraints.NotEmpty; +import java.util.List; import java.util.Map; /** @@ -30,4 +31,10 @@ public class BpmProcessInstanceCreateReqDTO { */ @NotEmpty(message = "业务的唯一标识") private String businessKey; + + /** + * 提前指派的审批人 + * 例如: { taskKey1 :[1,2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批 + */ + private Map> assignee; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java index 8fc544ef4..13b7c8f0b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import javax.validation.constraints.NotEmpty; +import java.util.List; import java.util.Map; @Schema(description = "管理后台 - 流程实例的创建 Request VO") @@ -17,4 +18,7 @@ public class BpmProcessInstanceCreateReqVO { @Schema(description = "变量实例") private Map variables; + @Schema(description = "提前指派的审批人", requiredMode = Schema.RequiredMode.REQUIRED, example = "{taskKey1:[1,2]}") + private Map> assignee; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java index 293cc2dd7..8aba9059f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java @@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import java.time.LocalDateTime; +import java.util.List; import java.util.Map; /** @@ -87,4 +88,9 @@ public class BpmProcessInstanceExtDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private Map formVariables; + /** + * 提前设定好的审批人 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map> assignee; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java index c6200b2dd..8c6ceaf48 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java @@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper; import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants; import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum; import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript; +import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.PostApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; @@ -39,6 +40,7 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.util.*; +import java.util.function.Function; import static cn.hutool.core.text.CharSequenceUtil.format; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -77,6 +79,9 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService { private DictDataApi dictDataApi; @Resource private PermissionApi permissionApi; + @Resource + @Lazy // 解决循环依赖 + private BpmProcessInstanceService processInstanceService; /** * 任务分配脚本 */ @@ -234,6 +239,12 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService { @Override @DataPermission(enable = false) // 忽略数据权限,不然分配会存在问题 public Set calculateTaskCandidateUsers(DelegateExecution execution) { + //1. 先从提前选好的审批人中获取 + List assignee = processInstanceService.getAssigneeByProcessInstanceIdAndTaskDefinitionKey(execution.getProcessInstanceId(), execution.getCurrentActivityId()); + if(CollUtil.isNotEmpty(assignee)){ + return convertSet(assignee, Function.identity()); + } + //2. 通过分配规则,计算审批人 BpmTaskAssignRuleDO rule = getTaskRule(execution); return calculateTaskCandidateUsers(execution, rule); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 23340ad19..4649475db 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -49,16 +49,17 @@ public interface BpmProcessInstanceService { /** * 获得流程实例的分页 * - * @param userId 用户编号 + * @param userId 用户编号 * @param pageReqVO 分页请求 * @return 流程实例的分页 */ PageResult getMyProcessInstancePage(Long userId, @Valid BpmProcessInstanceMyPageReqVO pageReqVO); + /** * 创建流程实例(提供给前端) * - * @param userId 用户编号 + * @param userId 用户编号 * @param createReqVO 创建信息 * @return 实例的编号 */ @@ -67,7 +68,7 @@ public interface BpmProcessInstanceService { /** * 创建流程实例(提供给内部) * - * @param userId 用户编号 + * @param userId 用户编号 * @param createReqDTO 创建信息 * @return 实例的编号 */ @@ -84,7 +85,7 @@ public interface BpmProcessInstanceService { /** * 取消流程实例 * - * @param userId 用户编号 + * @param userId 用户编号 * @param cancelReqVO 取消信息 */ void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO); @@ -139,9 +140,17 @@ public interface BpmProcessInstanceService { /** * 更新 ProcessInstance 拓展记录为不通过 * - * @param id 流程编号 + * @param id 流程编号 * @param reason 理由。例如说,审批不通过时,需要传递该值 */ void updateProcessInstanceExtReject(String id, String reason); + /** + * 去流程实例扩展表中,取出指定流程任务提前指定的审批人 + * + * @param processInstanceId 流程实例的编号 + * @param taskDefinitionKey 流程任务定义的 key + * @return 审批人集合 + */ + List getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 56115d79b..5d5e1ba10 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 * * ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. * * HistoricProcessInstance & ProcessInstance 的关系: * 1. * * 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String)event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables)); return instance.getId(); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.flowable.core.context.FlowableContextHolder; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } //设置上下文信息 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables).setAssignee(assignee)); return instance.getId(); } @Override public List getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { //1. 先从上下文获取,首次提交数据库中查询不到 List result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } //2. 从数据库中获取 BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } if(CollUtil.isNotEmpty(instance.getAssignee())){ return instance.getAssignee().get(taskDefinitionKey); } return Collections.emptyList(); } } \ No newline at end of file From 9537669d01cadbd46b4b22218baddfdd6e67cd0a Mon Sep 17 00:00:00 2001 From: kehaiyou <71740796@qq.com> Date: Sun, 5 Nov 2023 20:14:21 +0800 Subject: [PATCH 05/81] =?UTF-8?q?fix:=20=E5=8E=BB=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/iocoder/yudao/framework/common/package-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java index 09c43f748..f3f2574e5 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/package-info.java @@ -1,7 +1,6 @@ /** * 基础的通用类,和框架无关 * - * sdadsdsd * 例如说,CommonResult 为通用返回 */ package cn.iocoder.yudao.framework.common; From 09d45e639348aaf944da43b6628127a0dd05e165 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Mon, 6 Nov 2023 11:33:08 +0800 Subject: [PATCH 06/81] =?UTF-8?q?=E8=BD=AC=E8=B4=A6=20-=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=BD=AC=E8=B4=A6=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/pay_wallet.sql | 10 +++- .../notify/dto/PayTransferNotifyReqDTO.java | 27 +++++++++++ .../module/pay/enums/ErrorCodeConstants.java | 7 ++- .../pay/enums/notify/PayNotifyTypeEnum.java | 1 + .../admin/demo/PayDemoOrderController.java | 4 +- .../admin/demo/PayDemoTransferController.java | 15 +++++- .../{ => order}/PayDemoOrderCreateReqVO.java | 5 +- .../vo/{ => order}/PayDemoOrderRespVO.java | 2 +- .../pay/convert/demo/PayDemoOrderConvert.java | 4 +- .../pay/dal/dataobject/app/PayAppDO.java | 5 ++ .../dataobject/notify/PayNotifyTaskDO.java | 4 ++ .../pay/service/demo/PayDemoOrderService.java | 2 +- .../service/demo/PayDemoOrderServiceImpl.java | 2 +- .../service/demo/PayDemoTransferService.java | 8 ++++ .../demo/PayDemoTransferServiceImpl.java | 46 +++++++++++++++++++ .../service/notify/PayNotifyServiceImpl.java | 13 ++++++ .../transfer/PayTransferServiceImpl.java | 36 +++++++-------- 17 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java rename yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/{ => order}/PayDemoOrderCreateReqVO.java (76%) rename yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/{ => order}/PayDemoOrderRespVO.java (96%) diff --git a/sql/mysql/pay_wallet.sql b/sql/mysql/pay_wallet.sql index 291428ffa..9153948bf 100644 --- a/sql/mysql/pay_wallet.sql +++ b/sql/mysql/pay_wallet.sql @@ -245,4 +245,12 @@ INSERT INTO system_menu( VALUES ( '转账订单', '', 2, 3, 1117, 'transfer', 'ep:credit-card', 'pay/transfer/index', 0, 'PayTransfer' - ); \ No newline at end of file + ); + +-- 转账通知脚本 + +ALTER TABLE `pay_app` + ADD COLUMN `transfer_notify_url` varchar(1024) NOT NULL COMMENT '转账结果的回调地址' AFTER `refund_notify_url`; +ALTER TABLE `pay_notify_task` + MODIFY COLUMN `merchant_order_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '商户订单编号' AFTER `status`, + ADD COLUMN `merchant_transfer_id` varchar(64) COMMENT '商户转账单编号' AFTER `merchant_order_id`; \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java new file mode 100644 index 000000000..14ba64463 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.pay.api.notify.dto; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * 转账单的通知 Request DTO + * + * @author jason + */ +@Data +public class PayTransferNotifyReqDTO { + + /** + * 商户转账单号 + */ + @NotEmpty(message = "商户转账单号不能为空") + private String merchantTransferId; + + /** + * 转账订单编号 + */ + @NotNull(message = "转账订单编号不能为空") + private Long payTransferId; +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java index 95835a4b6..8fc38e61c 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java @@ -65,12 +65,13 @@ public interface ErrorCodeConstants { // ========== 转账模块 1-007-009-000 ========== ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}"); - ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账交易单不存在"); + ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在"); ErrorCode PAY_TRANSFER_STATUS_IS_SUCCESS = new ErrorCode(1_007_009_002, "转账单已成功转账"); ErrorCode PAY_TRANSFER_EXISTS = new ErrorCode(1_007_009_003, "已经存在转账单"); ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经存在,请查询转账订单相关状态"); ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账"); ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中"); + // ========== 示例订单 1-007-900-000 ========== ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在"); ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_900_001, "示例订单更新支付状态失败,订单不是【未支付】状态"); @@ -84,4 +85,8 @@ public interface ErrorCodeConstants { ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_900_009, "发起退款失败,退款单编号不匹配"); ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_900_010, "发起退款失败,退款单金额不匹配"); + // ========== 示例转账订单 1-007-901-001 ========== + ErrorCode DEMO_TRANSFER_NOT_FOUND = new ErrorCode(1_007_901_001, "示例转账单不存在"); + ErrorCode DEMO_TRANSFER_FAIL_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_002, "转账失败,转账单编号不匹配"); + ErrorCode DEMO_TRANSFER_FAIL_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "转账失败,转账单金额不匹配"); } diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java index 8c259d93c..873e015c6 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/notify/PayNotifyTypeEnum.java @@ -14,6 +14,7 @@ public enum PayNotifyTypeEnum { ORDER(1, "支付单"), REFUND(2, "退款单"), + TRANSFER(3, "转账单") ; /** diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java index 1e3a61eec..60c04c290 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java @@ -6,8 +6,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO; import cn.iocoder.yudao.module.pay.convert.demo.PayDemoOrderConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java index f6c7b31c0..ca01a1edb 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoTransferController.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.pay.controller.admin.demo; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferRespVO; import cn.iocoder.yudao.module.pay.convert.demo.PayDemoTransferConvert; @@ -14,6 +16,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; +import javax.annotation.security.PermitAll; import javax.validation.Valid; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -33,9 +36,19 @@ public class PayDemoTransferController { } @GetMapping("/page") - @Operation(summary = "获得示例订单分页") + @Operation(summary = "获得示例转账订单分页") public CommonResult> getDemoTransferPage(@Valid PageParam pageVO) { PageResult pageResult = demoTransferService.getDemoTransferPage(pageVO); return success(PayDemoTransferConvert.INSTANCE.convertPage(pageResult)); } + + @PostMapping("/update-status") + @Operation(summary = "更新示例转账订单的转账状态") // 由 pay-module 转账服务,进行回调 + @PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现 + @OperateLog(enable = false) // 禁用操作日志,因为没有操作人 + public CommonResult updateDemoTransferStatus(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { + demoTransferService.updateDemoTransferStatus(Long.valueOf(notifyReqDTO.getMerchantTransferId()), + notifyReqDTO.getPayTransferId()); + return success(true); + } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java similarity index 76% rename from yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderCreateReqVO.java rename to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java index 9960ada48..6c82a0ab6 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderCreateReqVO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderCreateReqVO.java @@ -1,9 +1,8 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo.vo; +package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order; -import lombok.*; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; -import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; @Schema(description = "管理后台 - 示例订单创建 Request VO") diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderRespVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java similarity index 96% rename from yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderRespVO.java rename to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java index 3404844dc..cb305631d 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderRespVO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/order/PayDemoOrderRespVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.pay.controller.admin.demo.vo; +package cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoOrderConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoOrderConvert.java index 313e5d266..8fca99791 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoOrderConvert.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/demo/PayDemoOrderConvert.java @@ -1,8 +1,8 @@ package cn.iocoder.yudao.module.pay.convert.demo; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderRespVO; import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java index 977eff93a..8f3490fc7 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/app/PayAppDO.java @@ -54,4 +54,9 @@ public class PayAppDO extends BaseDO { */ private String refundNotifyUrl; + /** + * 转账结果的回调地址 + */ + private String transferNotifyUrl; + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java index 181a32802..7bfabad3f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java @@ -66,6 +66,10 @@ public class PayNotifyTaskDO extends TenantBaseDO { * 商户订单编号 */ private String merchantOrderId; + /** + * 商户转账单编号 + */ + private String merchantTransferId; /** * 通知状态 * diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java index e6822e626..cf870253f 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.pay.service.demo; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; import javax.validation.Valid; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java index 7e1090248..57ff80637 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java @@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; -import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.order.PayDemoOrderCreateReqVO; import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO; import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java index 9116dcd9a..fb01ec43d 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferService.java @@ -28,4 +28,12 @@ public interface PayDemoTransferService { * @param pageVO 分页查询参数 */ PageResult getDemoTransferPage(PageParam pageVO); + + /** + * 更新转账业务示例订单的转账状态 + * + * @param id 编号 + * @param payTransferId 转账单编号 + */ + void updateDemoTransferStatus(Long id, Long payTransferId); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java index e892e4446..8de98da1e 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java @@ -1,18 +1,25 @@ package cn.iocoder.yudao.module.pay.service.demo; +import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO; import cn.iocoder.yudao.module.pay.convert.demo.PayDemoTransferConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoTransferDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoTransferMapper; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import javax.validation.Validator; +import java.util.Objects; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.WAITING; /** @@ -33,6 +40,8 @@ public class PayDemoTransferServiceImpl implements PayDemoTransferService { @Resource private PayDemoTransferMapper demoTransferMapper; @Resource + private PayTransferService payTransferService; + @Resource private Validator validator; @Override @@ -50,4 +59,41 @@ public class PayDemoTransferServiceImpl implements PayDemoTransferService { public PageResult getDemoTransferPage(PageParam pageVO) { return demoTransferMapper.selectPage(pageVO); } + + @Override + public void updateDemoTransferStatus(Long id, Long payTransferId) { + PayTransferDO payTransfer = validateDemoTransferStatusCanUpdate(id, payTransferId); + // 更新示例订单状态 + if (payTransfer != null) { + demoTransferMapper.updateById(new PayDemoTransferDO().setId(id) + .setPayTransferId(payTransferId) + .setPayChannelCode(payTransfer.getChannelCode()) + .setTransferStatus(payTransfer.getStatus()) + .setTransferTime(payTransfer.getSuccessTime())); + } + } + + private PayTransferDO validateDemoTransferStatusCanUpdate(Long id, Long payTransferId) { + PayDemoTransferDO demoTransfer = demoTransferMapper.selectById(id); + if (demoTransfer == null) { + throw exception(DEMO_TRANSFER_NOT_FOUND); + } + if (PayTransferStatusEnum.isSuccess(demoTransfer.getTransferStatus()) + || PayTransferStatusEnum.isClosed(demoTransfer.getTransferStatus())) { + // 无需更新返回 null + return null; + } + PayTransferDO transfer = payTransferService.getTransfer(payTransferId); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (!Objects.equals(demoTransfer.getPrice(), transfer.getPrice())) { + throw exception(DEMO_TRANSFER_FAIL_PRICE_NOT_MATCH); + } + if (ObjectUtil.notEqual(transfer.getMerchantTransferId(), id.toString())) { + throw exception(DEMO_TRANSFER_FAIL_TRANSFER_ID_ERROR); + } + // TODO 校验账号 + return transfer; + } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java index f6b17c565..1d356cf49 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java @@ -13,11 +13,13 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO; import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogMapper; import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper; import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO; @@ -25,6 +27,7 @@ import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; @@ -73,6 +76,9 @@ public class PayNotifyServiceImpl implements PayNotifyService { @Resource @Lazy // 循环依赖,避免报错 private PayRefundService refundService; + @Resource + @Lazy // 循环依赖,避免报错 + private PayTransferService transferService; @Resource private PayNotifyTaskMapper notifyTaskMapper; @@ -100,6 +106,10 @@ public class PayNotifyServiceImpl implements PayNotifyService { PayRefundDO refundDO = refundService.getRefund(task.getDataId()); task.setAppId(refundDO.getAppId()) .setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl()); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { + PayTransferDO transfer = transferService.getTransfer(task.getDataId()); + task.setAppId(transfer.getAppId()).setMerchantTransferId(transfer.getMerchantTransferId()) + .setNotifyUrl(transfer.getNotifyUrl()); } // 执行插入 @@ -214,6 +224,9 @@ public class PayNotifyServiceImpl implements PayNotifyService { } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId()) .payRefundId(task.getDataId()).build(); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { + request = new PayTransferNotifyReqDTO().setMerchantTransferId(task.getMerchantTransferId()) + .setPayTransferId(task.getDataId()); } else { throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task)); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java index 014a7aae7..27b9807bd 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -12,12 +12,15 @@ import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespE import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; +import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; +import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -51,6 +54,8 @@ public class PayTransferServiceImpl implements PayTransferService { @Resource private PayChannelService channelService; @Resource + private PayNotifyService notifyService; + @Resource private PayNoRedisDAO noRedisDAO; @Resource private Validator validator; @@ -73,7 +78,7 @@ public class PayTransferServiceImpl implements PayTransferService { // 1.1 校验转账单是否可以提交 validateTransferCanCreate(reqDTO.getAppId(), reqDTO.getMerchantTransferId()); // 1.2 校验 App - appService.validPayApp(reqDTO.getAppId()); + PayAppDO payApp = appService.validPayApp(reqDTO.getAppId()); // 1.3 校验支付渠道是否有效 PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode()); PayClient client = channelService.getPayClient(channel.getId()); @@ -86,7 +91,7 @@ public class PayTransferServiceImpl implements PayTransferService { PayTransferDO transfer = INSTANCE.convert(reqDTO) .setChannelId(channel.getId()) .setNo(no).setStatus(WAITING.getStatus()) - .setNotifyUrl("http://127.0.0.1:48080/admin-api/pay/todo"); // TODO 需要加个transfer Notify url + .setNotifyUrl(payApp.getTransferNotifyUrl()); transferMapper.insert(transfer); PayTransferRespDTO unifiedTransferResp = null; try { @@ -145,22 +150,13 @@ public class PayTransferServiceImpl implements PayTransferService { } private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { - // 1. 更新 PayTransferDO 转账成功 - Boolean transferred = updateTransferSuccess(channel, notify); - if (transferred) { - return; - } - // 2. TODO 插入转账通知记录 - } - - private Boolean updateTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { // 1.校验 PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo()); if (transfer == null) { throw exception(PAY_TRANSFER_NOT_FOUND); } if (isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新 - return Boolean.TRUE; + return; } if (!isPendingStatus(transfer.getStatus())) { throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); @@ -176,10 +172,13 @@ public class PayTransferServiceImpl implements PayTransferService { throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); } log.info("[updateTransferSuccess][transfer({}) 更新为已转账]", transfer.getId()); - return Boolean.FALSE; + + // 3. 插入转账通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), + transfer.getId()); } - private void updateTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { + private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { // 1.校验 PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo()); if (transfer == null) { @@ -192,6 +191,7 @@ public class PayTransferServiceImpl implements PayTransferService { if (!isPendingStatus(transfer.getStatus())) { throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); } + // 2.更新 int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(), CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), @@ -203,11 +203,11 @@ public class PayTransferServiceImpl implements PayTransferService { throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); } log.info("[updateTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId()); - } - private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { - // 更新 PayTransferDO 转账关闭 - updateTransferClosed(channel, notify); + // 3. 插入转账通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), + transfer.getId()); + } /** From 15313c2992258c248433b73b6d921189ad775d4f Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 7 Nov 2023 23:11:07 +0800 Subject: [PATCH 07/81] =?UTF-8?q?=E8=BD=AC=E8=B4=A6=20-=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=BD=AC=E8=B4=A6=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/pay/core/client/PayClient.java | 9 + .../dto/transfer/PayTransferRespDTO.java | 17 +- .../core/client/impl/AbstractPayClient.java | 22 ++- .../impl/alipay/AbstractAlipayPayClient.java | 108 ++++++++---- .../core/client/impl/mock/MockPayClient.java | 6 + .../impl/weixin/AbstractWxPayClient.java | 7 + .../transfer/PayTransferStatusRespEnum.java | 4 + .../module/pay/enums/ErrorCodeConstants.java | 6 +- .../enums/transfer/PayTransferStatusEnum.java | 4 + .../convert/transfer/PayTransferConvert.java | 2 +- .../dal/mysql/transfer/PayTransferMapper.java | 6 +- .../framework/pay/core/WalletPayClient.java | 6 + .../pay/job/transfer/PayTransferSyncJob.java | 30 ++++ .../service/transfer/PayTransferService.java | 6 + .../transfer/PayTransferServiceImpl.java | 164 +++++++++++++----- 15 files changed, 314 insertions(+), 83 deletions(-) create mode 100644 yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java index 18ae017d1..86e3566b2 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; import java.util.Map; @@ -86,4 +87,12 @@ public interface PayClient { */ PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO); + /** + * 获得转账订单信息 + * + * @param outTradeNo 外部订单号 + * @param type 转账类型 + * @return 转账信息 + */ + PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java index da6f22774..0f9b48240 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferRespDTO.java @@ -53,11 +53,24 @@ public class PayTransferRespDTO { /** * 创建【WAITING】状态的转账返回 */ - public static PayTransferRespDTO waitingOf(String channelOrderNo, + public static PayTransferRespDTO waitingOf(String channelTransferNo, String outTransferNo, Object rawData) { PayTransferRespDTO respDTO = new PayTransferRespDTO(); respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus(); - respDTO.channelTransferNo = channelOrderNo; + respDTO.channelTransferNo = channelTransferNo; + respDTO.outTransferNo = outTransferNo; + respDTO.rawData = rawData; + return respDTO; + } + + /** + * 创建【IN_PROGRESS】状态的转账返回 + */ + public static PayTransferRespDTO dealingOf(String channelTransferNo, + String outTransferNo, Object rawData) { + PayTransferRespDTO respDTO = new PayTransferRespDTO(); + respDTO.status = PayTransferStatusRespEnum.IN_PROGRESS.getStatus(); + respDTO.channelTransferNo = channelTransferNo; respDTO.outTransferNo = outTransferNo; respDTO.rawData = rawData; return respDTO; diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java index f06dab22e..82d68b58f 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java @@ -188,11 +188,11 @@ public abstract class AbstractPayClient implemen @Override public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + validatePayTransferReqDTO(reqDTO); PayTransferRespDTO resp; - try{ - validatePayTransferReqDTO(reqDTO); + try { resp = doUnifiedTransfer(reqDTO); - }catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 throw ex; } catch (Throwable ex) { // 系统异常,则包装成 PayException 异常抛出 @@ -219,9 +219,25 @@ public abstract class AbstractPayClient implemen } } + @Override + public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) { + try { + return doGetTransfer(outTradeNo, type); + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可 + throw ex; + } catch (Throwable ex) { + log.error("[getTransfer][客户端({}) outTradeNo({}) type({}) 查询转账单异常]", + getId(), outTradeNo, type, ex); + throw buildPayException(ex); + } + } + protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws Throwable; + protected abstract PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) + throws Throwable; + // ========== 各种工具方法 ========== private PayException buildPayException(Throwable ex) { diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java index fc9d658ac..4dcf23675 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java @@ -23,14 +23,8 @@ import com.alipay.api.AlipayResponse; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.*; import com.alipay.api.internal.util.AlipaySignature; -import com.alipay.api.request.AlipayFundTransUniTransferRequest; -import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest; -import com.alipay.api.request.AlipayTradeQueryRequest; -import com.alipay.api.request.AlipayTradeRefundRequest; -import com.alipay.api.response.AlipayFundTransUniTransferResponse; -import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse; -import com.alipay.api.response.AlipayTradeQueryResponse; -import com.alipay.api.response.AlipayTradeRefundResponse; +import com.alipay.api.request.*; +import com.alipay.api.response.*; import lombok.Getter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -126,7 +120,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient { + Assert.notNull(status, () -> { throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody())); }); return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), @@ -228,7 +222,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient { throw new UnsupportedOperationException("待实现"); } + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) { + throw new UnsupportedOperationException("待实现"); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java index f4f326a65..bb6feeb04 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java @@ -16,6 +16,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDT import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result; import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult; @@ -431,6 +432,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient { .betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(PayTransferDO::getId)); } + + default List selectListByStatus(Integer status){ + return selectList(PayTransferDO::getStatus, status); + } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java index c1b72ef9e..efd3d0ba1 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java @@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; @@ -181,4 +182,9 @@ public class WalletPayClient extends AbstractPayClient { throw new UnsupportedOperationException("待实现"); } + @Override + protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) { + throw new UnsupportedOperationException("待实现"); + } + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java new file mode 100644 index 000000000..191071e10 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/job/transfer/PayTransferSyncJob.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.pay.job.transfer; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 转账订单的同步 Job + * + * 由于转账订单的转账结果,有些渠道是异步通知进行同步的,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 + * + * @author jason + */ +@Component +public class PayTransferSyncJob implements JobHandler { + + @Resource + private PayTransferService transferService; + + @Override + @TenantJob + public String execute(String param) { + int count = transferService.syncTransfer(); + return StrUtil.format("同步转账订单 {} 个", count); + } +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java index 0848dc0ca..9a58cf06a 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java @@ -48,4 +48,10 @@ public interface PayTransferService { */ PageResult getTransferPage(PayTransferPageReqVO pageReqVO); + /** + * 同步渠道转账单状态 + * + * @return 同步到状态的转账数量,包括转账成功、转账失败、转账中的 + */ + int syncTransfer(); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java index 27b9807bd..73b726dcd 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -1,14 +1,16 @@ package cn.iocoder.yudao.module.pay.service.transfer; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; @@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper; import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; @@ -27,7 +30,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.validation.Validator; -import java.util.Objects; +import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert.INSTANCE; @@ -75,56 +78,60 @@ public class PayTransferServiceImpl implements PayTransferService { @Override public Long createTransfer(PayTransferCreateReqDTO reqDTO) { - // 1.1 校验转账单是否可以提交 - validateTransferCanCreate(reqDTO.getAppId(), reqDTO.getMerchantTransferId()); - // 1.2 校验 App + // 1.1 校验 App PayAppDO payApp = appService.validPayApp(reqDTO.getAppId()); - // 1.3 校验支付渠道是否有效 + // 1.2 校验支付渠道是否有效 PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode()); PayClient client = channelService.getPayClient(channel.getId()); if (client == null) { log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); throw exception(CHANNEL_NOT_FOUND); } - // 2.创建转账单 - String no = noRedisDAO.generate(TRANSFER_NO_PREFIX); - PayTransferDO transfer = INSTANCE.convert(reqDTO) - .setChannelId(channel.getId()) - .setNo(no).setStatus(WAITING.getStatus()) - .setNotifyUrl(payApp.getTransferNotifyUrl()); - transferMapper.insert(transfer); - PayTransferRespDTO unifiedTransferResp = null; + // 1.3 校验转账单已经发起过转账。 + PayTransferDO transfer = validateTransferCanCreate(reqDTO); + + if (transfer == null) { + // 2.不存在创建转账单. 否则允许使用相同的 no 再次发起转账 + String no = noRedisDAO.generate(TRANSFER_NO_PREFIX); + transfer = INSTANCE.convert(reqDTO) + .setChannelId(channel.getId()) + .setNo(no).setStatus(WAITING.getStatus()) + .setNotifyUrl(payApp.getTransferNotifyUrl()); + transferMapper.insert(transfer); + } try { // 3. 调用三方渠道发起转账 - PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(reqDTO) - .setOutTransferNo(no); - unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); - } catch (ServiceException ex) { - // 业务异常.直接返回转账失败的结果 - log.error("[createTransfer][转账 id({}) requestDTO({}) 发生业务异常]", transfer.getId(), reqDTO, ex); - unifiedTransferResp = PayTransferRespDTO.closedOf("", "", no, ex); - } catch (Throwable e) { - // 注意这里仅打印异常,不进行抛出。 - // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续通过转账回调、或者转账轮询可以拿到。 - // TODO 需要加转账回调业务接口 和 转账轮询未实现 - // 最终,在异常的情况下,支付中心会异步回调业务的转账回调接口,提供转账结果 - log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); - } - if (Objects.nonNull(unifiedTransferResp)) { + PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer) + .setOutTransferNo(transfer.getNo()); + PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); // 4. 通知转账结果 getSelf().notifyTransfer(channel, unifiedTransferResp); + } catch (Throwable e) { + // 注意这里仅打印异常,不进行抛出。 + // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续转账轮询可以拿到。 + // 或者使用相同 no 再次发起转账请求 + log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); } + return transfer.getId(); } - @Override - public PayTransferDO getTransfer(Long id) { - return transferMapper.selectById(id); - } - - @Override - public PageResult getTransferPage(PayTransferPageReqVO pageReqVO) { - return transferMapper.selectPage(pageReqVO); + private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto) { + PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(dto.getAppId(), dto.getMerchantTransferId()); + if (transfer != null) { + // 已经存在,并且状态不为等待状态。说明已经调用渠道转账并返回结果. + if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) { + throw exception(PAY_MERCHANT_TRANSFER_EXISTS); + } + if (ObjectUtil.notEqual(dto.getPrice(), transfer.getPrice())) { + throw exception(PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH); + } + if (ObjectUtil.notEqual(dto.getType(), transfer.getType())) { + throw exception(PAY_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH); + } + } + // 如果状态为等待状态。不知道渠道转账是否发起成功。 允许使用相同的 no 再次发起转账,渠道会保证幂等 + return transfer; } @Transactional(rollbackFor = Exception.class) @@ -138,17 +145,40 @@ public class PayTransferServiceImpl implements PayTransferService { if (PayTransferStatusRespEnum.isClosed(notify.getStatus())) { notifyTransferClosed(channel, notify); } + // 转账处理中的回调 + if (PayTransferStatusRespEnum.isInProgress(notify.getStatus())) { + notifyTransferInProgress(channel, notify); + } // WAITING 状态无需处理 - // TODO IN_PROGRESS 待处理 } - private void validateTransferCanCreate(Long appId, String merchantTransferId) { - PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, merchantTransferId); - if (transfer != null) { // 是否存在 - throw exception(PAY_MERCHANT_TRANSFER_EXISTS); + private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) { + // 1.校验 + PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); } + if (isInProgress(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + return; + } + if (!isWaiting(transfer.getStatus())) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING); + } + // 2.更新 + int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), + CollUtil.newArrayList(WAITING.getStatus()), + new PayTransferDO().setStatus(IN_PROGRESS.getStatus())); + if (updateCounts == 0) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING); + } + log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId()); + + // 3. 插入转账通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), + transfer.getId()); } + private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { // 1.校验 PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo()); @@ -210,6 +240,56 @@ public class PayTransferServiceImpl implements PayTransferService { } + @Override + public PayTransferDO getTransfer(Long id) { + return transferMapper.selectById(id); + } + + @Override + public PageResult getTransferPage(PayTransferPageReqVO pageReqVO) { + return transferMapper.selectPage(pageReqVO); + } + + @Override + public int syncTransfer() { + List list = transferMapper.selectListByStatus(WAITING.getStatus()); + if (CollUtil.isEmpty(list)) { + return 0; + } + int count = 0; + for (PayTransferDO transfer : list) { + count += syncTransfer(transfer) ? 1 : 0; + } + return count; + } + + private boolean syncTransfer(PayTransferDO transfer) { + try { + // 1. 查询转账订单信息 + PayClient payClient = channelService.getPayClient(transfer.getChannelId()); + if (payClient == null) { + log.error("[syncTransfer][渠道编号({}) 找不到对应的支付客户端]", transfer.getChannelId()); + return false; + } + PayTransferRespDTO resp = payClient.getTransfer(transfer.getNo(), + PayTransferTypeEnum.typeOf(transfer.getType())); + + // 2. 回调转账结果 + notifyTransfer(transfer.getChannelId(), resp); + return true; + } catch (Throwable ex) { + log.error("[syncTransfer][transfer({}) 同步转账单状态异常]", transfer.getId(), ex); + return false; + } + } + + private void notifyTransfer(Long channelId, PayTransferRespDTO notify) { + // 校验渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(channelId); + // 通知转账结果给对应的业务 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyTransfer(channel, notify)); + } + /** * 获得自身的代理对象,解决 AOP 生效问题 * From e04c4b11df95323cd090672b73678346c5edbf68 Mon Sep 17 00:00:00 2001 From: dhb52 Date: Wed, 8 Nov 2023 13:09:49 +0800 Subject: [PATCH 08/81] =?UTF-8?q?feat:=20=E8=BE=BE=E6=A2=A6flowable=206.8?= =?UTF-8?q?=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/flowable-6.8.md | 28 + .../liquibase/database/core/DmDatabase.java | 598 +++++ .../liquibase/datatype/core/BooleanType.java | 165 ++ .../impl/AbstractEngineConfiguration.java | 2068 +++++++++++++++++ .../main/resources/META-INF/package-info.md | 1 + .../services/liquibase.database.Database | 21 + 6 files changed, 2881 insertions(+) create mode 100644 sql/dm/flowable-6.8.md create mode 100644 sql/dm/flowable-patch/src/main/java/liquibase/database/core/DmDatabase.java create mode 100644 sql/dm/flowable-patch/src/main/java/liquibase/datatype/core/BooleanType.java create mode 100644 sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java create mode 100644 sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md create mode 100644 sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database diff --git a/sql/dm/flowable-6.8.md b/sql/dm/flowable-6.8.md new file mode 100644 index 000000000..7a7b10f6e --- /dev/null +++ b/sql/dm/flowable-6.8.md @@ -0,0 +1,28 @@ +# 达梦 flowable 适配 + +参考文档: [《Flowable6.8(6.x 版本通用)整合集成达梦 8 数据库(DM8)详解,解决自动生成表时 dmn 相关表语法报错问题》](https://blog.csdn.net/TangBoBoa/article/details/130392495) + +## 1. 覆盖 flowable,liquibase 相关代码 + +把`flowable-patch/src`下的文件按文件结果添加到`yudao-server`项目对应目录中 + +## 2. 修改相关 DAO + +例如`cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO` + +```diff +- @TableField("`type`") +private String type; +``` + +## 3. 关于`flowable.database-schema-update`配置 + +首次运行,修改`flowable.database-schema-update=true`,系统会自动建表,第二次运行需要修改为`false`。 + +## 4. TODO + +配置`flowable.database-schema-update=true`第二次运行失败,报错 + +```text +Object [FLW_EV_DATABASECHANGELOG] already exists +``` diff --git a/sql/dm/flowable-patch/src/main/java/liquibase/database/core/DmDatabase.java b/sql/dm/flowable-patch/src/main/java/liquibase/database/core/DmDatabase.java new file mode 100644 index 000000000..fbc4c6bc1 --- /dev/null +++ b/sql/dm/flowable-patch/src/main/java/liquibase/database/core/DmDatabase.java @@ -0,0 +1,598 @@ +package liquibase.database.core; + +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import liquibase.CatalogAndSchema; +import liquibase.Scope; +import liquibase.database.AbstractJdbcDatabase; +import liquibase.database.DatabaseConnection; +import liquibase.database.OfflineConnection; +import liquibase.database.jvm.JdbcConnection; +import liquibase.exception.DatabaseException; +import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.exception.ValidationErrors; +import liquibase.executor.ExecutorService; +import liquibase.statement.DatabaseFunction; +import liquibase.statement.SequenceCurrentValueFunction; +import liquibase.statement.SequenceNextValueFunction; +import liquibase.statement.core.RawCallStatement; +import liquibase.statement.core.RawSqlStatement; +import liquibase.structure.DatabaseObject; +import liquibase.structure.core.Catalog; +import liquibase.structure.core.Index; +import liquibase.structure.core.PrimaryKey; +import liquibase.structure.core.Schema; +import liquibase.util.JdbcUtils; +import liquibase.util.StringUtil; + +public class DmDatabase extends AbstractJdbcDatabase { + private static final String PRODUCT_NAME = "DM DBMS"; + + @Override + protected String getDefaultDatabaseProductName() { + return PRODUCT_NAME; + } + + /** + * Is this AbstractDatabase subclass the correct one to use for the given connection. + * + * @param conn + */ + @Override + public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { + return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName()); + } + + /** + * If this database understands the given url, return the default driver class name. Otherwise return null. + * + * @param url + */ + @Override + public String getDefaultDriver(String url) { + if(url.startsWith("jdbc:dm")) { + return "dm.jdbc.driver.DmDriver"; + } + + return null; + } + + /** + * Returns an all-lower-case short name of the product. Used for end-user selecting of database type + * such as the DBMS precondition. + */ + @Override + public String getShortName() { + return "dm"; + } + + @Override + public Integer getDefaultPort() { + return 5236; + } + + /** + * Returns whether this database support initially deferrable columns. + */ + @Override + public boolean supportsInitiallyDeferrableColumns() { + return true; + } + + @Override + public boolean supportsTablespaces() { + return true; + } + + @Override + public int getPriority() { + return PRIORITY_DEFAULT; + } + + private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*"); + + protected final int SHORT_IDENTIFIERS_LENGTH = 30; + protected final int LONG_IDENTIFIERS_LEGNTH = 128; + public static final int ORACLE_12C_MAJOR_VERSION = 12; + + private Set reservedWords = new HashSet<>(); + private Set userDefinedTypes; + private Map savedSessionNlsSettings; + + private Boolean canAccessDbaRecycleBin; + private Integer databaseMajorVersion; + private Integer databaseMinorVersion; + + /** + * Default constructor for an object that represents the Oracle Database DBMS. + */ + public DmDatabase() { + super.unquotedObjectsAreUppercased = true; + //noinspection HardCodedStringLiteral + super.setCurrentDateTimeFunction("SYSTIMESTAMP"); + // Setting list of Oracle's native functions + //noinspection HardCodedStringLiteral + dateFunctions.add(new DatabaseFunction("SYSDATE")); + //noinspection HardCodedStringLiteral + dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP")); + //noinspection HardCodedStringLiteral + dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP")); + //noinspection HardCodedStringLiteral + super.sequenceNextValueFunction = "%s.nextval"; + //noinspection HardCodedStringLiteral + super.sequenceCurrentValueFunction = "%s.currval"; + } + + private void tryProxySession(final String url, final Connection con) { + Matcher m = PROXY_USER.matcher(url); + if (m.matches()) { + Properties props = new Properties(); + props.put("PROXY_USER_NAME", m.group(1)); + try { + Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class); + method.setAccessible(true); + method.invoke(con, 1, props); + } catch (Exception e) { + Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage()); + } + } + } + + @Override + public int getDatabaseMajorVersion() throws DatabaseException { + if (databaseMajorVersion == null) { + return super.getDatabaseMajorVersion(); + } else { + return databaseMajorVersion; + } + } + + @Override + public int getDatabaseMinorVersion() throws DatabaseException { + if (databaseMinorVersion == null) { + return super.getDatabaseMinorVersion(); + } else { + return databaseMinorVersion; + } + } + + @Override + public String getJdbcCatalogName(CatalogAndSchema schema) { + return null; + } + + @Override + public String getJdbcSchemaName(CatalogAndSchema schema) { + return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class); + } + + @Override + protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) { + if (StringUtil.isEmpty(generationType)) { + return super.getAutoIncrementClause(); + } + + String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ] + String generationStrategy = generationType; + if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) { + generationStrategy += " ON NULL"; + } + return String.format(autoIncrementClause, generationStrategy); + } + + @Override + public String generatePrimaryKeyName(String tableName) { + if (tableName.length() > 27) { + //noinspection HardCodedStringLiteral + return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27); + } else { + //noinspection HardCodedStringLiteral + return "PK_" + tableName.toUpperCase(Locale.US); + } + } + + @Override + public boolean isReservedWord(String objectName) { + return reservedWords.contains(objectName.toUpperCase()); + } + + @Override + public boolean supportsSequences() { + return true; + } + + /** + * Oracle supports catalogs in liquibase terms + * + * @return false + */ + @Override + public boolean supportsSchemas() { + return false; + } + + @Override + protected String getConnectionCatalogName() throws DatabaseException { + if (getConnection() instanceof OfflineConnection) { + return getConnection().getCatalog(); + } + try { + //noinspection HardCodedStringLiteral + return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class); + } catch (Exception e) { + //noinspection HardCodedStringLiteral + Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e); + } + return null; + } + + @Override + public String getDefaultCatalogName() {//NOPMD + return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US); + } + + /** + *

Returns an Oracle date literal with the same value as a string formatted using ISO 8601.

+ * + *

Convert an ISO8601 date string to one of the following results: + * to_date('1995-05-23', 'YYYY-MM-DD') + * to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')

+ *

+ * Implementation restriction:
+ * Currently, only the following subsets of ISO8601 are supported:
+ *

    + *
  • YYYY-MM-DD
  • + *
  • YYYY-MM-DDThh:mm:ss
  • + *
+ */ + @Override + public String getDateLiteral(String isoDate) { + String normalLiteral = super.getDateLiteral(isoDate); + + if (isDateOnly(isoDate)) { + return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')"; + } else if (isTimeOnly(isoDate)) { + return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')"; + } else if (isTimestamp(isoDate)) { + return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')"; + } else if (isDateTime(isoDate)) { + int seppos = normalLiteral.lastIndexOf('.'); + if (seppos != -1) { + normalLiteral = normalLiteral.substring(0, seppos) + "'"; + } + return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')"; + } + return "UNSUPPORTED:" + isoDate; + } + + @Override + public boolean isSystemObject(DatabaseObject example) { + if (example == null) { + return false; + } + + if (this.isLiquibaseObject(example)) { + return false; + } + + if (example instanceof Schema) { + //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral + if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) { + return true; + } + //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral + if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) { + return true; + } + } else if (isSystemObject(example.getSchema())) { + return true; + } + if (example instanceof Catalog) { + //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral + if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) { + return true; + } + } else if (example.getName() != null) { + //noinspection HardCodedStringLiteral + if (example.getName().startsWith("BIN$")) { //oracle deleted table + boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin(); + if (!filteredInOriginalQuery) { + filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName()); + } + + if (filteredInOriginalQuery) { + return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof + liquibase.statement.UniqueConstraint)); + } else { + return true; + } + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("AQ$")) { //oracle AQ tables + return true; + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("DR$")) { //oracle index tables + return true; + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table + return true; + } else //noinspection HardCodedStringLiteral,HardCodedStringLiteral + if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) { + // CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed. + return true; + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations. + return true; + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations. + return true; + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables. + return true; + } else //noinspection HardCodedStringLiteral + if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object. + return true; + } else //noinspection HardCodedStringLiteral + if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema. + return true; + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence + return true; + } else //noinspection HardCodedStringLiteral + if (example.getName().startsWith("USLOG$")) { //for update materialized view + return true; + } else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables + return true; + } + } + + return super.isSystemObject(example); + } + + @Override + public boolean supportsAutoIncrement() { + // Oracle supports Identity beginning with version 12c + boolean isAutoIncrementSupported = false; + + try { + if (getDatabaseMajorVersion() >= 12) { + isAutoIncrementSupported = true; + } + + // Returning true will generate create table command with 'IDENTITY' clause, example: + // CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey)); + + // While returning false will continue to generate create table command without 'IDENTITY' clause, example: + // CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey)); + + } catch (DatabaseException ex) { + isAutoIncrementSupported = false; + } + + return isAutoIncrementSupported; + } + + +// public Set findUniqueConstraints(String schema) throws DatabaseException { +// Set returnSet = new HashSet(); +// +// List maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME")); +// +// UniqueConstraint constraint = null; +// for (Map map : maps) { +// if (constraint == null || !constraint.getName().equals(constraint.getName())) { +// returnSet.add(constraint); +// Table table = new Table((String) map.get("TABLE_NAME")); +// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table); +// } +// } +// if (constraint != null) { +// returnSet.add(constraint); +// } +// +// return returnSet; +// } + + @Override + public boolean supportsRestrictForeignKeys() { + return false; + } + + @Override + public int getDataTypeMaxParameters(String dataTypeName) { + //noinspection HardCodedStringLiteral + if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) { + return 0; + } + //noinspection HardCodedStringLiteral + if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) { + return 0; + } + return super.getDataTypeMaxParameters(dataTypeName); + } + + public String getSystemTableWhereClause(String tableNameColumn) { + List clauses = new ArrayList(Arrays.asList("BIN$", + "AQ$", + "DR$", + "SYS_IOT_OVER", + "MLOG$_", + "RUPD$_", + "WM$_", + "ISEQ$$_", + "USLOG$", + "SYS_FBA")); + + for (int i = 0;i getUserDefinedTypes() { + if (userDefinedTypes == null) { + userDefinedTypes = new HashSet<>(); + if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) { + try { + try { + //noinspection HardCodedStringLiteral + userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class)); + } catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES + //noinspection HardCodedStringLiteral + userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class)); + } + } catch (DatabaseException e) { + //ignore error + } + } + } + + return userDefinedTypes; + } + + @Override + public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) { + //noinspection HardCodedStringLiteral + if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) { + return databaseFunction.toString(); + } + if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof + SequenceCurrentValueFunction)) { + String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction); + // replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval + return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\""); + + } + + return super.generateDatabaseFunctionValue(databaseFunction); + } + + @Override + public ValidationErrors validate() { + ValidationErrors errors = super.validate(); + DatabaseConnection connection = getConnection(); + if ((connection == null) || (connection instanceof OfflineConnection)) { + //noinspection HardCodedStringLiteral + Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database"); + return errors; + } + + if (!canAccessDbaRecycleBin()) { + errors.addWarning(getDbaRecycleBinWarning()); + } + + return errors; + + } + + public String getDbaRecycleBinWarning() { + //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral, + // HardCodedStringLiteral + //noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral + return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " + + "constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " + + "referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" + + " issue.\n" + + "\n" + + "The user you used to connect to the database (" + getConnection().getConnectionUserName() + + ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " + + "Please run the following SQL to set the appropriate permissions, and try running the command again.\n" + + "\n" + + " GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";"; + } + + public boolean canAccessDbaRecycleBin() { + if (canAccessDbaRecycleBin == null) { + DatabaseConnection connection = getConnection(); + if ((connection == null) || (connection instanceof OfflineConnection)) { + return false; + } + + Statement statement = null; + try { + statement = ((JdbcConnection) connection).createStatement(); + @SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1"); + resultSet.close(); //don't need to do anything with the result set, just make sure statement ran. + this.canAccessDbaRecycleBin = true; + } catch (Exception e) { + //noinspection HardCodedStringLiteral + if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist + this.canAccessDbaRecycleBin = false; + } else { + //noinspection HardCodedStringLiteral + Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e); + this.canAccessDbaRecycleBin = false; + } + } finally { + JdbcUtils.close(null, statement); + } + } + + return canAccessDbaRecycleBin; + } + + @Override + public boolean supportsNotNullConstraintNames() { + return true; + } + + /** + * Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has + * the following form (case-insensitive comparison): + * 1st character: A-Z + * 2..n characters: A-Z0-9$_# + * The maximum length of an identifier differs by Oracle version and object type. + */ + public boolean isValidOracleIdentifier(String identifier, Class type) { + if ((identifier == null) || (identifier.length() < 1)) + return false; + + if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) + return false; + + /* + * @todo It seems we currently do not have a class for tablespace identifiers, and all other classes + * we do know seem to be supported as 12cR2 long identifiers, so: + */ + return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH); + } + + /** + * Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this + * is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare + * object types). + * + * @return the maximum length of an object identifier, in bytes + */ + public int getIdentifierMaximumLength() { + try { + if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) { + return SHORT_IDENTIFIERS_LENGTH; + } else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) { + return SHORT_IDENTIFIERS_LENGTH; + } else { + return LONG_IDENTIFIERS_LEGNTH; + } + } catch (DatabaseException ex) { + throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex); + } + + } +} diff --git a/sql/dm/flowable-patch/src/main/java/liquibase/datatype/core/BooleanType.java b/sql/dm/flowable-patch/src/main/java/liquibase/datatype/core/BooleanType.java new file mode 100644 index 000000000..cda2492e2 --- /dev/null +++ b/sql/dm/flowable-patch/src/main/java/liquibase/datatype/core/BooleanType.java @@ -0,0 +1,165 @@ +package liquibase.datatype.core; + +import liquibase.change.core.LoadDataChange; +import liquibase.database.Database; +import liquibase.database.core.*; +import liquibase.datatype.DataTypeInfo; +import liquibase.datatype.DatabaseDataType; +import liquibase.datatype.LiquibaseDataType; +import liquibase.exception.UnexpectedLiquibaseException; +import liquibase.statement.DatabaseFunction; +import liquibase.util.StringUtil; + +import java.util.Locale; +import java.util.regex.Pattern; + +@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT) +public class BooleanType extends LiquibaseDataType { + + @Override + public DatabaseDataType toDatabaseDataType(Database database) { + String originalDefinition = StringUtil.trimToEmpty(getRawDefinition()); + if ((database instanceof Firebird3Database)) { + return new DatabaseDataType("BOOLEAN"); + } + + if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) { + return new DatabaseDataType("SMALLINT"); + } else if (database instanceof MSSQLDatabase) { + return new DatabaseDataType(database.escapeDataTypeName("bit")); + } else if (database instanceof MySQLDatabase) { + if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) { + return new DatabaseDataType("BIT", getParameters()); + } + return new DatabaseDataType("BIT", 1); + } else if (database instanceof OracleDatabase) { + return new DatabaseDataType("NUMBER", 1); + } else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) { + return new DatabaseDataType("BIT"); + } else if (database instanceof DerbyDatabase) { + if (((DerbyDatabase) database).supportsBooleanDataType()) { + return new DatabaseDataType("BOOLEAN"); + } else { + return new DatabaseDataType("SMALLINT"); + } + } else if (database instanceof DB2Database) { + if (((DB2Database) database).supportsBooleanDataType()) + return new DatabaseDataType("BOOLEAN"); + else + return new DatabaseDataType("SMALLINT"); + } else if (database instanceof HsqlDatabase) { + return new DatabaseDataType("BOOLEAN"); + } else if (database instanceof PostgresDatabase) { + if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) { + return new DatabaseDataType("BIT", getParameters()); + } + } else if (database instanceof DmDatabase) { // dhb52: DM Support + return new DatabaseDataType("bit"); + } + + return super.toDatabaseDataType(database); + } + + @Override + public String objectToSql(Object value, Database database) { + if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) { + return null; + } + + String returnValue; + if (value instanceof String) { + value = ((String) value).replaceAll("'", ""); + if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) { + returnValue = this.getTrueBooleanValue(database); + } else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals( + ((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) { + returnValue = this.getFalseBooleanValue(database); + } else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) { + returnValue = "b'" + + value.toString() + .replace("b", "") + .replace("\"", "") + .replace("::it", "") + + "'::\"bit\""; + } else { + throw new UnexpectedLiquibaseException("Unknown boolean value: " + value); + } + } else if (value instanceof Long) { + if (Long.valueOf(1).equals(value)) { + returnValue = this.getTrueBooleanValue(database); + } else { + returnValue = this.getFalseBooleanValue(database); + } + } else if (value instanceof Number) { + if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) { + returnValue = this.getTrueBooleanValue(database); + } else { + returnValue = this.getFalseBooleanValue(database); + } + } else if (value instanceof DatabaseFunction) { + return value.toString(); + } else if (value instanceof Boolean) { + if (((Boolean) value)) { + returnValue = this.getTrueBooleanValue(database); + } else { + returnValue = this.getFalseBooleanValue(database); + } + } else { + throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value"); + } + + return returnValue; + } + + protected boolean isNumericBoolean(Database database) { + if (database instanceof Firebird3Database) { + return false; + } + if (database instanceof DerbyDatabase) { + return !((DerbyDatabase) database).supportsBooleanDataType(); + } else if (database instanceof DB2Database) { + return !((DB2Database) database).supportsBooleanDataType(); + } + return (database instanceof Db2zDatabase) + || (database instanceof FirebirdDatabase) + || (database instanceof MSSQLDatabase) + || (database instanceof MySQLDatabase) + || (database instanceof OracleDatabase) + || (database instanceof SQLiteDatabase) + || (database instanceof SybaseASADatabase) + || (database instanceof SybaseDatabase) + || (database instanceof DmDatabase); // dhb52: DM Support + } + + /** + * The database-specific value to use for "false" "boolean" columns. + */ + public String getFalseBooleanValue(Database database) { + if (isNumericBoolean(database)) { + return "0"; + } + if (database instanceof InformixDatabase) { + return "'f'"; + } + return "FALSE"; + } + + /** + * The database-specific value to use for "true" "boolean" columns. + */ + public String getTrueBooleanValue(Database database) { + if (isNumericBoolean(database)) { + return "1"; + } + if (database instanceof InformixDatabase) { + return "'t'"; + } + return "TRUE"; + } + + @Override + public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() { + return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN; + } + +} diff --git a/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java b/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java new file mode 100644 index 000000000..67e767fb4 --- /dev/null +++ b/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java @@ -0,0 +1,2068 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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 org.flowable.common.engine.impl; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; +import java.util.Set; + +import javax.naming.InitialContext; +import javax.sql.DataSource; + +import org.apache.commons.lang3.StringUtils; +import org.apache.ibatis.builder.xml.XMLConfigBuilder; +import org.apache.ibatis.builder.xml.XMLMapperBuilder; +import org.apache.ibatis.datasource.pooled.PooledDataSource; +import org.apache.ibatis.mapping.Environment; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory; +import org.apache.ibatis.transaction.TransactionFactory; +import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; +import org.apache.ibatis.transaction.managed.ManagedTransactionFactory; +import org.apache.ibatis.type.ArrayTypeHandler; +import org.apache.ibatis.type.BigDecimalTypeHandler; +import org.apache.ibatis.type.BlobInputStreamTypeHandler; +import org.apache.ibatis.type.BlobTypeHandler; +import org.apache.ibatis.type.BooleanTypeHandler; +import org.apache.ibatis.type.ByteTypeHandler; +import org.apache.ibatis.type.ClobTypeHandler; +import org.apache.ibatis.type.DateOnlyTypeHandler; +import org.apache.ibatis.type.DateTypeHandler; +import org.apache.ibatis.type.DoubleTypeHandler; +import org.apache.ibatis.type.FloatTypeHandler; +import org.apache.ibatis.type.IntegerTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.LongTypeHandler; +import org.apache.ibatis.type.NClobTypeHandler; +import org.apache.ibatis.type.NStringTypeHandler; +import org.apache.ibatis.type.ShortTypeHandler; +import org.apache.ibatis.type.SqlxmlTypeHandler; +import org.apache.ibatis.type.StringTypeHandler; +import org.apache.ibatis.type.TimeOnlyTypeHandler; +import org.apache.ibatis.type.TypeHandlerRegistry; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType; +import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher; +import org.flowable.common.engine.api.delegate.event.FlowableEventListener; +import org.flowable.common.engine.api.engine.EngineLifecycleListener; +import org.flowable.common.engine.impl.agenda.AgendaOperationRunner; +import org.flowable.common.engine.impl.cfg.CommandExecutorImpl; +import org.flowable.common.engine.impl.cfg.IdGenerator; +import org.flowable.common.engine.impl.cfg.TransactionContextFactory; +import org.flowable.common.engine.impl.cfg.standalone.StandaloneMybatisTransactionContextFactory; +import org.flowable.common.engine.impl.db.CommonDbSchemaManager; +import org.flowable.common.engine.impl.db.DbSqlSessionFactory; +import org.flowable.common.engine.impl.db.LogSqlExecutionTimePlugin; +import org.flowable.common.engine.impl.db.MybatisTypeAliasConfigurator; +import org.flowable.common.engine.impl.db.MybatisTypeHandlerConfigurator; +import org.flowable.common.engine.impl.db.SchemaManager; +import org.flowable.common.engine.impl.event.EventDispatchAction; +import org.flowable.common.engine.impl.event.FlowableEventDispatcherImpl; +import org.flowable.common.engine.impl.interceptor.Command; +import org.flowable.common.engine.impl.interceptor.CommandConfig; +import org.flowable.common.engine.impl.interceptor.CommandContextFactory; +import org.flowable.common.engine.impl.interceptor.CommandContextInterceptor; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.common.engine.impl.interceptor.CommandInterceptor; +import org.flowable.common.engine.impl.interceptor.CrDbRetryInterceptor; +import org.flowable.common.engine.impl.interceptor.DefaultCommandInvoker; +import org.flowable.common.engine.impl.interceptor.LogInterceptor; +import org.flowable.common.engine.impl.interceptor.SessionFactory; +import org.flowable.common.engine.impl.interceptor.TransactionContextInterceptor; +import org.flowable.common.engine.impl.lock.LockManager; +import org.flowable.common.engine.impl.lock.LockManagerImpl; +import org.flowable.common.engine.impl.logging.LoggingListener; +import org.flowable.common.engine.impl.logging.LoggingSession; +import org.flowable.common.engine.impl.logging.LoggingSessionFactory; +import org.flowable.common.engine.impl.persistence.GenericManagerFactory; +import org.flowable.common.engine.impl.persistence.StrongUuidGenerator; +import org.flowable.common.engine.impl.persistence.cache.EntityCache; +import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl; +import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManager; +import org.flowable.common.engine.impl.persistence.entity.ByteArrayEntityManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.Entity; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManager; +import org.flowable.common.engine.impl.persistence.entity.PropertyEntityManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.TableDataManager; +import org.flowable.common.engine.impl.persistence.entity.TableDataManagerImpl; +import org.flowable.common.engine.impl.persistence.entity.data.ByteArrayDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.PropertyDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisByteArrayDataManager; +import org.flowable.common.engine.impl.persistence.entity.data.impl.MybatisPropertyDataManager; +import org.flowable.common.engine.impl.runtime.Clock; +import org.flowable.common.engine.impl.service.CommonEngineServiceImpl; +import org.flowable.common.engine.impl.util.DefaultClockImpl; +import org.flowable.common.engine.impl.util.IoUtil; +import org.flowable.common.engine.impl.util.ReflectUtil; +import org.flowable.eventregistry.api.EventRegistryEventConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +public abstract class AbstractEngineConfiguration { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + /** The tenant id indicating 'no tenant' */ + public static final String NO_TENANT_ID = ""; + + /** + * Checks the version of the DB schema against the library when the form engine is being created and throws an exception if the versions don't match. + */ + public static final String DB_SCHEMA_UPDATE_FALSE = "false"; + public static final String DB_SCHEMA_UPDATE_CREATE = "create"; + public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop"; + + /** + * Creates the schema when the form engine is being created and drops the schema when the form engine is being closed. + */ + public static final String DB_SCHEMA_UPDATE_DROP_CREATE = "drop-create"; + + /** + * Upon building of the process engine, a check is performed and an update of the schema is performed if it is necessary. + */ + public static final String DB_SCHEMA_UPDATE_TRUE = "true"; + + protected boolean forceCloseMybatisConnectionPool = true; + + protected String databaseType; + protected String jdbcDriver = "org.h2.Driver"; + protected String jdbcUrl = "jdbc:h2:tcp://localhost/~/flowable"; + protected String jdbcUsername = "sa"; + protected String jdbcPassword = ""; + protected String dataSourceJndiName; + protected int jdbcMaxActiveConnections = 16; + protected int jdbcMaxIdleConnections = 8; + protected int jdbcMaxCheckoutTime; + protected int jdbcMaxWaitTime; + protected boolean jdbcPingEnabled; + protected String jdbcPingQuery; + protected int jdbcPingConnectionNotUsedFor; + protected int jdbcDefaultTransactionIsolationLevel; + protected DataSource dataSource; + protected SchemaManager commonSchemaManager; + protected SchemaManager schemaManager; + protected Command schemaManagementCmd; + + protected String databaseSchemaUpdate = DB_SCHEMA_UPDATE_FALSE; + + /** + * Whether to use a lock when performing the database schema create or update operations. + */ + protected boolean useLockForDatabaseSchemaUpdate = false; + + protected String xmlEncoding = "UTF-8"; + + // COMMAND EXECUTORS /////////////////////////////////////////////// + + protected CommandExecutor commandExecutor; + protected Collection defaultCommandInterceptors; + protected CommandConfig defaultCommandConfig; + protected CommandConfig schemaCommandConfig; + protected CommandContextFactory commandContextFactory; + protected CommandInterceptor commandInvoker; + + protected AgendaOperationRunner agendaOperationRunner = (commandContext, runnable) -> runnable.run(); + + protected List customPreCommandInterceptors; + protected List customPostCommandInterceptors; + protected List commandInterceptors; + + protected Map engineConfigurations = new HashMap<>(); + protected Map serviceConfigurations = new HashMap<>(); + + protected ClassLoader classLoader; + /** + * Either use Class.forName or ClassLoader.loadClass for class loading. See http://forums.activiti.org/content/reflectutilloadclass-and-custom- classloader + */ + protected boolean useClassForNameClassLoading = true; + + protected List engineLifecycleListeners; + + // Event Registry ////////////////////////////////////////////////// + protected Map eventRegistryEventConsumers = new HashMap<>(); + + // MYBATIS SQL SESSION FACTORY ///////////////////////////////////// + + protected boolean isDbHistoryUsed = true; + protected DbSqlSessionFactory dbSqlSessionFactory; + protected SqlSessionFactory sqlSessionFactory; + protected TransactionFactory transactionFactory; + protected TransactionContextFactory transactionContextFactory; + + /** + * If set to true, enables bulk insert (grouping sql inserts together). Default true. + * For some databases (eg DB2+z/OS) needs to be set to false. + */ + protected boolean isBulkInsertEnabled = true; + + /** + * Some databases have a limit of how many parameters one sql insert can have (eg SQL Server, 2000 params (!= insert statements) ). Tweak this parameter in case of exceptions indicating too much + * is being put into one bulk insert, or make it higher if your database can cope with it and there are inserts with a huge amount of data. + *

+ * By default: 100 (55 for mssql server as it has a hard limit of 2000 parameters in a statement) + */ + protected int maxNrOfStatementsInBulkInsert = 100; + + public int DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER = 55; // currently Execution has most params (35). 2000 / 35 = 57. + + protected String mybatisMappingFile; + protected Set> customMybatisMappers; + protected Set customMybatisXMLMappers; + protected List customMybatisInterceptors; + + protected Set dependentEngineMyBatisXmlMappers; + protected List dependentEngineMybatisTypeAliasConfigs; + protected List dependentEngineMybatisTypeHandlerConfigs; + + // SESSION FACTORIES /////////////////////////////////////////////// + protected List customSessionFactories; + protected Map, SessionFactory> sessionFactories; + + protected boolean enableEventDispatcher = true; + protected FlowableEventDispatcher eventDispatcher; + protected List eventListeners; + protected Map> typedEventListeners; + protected List additionalEventDispatchActions; + + protected LoggingListener loggingListener; + + protected boolean transactionsExternallyManaged; + + /** + * Flag that can be set to configure or not a relational database is used. This is useful for custom implementations that do not use relational databases at all. + * + * If true (default), the {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will be used to determine what needs to happen wrt the database schema. + * + * If false, no validation or schema creation will be done. That means that the database schema must have been created 'manually' before but the engine does not validate whether the schema is + * correct. The {@link AbstractEngineConfiguration#getDatabaseSchemaUpdate()} value will not be used. + */ + protected boolean usingRelationalDatabase = true; + + /** + * Flag that can be set to configure whether or not a schema is used. This is useful for custom implementations that do not use relational databases at all. + * Setting {@link #usingRelationalDatabase} to true will automatically imply using a schema. + */ + protected boolean usingSchemaMgmt = true; + + /** + * Allows configuring a database table prefix which is used for all runtime operations of the process engine. For example, if you specify a prefix named 'PRE1.', Flowable will query for executions + * in a table named 'PRE1.ACT_RU_EXECUTION_'. + * + *

+ * NOTE: the prefix is not respected by automatic database schema management. If you use {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_CREATE_DROP} or + * {@link AbstractEngineConfiguration#DB_SCHEMA_UPDATE_TRUE}, Flowable will create the database tables using the default names, regardless of the prefix configured here. + */ + protected String databaseTablePrefix = ""; + + /** + * Escape character for doing wildcard searches. + * + * This will be added at then end of queries that include for example a LIKE clause. For example: SELECT * FROM table WHERE column LIKE '%\%%' ESCAPE '\'; + */ + protected String databaseWildcardEscapeCharacter; + + /** + * database catalog to use + */ + protected String databaseCatalog = ""; + + /** + * In some situations you want to set the schema to use for table checks / generation if the database metadata doesn't return that correctly, see https://jira.codehaus.org/browse/ACT-1220, + * https://jira.codehaus.org/browse/ACT-1062 + */ + protected String databaseSchema; + + /** + * Set to true in case the defined databaseTablePrefix is a schema-name, instead of an actual table name prefix. This is relevant for checking if Flowable-tables exist, the databaseTablePrefix + * will not be used here - since the schema is taken into account already, adding a prefix for the table-check will result in wrong table-names. + */ + protected boolean tablePrefixIsSchema; + + /** + * Set to true if the latest version of a definition should be retrieved, ignoring a possible parent deployment id value + */ + protected boolean alwaysLookupLatestDefinitionVersion; + + /** + * Set to true if by default lookups should fallback to the default tenant (an empty string by default or a defined tenant value) + */ + protected boolean fallbackToDefaultTenant; + + /** + * Default tenant provider that is executed when looking up definitions, in case the global or local fallback to default tenant value is true + */ + protected DefaultTenantProvider defaultTenantProvider = (tenantId, scope, scopeKey) -> NO_TENANT_ID; + + /** + * Enables the MyBatis plugin that logs the execution time of sql statements. + */ + protected boolean enableLogSqlExecutionTime; + + protected Properties databaseTypeMappings = getDefaultDatabaseTypeMappings(); + + /** + * Duration between the checks when acquiring a lock. + */ + protected Duration lockPollRate = Duration.ofSeconds(10); + + /** + * Duration to wait for the DB Schema lock before giving up. + */ + protected Duration schemaLockWaitTime = Duration.ofMinutes(5); + + // DATA MANAGERS ////////////////////////////////////////////////////////////////// + + protected PropertyDataManager propertyDataManager; + protected ByteArrayDataManager byteArrayDataManager; + protected TableDataManager tableDataManager; + + // ENTITY MANAGERS //////////////////////////////////////////////////////////////// + + protected PropertyEntityManager propertyEntityManager; + protected ByteArrayEntityManager byteArrayEntityManager; + + protected List customPreDeployers; + protected List customPostDeployers; + protected List deployers; + + // CONFIGURATORS //////////////////////////////////////////////////////////// + + protected boolean enableConfiguratorServiceLoader = true; // Enabled by default. In certain environments this should be set to false (eg osgi) + protected List configurators; // The injected configurators + protected List allConfigurators; // Including auto-discovered configurators + protected EngineConfigurator idmEngineConfigurator; + protected EngineConfigurator eventRegistryConfigurator; + + public static final String PRODUCT_NAME_POSTGRES = "PostgreSQL"; + public static final String PRODUCT_NAME_CRDB = "CockroachDB"; + + public static final String DATABASE_TYPE_H2 = "h2"; + public static final String DATABASE_TYPE_HSQL = "hsql"; + public static final String DATABASE_TYPE_MYSQL = "mysql"; + public static final String DATABASE_TYPE_ORACLE = "oracle"; + public static final String DATABASE_TYPE_POSTGRES = "postgres"; + public static final String DATABASE_TYPE_MSSQL = "mssql"; + public static final String DATABASE_TYPE_DB2 = "db2"; + public static final String DATABASE_TYPE_COCKROACHDB = "cockroachdb"; + + public static Properties getDefaultDatabaseTypeMappings() { + Properties databaseTypeMappings = new Properties(); + databaseTypeMappings.setProperty("H2", DATABASE_TYPE_H2); + databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL); + databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL); + databaseTypeMappings.setProperty("MariaDB", DATABASE_TYPE_MYSQL); + databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE); + databaseTypeMappings.setProperty(PRODUCT_NAME_POSTGRES, DATABASE_TYPE_POSTGRES); + databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL); + databaseTypeMappings.setProperty(DATABASE_TYPE_DB2, DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/NT", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/NT64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2 UDP", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUX", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUX390", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXX8664", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXZ64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXPPC64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/LINUXPPC64LE", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/400 SQL", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/6000", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2 UDB iSeries", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/AIX64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/HPUX", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/HP64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/SUN", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/SUN64", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/PTX", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2); + databaseTypeMappings.setProperty(PRODUCT_NAME_CRDB, DATABASE_TYPE_COCKROACHDB); + databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_ORACLE); + return databaseTypeMappings; + } + + protected Map beans; + + protected IdGenerator idGenerator; + protected boolean usePrefixId; + + protected Clock clock; + protected ObjectMapper objectMapper; + + // Variables + + public static final int DEFAULT_GENERIC_MAX_LENGTH_STRING = 4000; + public static final int DEFAULT_ORACLE_MAX_LENGTH_STRING = 2000; + + /** + * Define a max length for storing String variable types in the database. Mainly used for the Oracle NVARCHAR2 limit of 2000 characters + */ + protected int maxLengthStringVariableType = -1; + + protected void initEngineConfigurations() { + addEngineConfiguration(getEngineCfgKey(), getEngineScopeType(), this); + } + + // DataSource + // /////////////////////////////////////////////////////////////// + + protected void initDataSource() { + if (dataSource == null) { + if (dataSourceJndiName != null) { + try { + dataSource = (DataSource) new InitialContext().lookup(dataSourceJndiName); + } catch (Exception e) { + throw new FlowableException("couldn't lookup datasource from " + dataSourceJndiName + ": " + e.getMessage(), e); + } + + } else if (jdbcUrl != null) { + if ((jdbcDriver == null) || (jdbcUsername == null)) { + throw new FlowableException("DataSource or JDBC properties have to be specified in a process engine configuration"); + } + + logger.debug("initializing datasource to db: {}", jdbcUrl); + + if (logger.isInfoEnabled()) { + logger.info("Configuring Datasource with following properties (omitted password for security)"); + logger.info("datasource driver : {}", jdbcDriver); + logger.info("datasource url : {}", jdbcUrl); + logger.info("datasource user name : {}", jdbcUsername); + } + + PooledDataSource pooledDataSource = new PooledDataSource(this.getClass().getClassLoader(), jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword); + + if (jdbcMaxActiveConnections > 0) { + pooledDataSource.setPoolMaximumActiveConnections(jdbcMaxActiveConnections); + } + if (jdbcMaxIdleConnections > 0) { + pooledDataSource.setPoolMaximumIdleConnections(jdbcMaxIdleConnections); + } + if (jdbcMaxCheckoutTime > 0) { + pooledDataSource.setPoolMaximumCheckoutTime(jdbcMaxCheckoutTime); + } + if (jdbcMaxWaitTime > 0) { + pooledDataSource.setPoolTimeToWait(jdbcMaxWaitTime); + } + if (jdbcPingEnabled) { + pooledDataSource.setPoolPingEnabled(true); + if (jdbcPingQuery != null) { + pooledDataSource.setPoolPingQuery(jdbcPingQuery); + } + pooledDataSource.setPoolPingConnectionsNotUsedFor(jdbcPingConnectionNotUsedFor); + } + if (jdbcDefaultTransactionIsolationLevel > 0) { + pooledDataSource.setDefaultTransactionIsolationLevel(jdbcDefaultTransactionIsolationLevel); + } + dataSource = pooledDataSource; + } + } + + if (databaseType == null) { + initDatabaseType(); + } + } + + public void initDatabaseType() { + Connection connection = null; + try { + connection = dataSource.getConnection(); + DatabaseMetaData databaseMetaData = connection.getMetaData(); + String databaseProductName = databaseMetaData.getDatabaseProductName(); + logger.debug("database product name: '{}'", databaseProductName); + + // CRDB does not expose the version through the jdbc driver, so we need to fetch it through version(). + if (PRODUCT_NAME_POSTGRES.equalsIgnoreCase(databaseProductName)) { + try (PreparedStatement preparedStatement = connection.prepareStatement("select version() as version;"); + ResultSet resultSet = preparedStatement.executeQuery()) { + String version = null; + if (resultSet.next()) { + version = resultSet.getString("version"); + } + + if (StringUtils.isNotEmpty(version) && version.toLowerCase().startsWith(PRODUCT_NAME_CRDB.toLowerCase())) { + databaseProductName = PRODUCT_NAME_CRDB; + logger.info("CockroachDB version '{}' detected", version); + } + } + } + + databaseType = databaseTypeMappings.getProperty(databaseProductName); + if (databaseType == null) { + throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'"); + } + logger.debug("using database type: {}", databaseType); + + } catch (SQLException e) { + throw new RuntimeException("Exception while initializing Database connection", e); + } finally { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + logger.error("Exception while closing the Database connection", e); + } + } + + // Special care for MSSQL, as it has a hard limit of 2000 params per statement (incl bulk statement). + // Especially with executions, with 100 as default, this limit is passed. + if (DATABASE_TYPE_MSSQL.equals(databaseType)) { + maxNrOfStatementsInBulkInsert = DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER; + } + } + + public void initSchemaManager() { + if (this.commonSchemaManager == null) { + this.commonSchemaManager = new CommonDbSchemaManager(); + } + } + + // session factories //////////////////////////////////////////////////////// + + public void addSessionFactory(SessionFactory sessionFactory) { + sessionFactories.put(sessionFactory.getSessionType(), sessionFactory); + } + + public void initCommandContextFactory() { + if (commandContextFactory == null) { + commandContextFactory = new CommandContextFactory(); + } + } + + public void initTransactionContextFactory() { + if (transactionContextFactory == null) { + transactionContextFactory = new StandaloneMybatisTransactionContextFactory(); + } + } + + public void initCommandExecutors() { + initDefaultCommandConfig(); + initSchemaCommandConfig(); + initCommandInvoker(); + initCommandInterceptors(); + initCommandExecutor(); + } + + + public void initDefaultCommandConfig() { + if (defaultCommandConfig == null) { + defaultCommandConfig = new CommandConfig(); + } + } + + public void initSchemaCommandConfig() { + if (schemaCommandConfig == null) { + schemaCommandConfig = new CommandConfig(); + } + } + + public void initCommandInvoker() { + if (commandInvoker == null) { + commandInvoker = new DefaultCommandInvoker(); + } + } + + public void initCommandInterceptors() { + if (commandInterceptors == null) { + commandInterceptors = new ArrayList<>(); + if (customPreCommandInterceptors != null) { + commandInterceptors.addAll(customPreCommandInterceptors); + } + commandInterceptors.addAll(getDefaultCommandInterceptors()); + if (customPostCommandInterceptors != null) { + commandInterceptors.addAll(customPostCommandInterceptors); + } + commandInterceptors.add(commandInvoker); + } + } + + public Collection getDefaultCommandInterceptors() { + if (defaultCommandInterceptors == null) { + List interceptors = new ArrayList<>(); + interceptors.add(new LogInterceptor()); + + if (DATABASE_TYPE_COCKROACHDB.equals(databaseType)) { + interceptors.add(new CrDbRetryInterceptor()); + } + + CommandInterceptor transactionInterceptor = createTransactionInterceptor(); + if (transactionInterceptor != null) { + interceptors.add(transactionInterceptor); + } + + if (commandContextFactory != null) { + String engineCfgKey = getEngineCfgKey(); + CommandContextInterceptor commandContextInterceptor = new CommandContextInterceptor(commandContextFactory, + classLoader, useClassForNameClassLoading, clock, objectMapper); + engineConfigurations.put(engineCfgKey, this); + commandContextInterceptor.setEngineCfgKey(engineCfgKey); + commandContextInterceptor.setEngineConfigurations(engineConfigurations); + interceptors.add(commandContextInterceptor); + } + + if (transactionContextFactory != null) { + interceptors.add(new TransactionContextInterceptor(transactionContextFactory)); + } + + List additionalCommandInterceptors = getAdditionalDefaultCommandInterceptors(); + if (additionalCommandInterceptors != null) { + interceptors.addAll(additionalCommandInterceptors); + } + + defaultCommandInterceptors = interceptors; + } + return defaultCommandInterceptors; + } + + public abstract String getEngineCfgKey(); + + public abstract String getEngineScopeType(); + + public List getAdditionalDefaultCommandInterceptors() { + return null; + } + + public void initCommandExecutor() { + if (commandExecutor == null) { + CommandInterceptor first = initInterceptorChain(commandInterceptors); + commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first); + } + } + + public CommandInterceptor initInterceptorChain(List chain) { + if (chain == null || chain.isEmpty()) { + throw new FlowableException("invalid command interceptor chain configuration: " + chain); + } + for (int i = 0; i < chain.size() - 1; i++) { + chain.get(i).setNext(chain.get(i + 1)); + } + return chain.get(0); + } + + public abstract CommandInterceptor createTransactionInterceptor(); + + + public void initBeans() { + if (beans == null) { + beans = new HashMap<>(); + } + } + + // id generator + // ///////////////////////////////////////////////////////////// + + public void initIdGenerator() { + if (idGenerator == null) { + idGenerator = new StrongUuidGenerator(); + } + } + + public void initObjectMapper() { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + } + } + + public void initClock() { + if (clock == null) { + clock = new DefaultClockImpl(); + } + } + + // Data managers /////////////////////////////////////////////////////////// + + public void initDataManagers() { + if (propertyDataManager == null) { + propertyDataManager = new MybatisPropertyDataManager(idGenerator); + } + + if (byteArrayDataManager == null) { + byteArrayDataManager = new MybatisByteArrayDataManager(idGenerator); + } + } + + // Entity managers ////////////////////////////////////////////////////////// + + public void initEntityManagers() { + if (propertyEntityManager == null) { + propertyEntityManager = new PropertyEntityManagerImpl(this, propertyDataManager); + } + + if (byteArrayEntityManager == null) { + byteArrayEntityManager = new ByteArrayEntityManagerImpl(byteArrayDataManager, getEngineCfgKey(), this::getEventDispatcher); + } + + if (tableDataManager == null) { + tableDataManager = new TableDataManagerImpl(this); + } + } + + // services + // ///////////////////////////////////////////////////////////////// + + protected void initService(Object service) { + if (service instanceof CommonEngineServiceImpl) { + ((CommonEngineServiceImpl) service).setCommandExecutor(commandExecutor); + } + } + + // myBatis SqlSessionFactory + // //////////////////////////////////////////////// + + public void initSessionFactories() { + if (sessionFactories == null) { + sessionFactories = new HashMap<>(); + + if (usingRelationalDatabase) { + initDbSqlSessionFactory(); + } + + addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class)); + + if (isLoggingSessionEnabled()) { + if (!sessionFactories.containsKey(LoggingSession.class)) { + LoggingSessionFactory loggingSessionFactory = new LoggingSessionFactory(); + loggingSessionFactory.setLoggingListener(loggingListener); + loggingSessionFactory.setObjectMapper(objectMapper); + sessionFactories.put(LoggingSession.class, loggingSessionFactory); + } + } + + commandContextFactory.setSessionFactories(sessionFactories); + + } else { + if (usingRelationalDatabase) { + initDbSqlSessionFactoryEntitySettings(); + } + } + + if (customSessionFactories != null) { + for (SessionFactory sessionFactory : customSessionFactories) { + addSessionFactory(sessionFactory); + } + } + } + + public void initDbSqlSessionFactory() { + if (dbSqlSessionFactory == null) { + dbSqlSessionFactory = createDbSqlSessionFactory(); + } + dbSqlSessionFactory.setDatabaseType(databaseType); + dbSqlSessionFactory.setSqlSessionFactory(sqlSessionFactory); + dbSqlSessionFactory.setDbHistoryUsed(isDbHistoryUsed); + dbSqlSessionFactory.setDatabaseTablePrefix(databaseTablePrefix); + dbSqlSessionFactory.setTablePrefixIsSchema(tablePrefixIsSchema); + dbSqlSessionFactory.setDatabaseCatalog(databaseCatalog); + dbSqlSessionFactory.setDatabaseSchema(databaseSchema); + dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(maxNrOfStatementsInBulkInsert); + + initDbSqlSessionFactoryEntitySettings(); + + addSessionFactory(dbSqlSessionFactory); + } + + public DbSqlSessionFactory createDbSqlSessionFactory() { + return new DbSqlSessionFactory(usePrefixId); + } + + protected abstract void initDbSqlSessionFactoryEntitySettings(); + + protected void defaultInitDbSqlSessionFactoryEntitySettings(List> insertOrder, List> deleteOrder) { + if (insertOrder != null) { + for (Class clazz : insertOrder) { + dbSqlSessionFactory.getInsertionOrder().add(clazz); + + if (isBulkInsertEnabled) { + dbSqlSessionFactory.getBulkInserteableEntityClasses().add(clazz); + } + } + } + + if (deleteOrder != null) { + for (Class clazz : deleteOrder) { + dbSqlSessionFactory.getDeletionOrder().add(clazz); + } + } + } + + public void initTransactionFactory() { + if (transactionFactory == null) { + if (transactionsExternallyManaged) { + transactionFactory = new ManagedTransactionFactory(); + Properties properties = new Properties(); + properties.put("closeConnection", "false"); + this.transactionFactory.setProperties(properties); + } else { + transactionFactory = new JdbcTransactionFactory(); + } + } + } + + public void initSqlSessionFactory() { + if (sqlSessionFactory == null) { + InputStream inputStream = null; + try { + inputStream = getMyBatisXmlConfigurationStream(); + + Environment environment = new Environment("default", transactionFactory, dataSource); + Reader reader = new InputStreamReader(inputStream); + Properties properties = new Properties(); + properties.put("prefix", databaseTablePrefix); + + String wildcardEscapeClause = ""; + if ((databaseWildcardEscapeCharacter != null) && (databaseWildcardEscapeCharacter.length() != 0)) { + wildcardEscapeClause = " escape '" + databaseWildcardEscapeCharacter + "'"; + } + properties.put("wildcardEscapeClause", wildcardEscapeClause); + + // set default properties + properties.put("limitBefore", ""); + properties.put("limitAfter", ""); + properties.put("limitBetween", ""); + properties.put("limitBeforeNativeQuery", ""); + properties.put("limitAfterNativeQuery", ""); + properties.put("blobType", "BLOB"); + properties.put("boolValue", "TRUE"); + + if (databaseType != null) { + properties.load(getResourceAsStream(pathToEngineDbProperties())); + } + + Configuration configuration = initMybatisConfiguration(environment, reader, properties); + sqlSessionFactory = new DefaultSqlSessionFactory(configuration); + + } catch (Exception e) { + throw new FlowableException("Error while building ibatis SqlSessionFactory: " + e.getMessage(), e); + } finally { + IoUtil.closeSilently(inputStream); + } + } else { + // This is needed when the SQL Session Factory is created by another engine. + // When custom XML Mappers are registered with this engine they need to be loaded in the configuration as well + applyCustomMybatisCustomizations(sqlSessionFactory.getConfiguration()); + } + } + + public String pathToEngineDbProperties() { + return "org/flowable/common/db/properties/" + databaseType + ".properties"; + } + + public Configuration initMybatisConfiguration(Environment environment, Reader reader, Properties properties) { + XMLConfigBuilder parser = new XMLConfigBuilder(reader, "", properties); + Configuration configuration = parser.getConfiguration(); + + if (databaseType != null) { + configuration.setDatabaseId(databaseType); + } + + configuration.setEnvironment(environment); + + initMybatisTypeHandlers(configuration); + initCustomMybatisInterceptors(configuration); + if (isEnableLogSqlExecutionTime()) { + initMyBatisLogSqlExecutionTimePlugin(configuration); + } + + configuration = parseMybatisConfiguration(parser); + return configuration; + } + + public void initCustomMybatisMappers(Configuration configuration) { + if (getCustomMybatisMappers() != null) { + for (Class clazz : getCustomMybatisMappers()) { + if (!configuration.hasMapper(clazz)) { + configuration.addMapper(clazz); + } + } + } + } + + public void initMybatisTypeHandlers(Configuration configuration) { + // When mapping into Map there is currently a problem with MyBatis. + // It will return objects which are driver specific. + // Therefore we are registering the mappings between Object.class and the specific jdbc type here. + // see https://github.com/mybatis/mybatis-3/issues/2216 for more info + TypeHandlerRegistry handlerRegistry = configuration.getTypeHandlerRegistry(); + + handlerRegistry.register(Object.class, JdbcType.BOOLEAN, new BooleanTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.BIT, new BooleanTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.TINYINT, new ByteTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.SMALLINT, new ShortTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.INTEGER, new IntegerTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.FLOAT, new FloatTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.DOUBLE, new DoubleTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.CHAR, new StringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.CLOB, new ClobTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.VARCHAR, new StringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.LONGVARCHAR, new StringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NVARCHAR, new NStringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NCHAR, new NStringTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NCLOB, new NClobTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.BIGINT, new LongTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.REAL, new BigDecimalTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.DECIMAL, new BigDecimalTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.NUMERIC, new BigDecimalTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.BLOB, new BlobInputStreamTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.DATE, new DateOnlyTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.TIME, new TimeOnlyTypeHandler()); + handlerRegistry.register(Object.class, JdbcType.TIMESTAMP, new DateTypeHandler()); + + handlerRegistry.register(Object.class, JdbcType.SQLXML, new SqlxmlTypeHandler()); + } + + public void initCustomMybatisInterceptors(Configuration configuration) { + if (customMybatisInterceptors!=null){ + for (Interceptor interceptor :customMybatisInterceptors){ + configuration.addInterceptor(interceptor); + } + } + } + + public void initMyBatisLogSqlExecutionTimePlugin(Configuration configuration) { + configuration.addInterceptor(new LogSqlExecutionTimePlugin()); + } + + public Configuration parseMybatisConfiguration(XMLConfigBuilder parser) { + Configuration configuration = parser.parse(); + + applyCustomMybatisCustomizations(configuration); + return configuration; + } + + protected void applyCustomMybatisCustomizations(Configuration configuration) { + initCustomMybatisMappers(configuration); + + if (dependentEngineMybatisTypeAliasConfigs != null) { + for (MybatisTypeAliasConfigurator typeAliasConfig : dependentEngineMybatisTypeAliasConfigs) { + typeAliasConfig.configure(configuration.getTypeAliasRegistry()); + } + } + if (dependentEngineMybatisTypeHandlerConfigs != null) { + for (MybatisTypeHandlerConfigurator typeHandlerConfig : dependentEngineMybatisTypeHandlerConfigs) { + typeHandlerConfig.configure(configuration.getTypeHandlerRegistry()); + } + } + + parseDependentEngineMybatisXMLMappers(configuration); + parseCustomMybatisXMLMappers(configuration); + } + + public void parseCustomMybatisXMLMappers(Configuration configuration) { + if (getCustomMybatisXMLMappers() != null) { + for (String resource : getCustomMybatisXMLMappers()) { + parseMybatisXmlMapping(configuration, resource); + } + } + } + + public void parseDependentEngineMybatisXMLMappers(Configuration configuration) { + if (getDependentEngineMyBatisXmlMappers() != null) { + for (String resource : getDependentEngineMyBatisXmlMappers()) { + parseMybatisXmlMapping(configuration, resource); + } + } + } + + protected void parseMybatisXmlMapping(Configuration configuration, String resource) { + // see XMLConfigBuilder.mapperElement() + XMLMapperBuilder mapperParser = new XMLMapperBuilder(getResourceAsStream(resource), configuration, resource, configuration.getSqlFragments()); + mapperParser.parse(); + } + + protected InputStream getResourceAsStream(String resource) { + ClassLoader classLoader = getClassLoader(); + if (classLoader != null) { + return getClassLoader().getResourceAsStream(resource); + } else { + return this.getClass().getClassLoader().getResourceAsStream(resource); + } + } + + public void setMybatisMappingFile(String file) { + this.mybatisMappingFile = file; + } + + public String getMybatisMappingFile() { + return mybatisMappingFile; + } + + public abstract InputStream getMyBatisXmlConfigurationStream(); + + public void initConfigurators() { + + allConfigurators = new ArrayList<>(); + allConfigurators.addAll(getEngineSpecificEngineConfigurators()); + + // Configurators that are explicitly added to the config + if (configurators != null) { + allConfigurators.addAll(configurators); + } + + // Auto discovery through ServiceLoader + if (enableConfiguratorServiceLoader) { + ClassLoader classLoader = getClassLoader(); + if (classLoader == null) { + classLoader = ReflectUtil.getClassLoader(); + } + + ServiceLoader configuratorServiceLoader = ServiceLoader.load(EngineConfigurator.class, classLoader); + int nrOfServiceLoadedConfigurators = 0; + for (EngineConfigurator configurator : configuratorServiceLoader) { + allConfigurators.add(configurator); + nrOfServiceLoadedConfigurators++; + } + + if (nrOfServiceLoadedConfigurators > 0) { + logger.info("Found {} auto-discoverable Process Engine Configurator{}", nrOfServiceLoadedConfigurators, nrOfServiceLoadedConfigurators > 1 ? "s" : ""); + } + + if (!allConfigurators.isEmpty()) { + + // Order them according to the priorities (useful for dependent + // configurator) + allConfigurators.sort(new Comparator() { + + @Override + public int compare(EngineConfigurator configurator1, EngineConfigurator configurator2) { + int priority1 = configurator1.getPriority(); + int priority2 = configurator2.getPriority(); + + if (priority1 < priority2) { + return -1; + } else if (priority1 > priority2) { + return 1; + } + return 0; + } + }); + + // Execute the configurators + logger.info("Found {} Engine Configurators in total:", allConfigurators.size()); + for (EngineConfigurator configurator : allConfigurators) { + logger.info("{} (priority:{})", configurator.getClass(), configurator.getPriority()); + } + + } + + } + } + + public void close() { + if (forceCloseMybatisConnectionPool && dataSource instanceof PooledDataSource) { + /* + * When the datasource is created by a Flowable engine (i.e. it's an instance of PooledDataSource), + * the connection pool needs to be closed when closing the engine. + * Note that calling forceCloseAll() multiple times (as is the case when running with multiple engine) is ok. + */ + ((PooledDataSource) dataSource).forceCloseAll(); + } + } + + protected List getEngineSpecificEngineConfigurators() { + // meant to be overridden if needed + return Collections.emptyList(); + } + + public void configuratorsBeforeInit() { + for (EngineConfigurator configurator : allConfigurators) { + logger.info("Executing beforeInit() of {} (priority:{})", configurator.getClass(), configurator.getPriority()); + configurator.beforeInit(this); + } + } + + public void configuratorsAfterInit() { + for (EngineConfigurator configurator : allConfigurators) { + logger.info("Executing configure() of {} (priority:{})", configurator.getClass(), configurator.getPriority()); + configurator.configure(this); + } + } + + public LockManager getLockManager(String lockName) { + return new LockManagerImpl(commandExecutor, lockName, getLockPollRate(), getEngineCfgKey()); + } + + // getters and setters + // ////////////////////////////////////////////////////// + + public abstract String getEngineName(); + + public ClassLoader getClassLoader() { + return classLoader; + } + + public AbstractEngineConfiguration setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + return this; + } + + public boolean isUseClassForNameClassLoading() { + return useClassForNameClassLoading; + } + + public AbstractEngineConfiguration setUseClassForNameClassLoading(boolean useClassForNameClassLoading) { + this.useClassForNameClassLoading = useClassForNameClassLoading; + return this; + } + + public void addEngineLifecycleListener(EngineLifecycleListener engineLifecycleListener) { + if (this.engineLifecycleListeners == null) { + this.engineLifecycleListeners = new ArrayList<>(); + } + this.engineLifecycleListeners.add(engineLifecycleListener); + } + + public List getEngineLifecycleListeners() { + return engineLifecycleListeners; + } + + public AbstractEngineConfiguration setEngineLifecycleListeners(List engineLifecycleListeners) { + this.engineLifecycleListeners = engineLifecycleListeners; + return this; + } + + public String getDatabaseType() { + return databaseType; + } + + public AbstractEngineConfiguration setDatabaseType(String databaseType) { + this.databaseType = databaseType; + return this; + } + + public DataSource getDataSource() { + return dataSource; + } + + public AbstractEngineConfiguration setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + return this; + } + + public SchemaManager getSchemaManager() { + return schemaManager; + } + + public AbstractEngineConfiguration setSchemaManager(SchemaManager schemaManager) { + this.schemaManager = schemaManager; + return this; + } + + public SchemaManager getCommonSchemaManager() { + return commonSchemaManager; + } + + public AbstractEngineConfiguration setCommonSchemaManager(SchemaManager commonSchemaManager) { + this.commonSchemaManager = commonSchemaManager; + return this; + } + + public Command getSchemaManagementCmd() { + return schemaManagementCmd; + } + + public AbstractEngineConfiguration setSchemaManagementCmd(Command schemaManagementCmd) { + this.schemaManagementCmd = schemaManagementCmd; + return this; + } + + public String getJdbcDriver() { + return jdbcDriver; + } + + public AbstractEngineConfiguration setJdbcDriver(String jdbcDriver) { + this.jdbcDriver = jdbcDriver; + return this; + } + + public String getJdbcUrl() { + return jdbcUrl; + } + + public AbstractEngineConfiguration setJdbcUrl(String jdbcUrl) { + this.jdbcUrl = jdbcUrl; + return this; + } + + public String getJdbcUsername() { + return jdbcUsername; + } + + public AbstractEngineConfiguration setJdbcUsername(String jdbcUsername) { + this.jdbcUsername = jdbcUsername; + return this; + } + + public String getJdbcPassword() { + return jdbcPassword; + } + + public AbstractEngineConfiguration setJdbcPassword(String jdbcPassword) { + this.jdbcPassword = jdbcPassword; + return this; + } + + public int getJdbcMaxActiveConnections() { + return jdbcMaxActiveConnections; + } + + public AbstractEngineConfiguration setJdbcMaxActiveConnections(int jdbcMaxActiveConnections) { + this.jdbcMaxActiveConnections = jdbcMaxActiveConnections; + return this; + } + + public int getJdbcMaxIdleConnections() { + return jdbcMaxIdleConnections; + } + + public AbstractEngineConfiguration setJdbcMaxIdleConnections(int jdbcMaxIdleConnections) { + this.jdbcMaxIdleConnections = jdbcMaxIdleConnections; + return this; + } + + public int getJdbcMaxCheckoutTime() { + return jdbcMaxCheckoutTime; + } + + public AbstractEngineConfiguration setJdbcMaxCheckoutTime(int jdbcMaxCheckoutTime) { + this.jdbcMaxCheckoutTime = jdbcMaxCheckoutTime; + return this; + } + + public int getJdbcMaxWaitTime() { + return jdbcMaxWaitTime; + } + + public AbstractEngineConfiguration setJdbcMaxWaitTime(int jdbcMaxWaitTime) { + this.jdbcMaxWaitTime = jdbcMaxWaitTime; + return this; + } + + public boolean isJdbcPingEnabled() { + return jdbcPingEnabled; + } + + public AbstractEngineConfiguration setJdbcPingEnabled(boolean jdbcPingEnabled) { + this.jdbcPingEnabled = jdbcPingEnabled; + return this; + } + + public int getJdbcPingConnectionNotUsedFor() { + return jdbcPingConnectionNotUsedFor; + } + + public AbstractEngineConfiguration setJdbcPingConnectionNotUsedFor(int jdbcPingConnectionNotUsedFor) { + this.jdbcPingConnectionNotUsedFor = jdbcPingConnectionNotUsedFor; + return this; + } + + public int getJdbcDefaultTransactionIsolationLevel() { + return jdbcDefaultTransactionIsolationLevel; + } + + public AbstractEngineConfiguration setJdbcDefaultTransactionIsolationLevel(int jdbcDefaultTransactionIsolationLevel) { + this.jdbcDefaultTransactionIsolationLevel = jdbcDefaultTransactionIsolationLevel; + return this; + } + + public String getJdbcPingQuery() { + return jdbcPingQuery; + } + + public AbstractEngineConfiguration setJdbcPingQuery(String jdbcPingQuery) { + this.jdbcPingQuery = jdbcPingQuery; + return this; + } + + public String getDataSourceJndiName() { + return dataSourceJndiName; + } + + public AbstractEngineConfiguration setDataSourceJndiName(String dataSourceJndiName) { + this.dataSourceJndiName = dataSourceJndiName; + return this; + } + + public CommandConfig getSchemaCommandConfig() { + return schemaCommandConfig; + } + + public AbstractEngineConfiguration setSchemaCommandConfig(CommandConfig schemaCommandConfig) { + this.schemaCommandConfig = schemaCommandConfig; + return this; + } + + public boolean isTransactionsExternallyManaged() { + return transactionsExternallyManaged; + } + + public AbstractEngineConfiguration setTransactionsExternallyManaged(boolean transactionsExternallyManaged) { + this.transactionsExternallyManaged = transactionsExternallyManaged; + return this; + } + + public Map getBeans() { + return beans; + } + + public AbstractEngineConfiguration setBeans(Map beans) { + this.beans = beans; + return this; + } + + public IdGenerator getIdGenerator() { + return idGenerator; + } + + public AbstractEngineConfiguration setIdGenerator(IdGenerator idGenerator) { + this.idGenerator = idGenerator; + return this; + } + + public boolean isUsePrefixId() { + return usePrefixId; + } + + public AbstractEngineConfiguration setUsePrefixId(boolean usePrefixId) { + this.usePrefixId = usePrefixId; + return this; + } + + public String getXmlEncoding() { + return xmlEncoding; + } + + public AbstractEngineConfiguration setXmlEncoding(String xmlEncoding) { + this.xmlEncoding = xmlEncoding; + return this; + } + + public CommandConfig getDefaultCommandConfig() { + return defaultCommandConfig; + } + + public AbstractEngineConfiguration setDefaultCommandConfig(CommandConfig defaultCommandConfig) { + this.defaultCommandConfig = defaultCommandConfig; + return this; + } + + public CommandExecutor getCommandExecutor() { + return commandExecutor; + } + + public AbstractEngineConfiguration setCommandExecutor(CommandExecutor commandExecutor) { + this.commandExecutor = commandExecutor; + return this; + } + + public CommandContextFactory getCommandContextFactory() { + return commandContextFactory; + } + + public AbstractEngineConfiguration setCommandContextFactory(CommandContextFactory commandContextFactory) { + this.commandContextFactory = commandContextFactory; + return this; + } + + public CommandInterceptor getCommandInvoker() { + return commandInvoker; + } + + public AbstractEngineConfiguration setCommandInvoker(CommandInterceptor commandInvoker) { + this.commandInvoker = commandInvoker; + return this; + } + + public AgendaOperationRunner getAgendaOperationRunner() { + return agendaOperationRunner; + } + + public AbstractEngineConfiguration setAgendaOperationRunner(AgendaOperationRunner agendaOperationRunner) { + this.agendaOperationRunner = agendaOperationRunner; + return this; + } + + public List getCustomPreCommandInterceptors() { + return customPreCommandInterceptors; + } + + public AbstractEngineConfiguration setCustomPreCommandInterceptors(List customPreCommandInterceptors) { + this.customPreCommandInterceptors = customPreCommandInterceptors; + return this; + } + + public List getCustomPostCommandInterceptors() { + return customPostCommandInterceptors; + } + + public AbstractEngineConfiguration setCustomPostCommandInterceptors(List customPostCommandInterceptors) { + this.customPostCommandInterceptors = customPostCommandInterceptors; + return this; + } + + public List getCommandInterceptors() { + return commandInterceptors; + } + + public AbstractEngineConfiguration setCommandInterceptors(List commandInterceptors) { + this.commandInterceptors = commandInterceptors; + return this; + } + + public Map getEngineConfigurations() { + return engineConfigurations; + } + + public AbstractEngineConfiguration setEngineConfigurations(Map engineConfigurations) { + this.engineConfigurations = engineConfigurations; + return this; + } + + public void addEngineConfiguration(String key, String scopeType, AbstractEngineConfiguration engineConfiguration) { + if (engineConfigurations == null) { + engineConfigurations = new HashMap<>(); + } + engineConfigurations.put(key, engineConfiguration); + engineConfigurations.put(scopeType, engineConfiguration); + } + + public Map getServiceConfigurations() { + return serviceConfigurations; + } + + public AbstractEngineConfiguration setServiceConfigurations(Map serviceConfigurations) { + this.serviceConfigurations = serviceConfigurations; + return this; + } + + public void addServiceConfiguration(String key, AbstractServiceConfiguration serviceConfiguration) { + if (serviceConfigurations == null) { + serviceConfigurations = new HashMap<>(); + } + serviceConfigurations.put(key, serviceConfiguration); + } + + public Map getEventRegistryEventConsumers() { + return eventRegistryEventConsumers; + } + + public AbstractEngineConfiguration setEventRegistryEventConsumers(Map eventRegistryEventConsumers) { + this.eventRegistryEventConsumers = eventRegistryEventConsumers; + return this; + } + + public void addEventRegistryEventConsumer(String key, EventRegistryEventConsumer eventRegistryEventConsumer) { + if (eventRegistryEventConsumers == null) { + eventRegistryEventConsumers = new HashMap<>(); + } + eventRegistryEventConsumers.put(key, eventRegistryEventConsumer); + } + + public AbstractEngineConfiguration setDefaultCommandInterceptors(Collection defaultCommandInterceptors) { + this.defaultCommandInterceptors = defaultCommandInterceptors; + return this; + } + + public SqlSessionFactory getSqlSessionFactory() { + return sqlSessionFactory; + } + + public AbstractEngineConfiguration setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { + this.sqlSessionFactory = sqlSessionFactory; + return this; + } + + public boolean isDbHistoryUsed() { + return isDbHistoryUsed; + } + + public AbstractEngineConfiguration setDbHistoryUsed(boolean isDbHistoryUsed) { + this.isDbHistoryUsed = isDbHistoryUsed; + return this; + } + + public DbSqlSessionFactory getDbSqlSessionFactory() { + return dbSqlSessionFactory; + } + + public AbstractEngineConfiguration setDbSqlSessionFactory(DbSqlSessionFactory dbSqlSessionFactory) { + this.dbSqlSessionFactory = dbSqlSessionFactory; + return this; + } + + public TransactionFactory getTransactionFactory() { + return transactionFactory; + } + + public AbstractEngineConfiguration setTransactionFactory(TransactionFactory transactionFactory) { + this.transactionFactory = transactionFactory; + return this; + } + + public TransactionContextFactory getTransactionContextFactory() { + return transactionContextFactory; + } + + public AbstractEngineConfiguration setTransactionContextFactory(TransactionContextFactory transactionContextFactory) { + this.transactionContextFactory = transactionContextFactory; + return this; + } + + public int getMaxNrOfStatementsInBulkInsert() { + return maxNrOfStatementsInBulkInsert; + } + + public AbstractEngineConfiguration setMaxNrOfStatementsInBulkInsert(int maxNrOfStatementsInBulkInsert) { + this.maxNrOfStatementsInBulkInsert = maxNrOfStatementsInBulkInsert; + return this; + } + + public boolean isBulkInsertEnabled() { + return isBulkInsertEnabled; + } + + public AbstractEngineConfiguration setBulkInsertEnabled(boolean isBulkInsertEnabled) { + this.isBulkInsertEnabled = isBulkInsertEnabled; + return this; + } + + public Set> getCustomMybatisMappers() { + return customMybatisMappers; + } + + public AbstractEngineConfiguration setCustomMybatisMappers(Set> customMybatisMappers) { + this.customMybatisMappers = customMybatisMappers; + return this; + } + + public Set getCustomMybatisXMLMappers() { + return customMybatisXMLMappers; + } + + public AbstractEngineConfiguration setCustomMybatisXMLMappers(Set customMybatisXMLMappers) { + this.customMybatisXMLMappers = customMybatisXMLMappers; + return this; + } + + public Set getDependentEngineMyBatisXmlMappers() { + return dependentEngineMyBatisXmlMappers; + } + + public AbstractEngineConfiguration setCustomMybatisInterceptors(List customMybatisInterceptors) { + this.customMybatisInterceptors = customMybatisInterceptors; + return this; + } + + public List getCustomMybatisInterceptors() { + return customMybatisInterceptors; + } + + public AbstractEngineConfiguration setDependentEngineMyBatisXmlMappers(Set dependentEngineMyBatisXmlMappers) { + this.dependentEngineMyBatisXmlMappers = dependentEngineMyBatisXmlMappers; + return this; + } + + public List getDependentEngineMybatisTypeAliasConfigs() { + return dependentEngineMybatisTypeAliasConfigs; + } + + public AbstractEngineConfiguration setDependentEngineMybatisTypeAliasConfigs(List dependentEngineMybatisTypeAliasConfigs) { + this.dependentEngineMybatisTypeAliasConfigs = dependentEngineMybatisTypeAliasConfigs; + return this; + } + + public List getDependentEngineMybatisTypeHandlerConfigs() { + return dependentEngineMybatisTypeHandlerConfigs; + } + + public AbstractEngineConfiguration setDependentEngineMybatisTypeHandlerConfigs(List dependentEngineMybatisTypeHandlerConfigs) { + this.dependentEngineMybatisTypeHandlerConfigs = dependentEngineMybatisTypeHandlerConfigs; + return this; + } + + public List getCustomSessionFactories() { + return customSessionFactories; + } + + public AbstractEngineConfiguration addCustomSessionFactory(SessionFactory sessionFactory) { + if (customSessionFactories == null) { + customSessionFactories = new ArrayList<>(); + } + customSessionFactories.add(sessionFactory); + return this; + } + + public AbstractEngineConfiguration setCustomSessionFactories(List customSessionFactories) { + this.customSessionFactories = customSessionFactories; + return this; + } + + public boolean isUsingRelationalDatabase() { + return usingRelationalDatabase; + } + + public AbstractEngineConfiguration setUsingRelationalDatabase(boolean usingRelationalDatabase) { + this.usingRelationalDatabase = usingRelationalDatabase; + return this; + } + + public boolean isUsingSchemaMgmt() { + return usingSchemaMgmt; + } + + public AbstractEngineConfiguration setUsingSchemaMgmt(boolean usingSchema) { + this.usingSchemaMgmt = usingSchema; + return this; + } + + public String getDatabaseTablePrefix() { + return databaseTablePrefix; + } + + public AbstractEngineConfiguration setDatabaseTablePrefix(String databaseTablePrefix) { + this.databaseTablePrefix = databaseTablePrefix; + return this; + } + + public String getDatabaseWildcardEscapeCharacter() { + return databaseWildcardEscapeCharacter; + } + + public AbstractEngineConfiguration setDatabaseWildcardEscapeCharacter(String databaseWildcardEscapeCharacter) { + this.databaseWildcardEscapeCharacter = databaseWildcardEscapeCharacter; + return this; + } + + public String getDatabaseCatalog() { + return databaseCatalog; + } + + public AbstractEngineConfiguration setDatabaseCatalog(String databaseCatalog) { + this.databaseCatalog = databaseCatalog; + return this; + } + + public String getDatabaseSchema() { + return databaseSchema; + } + + public AbstractEngineConfiguration setDatabaseSchema(String databaseSchema) { + this.databaseSchema = databaseSchema; + return this; + } + + public boolean isTablePrefixIsSchema() { + return tablePrefixIsSchema; + } + + public AbstractEngineConfiguration setTablePrefixIsSchema(boolean tablePrefixIsSchema) { + this.tablePrefixIsSchema = tablePrefixIsSchema; + return this; + } + + public boolean isAlwaysLookupLatestDefinitionVersion() { + return alwaysLookupLatestDefinitionVersion; + } + + public AbstractEngineConfiguration setAlwaysLookupLatestDefinitionVersion(boolean alwaysLookupLatestDefinitionVersion) { + this.alwaysLookupLatestDefinitionVersion = alwaysLookupLatestDefinitionVersion; + return this; + } + + public boolean isFallbackToDefaultTenant() { + return fallbackToDefaultTenant; + } + + public AbstractEngineConfiguration setFallbackToDefaultTenant(boolean fallbackToDefaultTenant) { + this.fallbackToDefaultTenant = fallbackToDefaultTenant; + return this; + } + + /** + * @return name of the default tenant + * @deprecated use {@link AbstractEngineConfiguration#getDefaultTenantProvider()} instead + */ + @Deprecated + public String getDefaultTenantValue() { + return getDefaultTenantProvider().getDefaultTenant(null, null, null); + } + + public AbstractEngineConfiguration setDefaultTenantValue(String defaultTenantValue) { + this.defaultTenantProvider = (tenantId, scope, scopeKey) -> defaultTenantValue; + return this; + } + + public DefaultTenantProvider getDefaultTenantProvider() { + return defaultTenantProvider; + } + + public AbstractEngineConfiguration setDefaultTenantProvider(DefaultTenantProvider defaultTenantProvider) { + this.defaultTenantProvider = defaultTenantProvider; + return this; + } + + public boolean isEnableLogSqlExecutionTime() { + return enableLogSqlExecutionTime; + } + + public void setEnableLogSqlExecutionTime(boolean enableLogSqlExecutionTime) { + this.enableLogSqlExecutionTime = enableLogSqlExecutionTime; + } + + public Map, SessionFactory> getSessionFactories() { + return sessionFactories; + } + + public AbstractEngineConfiguration setSessionFactories(Map, SessionFactory> sessionFactories) { + this.sessionFactories = sessionFactories; + return this; + } + + public String getDatabaseSchemaUpdate() { + return databaseSchemaUpdate; + } + + public AbstractEngineConfiguration setDatabaseSchemaUpdate(String databaseSchemaUpdate) { + this.databaseSchemaUpdate = databaseSchemaUpdate; + return this; + } + + public boolean isUseLockForDatabaseSchemaUpdate() { + return useLockForDatabaseSchemaUpdate; + } + + public AbstractEngineConfiguration setUseLockForDatabaseSchemaUpdate(boolean useLockForDatabaseSchemaUpdate) { + this.useLockForDatabaseSchemaUpdate = useLockForDatabaseSchemaUpdate; + return this; + } + + public boolean isEnableEventDispatcher() { + return enableEventDispatcher; + } + + public AbstractEngineConfiguration setEnableEventDispatcher(boolean enableEventDispatcher) { + this.enableEventDispatcher = enableEventDispatcher; + return this; + } + + public FlowableEventDispatcher getEventDispatcher() { + return eventDispatcher; + } + + public AbstractEngineConfiguration setEventDispatcher(FlowableEventDispatcher eventDispatcher) { + this.eventDispatcher = eventDispatcher; + return this; + } + + public List getEventListeners() { + return eventListeners; + } + + public AbstractEngineConfiguration setEventListeners(List eventListeners) { + this.eventListeners = eventListeners; + return this; + } + + public Map> getTypedEventListeners() { + return typedEventListeners; + } + + public AbstractEngineConfiguration setTypedEventListeners(Map> typedEventListeners) { + this.typedEventListeners = typedEventListeners; + return this; + } + + public List getAdditionalEventDispatchActions() { + return additionalEventDispatchActions; + } + + public AbstractEngineConfiguration setAdditionalEventDispatchActions(List additionalEventDispatchActions) { + this.additionalEventDispatchActions = additionalEventDispatchActions; + return this; + } + + public void initEventDispatcher() { + if (this.eventDispatcher == null) { + this.eventDispatcher = new FlowableEventDispatcherImpl(); + } + + initAdditionalEventDispatchActions(); + + this.eventDispatcher.setEnabled(enableEventDispatcher); + + initEventListeners(); + initTypedEventListeners(); + } + + protected void initEventListeners() { + if (eventListeners != null) { + for (FlowableEventListener listenerToAdd : eventListeners) { + this.eventDispatcher.addEventListener(listenerToAdd); + } + } + } + + protected void initAdditionalEventDispatchActions() { + if (this.additionalEventDispatchActions == null) { + this.additionalEventDispatchActions = new ArrayList<>(); + } + } + + protected void initTypedEventListeners() { + if (typedEventListeners != null) { + for (Map.Entry> listenersToAdd : typedEventListeners.entrySet()) { + // Extract types from the given string + FlowableEngineEventType[] types = FlowableEngineEventType.getTypesFromString(listenersToAdd.getKey()); + + for (FlowableEventListener listenerToAdd : listenersToAdd.getValue()) { + this.eventDispatcher.addEventListener(listenerToAdd, types); + } + } + } + } + + public boolean isLoggingSessionEnabled() { + return loggingListener != null; + } + + public LoggingListener getLoggingListener() { + return loggingListener; + } + + public void setLoggingListener(LoggingListener loggingListener) { + this.loggingListener = loggingListener; + } + + public Clock getClock() { + return clock; + } + + public AbstractEngineConfiguration setClock(Clock clock) { + this.clock = clock; + return this; + } + + public ObjectMapper getObjectMapper() { + return objectMapper; + } + + public AbstractEngineConfiguration setObjectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + return this; + } + + public int getMaxLengthString() { + if (maxLengthStringVariableType == -1) { + if ("oracle".equalsIgnoreCase(databaseType)) { + return DEFAULT_ORACLE_MAX_LENGTH_STRING; + } else { + return DEFAULT_GENERIC_MAX_LENGTH_STRING; + } + } else { + return maxLengthStringVariableType; + } + } + + public int getMaxLengthStringVariableType() { + return maxLengthStringVariableType; + } + + public AbstractEngineConfiguration setMaxLengthStringVariableType(int maxLengthStringVariableType) { + this.maxLengthStringVariableType = maxLengthStringVariableType; + return this; + } + + public PropertyDataManager getPropertyDataManager() { + return propertyDataManager; + } + + public Duration getLockPollRate() { + return lockPollRate; + } + + public AbstractEngineConfiguration setLockPollRate(Duration lockPollRate) { + this.lockPollRate = lockPollRate; + return this; + } + + public Duration getSchemaLockWaitTime() { + return schemaLockWaitTime; + } + + public void setSchemaLockWaitTime(Duration schemaLockWaitTime) { + this.schemaLockWaitTime = schemaLockWaitTime; + } + + public AbstractEngineConfiguration setPropertyDataManager(PropertyDataManager propertyDataManager) { + this.propertyDataManager = propertyDataManager; + return this; + } + + public PropertyEntityManager getPropertyEntityManager() { + return propertyEntityManager; + } + + public AbstractEngineConfiguration setPropertyEntityManager(PropertyEntityManager propertyEntityManager) { + this.propertyEntityManager = propertyEntityManager; + return this; + } + + public ByteArrayDataManager getByteArrayDataManager() { + return byteArrayDataManager; + } + + public AbstractEngineConfiguration setByteArrayDataManager(ByteArrayDataManager byteArrayDataManager) { + this.byteArrayDataManager = byteArrayDataManager; + return this; + } + + public ByteArrayEntityManager getByteArrayEntityManager() { + return byteArrayEntityManager; + } + + public AbstractEngineConfiguration setByteArrayEntityManager(ByteArrayEntityManager byteArrayEntityManager) { + this.byteArrayEntityManager = byteArrayEntityManager; + return this; + } + + public TableDataManager getTableDataManager() { + return tableDataManager; + } + + public AbstractEngineConfiguration setTableDataManager(TableDataManager tableDataManager) { + this.tableDataManager = tableDataManager; + return this; + } + + public List getDeployers() { + return deployers; + } + + public AbstractEngineConfiguration setDeployers(List deployers) { + this.deployers = deployers; + return this; + } + + public List getCustomPreDeployers() { + return customPreDeployers; + } + + public AbstractEngineConfiguration setCustomPreDeployers(List customPreDeployers) { + this.customPreDeployers = customPreDeployers; + return this; + } + + public List getCustomPostDeployers() { + return customPostDeployers; + } + + public AbstractEngineConfiguration setCustomPostDeployers(List customPostDeployers) { + this.customPostDeployers = customPostDeployers; + return this; + } + + public boolean isEnableConfiguratorServiceLoader() { + return enableConfiguratorServiceLoader; + } + + public AbstractEngineConfiguration setEnableConfiguratorServiceLoader(boolean enableConfiguratorServiceLoader) { + this.enableConfiguratorServiceLoader = enableConfiguratorServiceLoader; + return this; + } + + public List getConfigurators() { + return configurators; + } + + public AbstractEngineConfiguration addConfigurator(EngineConfigurator configurator) { + if (configurators == null) { + configurators = new ArrayList<>(); + } + configurators.add(configurator); + return this; + } + + /** + * @return All {@link EngineConfigurator} instances. Will only contain values after init of the engine. + * Use the {@link #getConfigurators()} or {@link #addConfigurator(EngineConfigurator)} methods otherwise. + */ + public List getAllConfigurators() { + return allConfigurators; + } + + public AbstractEngineConfiguration setConfigurators(List configurators) { + this.configurators = configurators; + return this; + } + + public EngineConfigurator getIdmEngineConfigurator() { + return idmEngineConfigurator; + } + + public AbstractEngineConfiguration setIdmEngineConfigurator(EngineConfigurator idmEngineConfigurator) { + this.idmEngineConfigurator = idmEngineConfigurator; + return this; + } + + public EngineConfigurator getEventRegistryConfigurator() { + return eventRegistryConfigurator; + } + + public AbstractEngineConfiguration setEventRegistryConfigurator(EngineConfigurator eventRegistryConfigurator) { + this.eventRegistryConfigurator = eventRegistryConfigurator; + return this; + } + + public AbstractEngineConfiguration setForceCloseMybatisConnectionPool(boolean forceCloseMybatisConnectionPool) { + this.forceCloseMybatisConnectionPool = forceCloseMybatisConnectionPool; + return this; + } + + public boolean isForceCloseMybatisConnectionPool() { + return forceCloseMybatisConnectionPool; + } +} diff --git a/sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md b/sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md new file mode 100644 index 000000000..1932c7a3f --- /dev/null +++ b/sql/dm/flowable-patch/src/main/resources/META-INF/package-info.md @@ -0,0 +1 @@ +防止IDEA将`.`和`/`混为一谈 \ No newline at end of file diff --git a/sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database b/sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database new file mode 100644 index 000000000..efbcfcca2 --- /dev/null +++ b/sql/dm/flowable-patch/src/main/resources/META-INF/services/liquibase.database.Database @@ -0,0 +1,21 @@ +liquibase.database.core.CockroachDatabase +liquibase.database.core.DB2Database +liquibase.database.core.Db2zDatabase +liquibase.database.core.DerbyDatabase +liquibase.database.core.Firebird3Database +liquibase.database.core.FirebirdDatabase +liquibase.database.core.H2Database +liquibase.database.core.HsqlDatabase +liquibase.database.core.InformixDatabase +liquibase.database.core.Ingres9Database +liquibase.database.core.MSSQLDatabase +liquibase.database.core.MariaDBDatabase +liquibase.database.core.MockDatabase +liquibase.database.core.MySQLDatabase +liquibase.database.core.OracleDatabase +liquibase.database.core.PostgresDatabase +liquibase.database.core.SQLiteDatabase +liquibase.database.core.SybaseASADatabase +liquibase.database.core.SybaseDatabase +liquibase.database.core.DmDatabase +liquibase.database.core.UnsupportedDatabase From cba0b087300c71fdffe06ce2ede5849924e36337 Mon Sep 17 00:00:00 2001 From: dhb52 Date: Wed, 8 Nov 2023 14:30:46 +0800 Subject: [PATCH 09/81] =?UTF-8?q?doc:=20=E4=BC=98=E5=8C=96=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=8Cpatch=E6=96=87=E4=BB=B6=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E6=94=BE=E5=85=A5=E4=BB=BB=E6=84=8F=E5=90=AF=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/flowable-6.8.md | 2 +- .../common/engine/impl/AbstractEngineConfiguration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/dm/flowable-6.8.md b/sql/dm/flowable-6.8.md index 7a7b10f6e..6619d7e6c 100644 --- a/sql/dm/flowable-6.8.md +++ b/sql/dm/flowable-6.8.md @@ -4,7 +4,7 @@ ## 1. 覆盖 flowable,liquibase 相关代码 -把`flowable-patch/src`下的文件按文件结果添加到`yudao-server`项目对应目录中 +把`flowable-patch/src`下的文件按文件结果添加到`yudao-server`或者`yudao-module-bpm-biz`项目对应目录中,甚至你可以做个独立模块。 ## 2. 修改相关 DAO diff --git a/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java b/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java index 67e767fb4..33c52d551 100644 --- a/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java +++ b/sql/dm/flowable-patch/src/main/java/org/flowable/common/engine/impl/AbstractEngineConfiguration.java @@ -408,7 +408,7 @@ public abstract class AbstractEngineConfiguration { databaseTypeMappings.setProperty("DB2/2", DATABASE_TYPE_DB2); databaseTypeMappings.setProperty("DB2 UDB AS400", DATABASE_TYPE_DB2); databaseTypeMappings.setProperty(PRODUCT_NAME_CRDB, DATABASE_TYPE_COCKROACHDB); - databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_ORACLE); + databaseTypeMappings.setProperty("DM DBMS", DATABASE_TYPE_ORACLE); // dhb52: DM support return databaseTypeMappings; } From daa7d14b092342b1bd7062873c9c9b7ec79cc2bc Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Wed, 8 Nov 2023 14:36:44 +0800 Subject: [PATCH 10/81] =?UTF-8?q?reafactor:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/core/mapper/BaseMapperX.java | 4 +- .../favorite/ProductFavoriteController.java | 41 +++++-------------- .../favorite/ProductFavoriteConvert.java | 11 ++--- .../favorite/ProductFavoriteDetailDO.java | 1 - .../mysql/favorite/ProductFavoriteMapper.java | 33 ++------------- .../favorite/ProductFavoriteService.java | 3 +- .../favorite/ProductFavoriteServiceImpl.java | 5 +-- 7 files changed, 24 insertions(+), 74 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index 2654bec52..db054e972 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -133,9 +133,9 @@ public interface BaseMapperX extends MPJBaseMapper { Db.saveOrUpdateBatch(collection); } - default PageResult selectJoinPage(PageParam pageParam, @Param("resultTypeClass_Eg1sG") Class var2, @Param("ew") MPJBaseJoin queryWrapper) { + default PageResult selectJoinPage(PageParam pageParam, Class resultTypeClass, MPJBaseJoin joinQueryWrapper) { IPage mpPage = MyBatisUtils.buildPage(pageParam); - selectJoinPage(mpPage, var2, queryWrapper); + selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper); // 转换返回 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java index e59c10827..9157e8a05 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java @@ -4,16 +4,15 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; -import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteBatchReqVO; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteReqVO; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteRespVO; import cn.iocoder.yudao.module.product.convert.favorite.ProductFavoriteConvert; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; -import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import cn.iocoder.yudao.module.product.service.favorite.ProductFavoriteService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -24,6 +23,7 @@ import javax.validation.Valid; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @Tag(name = "管理后台 - 商品收藏") @RestController @@ -34,6 +34,9 @@ public class ProductFavoriteController { @Resource private ProductFavoriteService productFavoriteService; + @Resource + private ProductSpuService productSpuService; + @PostMapping("/create") @Operation(summary = "添加单个商品收藏") @PreAuthorize("@ss.hasPermission('product:favorite:create')") @@ -41,14 +44,6 @@ public class ProductFavoriteController { return success(productFavoriteService.createFavorite(reqVO.getUserId(), reqVO.getSpuId())); } - @PostMapping("/create-list") - @Operation(summary = "添加多个商品收藏") - @PreAuthorize("@ss.hasPermission('product:favorite:create')") - public CommonResult createFavoriteList(@Valid @RequestBody ProductFavoriteBatchReqVO reqVO) { - // todo @jason:待实现;如果有已经收藏的,不用报错,忽略即可; - return success(Boolean.TRUE); - } - @DeleteMapping("/delete") @Operation(summary = "取消单个商品收藏") @PreAuthorize("@ss.hasPermission('product:favorite:delete')") @@ -57,26 +52,19 @@ public class ProductFavoriteController { return success(Boolean.TRUE); } - @DeleteMapping("/delete-list") - @Operation(summary = "取消单个商品收藏") - @PreAuthorize("@ss.hasPermission('product:favorite:delete')") - public CommonResult deleteFavoriteList(@Valid @RequestBody ProductFavoriteBatchReqVO reqVO) { - // todo @jason:待实现 -// productFavoriteService.deleteFavorite(getLoginUserId(), reqVO.getSpuId()); - return success(Boolean.TRUE); - } - @GetMapping("/page") @Operation(summary = "获得商品收藏分页") @PreAuthorize("@ss.hasPermission('product:favorite:query')") public CommonResult> getFavoritePage(@Valid ProductFavoritePageReqVO pageVO) { - PageResult favoritePage = productFavoriteService.getFavoritePageByFilter(pageVO); + PageResult favoritePage = productFavoriteService.getFavoritePage(pageVO); if (CollUtil.isEmpty(favoritePage.getList())) { return success(PageResult.empty()); } + List list = productSpuService.getSpuList(convertSet(favoritePage.getList(), ProductFavoriteDO::getSpuId)); + // 得到商品 spu 信息 - List favorites = ProductFavoriteConvert.INSTANCE.convertList2admin(favoritePage.getList()); + List favorites = ProductFavoriteConvert.INSTANCE.convertList2admin(favoritePage.getList(), list); // 转换 VO 结果 PageResult pageResult = new PageResult<>(favoritePage.getTotal()); @@ -92,13 +80,4 @@ public class ProductFavoriteController { ProductFavoriteDO favorite = productFavoriteService.getFavorite(reqVO.getUserId(), reqVO.getSpuId()); return success(favorite != null); } - - @GetMapping(value = "/get-count") - @Operation(summary = "获得商品收藏数量") - @Parameter(name = "userId", description = "用户编号", required = true) - @PreAuthenticated - public CommonResult getFavoriteCount(@RequestParam("userId") Long userId) { - return success(productFavoriteService.getFavoriteCount(userId)); - } - } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java index 22ffb8577..9adac8c86 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java @@ -13,6 +13,7 @@ import org.mapstruct.factory.Mappers; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; @@ -41,13 +42,13 @@ public interface ProductFavoriteConvert { @Mapping(target = "userId", source = "favorite.userId") @Mapping(target = "spuId", source = "favorite.spuId") @Mapping(target = "createTime", source = "favorite.createTime") - @Mapping(target = "favoriteStatus", constant = "1") ProductFavoriteRespVO convert2admin(ProductSpuDO spu, ProductFavoriteDO favorite); - default List convertList2admin(List favorites) { - List resultList = new ArrayList<>(favorites.size()); - for (ProductFavoriteDetailDO favorite : favorites) { - resultList.add(convert2admin(favorite.getSpuDO(), favorite)); + default List convertList2admin(List favorites, List spus) { + List resultList = new ArrayList<>(spus.size()); + for (ProductFavoriteDO favorite : favorites) { + Optional spu = spus.stream().filter(e -> e.getId().equals(favorite.getSpuId())).findFirst(); + resultList.add(convert2admin(spu.get(), favorite)); } return resultList; } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java index ba4d5b557..60e401e11 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java @@ -12,5 +12,4 @@ import lombok.Data; public class ProductFavoriteDetailDO extends ProductFavoriteDO { ProductSpuDO spuDO; - } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java index 08a5c3063..e116a7c4a 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java @@ -5,12 +5,8 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; -import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; -import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.github.yulichang.wrapper.MPJLambdaWrapper; -import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.annotations.Mapper; @Mapper @@ -27,34 +23,11 @@ public interface ProductFavoriteMapper extends BaseMapperX { .orderByDesc(ProductFavoriteDO::getId)); } - default PageResult selectPageByUserAndFields(ProductFavoritePageReqVO reqVO) { - MPJLambdaWrapper wrapper = new MPJLambdaWrapper() + default PageResult selectPageByUserId(ProductFavoritePageReqVO reqVO) { + return selectPage(reqVO, new MPJLambdaWrapper() .selectAll(ProductFavoriteDO.class) .eq(ProductFavoriteDO::getUserId, reqVO.getUserId()) - .selectAssociation(ProductSpuDO.class, ProductFavoriteDetailDO::getSpuDO); - if(StringUtils.isNotEmpty(reqVO.getName())){ - wrapper.likeRight(ProductSpuDO::getName, reqVO.getName()); - } - if(StringUtils.isNotEmpty(reqVO.getName()) && StringUtils.isNotEmpty(reqVO.getKeyword())){ - wrapper.or(); - } - if(StringUtils.isNotEmpty(reqVO.getKeyword())){ - wrapper.likeRight(ProductSpuDO::getKeyword, reqVO.getKeyword()); - } - - if(reqVO.getCreateTime() != null){ - if (reqVO.getCreateTime()[0] != null && reqVO.getCreateTime()[1] != null) { - wrapper.between(ProductFavoriteDO::getCreateTime, reqVO.getCreateTime()[0], reqVO.getCreateTime()[1]); - } - if (reqVO.getCreateTime()[0] != null) { - wrapper.ge(ProductFavoriteDO::getCreateTime, reqVO.getCreateTime()[0]); - } - if (reqVO.getCreateTime()[1] != null) { - wrapper.le(ProductFavoriteDO::getCreateTime, reqVO.getCreateTime()[1]); - } - } - - return selectJoinPage(reqVO, ProductFavoriteDetailDO.class, wrapper); + .orderByDesc(ProductFavoriteDO::getCreateTime)); } default Long selectCountByUserId(Long userId) { diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java index 87c9854a9..295af4c94 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; -import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; import javax.validation.Valid; @@ -44,7 +43,7 @@ public interface ProductFavoriteService { * * @param reqVO 请求 vo */ - PageResult getFavoritePageByFilter(@Valid ProductFavoritePageReqVO reqVO); + PageResult getFavoritePage(@Valid ProductFavoritePageReqVO reqVO); /** * 获取收藏过商品 diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java index 0f8d30ec0..945201286 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavor import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; import cn.iocoder.yudao.module.product.convert.favorite.ProductFavoriteConvert; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; -import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; import cn.iocoder.yudao.module.product.dal.mysql.favorite.ProductFavoriteMapper; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -56,8 +55,8 @@ public class ProductFavoriteServiceImpl implements ProductFavoriteService { } @Override - public PageResult getFavoritePageByFilter(@Valid ProductFavoritePageReqVO reqVO) { - return productFavoriteMapper.selectPageByUserAndFields(reqVO); + public PageResult getFavoritePage(@Valid ProductFavoritePageReqVO reqVO) { + return productFavoriteMapper.selectPageByUserId(reqVO); } @Override From 5facac733039e924c293ae892d289bd154187e9d Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Wed, 8 Nov 2023 14:37:18 +0800 Subject: [PATCH 11/81] =?UTF-8?q?refactor:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../favorite/vo/ProductFavoriteBaseVO.java | 2 -- .../vo/ProductFavoriteBatchReqVO.java | 21 ------------------- .../favorite/vo/ProductFavoritePageReqVO.java | 18 ---------------- .../favorite/vo/ProductFavoriteRespVO.java | 4 ---- 4 files changed, 45 deletions(-) delete mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java index 72b2613d5..68b0a0a16 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBaseVO.java @@ -16,6 +16,4 @@ public class ProductFavoriteBaseVO { @NotNull(message = "用户编号不能为空") private Long userId; - - } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java deleted file mode 100644 index d779ff3a8..000000000 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteBatchReqVO.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.module.product.controller.admin.favorite.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.ToString; - -import javax.validation.constraints.NotNull; -import java.util.List; - -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; - -@Schema(description = "管理后台 - 商品收藏的批量 Request VO") -@Data -@ToString(callSuper = true) -public class ProductFavoriteBatchReqVO extends ProductFavoriteBaseVO{ - - @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502") - @NotNull(message = "商品 SPU 编号数组不能为空") - private List spuIds; - -} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java index 37f8cecc3..9c0b33035 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java @@ -5,11 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @Schema(description = "管理后台 - 商品收藏分页 Request VO") @Data @@ -19,17 +14,4 @@ public class ProductFavoritePageReqVO extends PageParam { @Schema(description = "用户编号", example = "5036") private Long userId; - - @Schema(description = "商品 SPU 编号", example = "32734") - private Long spuId; - - @Schema(description = "收藏时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - - @Schema(description = "商品名称", example = "5036") - private String name; - - @Schema(description = "关键字", example = "5036") - private String keyword; } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java index 255fc631b..bbf972181 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java @@ -15,8 +15,4 @@ public class ProductFavoriteRespVO extends ProductSpuRespVO { @Schema(description = "spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") private Long spuId; - - @Schema(description = "收藏状态", example = "1") - private Integer favoriteStatus; - } From 670c0962a76fbf8e393d347c78ae66b2c4af1ee0 Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Wed, 8 Nov 2023 17:03:37 +0800 Subject: [PATCH 12/81] =?UTF-8?q?refactor:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../favorite/ProductFavoriteController.java | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java index 9157e8a05..6f66c2062 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java @@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.product.controller.admin.favorite; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; -import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteReqVO; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteRespVO; import cn.iocoder.yudao.module.product.convert.favorite.ProductFavoriteConvert; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; @@ -16,7 +14,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.validation.Valid; @@ -37,21 +37,6 @@ public class ProductFavoriteController { @Resource private ProductSpuService productSpuService; - @PostMapping("/create") - @Operation(summary = "添加单个商品收藏") - @PreAuthorize("@ss.hasPermission('product:favorite:create')") - public CommonResult createFavorite(@Valid @RequestBody ProductFavoriteReqVO reqVO) { - return success(productFavoriteService.createFavorite(reqVO.getUserId(), reqVO.getSpuId())); - } - - @DeleteMapping("/delete") - @Operation(summary = "取消单个商品收藏") - @PreAuthorize("@ss.hasPermission('product:favorite:delete')") - public CommonResult deleteFavorite(@Valid @RequestBody ProductFavoriteReqVO reqVO) { - productFavoriteService.deleteFavorite(reqVO.getUserId(), reqVO.getSpuId()); - return success(Boolean.TRUE); - } - @GetMapping("/page") @Operation(summary = "获得商品收藏分页") @PreAuthorize("@ss.hasPermission('product:favorite:query')") @@ -72,12 +57,4 @@ public class ProductFavoriteController { return success(pageResult); } - - @PostMapping(value = "/exits") - @Operation(summary = "检查是否收藏过商品") - @PreAuthenticated - public CommonResult isFavoriteExists(@Valid @RequestBody ProductFavoriteReqVO reqVO) { - ProductFavoriteDO favorite = productFavoriteService.getFavorite(reqVO.getUserId(), reqVO.getSpuId()); - return success(favorite != null); - } } From 92be763c6f35a1735d45e8fae8cce870c8808c10 Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Wed, 8 Nov 2023 17:18:28 +0800 Subject: [PATCH 13/81] =?UTF-8?q?refactor:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/product/service/favorite/ProductFavoriteService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java index 295af4c94..3dd3bd59e 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteService.java @@ -60,4 +60,5 @@ public interface ProductFavoriteService { * @return 数量 */ Long getFavoriteCount(Long userId); + } From 5a5a42463bbfad1f95a32a6c8ee1c13094f633c3 Mon Sep 17 00:00:00 2001 From: niou233 <2922564446@qq.com> Date: Wed, 8 Nov 2023 17:22:00 +0800 Subject: [PATCH 14/81] =?UTF-8?q?refactor:=20=E4=BC=9A=E5=91=98=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/favorite/ProductFavoriteServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java index 945201286..927d030d5 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/favorite/ProductFavoriteServiceImpl.java @@ -34,6 +34,7 @@ public class ProductFavoriteServiceImpl implements ProductFavoriteService { if (favorite != null) { throw exception(FAVORITE_EXISTS); } + ProductFavoriteDO entity = ProductFavoriteConvert.INSTANCE.convert(userId, spuId); productFavoriteMapper.insert(entity); return entity.getId(); From 330380c2d0dd58af81af921e6b781d95395721c4 Mon Sep 17 00:00:00 2001 From: owen Date: Fri, 17 Nov 2023 09:56:52 +0800 Subject: [PATCH 15/81] =?UTF-8?q?=E8=90=A5=E9=94=80=EF=BC=9A=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=95=86=E5=9F=8E=E8=A3=85=E4=BF=AE=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E3=80=90=E5=95=86=E5=93=81=E5=8D=A1=E7=89=87=E3=80=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/spu/vo/ProductSpuDetailRespVO.java | 14 +------------ .../admin/spu/vo/ProductSpuRespVO.java | 2 +- .../app/spu/AppProductSpuController.java | 20 +++++++++++++++++++ .../app/spu/vo/AppProductSpuPageRespVO.java | 3 +++ 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java index 1be96632d..336d44467 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java @@ -12,19 +12,7 @@ import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) -public class ProductSpuDetailRespVO extends ProductSpuBaseVO { - - @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1212") - private Long id; - - @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") - private Integer salesCount; - - @Schema(description = "浏览量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20000") - private Integer browseCount; - - @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer status; +public class ProductSpuDetailRespVO extends ProductSpuRespVO { // ========== SKU 相关字段 ========= diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java index 0148cb2a1..faf8a5572 100755 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuRespVO.java @@ -13,7 +13,7 @@ import java.time.LocalDateTime; @ToString(callSuper = true) public class ProductSpuRespVO extends ProductSpuBaseVO { - @Schema(description = "spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") private Long id; @Schema(description = "商品价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "1999") diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java index 8f49e7f74..9d0a1fe9f 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java @@ -30,6 +30,7 @@ import javax.annotation.Resource; import javax.validation.Valid; import java.util.Collections; import java.util.List; +import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -75,6 +76,25 @@ public class AppProductSpuController { return success(voList); } + @GetMapping("/list-by-ids") + @Operation(summary = "获得商品 SPU 列表") + @Parameters({ + @Parameter(name = "ids", description = "编号列表", required = true) + }) + public CommonResult> getSpuList(@RequestParam("ids") Set ids) { + List list = productSpuService.getSpuList(ids); + if (CollUtil.isEmpty(list)) { + return success(Collections.emptyList()); + } + + // 拼接返回 + List voList = ProductSpuConvert.INSTANCE.convertListForGetSpuList(list); + // 处理 vip 价格 + MemberLevelRespDTO memberLevel = getMemberLevel(); + voList.forEach(vo -> vo.setVipPrice(calculateVipPrice(vo.getPrice(), memberLevel))); + return success(voList); + } + @GetMapping("/page") @Operation(summary = "获得商品 SPU 分页") public CommonResult> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) { diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageRespVO.java index c4a66afd2..07b0d8e95 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppProductSpuPageRespVO.java @@ -15,6 +15,9 @@ public class AppProductSpuPageRespVO { @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") private String name; + @Schema(description = "商品简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉小短袖简介") + private String introduction; + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED) private Long categoryId; From df69fecbb4a7fbd3b671dd22b72e17258dc3c82c Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 17 Nov 2023 17:03:35 +0800 Subject: [PATCH 16/81] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=9A=E9=87=8D=E6=9E=84=20vue2=20=E6=A8=A1=E7=89=88?= =?UTF-8?q?=EF=BC=8C=E9=80=82=E9=85=8D=E6=A0=91=E8=A1=A8=E5=92=8C=E4=B8=BB?= =?UTF-8?q?=E5=AD=90=E8=A1=A8=EF=BC=8880%=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/ruoyi-vue-pro.sql | 5 + .../main/resources/codegen/vue/api/api.js.vm | 102 ++++- .../vue/views/components/form_sub_erp.vue.vm | 204 ++++++++++ .../views/components/form_sub_inner.vue.vm | 2 + .../views/components/form_sub_normal.vue.vm | 362 ++++++++++++++++++ .../vue/views/components/list_sub_erp.vue.vm | 181 +++++++++ .../views/components/list_sub_inner.vue.vm | 4 + .../resources/codegen/vue/views/form.vue.vm | 333 ++++++++++++++++ .../resources/codegen/vue/views/index.vue.vm | 340 ++++++++-------- .../resources/codegen/vue3/views/form.vue.vm | 2 + .../resources/codegen/vue3/views/index.vue.vm | 12 +- .../src/main/resources/application-local.yaml | 8 +- 12 files changed, 1351 insertions(+), 204 deletions(-) create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 7b13fbec0..ce201e19b 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -450,6 +450,11 @@ CREATE TABLE `infra_codegen_table` ( `template_type` tinyint NOT NULL DEFAULT 1 COMMENT '模板类型', `front_type` tinyint NOT NULL COMMENT '前端类型', `parent_menu_id` bigint NULL DEFAULT NULL COMMENT '父菜单编号', + `master_table_id` bigint NULL DEFAULT NULL COMMENT '主表的编号', + `sub_join_column_id` bigint NULL DEFAULT NULL COMMENT '【自己】子表关联主表的字段编号', + `sub_join_many` bit(1) NULL DEFAULT NULL COMMENT '主表与子表是否一对多', + `tree_parent_column_id` bigint NULL DEFAULT NULL COMMENT '树表的父字段编号', + `tree_name_column_id` bigint NULL DEFAULT NULL COMMENT '树表的名字字段编号', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm index 5e9da3238..fdafffd54 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm @@ -35,21 +35,113 @@ export function get${simpleClassName}(id) { }) } +#if ( $table.templateType != 2 ) // 获得${table.classComment}分页 -export function get${simpleClassName}Page(query) { +export function get${simpleClassName}Page(params) { return request({ url: '${baseURL}/page', method: 'get', - params: query + params }) } - +#else +// 获得${table.classComment}列表 +export function get${simpleClassName}List(params) { + return request({ + url: '${baseURL}/list', + method: 'get', + params + }) +} +#end // 导出${table.classComment} Excel -export function export${simpleClassName}Excel(query) { +export function export${simpleClassName}Excel(params) { return request({ url: '${baseURL}/export-excel', method: 'get', - params: query, + params, responseType: 'blob' }) } +## 特殊:主子表专属逻辑 +#foreach ($subTable in $subTables) + #set ($index = $foreach.count - 1) + #set ($subSimpleClassName = $subSimpleClassNames.get($index)) + #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 + #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 + #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index)) + #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) + #set ($subClassNameVar = $subClassNameVars.get($index)) + +// ==================== 子表($subTable.classComment) ==================== + ## 情况一:MASTER_ERP 时,需要分查询页子表 + #if ( $table.templateType == 11 ) + + // 获得${subTable.classComment}分页 + export function get${simpleClassName}Page(params) { + return request({ + url: '${baseURL}/${subSimpleClassName_strikeCase}/page', + method: 'get', + params + }) + } + ## 情况二:非 MASTER_ERP 时,需要列表查询子表 + #else + #if ( $subTable.subJoinMany ) + + // 获得${subTable.classComment}列表 + export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) { + return request({ + url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField}, + method: 'get' + }) + } + #else + + // 获得${subTable.classComment} + export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) { + return request({ + url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField}, + method: 'get' + }) + } + #end + #end + ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 + #if ( $table.templateType == 11 ) + // 新增${subTable.classComment} + export function create${subSimpleClassName}(data) { + return request({ + url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, + method: 'post', + data + }) + } + + // 修改${subTable.classComment} + export function update${subSimpleClassName}(data) { + return request({ + url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, + method: 'post', + data + }) + } + + // 删除${subTable.classComment} + export function delete${subSimpleClassName}(id) { + return request({ + url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id, + method: 'delete' + }) + } + + // 获得${subTable.classComment} + export function get${subSimpleClassName}(id) { + return request({ + url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id, + method: 'get' + }) + } + #end +#end \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm new file mode 100644 index 000000000..c1b4a0a0f --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm @@ -0,0 +1,204 @@ +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 + + \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm new file mode 100644 index 000000000..d8542c3d5 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm @@ -0,0 +1,2 @@ +## 主表的 normal 和 inner 使用相同的 form 表单 +#parse("codegen/vue3/views/components/form_sub_normal.vue.vm") \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm new file mode 100644 index 000000000..90df79812 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm @@ -0,0 +1,362 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + + \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm new file mode 100644 index 000000000..5ad208b3b --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm @@ -0,0 +1,181 @@ +#set ($subTable = $subTables.get($subIndex))##当前表 +#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) +#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 +#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + + \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm new file mode 100644 index 000000000..3fe648892 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm @@ -0,0 +1,4 @@ +## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: +## 1)inner 使用 list 不分页,erp 使用 page 分页 +## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 +#parse("codegen/vue3/views/components/list_sub_erp.vue.vm") \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm new file mode 100644 index 000000000..7be0ed089 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm @@ -0,0 +1,333 @@ + + + diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm index 7a6add604..58b41890d 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm @@ -47,18 +47,67 @@ - 新增 导出 + #if ( $table.templateType == 2 ) + + + 展开/折叠 + + + #end - - + ## 特殊:主子表专属逻辑 TODO puhui999: 普通模式 + #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 ) + + ## 特殊:树表专属逻辑 + #elseif ( $table.templateType == 2 ) + + #else + + #end + ## 特殊:主子表专属逻辑 TODO puhui999: 内嵌模式 + #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 ) + + + + + #end #foreach($column in $columns) #if ($column.listOperationResult) #set ($dictType=$column.dictType) @@ -84,7 +133,7 @@ #end diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm index 5536c39c4..432a968f8 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm @@ -146,9 +146,11 @@ import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.busine import { defaultProps, handleTree } from '@/utils/tree' #end ## 特殊:主子表专属逻辑 +#if ( $subTables && $subTables.size() > 0 ) #foreach ($subSimpleClassName in $subSimpleClassNames) import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue' #end +#end const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm index eab90c12a..13c448054 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm @@ -113,7 +113,7 @@ -## 特殊:主子表专属逻辑 +## 特殊:主子表专属逻辑 TODO puhui999: 普通模式 #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 ) #end -## 特殊:主子表专属逻辑 +## 特殊:主子表专属逻辑 TODO puhui999: 内嵌模式 #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 ) @@ -213,7 +213,7 @@ <${simpleClassName}Form ref="formRef" @success="getList" /> -## 特殊:主子表专属逻辑 +## 特殊:主子表专属逻辑 TODO puhui999: ERP 模式 #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 ) @@ -243,7 +243,7 @@ import download from '@/utils/download' import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}' import ${simpleClassName}Form from './${simpleClassName}Form.vue' ## 特殊:主子表专属逻辑 -#if ( $table.templateType != 10 ) +#if ( $subTables && $subTables.size() > 0 ) #foreach ($subSimpleClassName in $subSimpleClassNames) import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue' #end @@ -343,9 +343,9 @@ const handleExport = async () => { exportLoading.value = false } } -## 特殊:主子表专属逻辑 -#if ( $table.templateType == 11 ) +## 特殊:主子表专属逻辑 +#if ( $subTables && $subTables.size() > 0 ) /** 选中行操作 */ const currentRow = ref({}) // 选中行 const handleCurrentChange = (row) => { diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index f15b0cf8d..264ad7e10 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -48,7 +48,7 @@ spring: primary: master datasource: master: - name: ruoyi-vue-pro + name: ruoyi-vue-pro4 url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例 @@ -62,7 +62,7 @@ spring: # username: SYSDBA # DM 连接的示例 # password: SYSDBA # DM 连接的示例 slave: # 模拟从库,可根据自己需要修改 - name: ruoyi-vue-pro + name: ruoyi-vue-pro4 lazy: true # 开启懒加载,保证启动速度 url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 @@ -78,8 +78,8 @@ spring: redis: host: 127.0.0.1 # 地址 port: 6379 # 端口 - database: 0 # 数据库索引 -# password: dev # 密码,建议生产环境开启 + database: 4 # 数据库索引 + password: 123456 # 密码,建议生产环境开启 --- #################### 定时任务相关配置 #################### From a88c93fc609a3e435a98e492b53b4612a5297f57 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 17 Nov 2023 18:18:27 +0800 Subject: [PATCH 17/81] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=9A=E9=87=8D=E6=9E=84=20vue2=20=E6=A8=A1=E7=89=88?= =?UTF-8?q?=EF=BC=8C=E9=80=82=E9=85=8D=E6=A0=91=E8=A1=A8=E5=92=8C=E4=B8=BB?= =?UTF-8?q?=E5=AD=90=E8=A1=A8=EF=BC=8890%=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vue/views/components/form_sub_erp.vue.vm | 400 ++++++------ .../views/components/form_sub_inner.vue.vm | 2 +- .../views/components/form_sub_normal.vue.vm | 570 +++++++----------- .../views/components/list_sub_inner.vue.vm | 2 +- 4 files changed, 431 insertions(+), 543 deletions(-) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm index c1b4a0a0f..2293189f6 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm @@ -2,203 +2,211 @@ #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 - \ No newline at end of file + #if ($hasEditorColumn) + import Editor from '@/components/Editor'; + #end + export default { + name: "${simpleClassName}", + components: { + #if ($hasImageUploadColumn) + ImageUpload, + #end + #if ($hasFileUploadColumn) + FileUpload, + #end + #if ($hasEditorColumn) + Editor, + #end + }, + data() { + return { + // 弹出层标题 + dialogTitle: "", + // 是否显示弹出层 + dialogVisible: false, + // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + formLoading: false, + // 表单参数 + formData: { + #foreach ($column in $columns) + #if ($column.createOperation || $column.updateOperation) + #if ($column.htmlType == "checkbox") + $column.javaField: [], + #else + $column.javaField: undefined, + #end + #end + #end + }, + // 表单校验 + formRules: { + #foreach ($column in $columns) + #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键 + #set($comment=$column.columnComment) + $column.javaField: [{ required: true, message: "${comment}不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }], + #end + #end + }, + }; + }, + methods: { + /** 表单重置 */ + reset() { + this.formData = { + #foreach ($column in $columns) + #if ($column.createOperation || $column.updateOperation) + #if ($column.htmlType == "checkbox") + $column.javaField: [], + #else + $column.javaField: undefined, + #end + #end + #end + }; + this.resetForm("formRef"); + }, + /** 打开弹窗 */ + open(id) { + this.dialogVisible = true; + this.reset(); + const that = this; + // 修改时,设置数据 + if (id) { + this.formLoading = true; + try { + ${simpleClassName}Api.get${subSimpleClassName}(id).then(res=>{ + that.formData = res.data; + that.title = "修改${table.classComment}"; + }) + } finally { + this.formLoading = false; + } + } + this.title = "新增${table.classComment}"; + }, + /** 提交按钮 */ + submitForm() { + this.formLoading = true; + try { + let data = this.formData; + this.#[[$]]#refs["formRef"].validate(valid => { + if (!valid) { + return; + } + // 修改的提交 + if (data.${primaryColumn.javaField}) { + ${simpleClassName}Api.update${simpleClassName}(data).then(response => { + this.#[[$modal]]#.msgSuccess("修改成功"); + this.dialogVisible = false; + this.#[[$]]#emit('success'); + }); + return; + } + // 添加的提交 + ${simpleClassName}Api.create${simpleClassName}(data).then(response => { + this.#[[$modal]]#.msgSuccess("新增成功"); + this.dialogVisible = false; + this.#[[$]]#emit('success'); + }); + }); + }finally { + this.formLoading = false + } + } + } + }; + diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm index d8542c3d5..ca266be9d 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm @@ -1,2 +1,2 @@ ## 主表的 normal 和 inner 使用相同的 form 表单 -#parse("codegen/vue3/views/components/form_sub_normal.vue.vm") \ No newline at end of file +#parse("codegen/vue/views/components/form_sub_normal.vue.vm") \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm index 90df79812..b3f0a332b 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm @@ -4,359 +4,239 @@ #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 + - \ No newline at end of file + }; + diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm index 3fe648892..90b8e4153 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm @@ -1,4 +1,4 @@ ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: ## 1)inner 使用 list 不分页,erp 使用 page 分页 ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 -#parse("codegen/vue3/views/components/list_sub_erp.vue.vm") \ No newline at end of file +#parse("codegen/vue/views/components/list_sub_erp.vue.vm") \ No newline at end of file From 58ede7d69c013f43623f1815e15b34aab5662bbd Mon Sep 17 00:00:00 2001 From: liuhongfeng <291117974@qq.com> Date: Sat, 18 Nov 2023 23:30:24 +0800 Subject: [PATCH 18/81] =?UTF-8?q?=E3=80=90=E4=BF=AE=E6=94=B9=E3=80=91?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=9A=84SQL=E5=AD=97=E7=AC=A6=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E4=B8=8D=E4=B8=80=E8=87=B4=E5=AF=BC=E8=87=B4=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=A1=A8=E5=A4=B1=E8=B4=A5=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?utf8mb4=5F0900=5Fai=5Fci=3D=E3=80=8Butf8mb4=5Funicode=5Fci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/ruoyi-vue-pro.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index a9cca3ba8..9fec3ee01 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -535,8 +535,8 @@ CREATE TABLE `infra_demo01_contact` ( `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', `sex` tinyint(1) NOT NULL COMMENT '性别', `birthday` datetime NOT NULL COMMENT '出生年', - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介', - `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介', + `avatar` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', @@ -651,7 +651,7 @@ CREATE TABLE `infra_demo03_student` ( `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名字', `sex` tinyint NOT NULL COMMENT '性别', `birthday` datetime NOT NULL COMMENT '出生日期', - `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '简介', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '简介', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', From 6270975902665bf248b88a50e5480adcd444c341 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 19 Nov 2023 00:37:30 +0800 Subject: [PATCH 19/81] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- yudao-server/pom.xml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 828fb89c8..bcffebfd7 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ - yudao-module-crm + diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index f58aa5bd0..7af09761f 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -92,11 +92,11 @@ - - cn.iocoder.boot - yudao-module-crm-biz - ${revision} - + + + + + From c7a21b2a6447a547c6470a26180cbee6a2bb39bf Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 19 Nov 2023 00:58:12 +0800 Subject: [PATCH 20/81] =?UTF-8?q?mall=EF=BC=9A=E4=BC=98=E5=8C=96=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=94=B6=E8=97=8F=E7=9A=84=E5=88=86=E9=A1=B5=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/core/mapper/BaseMapperX.java | 1 + .../favorite/ProductFavoriteController.java | 19 +++++---------- .../favorite/vo/ProductFavoritePageReqVO.java | 1 + .../favorite/vo/ProductFavoriteRespVO.java | 1 + .../favorite/ProductFavoriteConvert.java | 23 ++++++++----------- .../favorite/ProductFavoriteDetailDO.java | 15 ------------ .../mysql/favorite/ProductFavoriteMapper.java | 9 ++++---- 7 files changed, 23 insertions(+), 46 deletions(-) delete mode 100644 yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index db054e972..57e7133fb 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -139,4 +139,5 @@ public interface BaseMapperX extends MPJBaseMapper { // 转换返回 return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); } + } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java index 6f66c2062..721cf19c0 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/ProductFavoriteController.java @@ -41,20 +41,13 @@ public class ProductFavoriteController { @Operation(summary = "获得商品收藏分页") @PreAuthorize("@ss.hasPermission('product:favorite:query')") public CommonResult> getFavoritePage(@Valid ProductFavoritePageReqVO pageVO) { - PageResult favoritePage = productFavoriteService.getFavoritePage(pageVO); - if (CollUtil.isEmpty(favoritePage.getList())) { + PageResult pageResult = productFavoriteService.getFavoritePage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { return success(PageResult.empty()); } - - List list = productSpuService.getSpuList(convertSet(favoritePage.getList(), ProductFavoriteDO::getSpuId)); - - // 得到商品 spu 信息 - List favorites = ProductFavoriteConvert.INSTANCE.convertList2admin(favoritePage.getList(), list); - - // 转换 VO 结果 - PageResult pageResult = new PageResult<>(favoritePage.getTotal()); - pageResult.setList(favorites); - - return success(pageResult); + // 拼接数据 + List spuList = productSpuService.getSpuList(convertSet(pageResult.getList(), ProductFavoriteDO::getSpuId)); + return success(ProductFavoriteConvert.INSTANCE.convertPage(pageResult, spuList)); } + } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java index 9c0b33035..3d78883ec 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoritePageReqVO.java @@ -14,4 +14,5 @@ public class ProductFavoritePageReqVO extends PageParam { @Schema(description = "用户编号", example = "5036") private Long userId; + } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java index bbf972181..3c09aa8fc 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/favorite/vo/ProductFavoriteRespVO.java @@ -15,4 +15,5 @@ public class ProductFavoriteRespVO extends ProductSpuRespVO { @Schema(description = "spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "111") private Long spuId; + } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java index 9adac8c86..7b419b6a8 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/favorite/ProductFavoriteConvert.java @@ -1,10 +1,10 @@ package cn.iocoder.yudao.module.product.convert.favorite; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoriteRespVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoriteRespVO; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; -import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDetailDO; import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -13,7 +13,6 @@ import org.mapstruct.factory.Mappers; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; @@ -38,20 +37,18 @@ public interface ProductFavoriteConvert { return resultList; } + default PageResult convertPage(PageResult pageResult, List spuList) { + Map spuMap = convertMap(spuList, ProductSpuDO::getId); + List voList = CollectionUtils.convertList(pageResult.getList(), favorite -> { + ProductSpuDO spu = spuMap.get(favorite.getSpuId()); + return convert02(spu, favorite); + }); + return new PageResult<>(voList, pageResult.getTotal()); + } @Mapping(target = "id", source = "favorite.id") @Mapping(target = "userId", source = "favorite.userId") @Mapping(target = "spuId", source = "favorite.spuId") @Mapping(target = "createTime", source = "favorite.createTime") - ProductFavoriteRespVO convert2admin(ProductSpuDO spu, ProductFavoriteDO favorite); + ProductFavoriteRespVO convert02(ProductSpuDO spu, ProductFavoriteDO favorite); - default List convertList2admin(List favorites, List spus) { - List resultList = new ArrayList<>(spus.size()); - for (ProductFavoriteDO favorite : favorites) { - Optional spu = spus.stream().filter(e -> e.getId().equals(favorite.getSpuId())).findFirst(); - resultList.add(convert2admin(spu.get(), favorite)); - } - return resultList; - } - - PageResult convertPage(PageResult page); } diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java deleted file mode 100644 index 60e401e11..000000000 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/favorite/ProductFavoriteDetailDO.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.product.dal.dataobject.favorite; - -import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; -import lombok.Data; - -/** - * 商品收藏 DO - * - * @author 芋道源码 - */ -@Data -public class ProductFavoriteDetailDO extends ProductFavoriteDO { - - ProductSpuDO spuDO; -} diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java index e116a7c4a..a681d42a7 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/favorite/ProductFavoriteMapper.java @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.product.dal.mysql.favorite; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.product.controller.admin.favorite.vo.ProductFavoritePageReqVO; import cn.iocoder.yudao.module.product.controller.app.favorite.vo.AppFavoritePageReqVO; import cn.iocoder.yudao.module.product.dal.dataobject.favorite.ProductFavoriteDO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.github.yulichang.wrapper.MPJLambdaWrapper; import org.apache.ibatis.annotations.Mapper; @Mapper @@ -24,10 +24,9 @@ public interface ProductFavoriteMapper extends BaseMapperX { } default PageResult selectPageByUserId(ProductFavoritePageReqVO reqVO) { - return selectPage(reqVO, new MPJLambdaWrapper() - .selectAll(ProductFavoriteDO.class) - .eq(ProductFavoriteDO::getUserId, reqVO.getUserId()) - .orderByDesc(ProductFavoriteDO::getCreateTime)); + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ProductFavoriteDO::getUserId, reqVO.getUserId()) + .orderByDesc(ProductFavoriteDO::getId)); } default Long selectCountByUserId(Long userId) { From 7f75f0abfce424f98226f55bfb71ddb82a2d9a7c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 19 Nov 2023 18:28:00 +0800 Subject: [PATCH 21/81] =?UTF-8?q?bpm=EF=BC=9Acode=20review=20=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E5=AE=A1=E6=89=B9=E4=BA=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flowable/core/context/FlowableContextHolder.java | 1 + .../api/task/dto/BpmProcessInstanceCreateReqDTO.java | 7 ++++++- .../vo/instance/BpmProcessInstanceCreateReqVO.java | 3 ++- .../dal/dataobject/task/BpmProcessInstanceExtDO.java | 4 +++- .../definition/BpmTaskAssignRuleServiceImpl.java | 10 ++++++---- .../bpm/service/task/BpmProcessInstanceService.java | 4 +++- .../service/task/BpmProcessInstanceServiceImpl.java | 2 +- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java index e6021c0b7..efc6d5340 100644 --- a/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java +++ b/yudao-framework/yudao-spring-boot-starter-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/context/FlowableContextHolder.java @@ -36,4 +36,5 @@ public class FlowableContextHolder { public static void setAssignee(Map> assignee) { ASSIGNEE.set(assignee); } + } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java index 6db999c1e..4403d3e68 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/task/dto/BpmProcessInstanceCreateReqDTO.java @@ -32,9 +32,14 @@ public class BpmProcessInstanceCreateReqDTO { @NotEmpty(message = "业务的唯一标识") private String businessKey; + // TODO @hai:assignees 复数 /** * 提前指派的审批人 - * 例如: { taskKey1 :[1,2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批 + * + * key:taskKey 任务编码 + * value:审批人的数组 + * 例如: { taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批 */ private Map> assignee; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java index 13b7c8f0b..93cf541bb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmProcessInstanceCreateReqVO.java @@ -18,7 +18,8 @@ public class BpmProcessInstanceCreateReqVO { @Schema(description = "变量实例") private Map variables; - @Schema(description = "提前指派的审批人", requiredMode = Schema.RequiredMode.REQUIRED, example = "{taskKey1:[1,2]}") + // TODO @hai:assignees 复数 + @Schema(description = "提前指派的审批人", requiredMode = Schema.RequiredMode.REQUIRED, example = "{taskKey1: [1, 2]}") private Map> assignee; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java index 8aba9059f..5a481fff0 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/task/BpmProcessInstanceExtDO.java @@ -88,9 +88,11 @@ public class BpmProcessInstanceExtDO extends BaseDO { @TableField(typeHandler = JacksonTypeHandler.class) private Map formVariables; + // TODO @hai:assignees 复数 /** * 提前设定好的审批人 */ - @TableField(typeHandler = JacksonTypeHandler.class) + @TableField(typeHandler = JacksonTypeHandler.class, exist = false) // TODO 芋艿:临时 exist = false,避免 db 报错; private Map> assignee; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java index 8c6ceaf48..9ef936376 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmTaskAssignRuleServiceImpl.java @@ -239,12 +239,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService { @Override @DataPermission(enable = false) // 忽略数据权限,不然分配会存在问题 public Set calculateTaskCandidateUsers(DelegateExecution execution) { - //1. 先从提前选好的审批人中获取 - List assignee = processInstanceService.getAssigneeByProcessInstanceIdAndTaskDefinitionKey(execution.getProcessInstanceId(), execution.getCurrentActivityId()); - if(CollUtil.isNotEmpty(assignee)){ + // 1. 先从提前选好的审批人中获取 + List assignee = processInstanceService.getAssigneeByProcessInstanceIdAndTaskDefinitionKey( + execution.getProcessInstanceId(), execution.getCurrentActivityId()); + if (CollUtil.isNotEmpty(assignee)) { + // TODO @hai:new HashSet 即可 return convertSet(assignee, Function.identity()); } - //2. 通过分配规则,计算审批人 + // 2. 通过分配规则,计算审批人 BpmTaskAssignRuleDO rule = getTaskRule(execution); return calculateTaskCandidateUsers(execution, rule); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java index 4649475db..cff0ec976 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java @@ -145,12 +145,14 @@ public interface BpmProcessInstanceService { */ void updateProcessInstanceExtReject(String id, String reason); + // TODO @hai:改成 getProcessInstanceAssigneesByTaskDefinitionKey(String id, String taskDefinitionKey) /** - * 去流程实例扩展表中,取出指定流程任务提前指定的审批人 + * 获取流程实例中,取出指定流程任务提前指定的审批人 * * @param processInstanceId 流程实例的编号 * @param taskDefinitionKey 流程任务定义的 key * @return 审批人集合 */ List getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 5d5e1ba10..415913a9a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -1 +1 @@ -package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.flowable.core.context.FlowableContextHolder; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } //设置上下文信息 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables).setAssignee(assignee)); return instance.getId(); } @Override public List getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { //1. 先从上下文获取,首次提交数据库中查询不到 List result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } //2. 从数据库中获取 BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } if(CollUtil.isNotEmpty(instance.getAssignee())){ return instance.getAssignee().get(taskDefinitionKey); } return Collections.emptyList(); } } \ No newline at end of file +package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.flowable.core.context.FlowableContextHolder; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*; import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum; import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher; import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService; import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.HistoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.event.FlowableCancelledEvent; import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Valid; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; /** * 流程实例 Service 实现类 *

* ProcessDefinition & ProcessInstance & Execution & Task 的关系: * 1. *

* HistoricProcessInstance & ProcessInstance 的关系: * 1. *

* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例 * * @author 芋道源码 */ @Service @Validated @Slf4j public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService { @Resource private RuntimeService runtimeService; @Resource private BpmProcessInstanceExtMapper processInstanceExtMapper; @Resource @Lazy // 解决循环依赖 private BpmTaskService taskService; @Resource private BpmProcessDefinitionService processDefinitionService; @Resource private HistoryService historyService; @Resource private AdminUserApi adminUserApi; @Resource private DeptApi deptApi; @Resource private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher; @Resource private BpmMessageService messageService; @Override public ProcessInstance getProcessInstance(String id) { return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getProcessInstances(Set ids) { return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public PageResult getMyProcessInstancePage(Long userId, BpmProcessInstanceMyPageReqVO pageReqVO) { // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页 PageResult pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO); if (CollUtil.isEmpty(pageResult.getList())) { return new PageResult<>(pageResult.getTotal()); } // 获得流程 Task Map List processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId); Map> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds); // 转换返回 return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap); } @Override @Transactional(rollbackFor = Exception.class) public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId()); // 发起流程 return createProcessInstance0(userId, definition, createReqVO.getVariables(), null, createReqVO.getAssignee()); } @Override public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey()); // 发起流程 return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(), createReqDTO.getAssignee()); } @Override public BpmProcessInstanceRespVO getProcessInstanceVO(String id) { // 获得流程实例 HistoricProcessInstance processInstance = getHistoricProcessInstance(id); if (processInstance == null) { return null; } BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id); Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id); // 获得流程定义 ProcessDefinition processDefinition = processDefinitionService .getProcessDefinition(processInstance.getProcessDefinitionId()); Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId()); BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt( processInstance.getProcessDefinitionId()); Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id); String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId()); // 获得 User AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())); DeptRespDTO dept = null; if (startUser != null) { dept = deptApi.getDept(startUser.getDeptId()); } // 拼接结果 return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt, processDefinition, processDefinitionExt, bpmnXml, startUser, dept); } @Override public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } // 只能取消自己的 if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF); } // 通过删除流程实例,实现流程实例的取消, // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。通过历史表查询 deleteProcessInstance(cancelReqVO.getId(), BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason())); } /** * 获得历史的流程实例 * * @param id 流程实例的编号 * @return 历史的流程实例 */ @Override public HistoricProcessInstance getHistoricProcessInstance(String id) { return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); } @Override public List getHistoricProcessInstances(Set ids) { return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list(); } @Override public void createProcessInstanceExt(ProcessInstance instance) { // 获得流程定义 ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId()); // 插入 BpmProcessInstanceExtDO 对象 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getId()) .setProcessDefinitionId(definition.getId()) .setName(instance.getProcessDefinitionName()) .setStartUserId(Long.valueOf(instance.getStartUserId())) .setCategory(definition.getCategory()) .setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus()) .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()); processInstanceExtMapper.insert(instanceExtDO); } @Override public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) { // 判断是否为 Reject 不通过。如果是,则不进行更新. // 因为,updateProcessInstanceExtReject 方法,已经进行更新了 if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String) event.getCause())) { return; } // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(event.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override public void updateProcessInstanceExtComplete(ProcessInstance instance) { // 需要主动查询,因为 instance 只有 id 属性 // 另外,此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId()); // 更新拓展表 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO() .setProcessInstanceId(instance.getProcessInstanceId()) .setEndTime(LocalDateTime.now()) // 由于 ProcessInstance 里没有办法拿到 endTime,所以这里设置 .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全,说明审批通过 processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被通过的消息 messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } @Override @Transactional(rollbackFor = Exception.class) public void updateProcessInstanceExtReject(String id, String reason) { // 需要主动查询,因为 instance 只有 id 属性 ProcessInstance processInstance = getProcessInstance(id); // 删除流程实例,以实现驳回任务时,取消整个审批流程 deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason))); // 更新 status + result // 注意,不能和上面的逻辑更换位置。因为 deleteProcessInstance 会触发流程的取消,进而调用 updateProcessInstanceExtCancel 方法, // 设置 result 为 BpmProcessInstanceStatusEnum.CANCEL,显然和 result 不一定是一致的 BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id) .setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus()) .setResult(BpmProcessInstanceResultEnum.REJECT.getResult()); processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO); // 发送流程被不通过的消息 messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason)); // 发送流程实例的状态事件 processInstanceResultEventPublisher.sendProcessInstanceResultEvent( BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult())); } private void deleteProcessInstance(String id, String reason) { runtimeService.deleteProcessInstance(id, reason); } private String createProcessInstance0(Long userId, ProcessDefinition definition, Map variables, String businessKey, Map> assignee) { // 校验流程定义 if (definition == null) { throw exception(PROCESS_DEFINITION_NOT_EXISTS); } if (definition.isSuspended()) { throw exception(PROCESS_DEFINITION_IS_SUSPENDED); } // 设置上下文信息 // TODO @hai:要不往 variables 存到一个全局固定 key 里,减少对上下文的依赖 FlowableContextHolder.setAssignee(assignee); // 创建流程实例 ProcessInstance instance = runtimeService.createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .businessKey(businessKey) .name(definition.getName().trim()) .variables(variables) .start(); // 设置流程名字 runtimeService.setProcessInstanceName(instance.getId(), definition.getName()); // 补全流程实例的拓展表 processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId()) .setFormVariables(variables).setAssignee(assignee)); return instance.getId(); } @Override public List getAssigneeByProcessInstanceIdAndTaskDefinitionKey(String processInstanceId, String taskDefinitionKey) { // 1. 先从上下文获取,首次提交数据库中查询不到 List result = FlowableContextHolder.getAssigneeByTaskDefinitionKey(taskDefinitionKey); if (CollUtil.isNotEmpty(result)) { return result; } // 2. 从数据库中获取 BpmProcessInstanceExtDO instance = processInstanceExtMapper.selectByProcessInstanceId(processInstanceId); if (instance == null) { throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS); } if (CollUtil.isNotEmpty(instance.getAssignee())) { return instance.getAssignee().get(taskDefinitionKey); } return Collections.emptyList(); } } \ No newline at end of file From 827897807febe62d7d661243d13f78b9d1e2deeb Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 19 Nov 2023 18:46:16 +0800 Subject: [PATCH 22/81] =?UTF-8?q?=E6=8F=90=E5=8D=87=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E9=80=9F=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-server/pom.xml | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index fb50caa4f..20b67725b 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -37,11 +37,11 @@ - - cn.iocoder.boot - yudao-module-member-biz - ${revision} - + + + + + @@ -56,11 +56,11 @@ - - cn.iocoder.boot - yudao-module-pay-biz - ${revision} - + + + + + @@ -70,26 +70,26 @@ - - cn.iocoder.boot - yudao-module-promotion-biz - ${revision} - - - cn.iocoder.boot - yudao-module-product-biz - ${revision} - - - cn.iocoder.boot - yudao-module-trade-biz - ${revision} - - - cn.iocoder.boot - yudao-module-statistics-biz - ${revision} - + + + + + + + + + + + + + + + + + + + + From 822e441765f191991b950c95a1175239e34d3bc8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 20 Nov 2023 23:27:41 +0800 Subject: [PATCH 23/81] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20justauth=20=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=E7=8E=B0=E6=9C=89=20yudao-spring-boot-starter-biz-soc?= =?UTF-8?q?ial=20=E4=BE=9D=E8=B5=96=EF=BC=8C=E5=87=8F=E5=B0=91=20starter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-dependencies/pom.xml | 8 +- yudao-framework/pom.xml | 1 - .../pom.xml | 56 ------ .../config/YudaoSocialAutoConfiguration.java | 36 ---- .../social/core/YudaoAuthRequestFactory.java | 94 --------- .../social/core/enums/AuthExtendSource.java | 45 ----- .../request/AuthWeChatMiniAppRequest.java | 97 ---------- .../core/request/AuthWeChatMpRequest.java | 178 ------------------ ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../social/SocialClientServiceImpl.java | 8 +- .../social/SocialUserServiceImplTest.java | 4 +- 11 files changed, 13 insertions(+), 515 deletions(-) delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMpRequest.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index dbd6e2f3c..81c3ecea5 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -70,7 +70,7 @@ 4.6.4 2.2.1 3.1.880 - 1.0.7 + 1.0.8 1.6.1 2.12.2 4.5.0 @@ -605,6 +605,12 @@ com.xingyuv spring-boot-starter-justauth ${justauth.version} + + + cn.hutool + hutool-core + + diff --git a/yudao-framework/pom.xml b/yudao-framework/pom.xml index 12244f5ce..f2e39a908 100644 --- a/yudao-framework/pom.xml +++ b/yudao-framework/pom.xml @@ -32,7 +32,6 @@ yudao-spring-boot-starter-biz-pay yudao-spring-boot-starter-biz-weixin - yudao-spring-boot-starter-biz-social yudao-spring-boot-starter-biz-tenant yudao-spring-boot-starter-biz-data-permission yudao-spring-boot-starter-biz-error-code diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml b/yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml deleted file mode 100644 index e39107918..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - cn.iocoder.boot - yudao-framework - ${revision} - - jar - 4.0.0 - - yudao-spring-boot-starter-biz-social - ${project.artifactId} - - - - cn.iocoder.boot - yudao-common - - - - org.springframework.boot - spring-boot-starter-aop - - - - cn.iocoder.boot - yudao-spring-boot-starter-web - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - com.xingyuv - spring-boot-starter-justauth - - - cn.hutool - hutool-core - - - - - cn.iocoder.boot - yudao-spring-boot-starter-redis - - - - - - \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java deleted file mode 100644 index 57accd20d..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/config/YudaoSocialAutoConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.framework.social.config; - -import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; -import com.xingyuv.http.HttpUtil; -import com.xingyuv.http.support.hutool.HutoolImpl; -import com.xingyuv.jushauth.cache.AuthStateCache; -import com.xingyuv.justauth.autoconfigure.JustAuthProperties; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; - -/** - * 社交自动装配类 - * - * @author timfruit - * @date 2021-10-30 - */ -@Slf4j -@AutoConfiguration -@EnableConfigurationProperties(JustAuthProperties.class) -public class YudaoSocialAutoConfiguration { - - @Bean - @Primary - @ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true) - public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) { - // 需要修改 HttpUtil 使用的实现,避免类报错 - HttpUtil.setHttp(new HutoolImpl()); - // 创建 YudaoAuthRequestFactory - return new YudaoAuthRequestFactory(properties, authStateCache); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java deleted file mode 100644 index 6cabb0ccd..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/YudaoAuthRequestFactory.java +++ /dev/null @@ -1,94 +0,0 @@ -package cn.iocoder.yudao.framework.social.core; - -import cn.hutool.core.util.EnumUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; -import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMiniAppRequest; -import cn.iocoder.yudao.framework.social.core.request.AuthWeChatMpRequest; -import com.xingyuv.jushauth.cache.AuthStateCache; -import com.xingyuv.jushauth.config.AuthConfig; -import com.xingyuv.jushauth.config.AuthSource; -import com.xingyuv.jushauth.request.AuthRequest; -import com.xingyuv.justauth.AuthRequestFactory; -import com.xingyuv.justauth.autoconfigure.JustAuthProperties; - -import java.lang.reflect.Method; - -import static com.xingyuv.jushauth.config.AuthDefaultSource.WECHAT_MP; - -/** - * 第三方授权拓展 request 工厂类 - * 为使得拓展配置 {@link AuthConfig} 和默认配置齐平,所以自定义本工厂类 - * - * @author timfruit - * @date 2021-10-31 - */ -public class YudaoAuthRequestFactory extends AuthRequestFactory { - - protected JustAuthProperties properties; - protected AuthStateCache authStateCache; - - /** - * 由于父类 configureHttpConfig 方法是 private 修饰,所以获取后,进行反射调用 - */ - private final Method configureHttpConfigMethod = ReflectUtil.getMethod(AuthRequestFactory.class, - "configureHttpConfig", String.class, AuthConfig.class, JustAuthProperties.JustAuthHttpConfig.class); - - public YudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) { - super(properties, authStateCache); - this.properties = properties; - this.authStateCache = authStateCache; - } - - /** - * 返回 AuthRequest 对象 - * - * @param source {@link AuthSource} - * @return {@link AuthRequest} - */ - @Override - public AuthRequest get(String source) { - // 先尝试获取自定义扩展的 - AuthRequest authRequest = getExtendRequest(source); - // 找不到,使用默认拓展 - if (authRequest == null) { - authRequest = super.get(source); - } - return authRequest; - } - - protected AuthRequest getExtendRequest(String source) { - // TODO 芋艿:临时兼容 justauth 迁移的类型不对问题; - if (WECHAT_MP.name().equalsIgnoreCase(source)) { - AuthConfig config = properties.getType().get(WECHAT_MP.name()); - return new AuthWeChatMpRequest(config, authStateCache); - } - - AuthExtendSource authExtendSource; - try { - authExtendSource = EnumUtil.fromString(AuthExtendSource.class, source.toUpperCase()); - } catch (IllegalArgumentException e) { - // 无自定义匹配 - return null; - } - - // 拓展配置和默认配置齐平,properties 放在一起 - AuthConfig config = properties.getType().get(authExtendSource.name()); - // 找不到对应关系,直接返回空 - if (config == null) { - return null; - } - // 反射调用,配置 http config - ReflectUtil.invoke(this, configureHttpConfigMethod, authExtendSource.name(), config, properties.getHttpConfig()); - - // 获得拓展的 Request - // noinspection SwitchStatementWithTooFewBranches - switch (authExtendSource) { - case WECHAT_MINI_APP: - return new AuthWeChatMiniAppRequest(config, authStateCache); - default: - return null; - } - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java deleted file mode 100644 index fd0fff709..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/enums/AuthExtendSource.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.framework.social.core.enums; - -import com.xingyuv.jushauth.config.AuthSource; -import com.xingyuv.jushauth.request.AuthDefaultRequest; - -/** - * 拓展 JustAuth 各 api 需要的 url, 用枚举类分平台类型管理 - * - * 默认配置 {@link com.xingyuv.jushauth.config.AuthDefaultSource} - * - * @author timfruit - */ -public enum AuthExtendSource implements AuthSource { - - /** - * 微信小程序授权登录 - */ - WECHAT_MINI_APP { - - @Override - public String authorize() { - // 参见 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 文档 - throw new UnsupportedOperationException("不支持获取授权 url,请使用小程序内置函数 wx.login() 登录获取 code"); - } - - @Override - public String accessToken() { - // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档 - // 获取 openid, unionId , session_key 等字段 - return "https://api.weixin.qq.com/sns/jscode2session"; - } - - @Override - public String userInfo() { - // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档 - throw new UnsupportedOperationException("不支持获取用户信息 url,请使用小程序内置函数 wx.getUserProfile() 获取用户信息"); - } - - @Override - public Class getTargetClass() { - return null; - } - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java deleted file mode 100644 index 964eb31cd..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMiniAppRequest.java +++ /dev/null @@ -1,97 +0,0 @@ -package cn.iocoder.yudao.framework.social.core.request; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.framework.social.core.enums.AuthExtendSource; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.xingyuv.jushauth.cache.AuthStateCache; -import com.xingyuv.jushauth.config.AuthConfig; -import com.xingyuv.jushauth.exception.AuthException; -import com.xingyuv.jushauth.model.AuthCallback; -import com.xingyuv.jushauth.model.AuthToken; -import com.xingyuv.jushauth.model.AuthUser; -import com.xingyuv.jushauth.request.AuthDefaultRequest; -import com.xingyuv.jushauth.utils.HttpUtils; -import com.xingyuv.jushauth.utils.UrlBuilder; -import lombok.Data; - -/** - * 微信小程序登陆 Request 请求 - * - * 由于 JustAuth 定位是面向 Web 为主的三方登录,所以微信小程序只能自己封装 - * - * @author timfruit - * @date 2021-10-29 - */ -public class AuthWeChatMiniAppRequest extends AuthDefaultRequest { - - public AuthWeChatMiniAppRequest(AuthConfig config, AuthStateCache authStateCache) { - super(config, AuthExtendSource.WECHAT_MINI_APP, authStateCache); - } - - @Override - protected AuthToken getAccessToken(AuthCallback authCallback) { - // 参见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html 文档 - // 使用 code 获取对应的 openId、unionId 等字段 - String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode())).getBody(); - JSCode2SessionResponse accessTokenObject = JsonUtils.parseObject(response, JSCode2SessionResponse.class); - assert accessTokenObject != null; - checkResponse(accessTokenObject); - // 拼装结果 - return AuthToken.builder() - .openId(accessTokenObject.getOpenid()) - .unionId(accessTokenObject.getUnionId()) - .build(); - } - - @Override - protected AuthUser getUserInfo(AuthToken authToken) { - // 参见 https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html 文档 - // 如果需要用户信息,需要在小程序调用函数后传给后端 - return AuthUser.builder() - .username("") - .nickname("") - .avatar("") - .uuid(authToken.getOpenId()) - .token(authToken) - .source(source.toString()) - .build(); - } - - /** - * 检查响应内容是否正确 - * - * @param response 请求响应内容 - */ - private void checkResponse(JSCode2SessionResponse response) { - if (response.getErrorCode() != 0) { - throw new AuthException(response.getErrorCode(), response.getErrorMsg()); - } - } - - @Override - protected String accessTokenUrl(String code) { - return UrlBuilder.fromBaseUrl(source.accessToken()) - .queryParam("appid", config.getClientId()) - .queryParam("secret", config.getClientSecret()) - .queryParam("js_code", code) - .queryParam("grant_type", "authorization_code") - .build(); - } - - @Data - @SuppressWarnings("SpellCheckingInspection") - private static class JSCode2SessionResponse { - - @JsonProperty("errcode") - private int errorCode; - @JsonProperty("errmsg") - private String errorMsg; - @JsonProperty("session_key") - private String sessionKey; - private String openid; - @JsonProperty("unionid") - private String unionId; - - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMpRequest.java b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMpRequest.java deleted file mode 100644 index 13b77cd35..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/java/cn/iocoder/yudao/framework/social/core/request/AuthWeChatMpRequest.java +++ /dev/null @@ -1,178 +0,0 @@ -package cn.iocoder.yudao.framework.social.core.request; - -import com.alibaba.fastjson.JSONObject; -import com.xingyuv.jushauth.cache.AuthStateCache; -import com.xingyuv.jushauth.config.AuthConfig; -import com.xingyuv.jushauth.config.AuthDefaultSource; -import com.xingyuv.jushauth.enums.AuthResponseStatus; -import com.xingyuv.jushauth.enums.AuthUserGender; -import com.xingyuv.jushauth.enums.scope.AuthWechatMpScope; -import com.xingyuv.jushauth.exception.AuthException; -import com.xingyuv.jushauth.model.AuthCallback; -import com.xingyuv.jushauth.model.AuthResponse; -import com.xingyuv.jushauth.model.AuthToken; -import com.xingyuv.jushauth.model.AuthUser; -import com.xingyuv.jushauth.request.AuthDefaultRequest; -import com.xingyuv.jushauth.utils.AuthScopeUtils; -import com.xingyuv.jushauth.utils.GlobalAuthUtils; -import com.xingyuv.jushauth.utils.HttpUtils; -import com.xingyuv.jushauth.utils.UrlBuilder; - -/** - * 微信公众平台登录 - * - * @author yangkai.shen (https://xkcoding.com) - * @since 1.1.0 - */ -public class AuthWeChatMpRequest extends AuthDefaultRequest { - public AuthWeChatMpRequest(AuthConfig config) { - super(config, AuthDefaultSource.WECHAT_MP); - } - - public AuthWeChatMpRequest(AuthConfig config, AuthStateCache authStateCache) { - super(config, AuthDefaultSource.WECHAT_MP, authStateCache); - } - - /** - * 微信的特殊性,此时返回的信息同时包含 openid 和 access_token - * - * @param authCallback 回调返回的参数 - * @return 所有信息 - */ - @Override - protected AuthToken getAccessToken(AuthCallback authCallback) { - return this.getToken(accessTokenUrl(authCallback.getCode())); - } - - @Override - protected AuthUser getUserInfo(AuthToken authToken) { - String openId = authToken.getOpenId(); - - String response = doGetUserInfo(authToken); - JSONObject object = JSONObject.parseObject(response); - - this.checkResponse(object); - - String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city")); - - if (object.containsKey("unionid")) { - authToken.setUnionId(object.getString("unionid")); - } - - return AuthUser.builder() - .rawUserInfo(object) - .username(object.getString("nickname")) - .nickname(object.getString("nickname")) - .avatar(object.getString("headimgurl")) - .location(location) - .uuid(openId) - .gender(AuthUserGender.getWechatRealGender(object.getString("sex"))) - .token(authToken) - .source(source.toString()) - .build(); - } - - @Override - public AuthResponse refresh(AuthToken oldToken) { - return AuthResponse.builder() - .code(AuthResponseStatus.SUCCESS.getCode()) - .data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken()))) - .build(); - } - - /** - * 检查响应内容是否正确 - * - * @param object 请求响应内容 - */ - private void checkResponse(JSONObject object) { - if (object.containsKey("errcode")) { - throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg")); - } - } - - /** - * 获取token,适用于获取access_token和刷新token - * - * @param accessTokenUrl 实际请求token的地址 - * @return token对象 - */ - private AuthToken getToken(String accessTokenUrl) { - String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl).getBody(); - JSONObject accessTokenObject = JSONObject.parseObject(response); - - this.checkResponse(accessTokenObject); - - return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .openId(accessTokenObject.getString("openid")) - .scope(accessTokenObject.getString("scope")) - .build(); - } - - /** - * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} - * - * @param state state 验证授权流程的参数,可以防止csrf - * @return 返回授权地址 - * @since 1.9.3 - */ - @Override - public String authorize(String state) { - return UrlBuilder.fromBaseUrl(source.authorize()) - .queryParam("appid", config.getClientId()) - .queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri())) - .queryParam("response_type", "code") - .queryParam("scope", this.getScopes(",", false, AuthScopeUtils.getDefaultScopes(AuthWechatMpScope.values()))) - .queryParam("state", getRealState(state).concat("#wechat_redirect")) - .build(); - } - - /** - * 返回获取accessToken的url - * - * @param code 授权码 - * @return 返回获取accessToken的url - */ - @Override - protected String accessTokenUrl(String code) { - return UrlBuilder.fromBaseUrl(source.accessToken()) - .queryParam("appid", config.getClientId()) - .queryParam("secret", config.getClientSecret()) - .queryParam("code", code) - .queryParam("grant_type", "authorization_code") - .build(); - } - - /** - * 返回获取userInfo的url - * - * @param authToken 用户授权后的token - * @return 返回获取userInfo的url - */ - @Override - protected String userInfoUrl(AuthToken authToken) { - return UrlBuilder.fromBaseUrl(source.userInfo()) - .queryParam("access_token", authToken.getAccessToken()) - .queryParam("openid", authToken.getOpenId()) - .queryParam("lang", "zh_CN") - .build(); - } - - /** - * 返回获取userInfo的url - * - * @param refreshToken getAccessToken方法返回的refreshToken - * @return 返回获取userInfo的url - */ - @Override - protected String refreshTokenUrl(String refreshToken) { - return UrlBuilder.fromBaseUrl(source.refresh()) - .queryParam("appid", config.getClientId()) - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) - .build(); - } -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 5d9b35599..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -cn.iocoder.yudao.framework.social.config.YudaoSocialAutoConfiguration \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index 6c8381663..29184bee7 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; -import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientCreateReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientUpdateReqVO; @@ -31,6 +30,7 @@ import com.xingyuv.jushauth.model.AuthResponse; import com.xingyuv.jushauth.model.AuthUser; import com.xingyuv.jushauth.request.AuthRequest; import com.xingyuv.jushauth.utils.AuthStateUtils; +import com.xingyuv.justauth.AuthRequestFactory; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.bean.WxJsapiSignature; @@ -59,8 +59,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @Slf4j public class SocialClientServiceImpl implements SocialClientService { - @Resource // 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它 - private YudaoAuthRequestFactory yudaoAuthRequestFactory; + @Resource + private AuthRequestFactory authRequestFactory; @Resource private WxMpService wxMpService; @@ -145,7 +145,7 @@ public class SocialClientServiceImpl implements SocialClientService { */ private AuthRequest buildAuthRequest(Integer socialType, Integer userType) { // 1. 先查找默认的配置项,从 application-*.yaml 中读取 - AuthRequest request = yudaoAuthRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource()); + AuthRequest request = authRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource()); Assert.notNull(request, String.format("社交平台(%d) 不存在", socialType)); // 2. 查询 DB 的配置项,如果存在则进行覆盖 SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(socialType, userType); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java index 5b09a0f2b..8a4f3df5e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImplTest.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.system.service.social; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; @@ -15,6 +14,7 @@ import com.xingyuv.jushauth.model.AuthCallback; import com.xingyuv.jushauth.model.AuthResponse; import com.xingyuv.jushauth.model.AuthUser; import com.xingyuv.jushauth.request.AuthRequest; +import com.xingyuv.justauth.AuthRequestFactory; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; @@ -46,7 +46,7 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest { private SocialUserBindMapper socialUserBindMapper; @MockBean - private YudaoAuthRequestFactory authRequestFactory; + private AuthRequestFactory authRequestFactory; // TODO 芋艿:后续统一修复 // @Test From 80db28ee5bccf8612394e48b9f9d6e9c58d42da7 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 21 Nov 2023 10:49:43 +0800 Subject: [PATCH 24/81] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=9A=E9=87=8D=E6=9E=84=20vue2=20=E6=A8=A1=E7=89=88?= =?UTF-8?q?=EF=BC=8C=E9=80=82=E9=85=8D=E6=A0=91=E8=A1=A8=E5=92=8C=E4=B8=BB?= =?UTF-8?q?=E5=AD=90=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/codegen/inner/CodegenEngine.java | 14 +- .../vue/views/components/form_sub_erp.vue.vm | 86 +-- .../views/components/form_sub_normal.vue.vm | 214 ++++-- .../vue/views/components/list_sub_erp.vue.vm | 319 ++++----- .../resources/codegen/vue/views/form.vue.vm | 294 ++++---- .../resources/codegen/vue/views/index.vue.vm | 25 +- .../resources/codegen/vue3/views/form.vue.vm | 540 +++++++-------- .../resources/codegen/vue3/views/index.vue.vm | 647 +++++++++--------- .../codegen/inner/CodegenEngineVue2Test.java | 202 ++++++ 9 files changed, 1342 insertions(+), 999 deletions(-) create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineVue2Test.java diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java index 4ce4c2f98..933b27a46 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java @@ -104,6 +104,18 @@ public class CodegenEngine { vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue")) .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"), vueFilePath("api/${table.moduleName}/${classNameVar}.js")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/form.vue"), + vueFilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_normal.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_inner.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_erp.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_inner.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue")) + .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_erp.vue"), // 特殊:主子表专属逻辑 + vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue")) // Vue3 标准模版 .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"), vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue")) @@ -452,7 +464,7 @@ public class CodegenEngine { } private static String vueFilePath(String path) { - return "yudao-ui-${sceneEnum.basePackage}/" + // 顶级目录 + return "yudao-ui-${sceneEnum.basePackage}-vue2/" + // 顶级目录 "src/" + path; } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm index 2293189f6..eb7c45b3b 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm @@ -4,8 +4,8 @@