diff --git a/README.md b/README.md index 4b924ca9..c53b1d9f 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ dev环境启动参数: -Dspring.datasource.url=jdbc:mysql://172.16.2.197:3311/pudge-dev?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=true&verifyServerCertificate=false&rewriteBatchedStatements=true --Drocketmq.name-server=114.116.202.128:9876 +-Drocketmq.name-server=172.16.2.82:9876 -DCUSTOM_ENV=dev -Dspring.profiles.active=dev --Dspring.redis.host=123.249.44.111 +-Dspring.redis.host=172.16.2.23 -Dspring.redis.port=31270 -Dserver.port=8080 \ No newline at end of file diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/feign/ProductApi.java b/tyr-api/src/main/java/cn/axzo/tyr/client/feign/ProductApi.java index c042e336..8c59a6dc 100644 --- a/tyr-api/src/main/java/cn/axzo/tyr/client/feign/ProductApi.java +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/feign/ProductApi.java @@ -10,6 +10,9 @@ import cn.axzo.tyr.client.model.product.ProductSearchListReq; import cn.axzo.tyr.client.model.product.ProductSearchPageReq; import cn.axzo.tyr.client.model.product.ProductUpdateReq; import cn.axzo.tyr.client.model.product.ProductVO; +import cn.axzo.tyr.client.model.req.ProductSaveReq; +import cn.axzo.tyr.client.model.req.UpdateProductStatusReq; +import cn.axzo.tyr.client.model.res.ChiefTerminalResp; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -113,4 +116,33 @@ public interface ProductApi { */ @PostMapping("api/auth/product/feature/query") ApiResult>> queryProductFeatureRelationByWorkspace(@RequestBody Set workspaceIds); + + + /** + * 产品上下架 + * + * @param req 产品id及状态 + * @return 是否更新成功 + */ + @PostMapping("api/auth/product/updateStatus") + ApiResult updateStatus(@Validated @RequestBody UpdateProductStatusReq req); + + + /** + * 保存、更新产品(v2版本) + * + * @param req 产品信息 + * @return 返回产品ID + */ + @PostMapping("api/auth/product/saveOrUpdate") + ApiResult saveOrUpdate(@Validated @RequestBody ProductSaveReq req); + + /** + * 获取政务端列表 + * + * @param terminal 政务端 + * @return {@link ChiefTerminalResp} + */ + @GetMapping("api/auth/product/getChiefTerminal") + ApiResult> getChiefTerminal(@RequestParam @NotNull(message = "terminal不能为空") String terminal); } diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/model/enums/ProductModuleCategoryEnum.java b/tyr-api/src/main/java/cn/axzo/tyr/client/model/enums/ProductModuleCategoryEnum.java new file mode 100644 index 00000000..3f0e2aa4 --- /dev/null +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/model/enums/ProductModuleCategoryEnum.java @@ -0,0 +1,60 @@ +package cn.axzo.tyr.client.model.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * @author likunpeng + * @version 1.0 + * @date 2024/5/6 + */ +@Getter +@AllArgsConstructor +public enum ProductModuleCategoryEnum { + + /** + * 产品版本 + */ + PRODUCT_VERSION("PRODUCT_VERSION", "产品版本"), + + /** + * 增值服务包 + */ + ADD_VALUE_SERVICE("ADD_VALUE_SERVICE", "增值服务包"), + + /** + * 通用产品 + */ + GENERAL_SERVICE("GENERAL_SERVICE", "通用产品"), + + /** + * 硬件产品 + */ + HARD_WARE("HARD_WARE", "硬件产品"), + + ; + + @EnumValue + @JsonValue + private String code; + private String desc; + + /** + * 通过value值获取枚举类型 + * + * @param code code值 + * @return + */ + public static ProductModuleCategoryEnum getByCode(String code) { + return Arrays.stream(values()).filter(l -> l.getCode().equals(code)).findFirst().orElse(null); + } + + public static String getDescByCode(String code) { + return Optional.ofNullable(getByCode(code)).map(ProductModuleCategoryEnum::getDesc).orElse(null); + } +} diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/model/enums/WorkspaceTypeCodeEnum.java b/tyr-api/src/main/java/cn/axzo/tyr/client/model/enums/WorkspaceTypeCodeEnum.java new file mode 100644 index 00000000..693916f1 --- /dev/null +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/model/enums/WorkspaceTypeCodeEnum.java @@ -0,0 +1,72 @@ +package cn.axzo.tyr.client.model.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * @author likunpeng + * @version 1.0 + * @date 2024/5/6 + */ +@Getter +@AllArgsConstructor +public enum WorkspaceTypeCodeEnum { + + /** + * 企业 + */ + GENERAL_ENT("1", "企业"), + + /** + * 项目 + */ + GENERAL_PROJECT("2", "项目"), + + /** + * 政务 + */ + CHIEF("3", "政务"), + + /** + * 班组 + */ + TEAM("5", "班组"), + + /** + * OMS + */ + OMS("6", "OMS"), + + ; + + @EnumValue + @JsonValue + private String code; + private String desc; + + /** + * 通过value值获取枚举类型 + * + * @param code code值 + * @return + */ + public static WorkspaceTypeCodeEnum getByCode(String code) { + return Arrays.stream(values()).filter(l -> l.getCode().equals(code)).findFirst().orElse(null); + } + + public static String getDescByCode(String code) { + return Optional.ofNullable(getByCode(code)).map(WorkspaceTypeCodeEnum::getDesc).orElse(null); + } + + /** + * 后台允许新增的租户类型 + */ + public static final List ALLOW_ADD_WORKSPACE_TYPE_CODE_ENUM = Lists.newArrayList(GENERAL_ENT, GENERAL_PROJECT, CHIEF); +} diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductSearchPageReq.java b/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductSearchPageReq.java index 6f16df0d..42df822a 100644 --- a/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductSearchPageReq.java +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductSearchPageReq.java @@ -27,9 +27,18 @@ public class ProductSearchPageReq extends PageRequest { */ private Long dictWorkspaceTypeId; + /** + * 租户编码(工作台编码) + */ + private Long dictWorkspaceTypeCode; + /** * 状态 */ private Integer status; + /** + * 产品类型 + */ + private String productCategory; } diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductVO.java b/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductVO.java index 8530247b..63f27651 100644 --- a/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductVO.java +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/model/product/ProductVO.java @@ -97,4 +97,111 @@ public class ProductVO { */ private List ouTypes; + /** + * 产品类型 + * PRODUCT_VERSON:产品版本类型、ADD_VALUE_SERVICE:增值服务类型、 + * GENERAL_SERVICE:通用产品类型、HARD_WARE:硬件产品类型 + */ + private String category; + + /** + * 产品类型名称 + */ + private String productCategoryDesc; + + /** + * 版本升级序列(数字越小,版本越低,不能降级,只能升级) <企业、项目产品> + */ + private Integer version; + + /** + * 人数上限 <企业、项目产品> + */ + private Integer maxPersonCount; + + /** + * 最大项目数 <企业产品> + */ + private Integer maxWorkspaceCount; + + /** + * 价格(单位:分) + */ + private Long price; + + /** + * 产品详情 + */ + private List skus; + + /** + * 素材<仅硬件产品支持> + */ + private Material material; + + /** + * 功能范围(政务产品,只取根节点的featureId,后台取拉群所选根节点的所有子节点featureId) + */ + private FeatureScope featureScope; + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Sku { + + /** + * SKU名称 + */ + private String skuName; + + /** + * 规格型号 + */ + private String model; + + /** + * 数量 + */ + private Integer count; + + /** + * 单位 + */ + private String unit; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Material { + + /** + * 图片 + */ + private List images; + + /** + * 视频 + */ + private List videos; + + /** + * 详情大图 + */ + private List detailImages; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class FeatureScope { + + /** + * 政务端featureResourceId + */ + private Long chiefFeatureResourceId; + } } diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/model/req/ProductSaveReq.java b/tyr-api/src/main/java/cn/axzo/tyr/client/model/req/ProductSaveReq.java new file mode 100644 index 00000000..47dc629e --- /dev/null +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/model/req/ProductSaveReq.java @@ -0,0 +1,167 @@ +package cn.axzo.tyr.client.model.req; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import javax.validation.Valid; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * @author likunpeng + * @version 1.0 + * @date 2024/4/26 + */ +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductSaveReq { + + /** + * 产品ID + */ + private Long id; + + /** + * 产品名称 + */ + @NotBlank(message = "产品名称不能为空") + @Length(max = 35, message = "产品名称长度不能超过 35 个字符") + private String productName; + + /** + * 产品icon + */ + @NotBlank(message = "产品icon不能为空") + private String icon; + + /** + * 产品所属租户类型 + */ + @NotNull(message = "租户类型不能为空") + private Long dictWorkspaceTypeId; + + /** + * 产品类型 + */ + @NotBlank(message = "产品类型不能为空") + private String productCategory; + + /** + * 版本升级序列(数字越小,版本越低,不能降级,只能升级) <企业、项目产品> + */ + @Min(value = 0, message = "版本升级序列有误") + private Integer version; + + /** + * 人数上限 <企业、项目产品> + */ + @Min(value = 0, message = "人数上限有误") + private Integer maxPersonCount; + + /** + * 最大项目数 <企业产品> + */ + @Min(value = 0, message = "最大项目数有误") + private Integer maxWorkspaceCount; + + /** + * 价格(单位:分) <企业、项目产品> + */ + @Min(value = 0, message = "价格有误") + private Long price; + + /** + * SKU列表 + */ + @Valid + private List skus; + + /** + * 素材<仅硬件产品支持> + */ + private Material material; + + /** + * 功能范围(政务产品,只取根节点的featureId,后台取拉群所选根节点的所有子节点featureId) + */ + private FeatureScope featureScope; + + /** + * 操作人 + */ + private Long operator; + + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Sku { + + /** + * SKU名称 + */ + @NotNull(message = "SKU名称不能为空") + private String skuName; + + /** + * 规格型号 + */ + @NotBlank(message = "规格型号不能为空") + private String model; + + /** + * 数量 + */ + @NotNull(message = "数量不能为空") + @Min(value = 1, message = "数量有误") + private Integer count; + + /** + * 单位 + */ + @NotBlank(message = "单位不能为空") + private String unit; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Material { + + /** + * 图片素材 + */ + private List images; + + /** + * 视频素材 + */ + private List videos; + + /** + * 详情介绍大图 + */ + private List detailImages; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class FeatureScope { + + /** + * 政务端featureResourceId + */ + private Long chiefFeatureResourceId; + } +} diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/model/req/UpdateProductStatusReq.java b/tyr-api/src/main/java/cn/axzo/tyr/client/model/req/UpdateProductStatusReq.java new file mode 100644 index 00000000..1640e810 --- /dev/null +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/model/req/UpdateProductStatusReq.java @@ -0,0 +1,35 @@ +package cn.axzo.tyr.client.model.req; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +/** + * @author likunpeng + * @version 1.0 + * @date 2024/4/26 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UpdateProductStatusReq { + + /** + * 产品ID + */ + @NotNull(message = "产品ID不能为空") + @Min(value = 1, message = "产品ID有误") + private Long id; + + /** + * 状态 + */ + @NotNull(message = "状态不能为空") + @Min(value = 0, message = "状态有误") + @Max(value = 1, message = "状态有误") + private Integer status; +} diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/model/res/ChiefTerminalResp.java b/tyr-api/src/main/java/cn/axzo/tyr/client/model/res/ChiefTerminalResp.java new file mode 100644 index 00000000..843c753e --- /dev/null +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/model/res/ChiefTerminalResp.java @@ -0,0 +1,28 @@ +package cn.axzo.tyr.client.model.res; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author likunpeng + * @version 1.0 + * @date 2024/4/30 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ChiefTerminalResp { + + /** + * 功能资源ID + */ + private Long featureResourceId; + + /** + * 功能资源名称 + */ + private String featureResourceName; +} diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/controller/product/ProductController.java b/tyr-server/src/main/java/cn/axzo/tyr/server/controller/product/ProductController.java index bc2d5381..98ccdaa7 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/controller/product/ProductController.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/controller/product/ProductController.java @@ -11,6 +11,9 @@ import cn.axzo.tyr.client.model.product.ProductSearchListReq; import cn.axzo.tyr.client.model.product.ProductSearchPageReq; import cn.axzo.tyr.client.model.product.ProductUpdateReq; import cn.axzo.tyr.client.model.product.ProductVO; +import cn.axzo.tyr.client.model.req.ProductSaveReq; +import cn.axzo.tyr.client.model.req.UpdateProductStatusReq; +import cn.axzo.tyr.client.model.res.ChiefTerminalResp; import cn.axzo.tyr.server.model.PermissionCacheKey; import cn.axzo.tyr.server.service.PermissionCacheService; import cn.axzo.tyr.server.service.ProductFeatureRelationService; @@ -135,4 +138,37 @@ public class ProductController implements ProductApi { public ApiResult>> queryProductFeatureRelationByWorkspace(Set workspaceIds) { return ApiResult.ok(productFeatureRelationService.getByWorkspace(workspaceIds)); } + + /** + * 产品上下架 + * + * @param req 产品及状态信息 + * @return 返回是否更新成功 + */ + @Override + public ApiResult updateStatus(UpdateProductStatusReq req) { + return productService.updateStatus(req); + } + + /** + * 保存、更新产品(v2版本) + * + * @param req 产品信息 + * @return 返回产品ID + */ + @Override + public ApiResult saveOrUpdate(ProductSaveReq req) { + return productService.saveOrUpdate(req); + } + + /** + * 获取政务端列表 + * + * @param terminal 政务端 + * @return 返回端列表 + */ + @Override + public ApiResult> getChiefTerminal(String terminal) { + return productService.getChiefTerminal(terminal); + } } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/repository/entity/ProductModule.java b/tyr-server/src/main/java/cn/axzo/tyr/server/repository/entity/ProductModule.java index 8d9bb5af..f7b523b5 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/repository/entity/ProductModule.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/repository/entity/ProductModule.java @@ -1,11 +1,11 @@ package cn.axzo.tyr.server.repository.entity; import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; +import cn.axzo.tyr.server.repository.handler.ProductModuleSkuHandler; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; +import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler; +import lombok.*; import org.apache.commons.lang3.StringUtils; import java.io.Serializable; @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * saas-产品表(SaasProduct)表实体类 @@ -25,7 +24,7 @@ import java.util.stream.Stream; @Setter @ToString @EqualsAndHashCode(callSuper = true) -@TableName("product_module") +@TableName(value = "product_module", autoResultMap = true) public class ProductModule extends BaseEntity { /** @@ -82,6 +81,56 @@ public class ProductModule extends BaseEntity { */ private String ouType; + /** + * 创建人 + */ + private Long createBy; + + /** + * 修改人 + */ + private Long updateBy; + + /** + * 产品类型 + * PRODUCT_VERSION:产品版本类型、ADD_VALUE_SERVICE:增值服务类型、 + * GENERAL_SERVICE:通用产品类型、HARD_WARE:硬件产品类型 + */ + private String category; + + /** + * 版本升级序列(数字越小,版本越低,不能降级,只能升级) <企业、项目产品> + */ + private Integer version; + + /** + * 人数上限 <企业、项目产品> + */ + private Integer maxPersonCount; + + /** + * 最大项目数 <企业产品> + */ + private Integer maxWorkspaceCount; + + /** + * 价格(单位:分) + */ + private Long price; + + /** + * 产品详情 jsonList(skuNameSKU名称、model规格型号、count数量、unit单位) + */ + @TableField(value = "skus", typeHandler = ProductModuleSkuHandler.class) + private List skus; + + /** + * 素材<仅硬件产品支持>json类型 + * ({"miages":List,"videos":List,"detailImages":List}) + */ + @TableField(value = "material", typeHandler = FastjsonTypeHandler.class) + private Material material; + /** * 获取主键值 * @@ -100,5 +149,54 @@ public class ProductModule extends BaseEntity { return new ArrayList<>(); } } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Sku { + + /** + * SKU名称 + */ + private String skuName; + + /** + * 规格型号 + */ + private String model; + + /** + * 数量 + */ + private Integer count; + + /** + * 单位 + */ + private String unit; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Material { + + /** + * 图片 + */ + private List images; + + /** + * 视频 + */ + private List videos; + + /** + * 详情大图 + */ + private List detailImages; + } } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/repository/handler/ListTypeHandler.java b/tyr-server/src/main/java/cn/axzo/tyr/server/repository/handler/ListTypeHandler.java new file mode 100644 index 00000000..8e46414c --- /dev/null +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/repository/handler/ListTypeHandler.java @@ -0,0 +1,42 @@ +package cn.axzo.tyr.server.repository.handler; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * @author likunpeng + * @date 2024/5/16 + * @version 1.0 + */ +public abstract class ListTypeHandler extends BaseTypeHandler> { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException { + ps.setString(i, JSON.toJSONString(parameter)); + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + return rs.wasNull() ? null : JSON.parseObject(rs.getString(columnName), specificType()); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return rs.wasNull() ? null : JSON.parseObject(rs.getString(columnIndex), specificType()); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return cs.wasNull() ? null : JSON.parseObject(cs.getString(columnIndex), specificType()); + } + + protected abstract TypeReference> specificType(); +} diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/repository/handler/ProductModuleSkuHandler.java b/tyr-server/src/main/java/cn/axzo/tyr/server/repository/handler/ProductModuleSkuHandler.java new file mode 100644 index 00000000..9ed3afc0 --- /dev/null +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/repository/handler/ProductModuleSkuHandler.java @@ -0,0 +1,20 @@ +package cn.axzo.tyr.server.repository.handler; + +import cn.axzo.tyr.server.repository.entity.ProductModule; +import com.alibaba.fastjson.TypeReference; +import org.apache.ibatis.type.MappedTypes; + +import java.util.List; + +/** + * @author likunpeng + * @version 1.0 + * @date 2024/5/6 + */ +@MappedTypes({List.class}) +public class ProductModuleSkuHandler extends ListTypeHandler { + @Override + protected TypeReference> specificType() { + return new TypeReference>(){}; + } +} diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/ProductService.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/ProductService.java index df47aaf6..119d5453 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/ProductService.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/ProductService.java @@ -7,6 +7,9 @@ import cn.axzo.tyr.client.model.product.ProductSearchListReq; import cn.axzo.tyr.client.model.product.ProductSearchPageReq; import cn.axzo.tyr.client.model.product.ProductUpdateReq; import cn.axzo.tyr.client.model.product.ProductVO; +import cn.axzo.tyr.client.model.req.ProductSaveReq; +import cn.axzo.tyr.client.model.req.UpdateProductStatusReq; +import cn.axzo.tyr.client.model.res.ChiefTerminalResp; import java.util.List; @@ -29,4 +32,10 @@ public interface ProductService { ApiResult update(ProductUpdateReq req); ApiResult delete(Long id); + + ApiResult updateStatus(UpdateProductStatusReq req); + + ApiResult saveOrUpdate(ProductSaveReq req); + + ApiResult> getChiefTerminal(String terminal); } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java index ec83e928..9426e4c0 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java @@ -51,4 +51,6 @@ public interface SaasFeatureResourceService { Set listAuthFree(); List listNavMenu(String terminal); + + List listByParentIdAndTerminalAndIds(Long parentId, String terminal, List featureIds); } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/ProductServiceImpl.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/ProductServiceImpl.java index 5fb9196a..bdf0cc56 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/ProductServiceImpl.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/ProductServiceImpl.java @@ -5,23 +5,31 @@ import cn.axzo.basics.common.util.AssertUtil; import cn.axzo.framework.domain.page.PageResp; import cn.axzo.framework.domain.web.result.ApiPageResult; import cn.axzo.framework.domain.web.result.ApiResult; -import cn.axzo.tyr.client.model.product.ProductAddReq; -import cn.axzo.tyr.client.model.product.ProductSearchListReq; -import cn.axzo.tyr.client.model.product.ProductSearchPageReq; -import cn.axzo.tyr.client.model.product.ProductUpdateReq; -import cn.axzo.tyr.client.model.product.ProductVO; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; +import cn.axzo.tyr.client.model.dict.response.BasicDictNodeResp; +import cn.axzo.tyr.client.model.enums.ProductModuleCategoryEnum; +import cn.axzo.tyr.client.model.enums.WorkspaceTypeCodeEnum; +import cn.axzo.tyr.client.model.product.*; +import cn.axzo.tyr.client.model.req.ProductSaveReq; +import cn.axzo.tyr.client.model.req.UpdateProductStatusReq; +import cn.axzo.tyr.client.model.res.ChiefTerminalResp; import cn.axzo.tyr.server.repository.entity.ProductModule; import cn.axzo.tyr.server.repository.dao.ProductModuleDao; +import cn.axzo.tyr.server.repository.entity.SaasFeatureResource; import cn.axzo.tyr.server.service.ProductFeatureRelationService; import cn.axzo.tyr.server.service.ProductService; -import cn.hutool.core.util.StrUtil; +import cn.axzo.tyr.server.service.SaasBasicDictService; +import cn.axzo.tyr.server.service.SaasFeatureResourceService; +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; -import io.swagger.models.auth.In; +import com.google.common.collect.Sets; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.util.Integers; +import org.apache.commons.compress.utils.Lists; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -40,6 +48,11 @@ import java.util.stream.Collectors; public class ProductServiceImpl implements ProductService { private final ProductModuleDao productModuleDao; private final ProductFeatureRelationService productFeatureRelationService; + private final SaasFeatureResourceService saasFeatureResourceService; + private final SaasBasicDictService saasBasicDictService; + + // todo 政务端最终使用常量 + private final static String CHIEF_TERMINAL = "NT_CHIEF"; @Override public ApiResult> list(ProductSearchListReq req) { @@ -76,9 +89,15 @@ public class ProductServiceImpl implements ProductService { IPage page = productModuleDao.lambdaQuery() .like(StringUtils.hasLength(req.getName()), ProductModule::getProductName, req.getName()) .eq(Objects.nonNull(req.getDictWorkspaceTypeId()), ProductModule::getDictWorkspaceTypeId, req.getDictWorkspaceTypeId()) + .eq(Objects.nonNull(req.getDictWorkspaceTypeCode()), ProductModule::getDictWorkspaceTypeCode, req.getDictWorkspaceTypeCode()) .eq(Objects.nonNull(req.getStatus()), ProductModule::getStatus, req.getStatus()) + .eq(org.apache.commons.lang3.StringUtils.isNotBlank(req.getProductCategory()), ProductModule::getCategory, req.getProductCategory()) .page(req.toPage()); List list = BeanMapper.copyList(page.getRecords(), ProductVO.class); + list.forEach(e -> { + e.setProductCategoryDesc(ProductModuleCategoryEnum.getDescByCode(e.getCategory())); + e.setDictWorkspaceTypeDesc(WorkspaceTypeCodeEnum.getDescByCode(e.getDictWorkspaceTypeCode())); + }); PageResp data = PageResp.list(page.getCurrent(), page.getSize(), page.getTotal(), list); return ApiPageResult.ok(data); } @@ -88,6 +107,23 @@ public class ProductServiceImpl implements ProductService { ProductModule byId = productModuleDao.getById(id); ProductVO productVO = BeanMapper.copyBean(byId, ProductVO.class); productVO.setOuTypes(byId.parseOuType()); + productVO.setProductCategoryDesc(ProductModuleCategoryEnum.getDescByCode(productVO.getCategory())); + productVO.setDictWorkspaceTypeDesc(WorkspaceTypeCodeEnum.getDescByCode(productVO.getDictWorkspaceTypeCode())); + productVO.setSkus(org.apache.commons.collections.CollectionUtils.isEmpty(byId.getSkus()) ? Collections.emptyList() + : byId.getSkus().stream().map(e -> ProductVO.Sku.builder() + .skuName(e.getSkuName()) + .model(e.getModel()) + .count(e.getCount()) + .unit(e.getUnit()) + .build()).collect(Collectors.toList())); + productVO.setMaterial(Objects.isNull(byId.getMaterial()) ? null : ProductVO.Material.builder() + .images(byId.getMaterial().getImages()) + .videos(byId.getMaterial().getVideos()) + .detailImages(byId.getMaterial().getDetailImages()) + .build()); + if (WorkspaceTypeCodeEnum.CHIEF.getCode().equals(productVO.getDictWorkspaceTypeCode())) { + fillFeatureScope(productVO); + } return ApiResult.ok(productVO); } @@ -137,4 +173,142 @@ public class ProductServiceImpl implements ProductService { productFeatureRelationService.removeByProductId(id); return ApiResult.ok(BeanMapper.copyBean(productModule, ProductVO.class)); } + + @Override + public ApiResult updateStatus(UpdateProductStatusReq req) { + ProductModule productModule = productModuleDao.getOne(new LambdaQueryWrapper() + .eq(BaseEntity::getId, req.getId()) + .eq(BaseEntity::getIsDelete, Boolean.FALSE) + .last("LIMIT 1")); + AssertUtil.notNull(productModule, "产品不存在"); + productModuleDao.lambdaUpdate() + .eq(ProductModule::getId, req.getId()) + .set(ProductModule::getStatus, req.getStatus()) + .update(); + return ApiResult.ok(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ApiResult saveOrUpdate(ProductSaveReq req) { + ProductModule productModule = new ProductModule(); + if (Objects.nonNull(req.getId())) { + productModule = productModuleDao.getOne(new LambdaQueryWrapper() + .eq(BaseEntity::getId, req.getId()) + .eq(BaseEntity::getIsDelete, Boolean.FALSE) + .last("LIMIT 1")); + AssertUtil.notNull(productModule, "产品不存在"); + if (Objects.nonNull(req.getVersion()) && Objects.nonNull(productModule.getVersion())) { + AssertUtil.isTrue(req.getVersion() >= productModule.getVersion(), "版本升级序列不允许降级"); + } + } + + validAndFillEntity(req, productModule); + + if (Objects.nonNull(req.getId())) { + productModule.setUpdateBy(req.getOperator()); + productModuleDao.updateById(productModule); + } else { + productModule.setCreateBy(req.getOperator()); + productModuleDao.save(productModule); + } + // 保存商品权限信息 + if (WorkspaceTypeCodeEnum.CHIEF.getCode().equals(productModule.getDictWorkspaceTypeCode())) { + saveChiefFeatureResource(productModule.getId(), productModule.getDictWorkspaceTypeId(), productModule.getDictWorkspaceTypeCode(), req.getFeatureScope().getChiefFeatureResourceId()); + } + return ApiResult.ok(productModule.getId()); + } + + @Override + public ApiResult> getChiefTerminal(String terminal) { + List featureResources = saasFeatureResourceService.listByParentIdAndTerminalAndIds(0L, terminal, null); + List resps = CollectionUtil.isEmpty(featureResources) ? Collections.emptyList() : featureResources.stream().map(e -> ChiefTerminalResp.builder() + .featureResourceId(e.getId()) + .featureResourceName(e.getFeatureName()) + .build()).collect(Collectors.toList()); + return ApiResult.ok(resps); + } + + private void validAndFillEntity(ProductSaveReq req, ProductModule productModule) { + BasicDictNodeResp basicDictNodeResp = saasBasicDictService.getById(req.getDictWorkspaceTypeId()); + AssertUtil.notNull(basicDictNodeResp, "租户类型有误"); + WorkspaceTypeCodeEnum workspaceTypeCodeEnum = WorkspaceTypeCodeEnum.getByCode(basicDictNodeResp.getCode()); + AssertUtil.notNull(workspaceTypeCodeEnum, "租户类型有误"); + AssertUtil.isTrue(WorkspaceTypeCodeEnum.ALLOW_ADD_WORKSPACE_TYPE_CODE_ENUM.contains(workspaceTypeCodeEnum), "租户类型选择有误"); + // TODO 一期不允许创建企业、项目租户的产品 + AssertUtil.isFalse(WorkspaceTypeCodeEnum.GENERAL_ENT.equals(workspaceTypeCodeEnum) || WorkspaceTypeCodeEnum.GENERAL_PROJECT.equals(workspaceTypeCodeEnum), "企业、项目租户请在老页面创建产品"); + + ProductModuleCategoryEnum productModuleCategoryEnum = ProductModuleCategoryEnum.getByCode(req.getProductCategory()); + AssertUtil.notNull(productModuleCategoryEnum, "产品类型有误"); + + if (WorkspaceTypeCodeEnum.GENERAL_ENT.equals(workspaceTypeCodeEnum) || WorkspaceTypeCodeEnum.GENERAL_PROJECT.equals(workspaceTypeCodeEnum)) { + AssertUtil.notNull(req.getVersion(), "版本升级序列不能为空"); + AssertUtil.notNull(req.getMaxPersonCount(), "人数上限不能为空"); + AssertUtil.notNull(req.getPrice(), "价格不能为空"); + } + if (WorkspaceTypeCodeEnum.CHIEF.equals(workspaceTypeCodeEnum)) { + AssertUtil.isTrue(Objects.nonNull(req.getFeatureScope()) && Objects.nonNull(req.getFeatureScope().getChiefFeatureResourceId()) + && req.getFeatureScope().getChiefFeatureResourceId() > 0, "功能范围选择有误"); + } + productModule.setProductName(req.getProductName()); + productModule.setIcon(req.getIcon()); + productModule.setDictWorkspaceTypeId(req.getDictWorkspaceTypeId()); + productModule.setDictWorkspaceTypeCode(workspaceTypeCodeEnum.getCode()); + productModule.setProductType(0); + productModule.setStatus(0); + productModule.setCategory(req.getProductCategory()); + productModule.setVersion(req.getVersion()); + productModule.setMaxPersonCount(req.getMaxPersonCount()); + productModule.setMaxWorkspaceCount(req.getMaxWorkspaceCount()); + productModule.setPrice(req.getPrice()); + productModule.setSkus(org.apache.commons.collections.CollectionUtils.isEmpty(req.getSkus()) ? Lists.newArrayList() + : req.getSkus().stream().map(e -> ProductModule.Sku.builder() + .skuName(e.getSkuName()) + .model(e.getModel()) + .count(e.getCount()) + .unit(e.getUnit()) + .build()).collect(Collectors.toList())); + productModule.setMaterial(Optional.ofNullable(req.getMaterial()).map(e -> ProductModule.Material.builder() + .images(e.getImages()) + .videos(e.getVideos()) + .detailImages(e.getDetailImages()) + .build()).orElse(ProductModule.Material.builder().build())); + } + + /** + * 保存政务端产品功能权限 + */ + private void saveChiefFeatureResource(Long productId, Long dictWorkspaceTypeId, String dictWorkspaceTypeCode, Long rootFeatureId) { + SaasFeatureResource saasFeatureResource = saasFeatureResourceService.featureResourceById(rootFeatureId); + AssertUtil.notNull(saasFeatureResource, "功能范围选择端不能为空"); + AssertUtil.isTrue(saasFeatureResource.getParentId() == 0 && CHIEF_TERMINAL.equals(saasFeatureResource.getTerminal()), "功能范围选择端有误"); + + List saasFeatureResources = saasFeatureResourceService.listDescendant(rootFeatureId); + ProductFeatureRelationUpdateReq req = new ProductFeatureRelationUpdateReq(); + req.setDictCodeId(dictWorkspaceTypeId); + req.setDictCode(dictWorkspaceTypeCode); + req.setProductModuleId(productId); + req.setProductModuleId(productId); + Set featureIds = org.apache.commons.collections.CollectionUtils.isEmpty(saasFeatureResources) ? Sets.newHashSet() + : saasFeatureResources.stream().map(BaseEntity::getId).collect(Collectors.toSet()); + featureIds.add(saasFeatureResource.getId()); + req.getFeatureIds().addAll(featureIds); + productFeatureRelationService.updateFeatureRelation(Collections.singletonList(req)); + } + + private void fillFeatureScope(ProductVO product) { + ProductFeatureRelationSearchReq req = new ProductFeatureRelationSearchReq(); + req.setProductModuleId(product.getId()); + req.setDictCodeId(product.getDictWorkspaceTypeId()); + req.setDictCode(product.getDictWorkspaceTypeCode()); + ApiResult> result = productFeatureRelationService.featureList(req); + if (org.apache.commons.collections.CollectionUtils.isEmpty(result.getData())) { + return; + } + List featureIds = result.getData().stream().map(ProductFeatureRelationVO::getFeatureId).collect(Collectors.toList()); + List featureResources = saasFeatureResourceService.listByParentIdAndTerminalAndIds(0L, CHIEF_TERMINAL, featureIds); + if (CollectionUtil.isNotEmpty(featureResources)) { + product.setFeatureScope(ProductVO.FeatureScope.builder().chiefFeatureResourceId(featureResources.get(0).getId()).build()); + } + } } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java index 0e0e7fb9..1c9d8919 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java @@ -479,4 +479,14 @@ public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceServic .orderByAsc(SaasFeatureResource::getDisplayOrder) .list(); } + + @Override + public List listByParentIdAndTerminalAndIds(Long parentId, String terminal, List featureIds) { + return featureResourceDao.lambdaQuery() + .eq(BaseEntity::getIsDelete, 0) + .eq(Objects.nonNull(parentId), SaasFeatureResource::getParentId, parentId) + .eq(StringUtils.isNotBlank(terminal), SaasFeatureResource::getTerminal, terminal) + .in(CollectionUtil.isNotEmpty(featureIds), SaasFeatureResource::getId, featureIds) + .list(); + } }