docs(product): 删除产品管理SQL口径和业务设计文档
- 移除02-产品管理SQL已确认口径文档 - 移除02-产品管理业务设计文档 - 清理产品管理模块的详细设计说明 - 删除产品需求状态字段口径定义 - 移除来源承接与需求拆分口径说明 - 清理需求终态原因承接口径内容 - 删除产品生命周期管理设计 - 移除产品团队权限管理规范 - 清理产品与项目关系约束说明 - 删除轻量需求管理业务规则 - 移除产品状态机与流程设计 - 清理权限与动作矩阵定义
This commit is contained in:
@@ -3,6 +3,7 @@ package com.njcn.rdms.module.project.controller.admin.product;
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductRespVO;
|
||||
@@ -39,7 +40,6 @@ public class ProductController {
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新产品")
|
||||
@PreAuthorize("@ss.hasPermission('project:product:update')")
|
||||
public CommonResult<Boolean> updateProduct(@Valid @RequestBody ProductSaveReqVO updateReqVO) {
|
||||
productService.updateProduct(updateReqVO);
|
||||
return success(true);
|
||||
@@ -48,12 +48,18 @@ public class ProductController {
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获取产品详情")
|
||||
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('project:product:query')")
|
||||
public CommonResult<ProductRespVO> getProduct(@RequestParam("id") Long id) {
|
||||
ProductDO product = productService.getProduct(id);
|
||||
return success(BeanUtils.toBean(product, ProductRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/context")
|
||||
@Operation(summary = "获取产品上下文")
|
||||
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<ProductContextRespVO> getProductContext(@PathVariable("id") Long id) {
|
||||
return success(productService.getProductContext(id));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获取产品分页")
|
||||
@PreAuthorize("@ss.hasPermission('project:product:query')")
|
||||
@@ -64,7 +70,6 @@ public class ProductController {
|
||||
|
||||
@PostMapping("/change-status")
|
||||
@Operation(summary = "变更产品状态")
|
||||
@PreAuthorize("@ss.hasPermission('project:product:status')")
|
||||
public CommonResult<Boolean> changeProductStatus(@Valid @RequestBody ProductStatusActionReqVO reqVO) {
|
||||
productService.changeProductStatus(reqVO);
|
||||
return success(true);
|
||||
@@ -72,7 +77,6 @@ public class ProductController {
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Operation(summary = "删除产品")
|
||||
@PreAuthorize("@ss.hasPermission('project:product:delete')")
|
||||
public CommonResult<Boolean> deleteProduct(@Valid @RequestBody ProductDeleteReqVO reqVO) {
|
||||
productService.deleteProduct(reqVO);
|
||||
return success(true);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberInactiveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.service.product.ProductMemberService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 产品团队")
|
||||
@RestController
|
||||
@RequestMapping("/project/product")
|
||||
@Validated
|
||||
public class ProductMemberController {
|
||||
|
||||
@Resource
|
||||
private ProductMemberService productMemberService;
|
||||
|
||||
@GetMapping("/{id}/members")
|
||||
@Operation(summary = "获取产品团队成员列表")
|
||||
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<List<ProductMemberRespVO>> getProductMemberList(@PathVariable("id") Long productId) {
|
||||
return success(productMemberService.getProductMemberList(productId));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/members")
|
||||
@Operation(summary = "新增产品团队成员")
|
||||
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<Long> createProductMember(@PathVariable("id") Long productId,
|
||||
@Valid @RequestBody ProductMemberSaveReqVO reqVO) {
|
||||
return success(productMemberService.createProductMember(productId, reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/members/{memberId}")
|
||||
@Operation(summary = "调整产品团队成员角色")
|
||||
public CommonResult<Boolean> updateProductMember(@PathVariable("id") Long productId,
|
||||
@PathVariable("memberId") Long memberId,
|
||||
@Valid @RequestBody ProductMemberUpdateReqVO reqVO) {
|
||||
productMemberService.updateProductMember(productId, memberId, reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/members/{memberId}/inactive")
|
||||
@Operation(summary = "移出产品团队成员")
|
||||
public CommonResult<Boolean> inactiveProductMember(@PathVariable("id") Long productId,
|
||||
@PathVariable("memberId") Long memberId,
|
||||
@Valid @RequestBody ProductMemberInactiveReqVO reqVO) {
|
||||
productMemberService.inactiveProductMember(productId, memberId, reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingRespVO;
|
||||
import com.njcn.rdms.module.project.service.product.ProductService;
|
||||
import com.njcn.rdms.module.project.service.product.ProductSettingService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 产品设置")
|
||||
@RestController
|
||||
@RequestMapping("/project/product")
|
||||
@Validated
|
||||
public class ProductSettingController {
|
||||
|
||||
@Resource
|
||||
private ProductSettingService productSettingService;
|
||||
@Resource
|
||||
private ProductService productService;
|
||||
|
||||
@GetMapping("/{id}/settings")
|
||||
@Operation(summary = "获取产品设置")
|
||||
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<ProductSettingRespVO> getProductSettings(@PathVariable("id") Long id) {
|
||||
return success(productSettingService.getProductSettings(id));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/activities")
|
||||
@Operation(summary = "获取产品动态")
|
||||
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<PageResult<ProductActivityRespVO>> getProductActivities(@PathVariable("id") Long id,
|
||||
@Valid ProductActivityPageReqVO reqVO) {
|
||||
return success(productSettingService.getProductActivities(id, reqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/settings/base-info")
|
||||
@Operation(summary = "更新产品设置基础信息")
|
||||
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
|
||||
public CommonResult<Boolean> updateProductBaseInfo(@PathVariable("id") Long id,
|
||||
@Valid @RequestBody ProductSettingBaseInfoUpdateReqVO reqVO) {
|
||||
productService.updateProductBaseInfo(id, reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.activity;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 产品动态分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProductActivityPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "动态类型", example = "status")
|
||||
@Size(max = 16, message = "动态类型长度不能超过16个字符")
|
||||
private String activityType;
|
||||
|
||||
@Schema(description = "动作编码", example = "pause")
|
||||
@Size(max = 32, message = "动作编码长度不能超过32个字符")
|
||||
private String actionType;
|
||||
|
||||
@Schema(description = "操作时间区间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] operateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.activity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 产品动态 Response VO")
|
||||
@Data
|
||||
public class ProductActivityRespVO {
|
||||
|
||||
@Schema(description = "动态类型", example = "status")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "动作编码", example = "pause")
|
||||
private String actionType;
|
||||
|
||||
@Schema(description = "动作名称", example = "暂停")
|
||||
private String actionName;
|
||||
|
||||
@Schema(description = "原状态", example = "active")
|
||||
private String fromStatus;
|
||||
|
||||
@Schema(description = "目标状态", example = "paused")
|
||||
private String toStatus;
|
||||
|
||||
@Schema(description = "动作原因", example = "资源不足")
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "操作人用户编号", example = "1024")
|
||||
private Long operatorUserId;
|
||||
|
||||
@Schema(description = "操作人名称", example = "张三")
|
||||
private String operatorName;
|
||||
|
||||
@Schema(description = "操作时间", example = "2026-04-21 12:00:00")
|
||||
private LocalDateTime operateTime;
|
||||
|
||||
@Schema(description = "展示摘要", example = "张三执行了【暂停】:资源不足")
|
||||
private String summary;
|
||||
|
||||
@Schema(description = "补充详情")
|
||||
private String details;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品团队成员移出 Request VO")
|
||||
@Data
|
||||
public class ProductMemberInactiveReqVO {
|
||||
|
||||
@Schema(description = "移出原因", example = "已退出当前产品协作")
|
||||
@Size(max = 500, message = "移出原因长度不能超过500个字符")
|
||||
private String reason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 产品团队成员 Response VO")
|
||||
@Data
|
||||
public class ProductMemberRespVO {
|
||||
|
||||
@Schema(description = "团队关系编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户昵称", example = "小王")
|
||||
private String userNickname;
|
||||
|
||||
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3100000002001")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "角色名称", example = "产品经理")
|
||||
private String roleName;
|
||||
|
||||
@Schema(description = "角色编码", example = "product_manager")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(description = "是否当前产品经理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean managerFlag;
|
||||
|
||||
@Schema(description = "状态(0有效 1失效)", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "加入时间")
|
||||
private LocalDateTime joinedTime;
|
||||
|
||||
@Schema(description = "退出时间")
|
||||
private LocalDateTime leftTime;
|
||||
|
||||
@Schema(description = "备注", example = "当前负责需求收敛")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品团队成员新增 Request VO")
|
||||
@Data
|
||||
public class ProductMemberSaveReqVO {
|
||||
|
||||
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3100000002001")
|
||||
@NotNull(message = "角色编号不能为空")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "备注", example = "来自产品团队维护")
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "原产品经理用户编号,仅切换产品经理时传递", example = "2048")
|
||||
private Long previousManagerUserId;
|
||||
|
||||
@Schema(description = "原产品经理交接后的角色编号,仅切换产品经理时传递", example = "3100000002002")
|
||||
private Long previousManagerRoleId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品团队成员更新 Request VO")
|
||||
@Data
|
||||
public class ProductMemberUpdateReqVO {
|
||||
|
||||
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3100000002002")
|
||||
@NotNull(message = "角色编号不能为空")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "备注", example = "调整为产品观察者")
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "变更原因", example = "职责调整")
|
||||
@Size(max = 500, message = "变更原因长度不能超过500个字符")
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "原产品经理用户编号,仅切换产品经理时传递", example = "2048")
|
||||
private Long previousManagerUserId;
|
||||
|
||||
@Schema(description = "原产品经理交接后的角色编号,仅切换产品经理时传递", example = "3100000002002")
|
||||
private Long previousManagerRoleId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品上下文导航 Response VO")
|
||||
@Data
|
||||
public class ProductContextNavRespVO {
|
||||
|
||||
@Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3201")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "概览")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "菜单路径", example = "/project/product/overview")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "菜单图标", example = "mdi:view-dashboard-outline")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "显示顺序", example = "10")
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品上下文中的当前产品摘要 Response VO")
|
||||
@Data
|
||||
public class ProductContextProductRespVO {
|
||||
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPD2026001")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "产品经理用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long managerUserId;
|
||||
|
||||
@Schema(description = "产品状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "active")
|
||||
private String statusCode;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 产品上下文 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProductContextRespVO {
|
||||
|
||||
@Schema(description = "当前产品摘要")
|
||||
private ProductContextProductRespVO currentProduct;
|
||||
|
||||
@Schema(description = "当前用户在该产品下的角色信息")
|
||||
private ProductContextRoleRespVO currentRole;
|
||||
|
||||
@Schema(description = "当前产品下可见导航集合")
|
||||
private List<ProductContextNavRespVO> navs;
|
||||
|
||||
@Schema(description = "当前产品下按钮权限码集合")
|
||||
private List<String> buttons;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品上下文中的当前角色 Response VO")
|
||||
@Data
|
||||
public class ProductContextRoleRespVO {
|
||||
|
||||
@Schema(description = "对象角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3201")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "对象角色编码", example = "product_manager")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(description = "对象角色名称", example = "产品经理")
|
||||
private String roleName;
|
||||
|
||||
}
|
||||
@@ -24,4 +24,9 @@ public class ProductDeleteReqVO {
|
||||
@Size(max = 500, message = "删除原因长度不能超过500个字符")
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "删除确认口令,当前固定输入 DELETE", requiredMode = Schema.RequiredMode.REQUIRED, example = "DELETE")
|
||||
@NotBlank(message = "删除确认口令不能为空")
|
||||
@Size(max = 32, message = "删除确认口令长度不能超过32个字符")
|
||||
private String confirmText;
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.njcn.rdms.module.project.controller.admin.product.vo.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageParam;
|
||||
import com.njcn.rdms.framework.dict.validation.InDict;
|
||||
import com.njcn.rdms.module.project.enums.ProjectDictTypeConstants;
|
||||
import com.njcn.rdms.module.system.enums.DictTypeConstants;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
@@ -21,8 +21,8 @@ public class ProductPageReqVO extends PageParam {
|
||||
@Schema(description = "关键词,匹配产品编码或产品名称", example = "CNPD2026001")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "产品方向字典值", example = "embedded")
|
||||
@InDict(type = ProjectDictTypeConstants.PRODUCT_DIRECTION)
|
||||
@Schema(description = "产品方向字典值", example = "direction_value")
|
||||
@InDict(type = DictTypeConstants.OBJECT_DIRECTION)
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "产品经理用户编号", example = "1024")
|
||||
|
||||
@@ -15,7 +15,7 @@ public class ProductRespVO {
|
||||
@Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPD2026001")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "embedded")
|
||||
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
|
||||
@@ -33,9 +33,6 @@ public class ProductRespVO {
|
||||
@Schema(description = "最近一次状态动作原因", example = "阶段性暂停")
|
||||
private String lastStatusReason;
|
||||
|
||||
@Schema(description = "备注", example = "预留")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
|
||||
|
||||
import com.njcn.rdms.framework.dict.validation.InDict;
|
||||
import com.njcn.rdms.module.project.enums.ProjectDictTypeConstants;
|
||||
import com.njcn.rdms.module.system.enums.DictTypeConstants;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -19,10 +19,10 @@ public class ProductSaveReqVO {
|
||||
@Size(max = 64, message = "产品编码长度不能超过64个字符")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "embedded")
|
||||
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
|
||||
@NotBlank(message = "产品方向不能为空")
|
||||
@Size(max = 32, message = "产品方向长度不能超过32个字符")
|
||||
@InDict(type = ProjectDictTypeConstants.PRODUCT_DIRECTION)
|
||||
@InDict(type = DictTypeConstants.OBJECT_DIRECTION)
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
|
||||
@@ -37,8 +37,4 @@ public class ProductSaveReqVO {
|
||||
@Schema(description = "产品描述", example = "面向研发管理的一体化产品")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "备注", example = "预留")
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Schema(description = "管理后台 - 产品设置生命周期动作 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProductSettingActionRespVO {
|
||||
|
||||
@Schema(description = "动作编码", example = "archive")
|
||||
private String actionCode;
|
||||
|
||||
@Schema(description = "动作名称", example = "归档")
|
||||
private String actionName;
|
||||
|
||||
@Schema(description = "是否必须填写原因", example = "true")
|
||||
private Boolean needReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Schema(description = "管理后台 - 产品设置基础信息 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProductSettingBaseInfoRespVO {
|
||||
|
||||
@Schema(description = "产品编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "产品编码", example = "CNPD2026001")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "产品方向字典值", example = "direction_value")
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "产品名称", example = "统一交付平台")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "产品经理用户编号", example = "10001")
|
||||
private Long managerUserId;
|
||||
|
||||
@Schema(description = "产品经理昵称", example = "张三")
|
||||
private String managerUserNickname;
|
||||
|
||||
@Schema(description = "产品描述", example = "产品描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "产品状态编码", example = "active")
|
||||
private String statusCode;
|
||||
|
||||
@Schema(description = "最近一次状态动作原因", example = "恢复正常推进")
|
||||
private String lastStatusReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
|
||||
|
||||
import com.njcn.rdms.framework.dict.validation.InDict;
|
||||
import com.njcn.rdms.module.system.enums.DictTypeConstants;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 产品设置基础信息更新 Request VO")
|
||||
@Data
|
||||
public class ProductSettingBaseInfoUpdateReqVO {
|
||||
|
||||
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
|
||||
@NotBlank(message = "产品方向不能为空")
|
||||
@Size(max = 32, message = "产品方向长度不能超过32个字符")
|
||||
@InDict(type = DictTypeConstants.OBJECT_DIRECTION)
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
|
||||
@NotBlank(message = "产品名称不能为空")
|
||||
@Size(max = 128, message = "产品名称长度不能超过128个字符")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "产品描述", example = "面向研发管理的一体化产品")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 产品设置生命周期 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProductSettingLifecycleRespVO {
|
||||
|
||||
@Schema(description = "当前状态编码", example = "active")
|
||||
private String statusCode;
|
||||
|
||||
@Schema(description = "当前状态名称", example = "启用")
|
||||
private String statusName;
|
||||
|
||||
@Schema(description = "最近一次状态动作原因", example = "恢复正常推进")
|
||||
private String lastStatusReason;
|
||||
|
||||
@Schema(description = "是否终态", example = "false")
|
||||
private Boolean terminal;
|
||||
|
||||
@Schema(description = "是否允许编辑", example = "true")
|
||||
private Boolean allowEdit;
|
||||
|
||||
@Schema(description = "当前状态可执行动作")
|
||||
private List<ProductSettingActionRespVO> availableActions;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Schema(description = "管理后台 - 产品设置 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProductSettingRespVO {
|
||||
|
||||
@Schema(description = "产品基础信息")
|
||||
private ProductSettingBaseInfoRespVO baseInfo;
|
||||
|
||||
@Schema(description = "产品生命周期信息")
|
||||
private ProductSettingLifecycleRespVO lifecycle;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.member;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* RDMS 对象成员角色关系表
|
||||
*/
|
||||
@TableName("rdms_user_object_role")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UserObjectRoleDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 对象类型
|
||||
*/
|
||||
private String objectType;
|
||||
/**
|
||||
* 对象ID
|
||||
*/
|
||||
private Long objectId;
|
||||
/**
|
||||
* 对象角色ID
|
||||
*/
|
||||
private Long roleId;
|
||||
/**
|
||||
* 状态(0有效 1失效)
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 加入时间
|
||||
*/
|
||||
private LocalDateTime joinedTime;
|
||||
/**
|
||||
* 退出时间
|
||||
*/
|
||||
private LocalDateTime leftTime;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.permission;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 对象菜单表
|
||||
*/
|
||||
@TableName("system_menu")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SystemMenuDO extends BaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String permission;
|
||||
|
||||
private String scopeType;
|
||||
|
||||
private String objectType;
|
||||
|
||||
private Integer type;
|
||||
|
||||
private Integer sort;
|
||||
|
||||
private Long parentId;
|
||||
|
||||
private String path;
|
||||
|
||||
private String icon;
|
||||
|
||||
private Integer status;
|
||||
|
||||
private Boolean visible;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.permission;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 角色信息表
|
||||
*/
|
||||
@TableName("system_role")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SystemRoleDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
private String code;
|
||||
/**
|
||||
* 作用域类型
|
||||
*/
|
||||
private String scopeType;
|
||||
/**
|
||||
* 对象类型
|
||||
*/
|
||||
private String objectType;
|
||||
/**
|
||||
* 显示顺序
|
||||
*/
|
||||
private Integer sort;
|
||||
/**
|
||||
* 角色状态
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 角色类型
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.permission;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 对象角色菜单关联表
|
||||
*/
|
||||
@TableName("system_role_menu")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SystemRoleMenuDO extends BaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
private Long roleId;
|
||||
|
||||
private Long menuId;
|
||||
|
||||
}
|
||||
@@ -47,9 +47,5 @@ public class ProductDO extends BaseDO {
|
||||
* 最近一次状态动作原因
|
||||
*/
|
||||
private String lastStatusReason;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
|
||||
@@ -51,14 +51,6 @@ public class ObjectStatusModelDO extends BaseDO {
|
||||
* 是否允许编辑对象主数据
|
||||
*/
|
||||
private Boolean allowEdit;
|
||||
/**
|
||||
* 是否允许新建项目
|
||||
*/
|
||||
private Boolean allowCreateProject;
|
||||
/**
|
||||
* 是否允许新增需求
|
||||
*/
|
||||
private Boolean allowCreateRequirement;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.audit;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface BizAuditLogMapper extends BaseMapperX<BizAuditLogDO> {
|
||||
|
||||
default List<BizAuditLogDO> selectListByBiz(String bizType, Long bizId, String actionType, LocalDateTime[] operateTime) {
|
||||
return selectList(new LambdaQueryWrapperX<BizAuditLogDO>()
|
||||
.eq(BizAuditLogDO::getBizType, bizType)
|
||||
.eq(BizAuditLogDO::getBizId, bizId)
|
||||
.eqIfPresent(BizAuditLogDO::getActionType, actionType)
|
||||
.betweenIfPresent(BaseDO::getCreateTime, operateTime)
|
||||
.orderByDesc(BaseDO::getCreateTime)
|
||||
.orderByDesc(BizAuditLogDO::getId));
|
||||
}
|
||||
|
||||
default List<BizAuditLogDO> selectListByBizType(String bizType, String actionType, LocalDateTime[] operateTime) {
|
||||
return selectList(new LambdaQueryWrapperX<BizAuditLogDO>()
|
||||
.eq(BizAuditLogDO::getBizType, bizType)
|
||||
.eqIfPresent(BizAuditLogDO::getActionType, actionType)
|
||||
.betweenIfPresent(BaseDO::getCreateTime, operateTime)
|
||||
.orderByDesc(BaseDO::getCreateTime)
|
||||
.orderByDesc(BizAuditLogDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.member;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserObjectRoleMapper extends BaseMapperX<UserObjectRoleDO> {
|
||||
|
||||
default List<UserObjectRoleDO> selectListByObject(String objectType, Long objectId) {
|
||||
return selectList(new LambdaQueryWrapperX<UserObjectRoleDO>()
|
||||
.eq(UserObjectRoleDO::getObjectType, objectType)
|
||||
.eq(UserObjectRoleDO::getObjectId, objectId)
|
||||
.orderByAsc(UserObjectRoleDO::getStatus)
|
||||
.orderByAsc(UserObjectRoleDO::getJoinedTime)
|
||||
.orderByAsc(UserObjectRoleDO::getId));
|
||||
}
|
||||
|
||||
default UserObjectRoleDO selectByObjectAndUserId(String objectType, Long objectId, Long userId) {
|
||||
return selectOne(new LambdaQueryWrapperX<UserObjectRoleDO>()
|
||||
.eq(UserObjectRoleDO::getObjectType, objectType)
|
||||
.eq(UserObjectRoleDO::getObjectId, objectId)
|
||||
.eq(UserObjectRoleDO::getUserId, userId));
|
||||
}
|
||||
|
||||
default UserObjectRoleDO selectByIdAndObject(Long id, String objectType, Long objectId) {
|
||||
return selectOne(new LambdaQueryWrapperX<UserObjectRoleDO>()
|
||||
.eq(UserObjectRoleDO::getId, id)
|
||||
.eq(UserObjectRoleDO::getObjectType, objectType)
|
||||
.eq(UserObjectRoleDO::getObjectId, objectId));
|
||||
}
|
||||
|
||||
default UserObjectRoleDO selectActiveByObjectAndUserId(String objectType, Long objectId, Long userId) {
|
||||
return selectOne(new LambdaQueryWrapperX<UserObjectRoleDO>()
|
||||
.eq(UserObjectRoleDO::getObjectType, objectType)
|
||||
.eq(UserObjectRoleDO::getObjectId, objectId)
|
||||
.eq(UserObjectRoleDO::getUserId, userId)
|
||||
.eq(UserObjectRoleDO::getStatus, 0));
|
||||
}
|
||||
|
||||
default List<UserObjectRoleDO> selectListByIdsAndObject(List<Long> ids, String objectType, Long objectId) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return selectList(new LambdaQueryWrapperX<UserObjectRoleDO>()
|
||||
.in(UserObjectRoleDO::getId, ids)
|
||||
.eq(UserObjectRoleDO::getObjectType, objectType)
|
||||
.eq(UserObjectRoleDO::getObjectId, objectId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.permission;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface SystemMenuMapper extends BaseMapperX<SystemMenuDO> {
|
||||
|
||||
default List<SystemMenuDO> selectListByIdsAndScopeAndObjectType(Collection<Long> ids,
|
||||
String scopeType,
|
||||
String objectType) {
|
||||
return selectList(new LambdaQueryWrapperX<SystemMenuDO>()
|
||||
.inIfPresent(SystemMenuDO::getId, ids)
|
||||
.eq(SystemMenuDO::getScopeType, scopeType)
|
||||
.eq(SystemMenuDO::getObjectType, objectType));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.permission;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface SystemRoleMapper extends BaseMapperX<SystemRoleDO> {
|
||||
|
||||
default SystemRoleDO selectByIdAndScopeAndObjectType(Long id, String scopeType, String objectType) {
|
||||
return selectOne(new LambdaQueryWrapperX<SystemRoleDO>()
|
||||
.eq(SystemRoleDO::getId, id)
|
||||
.eq(SystemRoleDO::getScopeType, scopeType)
|
||||
.eq(SystemRoleDO::getObjectType, objectType)
|
||||
.eq(SystemRoleDO::getStatus, 0));
|
||||
}
|
||||
|
||||
default List<SystemRoleDO> selectListByIdsAndScopeAndObjectType(Collection<Long> ids,
|
||||
String scopeType,
|
||||
String objectType) {
|
||||
return selectList(new LambdaQueryWrapperX<SystemRoleDO>()
|
||||
.inIfPresent(SystemRoleDO::getId, ids)
|
||||
.eq(SystemRoleDO::getScopeType, scopeType)
|
||||
.eq(SystemRoleDO::getObjectType, objectType)
|
||||
.eq(SystemRoleDO::getStatus, 0));
|
||||
}
|
||||
|
||||
default SystemRoleDO selectByScopeAndObjectTypeAndCode(String scopeType, String objectType, String code) {
|
||||
return selectOne(new LambdaQueryWrapperX<SystemRoleDO>()
|
||||
.eq(SystemRoleDO::getScopeType, scopeType)
|
||||
.eq(SystemRoleDO::getObjectType, objectType)
|
||||
.eq(SystemRoleDO::getCode, code)
|
||||
.eq(SystemRoleDO::getStatus, 0));
|
||||
}
|
||||
|
||||
default SystemRoleDO selectByScopeAndObjectTypeAndName(String scopeType, String objectType, String name) {
|
||||
return selectOne(new LambdaQueryWrapperX<SystemRoleDO>()
|
||||
.eq(SystemRoleDO::getScopeType, scopeType)
|
||||
.eq(SystemRoleDO::getObjectType, objectType)
|
||||
.eq(SystemRoleDO::getName, name)
|
||||
.eq(SystemRoleDO::getStatus, 0));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.permission;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface SystemRoleMenuMapper extends BaseMapperX<SystemRoleMenuDO> {
|
||||
|
||||
default List<SystemRoleMenuDO> selectListByRoleId(Long roleId) {
|
||||
return selectList(SystemRoleMenuDO::getRoleId, roleId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,4 +43,19 @@ public interface ProductMapper extends BaseMapperX<ProductDO> {
|
||||
.orderByDesc(ProductDO::getCode));
|
||||
}
|
||||
|
||||
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
|
||||
ProductDO update = new ProductDO();
|
||||
update.setStatusCode(toStatus);
|
||||
update.setLastStatusReason(lastStatusReason);
|
||||
return update(update, new LambdaQueryWrapperX<ProductDO>()
|
||||
.eq(ProductDO::getId, id)
|
||||
.eq(ProductDO::getStatusCode, fromStatus));
|
||||
}
|
||||
|
||||
default int deleteByIdAndStatus(Long id, String statusCode) {
|
||||
return delete(new LambdaQueryWrapperX<ProductDO>()
|
||||
.eq(ProductDO::getId, id)
|
||||
.eq(ProductDO::getStatusCode, statusCode));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.product;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductStatusLogMapper extends BaseMapperX<ProductStatusLogDO> {
|
||||
|
||||
default List<ProductStatusLogDO> selectListByProductId(Long productId, String actionType, LocalDateTime[] operateTime) {
|
||||
return selectList(new LambdaQueryWrapperX<ProductStatusLogDO>()
|
||||
.eq(ProductStatusLogDO::getProductId, productId)
|
||||
.eqIfPresent(ProductStatusLogDO::getActionType, actionType)
|
||||
.betweenIfPresent(BaseDO::getCreateTime, operateTime)
|
||||
.orderByDesc(BaseDO::getCreateTime)
|
||||
.orderByDesc(ProductStatusLogDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@ public interface ObjectStatusModelMapper extends BaseMapperX<ObjectStatusModelDO
|
||||
.eq(ObjectStatusModelDO::getStatusCode, statusCode));
|
||||
}
|
||||
|
||||
default ObjectStatusModelDO selectByObjectTypeAndStatusCodeEnabled(String objectType, String statusCode) {
|
||||
return selectOne(new LambdaQueryWrapperX<ObjectStatusModelDO>()
|
||||
.eq(ObjectStatusModelDO::getObjectType, objectType)
|
||||
.eq(ObjectStatusModelDO::getStatusCode, statusCode)
|
||||
.eq(ObjectStatusModelDO::getStatus, 0));
|
||||
}
|
||||
|
||||
default List<ObjectStatusModelDO> selectListByObjectType(String objectType) {
|
||||
return selectList(new LambdaQueryWrapperX<ObjectStatusModelDO>()
|
||||
.eq(ObjectStatusModelDO::getObjectType, objectType)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.njcn.rdms.module.project.framework.security.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 对象级权限校验注解。
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CheckObjectPermission {
|
||||
|
||||
/**
|
||||
* 对象类型,例如 product。
|
||||
*/
|
||||
String objectType();
|
||||
|
||||
/**
|
||||
* 对象编号 SpEL 表达式。
|
||||
*/
|
||||
String objectId();
|
||||
|
||||
/**
|
||||
* 对象权限码。
|
||||
*/
|
||||
String permission() default "";
|
||||
|
||||
/**
|
||||
* 是否仅校验成员身份。
|
||||
*/
|
||||
boolean memberOnly() default false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.njcn.rdms.module.project.framework.security.aop;
|
||||
|
||||
import com.njcn.rdms.framework.common.util.spring.SpringExpressionUtils;
|
||||
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
|
||||
import com.njcn.rdms.module.project.framework.security.service.ObjectPermissionService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
|
||||
/**
|
||||
* 对象级权限切面。
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ObjectPermissionAspect {
|
||||
|
||||
private final Map<String, ObjectPermissionService> objectPermissionServiceMap;
|
||||
|
||||
public ObjectPermissionAspect(List<ObjectPermissionService> objectPermissionServices) {
|
||||
this.objectPermissionServiceMap = objectPermissionServices.stream()
|
||||
.collect(Collectors.toMap(ObjectPermissionService::getObjectType, Function.identity()));
|
||||
}
|
||||
|
||||
@Around("@annotation(checkObjectPermission)")
|
||||
public Object aroundPointCut(ProceedingJoinPoint joinPoint,
|
||||
CheckObjectPermission checkObjectPermission) throws Throwable {
|
||||
ObjectPermissionService permissionService = objectPermissionServiceMap.get(checkObjectPermission.objectType());
|
||||
if (permissionService == null) {
|
||||
throw invalidParamException("暂不支持对象类型:{}", checkObjectPermission.objectType());
|
||||
}
|
||||
Long objectId = resolveObjectId(joinPoint, checkObjectPermission.objectId());
|
||||
permissionService.checkPermission(objectId, checkObjectPermission.permission(),
|
||||
checkObjectPermission.memberOnly());
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
private Long resolveObjectId(ProceedingJoinPoint joinPoint, String expression) {
|
||||
Object parsedValue = SpringExpressionUtils.parseExpression(joinPoint, expression);
|
||||
if (parsedValue instanceof Number number) {
|
||||
return number.longValue();
|
||||
}
|
||||
if (parsedValue instanceof String value && StringUtils.hasText(value)) {
|
||||
try {
|
||||
return Long.parseLong(value.trim());
|
||||
} catch (NumberFormatException ex) {
|
||||
log.warn("[resolveObjectId][expression({}) value({}) 不是合法数字]", expression, value);
|
||||
}
|
||||
}
|
||||
if (Objects.isNull(parsedValue)) {
|
||||
throw invalidParamException("对象编号不能为空");
|
||||
}
|
||||
throw invalidParamException("对象编号解析失败:{}", expression);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.njcn.rdms.module.project.framework.security.service;
|
||||
|
||||
/**
|
||||
* 对象级权限服务。
|
||||
*/
|
||||
public interface ObjectPermissionService {
|
||||
|
||||
/**
|
||||
* 返回支持的对象类型。
|
||||
*/
|
||||
String getObjectType();
|
||||
|
||||
/**
|
||||
* 校验当前登录用户是否具备对象权限。
|
||||
*
|
||||
* @param objectId 对象编号
|
||||
* @param permission 权限码
|
||||
* @param memberOnly 是否只要求成员身份
|
||||
*/
|
||||
void checkPermission(Long objectId, String permission, boolean memberOnly);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.njcn.rdms.module.project.framework.security.service;
|
||||
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.permission.SystemMenuMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMenuMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
|
||||
/**
|
||||
* 产品对象权限服务。
|
||||
*/
|
||||
@Service
|
||||
public class ProductObjectPermissionService implements ObjectPermissionService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String ROLE_SCOPE_OBJECT = PermissionScopeTypeEnum.OBJECT.getScopeType();
|
||||
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
private SystemRoleMenuMapper systemRoleMenuMapper;
|
||||
@Resource
|
||||
private SystemMenuMapper systemMenuMapper;
|
||||
|
||||
@Override
|
||||
public String getObjectType() {
|
||||
return PRODUCT_OBJECT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkPermission(Long objectId, String permission, boolean memberOnly) {
|
||||
if (objectId == null) {
|
||||
throw invalidParamException("对象编号不能为空");
|
||||
}
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
UserObjectRoleDO currentMember = userObjectRoleMapper
|
||||
.selectActiveByObjectAndUserId(PRODUCT_OBJECT_TYPE, objectId, loginUserId);
|
||||
if (currentMember == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED,
|
||||
buildDeniedPermission(permission, memberOnly));
|
||||
}
|
||||
if (memberOnly) {
|
||||
return;
|
||||
}
|
||||
String normalizedPermission = normalizePermission(permission);
|
||||
if (!getRolePermissions(currentMember.getRoleId()).contains(normalizedPermission)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED, normalizedPermission);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getRolePermissions(Long roleId) {
|
||||
List<SystemRoleMenuDO> roleMenus = systemRoleMenuMapper.selectListByRoleId(roleId);
|
||||
if (roleMenus == null || roleMenus.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<Long> menuIds = roleMenus.stream()
|
||||
.map(SystemRoleMenuDO::getMenuId)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
if (menuIds.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
List<SystemMenuDO> menus = systemMenuMapper.selectListByIdsAndScopeAndObjectType(
|
||||
menuIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
|
||||
if (menus == null || menus.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return menus.stream()
|
||||
.filter(menu -> ROLE_SCOPE_OBJECT.equals(menu.getScopeType()))
|
||||
.filter(menu -> PRODUCT_OBJECT_TYPE.equals(menu.getObjectType()))
|
||||
.filter(menu -> Integer.valueOf(0).equals(menu.getStatus()))
|
||||
.map(SystemMenuDO::getPermission)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
private String normalizePermission(String permission) {
|
||||
if (!StringUtils.hasText(permission)) {
|
||||
throw invalidParamException("对象权限码不能为空");
|
||||
}
|
||||
return permission.trim();
|
||||
}
|
||||
|
||||
private String buildDeniedPermission(String permission, boolean memberOnly) {
|
||||
return memberOnly ? "member" : normalizePermission(permission);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.PageUtils;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityRespVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductStatusLogMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class ProductActivityQueryService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String MEMBER_BIZ_TYPE = "rdms_user_object_role";
|
||||
|
||||
private static final String ACTIVITY_TYPE_STATUS = "status";
|
||||
private static final String ACTIVITY_TYPE_PRODUCT = "product";
|
||||
private static final String ACTIVITY_TYPE_MEMBER = "member";
|
||||
|
||||
@Resource
|
||||
private ProductStatusLogMapper productStatusLogMapper;
|
||||
@Resource
|
||||
private BizAuditLogMapper bizAuditLogMapper;
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
|
||||
public PageResult<ProductActivityRespVO> getProductActivities(Long productId, ProductActivityPageReqVO reqVO) {
|
||||
List<ActivityItem> items = new ArrayList<>();
|
||||
if (includeType(reqVO.getActivityType(), ACTIVITY_TYPE_STATUS)) {
|
||||
productStatusLogMapper.selectListByProductId(productId, reqVO.getActionType(), reqVO.getOperateTime())
|
||||
.forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toStatusActivity(log))));
|
||||
}
|
||||
if (includeType(reqVO.getActivityType(), ACTIVITY_TYPE_PRODUCT)) {
|
||||
bizAuditLogMapper.selectListByBiz(PRODUCT_OBJECT_TYPE, productId, reqVO.getActionType(), reqVO.getOperateTime())
|
||||
.forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toProductActivity(log))));
|
||||
}
|
||||
if (includeType(reqVO.getActivityType(), ACTIVITY_TYPE_MEMBER)) {
|
||||
appendMemberActivities(productId, reqVO, items);
|
||||
}
|
||||
|
||||
items.sort(Comparator.comparing(ActivityItem::operateTime, Comparator.nullsLast(LocalDateTime::compareTo))
|
||||
.thenComparing(ActivityItem::sourceId, Comparator.nullsLast(Long::compareTo))
|
||||
.reversed());
|
||||
|
||||
List<ProductActivityRespVO> activities = items.stream()
|
||||
.map(ActivityItem::respVO)
|
||||
.toList();
|
||||
return buildPageResult(activities, reqVO);
|
||||
}
|
||||
|
||||
private void appendMemberActivities(Long productId, ProductActivityPageReqVO reqVO, List<ActivityItem> items) {
|
||||
List<BizAuditLogDO> memberLogs = bizAuditLogMapper
|
||||
.selectListByBizType(MEMBER_BIZ_TYPE, reqVO.getActionType(), reqVO.getOperateTime());
|
||||
if (memberLogs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Long> memberIds = memberLogs.stream()
|
||||
.map(BizAuditLogDO::getBizId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (memberIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, UserObjectRoleDO> memberMap = userObjectRoleMapper
|
||||
.selectListByIdsAndObject(memberIds, PRODUCT_OBJECT_TYPE, productId)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(UserObjectRoleDO::getId, Function.identity()));
|
||||
memberLogs.stream()
|
||||
.filter(log -> memberMap.containsKey(log.getBizId()))
|
||||
.forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toMemberActivity(log))));
|
||||
}
|
||||
|
||||
private PageResult<ProductActivityRespVO> buildPageResult(List<ProductActivityRespVO> activities,
|
||||
ProductActivityPageReqVO reqVO) {
|
||||
if (activities.isEmpty()) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
int start = PageUtils.getStart(reqVO);
|
||||
if (start >= activities.size()) {
|
||||
return PageResult.empty((long) activities.size());
|
||||
}
|
||||
int end = Math.min(start + reqVO.getPageSize(), activities.size());
|
||||
return new PageResult<>(activities.subList(start, end), (long) activities.size());
|
||||
}
|
||||
|
||||
private boolean includeType(String actual, String expected) {
|
||||
return !StringUtils.hasText(actual) || Objects.equals(actual.trim(), expected);
|
||||
}
|
||||
|
||||
private ProductActivityRespVO toStatusActivity(ProductStatusLogDO log) {
|
||||
ProductActivityRespVO respVO = new ProductActivityRespVO();
|
||||
respVO.setType(ACTIVITY_TYPE_STATUS);
|
||||
respVO.setActionType(log.getActionType());
|
||||
respVO.setActionName(resolveActionName(log.getActionType()));
|
||||
respVO.setFromStatus(log.getFromStatus());
|
||||
respVO.setToStatus(log.getToStatus());
|
||||
respVO.setReason(log.getReason());
|
||||
respVO.setOperatorUserId(log.getOperatorUserId());
|
||||
respVO.setOperatorName(log.getOperatorName());
|
||||
respVO.setOperateTime(log.getCreateTime());
|
||||
respVO.setSummary(buildSummary(log.getOperatorName(), respVO.getActionName(), log.getReason()));
|
||||
respVO.setDetails(JsonUtils.toJsonString(buildStatusDetails(log)));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildStatusDetails(ProductStatusLogDO log) {
|
||||
Map<String, Object> details = new LinkedHashMap<>();
|
||||
details.put("productCodeSnapshot", log.getProductCodeSnapshot());
|
||||
details.put("productNameSnapshot", log.getProductNameSnapshot());
|
||||
return details;
|
||||
}
|
||||
|
||||
private ProductActivityRespVO toProductActivity(BizAuditLogDO log) {
|
||||
ProductActivityRespVO respVO = new ProductActivityRespVO();
|
||||
respVO.setType(ACTIVITY_TYPE_PRODUCT);
|
||||
respVO.setActionType(log.getActionType());
|
||||
respVO.setActionName(resolveActionName(log.getActionType()));
|
||||
respVO.setFromStatus(log.getFromStatus());
|
||||
respVO.setToStatus(log.getToStatus());
|
||||
respVO.setReason(log.getReason());
|
||||
respVO.setOperatorUserId(log.getOperatorUserId());
|
||||
respVO.setOperatorName(log.getOperatorName());
|
||||
respVO.setOperateTime(log.getCreateTime());
|
||||
respVO.setSummary(buildSummary(log.getOperatorName(), respVO.getActionName(), log.getReason()));
|
||||
respVO.setDetails(log.getFieldChanges());
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private ProductActivityRespVO toMemberActivity(BizAuditLogDO log) {
|
||||
ProductActivityRespVO respVO = toProductActivity(log);
|
||||
respVO.setType(ACTIVITY_TYPE_MEMBER);
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private String resolveActionName(String actionType) {
|
||||
if (!StringUtils.hasText(actionType)) {
|
||||
return actionType;
|
||||
}
|
||||
return switch (actionType.trim()) {
|
||||
case "create" -> "创建";
|
||||
case "update" -> "更新";
|
||||
case "delete" -> "删除";
|
||||
case "pause" -> "暂停";
|
||||
case "resume" -> "恢复";
|
||||
case "archive" -> "归档";
|
||||
case "abandon" -> "废弃";
|
||||
case "change_manager" -> "切换产品经理";
|
||||
case "add_member" -> "新增成员";
|
||||
case "update_member" -> "调整成员";
|
||||
case "remove_member" -> "移出成员";
|
||||
default -> actionType.trim();
|
||||
};
|
||||
}
|
||||
|
||||
private String buildSummary(String operatorName, String actionName, String reason) {
|
||||
String actualOperatorName = StringUtils.hasText(operatorName) ? operatorName : "系统";
|
||||
if (StringUtils.hasText(reason)) {
|
||||
return String.format("%s执行了【%s】:%s", actualOperatorName, actionName, reason);
|
||||
}
|
||||
return String.format("%s执行了【%s】", actualOperatorName, actionName);
|
||||
}
|
||||
|
||||
private record ActivityItem(Long sourceId, LocalDateTime operateTime, ProductActivityRespVO respVO) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberInactiveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberUpdateReqVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 产品团队 Service 接口
|
||||
*/
|
||||
public interface ProductMemberService {
|
||||
|
||||
/**
|
||||
* 获取产品团队成员列表
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @return 成员列表
|
||||
*/
|
||||
List<ProductMemberRespVO> getProductMemberList(Long productId);
|
||||
|
||||
/**
|
||||
* 新增产品团队成员
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @param reqVO 请求参数
|
||||
* @return 团队关系编号
|
||||
*/
|
||||
Long createProductMember(Long productId, ProductMemberSaveReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 调整产品团队成员角色
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @param memberId 成员关系编号
|
||||
* @param reqVO 请求参数
|
||||
*/
|
||||
void updateProductMember(Long productId, Long memberId, ProductMemberUpdateReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 移出产品团队成员
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @param memberId 成员关系编号
|
||||
* @param reqVO 请求参数
|
||||
*/
|
||||
void inactiveProductMember(Long productId, Long memberId, ProductMemberInactiveReqVO reqVO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberInactiveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
/**
|
||||
* 产品团队 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String ROLE_SCOPE_OBJECT = "object";
|
||||
private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
|
||||
private static final String PRODUCT_UPDATE_PERMISSION = "project:product:update";
|
||||
|
||||
private static final Integer MEMBER_STATUS_ACTIVE = 0;
|
||||
private static final Integer MEMBER_STATUS_INACTIVE = 1;
|
||||
|
||||
private static final String PRODUCT_MANAGER_ROLE_CODE = "product_manager";
|
||||
|
||||
private static final String AUDIT_BIZ_TYPE_MEMBER = "rdms_user_object_role";
|
||||
private static final String AUDIT_BIZ_TYPE_PRODUCT = "product";
|
||||
private static final String AUDIT_ACTION_ADD_MEMBER = "add_member";
|
||||
private static final String AUDIT_ACTION_UPDATE_MEMBER = "update_member";
|
||||
private static final String AUDIT_ACTION_REMOVE_MEMBER = "remove_member";
|
||||
private static final String AUDIT_ACTION_CHANGE_MANAGER = "change_manager";
|
||||
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
private SystemRoleMapper systemRoleMapper;
|
||||
@Resource
|
||||
private BizAuditLogMapper bizAuditLogMapper;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public List<ProductMemberRespVO> getProductMemberList(Long productId) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
List<UserObjectRoleDO> members = userObjectRoleMapper.selectListByObject(PRODUCT_OBJECT_TYPE, productId);
|
||||
Map<Long, SystemRoleDO> roleMap = getRoleMap(members.stream().map(UserObjectRoleDO::getRoleId).collect(Collectors.toSet()));
|
||||
Map<Long, AdminUserRespDTO> userMap = getUserMap(members.stream().map(UserObjectRoleDO::getUserId).collect(Collectors.toSet()));
|
||||
return members.stream().map(member -> {
|
||||
ProductMemberRespVO respVO = new ProductMemberRespVO();
|
||||
respVO.setId(member.getId());
|
||||
respVO.setUserId(member.getUserId());
|
||||
AdminUserRespDTO user = userMap.get(member.getUserId());
|
||||
respVO.setUserNickname(user == null ? null : user.getNickname());
|
||||
respVO.setRoleId(member.getRoleId());
|
||||
SystemRoleDO role = roleMap.get(member.getRoleId());
|
||||
respVO.setRoleName(role == null ? null : role.getName());
|
||||
respVO.setRoleCode(role == null ? null : role.getCode());
|
||||
respVO.setManagerFlag(Objects.equals(member.getUserId(), product.getManagerUserId())
|
||||
&& Objects.equals(member.getStatus(), MEMBER_STATUS_ACTIVE));
|
||||
respVO.setStatus(member.getStatus());
|
||||
respVO.setJoinedTime(member.getJoinedTime());
|
||||
respVO.setLeftTime(member.getLeftTime());
|
||||
respVO.setRemark(member.getRemark());
|
||||
return respVO;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
public Long createProductMember(Long productId, ProductMemberSaveReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
SystemRoleDO targetRole = validateProductRole(reqVO.getRoleId());
|
||||
UserObjectRoleDO existingMember = userObjectRoleMapper
|
||||
.selectByObjectAndUserId(PRODUCT_OBJECT_TYPE, productId, reqVO.getUserId());
|
||||
if (existingMember != null && Objects.equals(existingMember.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
UserObjectRoleDO member;
|
||||
UserObjectRoleDO before = null;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (existingMember == null) {
|
||||
member = new UserObjectRoleDO();
|
||||
member.setUserId(reqVO.getUserId());
|
||||
member.setObjectType(PRODUCT_OBJECT_TYPE);
|
||||
member.setObjectId(productId);
|
||||
member.setRoleId(targetRole.getId());
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||
userObjectRoleMapper.insert(member);
|
||||
} else {
|
||||
before = cloneMember(existingMember);
|
||||
member = existingMember;
|
||||
member.setRoleId(targetRole.getId());
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||
userObjectRoleMapper.updateById(member);
|
||||
}
|
||||
|
||||
writeMemberAuditLog(member, AUDIT_ACTION_ADD_MEMBER, before, member, null);
|
||||
if (isManagerRole(targetRole)) {
|
||||
transferManager(product, member, reqVO.getPreviousManagerUserId(), reqVO.getPreviousManagerRoleId(), null);
|
||||
}
|
||||
return member.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
public void updateProductMember(Long productId, Long memberId, ProductMemberUpdateReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
UserObjectRoleDO member = validateMemberExists(productId, memberId);
|
||||
if (!Objects.equals(member.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_ACTIVE);
|
||||
}
|
||||
|
||||
SystemRoleDO targetRole = validateProductRole(reqVO.getRoleId());
|
||||
UserObjectRoleDO before = cloneMember(member);
|
||||
member.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||
|
||||
if (isManagerRole(targetRole)) {
|
||||
member.setRoleId(targetRole.getId());
|
||||
userObjectRoleMapper.updateById(member);
|
||||
transferManager(product, member, reqVO.getPreviousManagerUserId(),
|
||||
reqVO.getPreviousManagerRoleId(), normalizeNullableText(reqVO.getReason()));
|
||||
} else {
|
||||
if (Objects.equals(member.getUserId(), product.getManagerUserId())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_MEMBER_NOT_ALLOW_DOWNGRADE);
|
||||
}
|
||||
member.setRoleId(targetRole.getId());
|
||||
userObjectRoleMapper.updateById(member);
|
||||
}
|
||||
|
||||
writeMemberAuditLog(member, AUDIT_ACTION_UPDATE_MEMBER, before, member, normalizeNullableText(reqVO.getReason()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
public void inactiveProductMember(Long productId, Long memberId, ProductMemberInactiveReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
UserObjectRoleDO member = validateMemberExists(productId, memberId);
|
||||
if (!Objects.equals(member.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_ACTIVE);
|
||||
}
|
||||
if (Objects.equals(member.getUserId(), product.getManagerUserId())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_MEMBER_NOT_ALLOW_REMOVE);
|
||||
}
|
||||
|
||||
UserObjectRoleDO before = cloneMember(member);
|
||||
member.setStatus(MEMBER_STATUS_INACTIVE);
|
||||
member.setLeftTime(LocalDateTime.now());
|
||||
userObjectRoleMapper.updateById(member);
|
||||
|
||||
writeMemberAuditLog(member, AUDIT_ACTION_REMOVE_MEMBER, before, member,
|
||||
normalizeNullableText(reqVO.getReason()));
|
||||
}
|
||||
|
||||
private ProductDO validateProductExists(Long productId) {
|
||||
if (productId == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
ProductDO product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
private UserObjectRoleDO validateMemberExists(Long productId, Long memberId) {
|
||||
UserObjectRoleDO member = userObjectRoleMapper.selectByIdAndObject(memberId, PRODUCT_OBJECT_TYPE, productId);
|
||||
if (member == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_EXISTS);
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
private SystemRoleDO validateProductRole(Long roleId) {
|
||||
SystemRoleDO role = systemRoleMapper.selectByIdAndScopeAndObjectType(roleId, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
|
||||
if (role == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_ROLE_INVALID);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
private void transferManager(ProductDO product,
|
||||
UserObjectRoleDO targetManagerMember,
|
||||
Long previousManagerUserId,
|
||||
Long previousManagerRoleId,
|
||||
String reason) {
|
||||
Long currentManagerUserId = product.getManagerUserId();
|
||||
Long targetManagerUserId = targetManagerMember.getUserId();
|
||||
if (Objects.equals(currentManagerUserId, targetManagerUserId)) {
|
||||
product.setManagerUserId(targetManagerUserId);
|
||||
productMapper.updateById(product);
|
||||
return;
|
||||
}
|
||||
|
||||
SystemRoleDO previousManagerRole = validatePreviousManagerTransfer(currentManagerUserId,
|
||||
previousManagerUserId, previousManagerRoleId);
|
||||
transferPreviousManager(product.getId(), previousManagerUserId, previousManagerRole.getId(), reason);
|
||||
|
||||
product.setManagerUserId(targetManagerUserId);
|
||||
productMapper.updateById(product);
|
||||
writeManagerChangeAuditLog(product.getId(), currentManagerUserId, targetManagerUserId, reason);
|
||||
}
|
||||
|
||||
private SystemRoleDO validatePreviousManagerTransfer(Long currentManagerUserId,
|
||||
Long previousManagerUserId,
|
||||
Long previousManagerRoleId) {
|
||||
if (currentManagerUserId == null
|
||||
|| previousManagerUserId == null
|
||||
|| previousManagerRoleId == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_INFO_REQUIRED);
|
||||
}
|
||||
if (!Objects.equals(currentManagerUserId, previousManagerUserId)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_SOURCE_INVALID);
|
||||
}
|
||||
SystemRoleDO previousManagerRole = validateProductRole(previousManagerRoleId);
|
||||
if (isManagerRole(previousManagerRole)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_TRANSFER_ROLE_INVALID);
|
||||
}
|
||||
return previousManagerRole;
|
||||
}
|
||||
|
||||
private void transferPreviousManager(Long productId, Long previousManagerUserId, Long previousManagerRoleId, String reason) {
|
||||
UserObjectRoleDO existingMember = userObjectRoleMapper
|
||||
.selectByObjectAndUserId(PRODUCT_OBJECT_TYPE, productId, previousManagerUserId);
|
||||
UserObjectRoleDO before = existingMember == null ? null : cloneMember(existingMember);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
UserObjectRoleDO member;
|
||||
String actionType;
|
||||
if (existingMember == null) {
|
||||
member = new UserObjectRoleDO();
|
||||
member.setUserId(previousManagerUserId);
|
||||
member.setObjectType(PRODUCT_OBJECT_TYPE);
|
||||
member.setObjectId(productId);
|
||||
member.setRoleId(previousManagerRoleId);
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(null);
|
||||
userObjectRoleMapper.insert(member);
|
||||
actionType = AUDIT_ACTION_ADD_MEMBER;
|
||||
} else {
|
||||
member = existingMember;
|
||||
member.setRoleId(previousManagerRoleId);
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setLeftTime(null);
|
||||
if (!Objects.equals(before.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
member.setJoinedTime(now);
|
||||
actionType = AUDIT_ACTION_ADD_MEMBER;
|
||||
} else {
|
||||
actionType = AUDIT_ACTION_UPDATE_MEMBER;
|
||||
}
|
||||
userObjectRoleMapper.updateById(member);
|
||||
}
|
||||
writeMemberAuditLog(member, actionType, before, member, reason);
|
||||
}
|
||||
|
||||
private boolean isManagerRole(SystemRoleDO role) {
|
||||
return Objects.equals(PRODUCT_MANAGER_ROLE_CODE, role.getCode());
|
||||
}
|
||||
|
||||
private Map<Long, SystemRoleDO> getRoleMap(Set<Long> roleIds) {
|
||||
if (roleIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<SystemRoleDO> roles = systemRoleMapper
|
||||
.selectListByIdsAndScopeAndObjectType(roleIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
|
||||
return roles.stream().collect(Collectors.toMap(SystemRoleDO::getId, Function.identity()));
|
||||
}
|
||||
|
||||
private Map<Long, AdminUserRespDTO> getUserMap(Set<Long> userIds) {
|
||||
if (userIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return adminUserApi.getUserMap(userIds);
|
||||
}
|
||||
|
||||
private void writeMemberAuditLog(UserObjectRoleDO member,
|
||||
String actionType,
|
||||
UserObjectRoleDO before,
|
||||
UserObjectRoleDO after,
|
||||
String reason) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(AUDIT_BIZ_TYPE_MEMBER);
|
||||
auditLog.setBizId(member.getId());
|
||||
auditLog.setActionType(actionType);
|
||||
auditLog.setFieldChanges(buildMemberFieldChanges(before, after));
|
||||
auditLog.setReason(reason);
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private void writeManagerChangeAuditLog(Long productId, Long beforeManagerUserId, Long afterManagerUserId, String reason) {
|
||||
if (Objects.equals(beforeManagerUserId, afterManagerUserId)) {
|
||||
return;
|
||||
}
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(AUDIT_BIZ_TYPE_PRODUCT);
|
||||
auditLog.setBizId(productId);
|
||||
auditLog.setActionType(AUDIT_ACTION_CHANGE_MANAGER);
|
||||
auditLog.setFieldChanges(buildManagerFieldChanges(beforeManagerUserId, afterManagerUserId));
|
||||
auditLog.setReason(reason);
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private String buildMemberFieldChanges(UserObjectRoleDO before, UserObjectRoleDO after) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "userId", valueOf(before, UserObjectRoleDO::getUserId),
|
||||
valueOf(after, UserObjectRoleDO::getUserId));
|
||||
appendFieldChange(fieldChanges, "roleId", valueOf(before, UserObjectRoleDO::getRoleId),
|
||||
valueOf(after, UserObjectRoleDO::getRoleId));
|
||||
appendFieldChange(fieldChanges, "status", valueOf(before, UserObjectRoleDO::getStatus),
|
||||
valueOf(after, UserObjectRoleDO::getStatus));
|
||||
appendFieldChange(fieldChanges, "joinedTime", valueOf(before, UserObjectRoleDO::getJoinedTime),
|
||||
valueOf(after, UserObjectRoleDO::getJoinedTime));
|
||||
appendFieldChange(fieldChanges, "leftTime", valueOf(before, UserObjectRoleDO::getLeftTime),
|
||||
valueOf(after, UserObjectRoleDO::getLeftTime));
|
||||
appendFieldChange(fieldChanges, "remark", valueOf(before, UserObjectRoleDO::getRemark),
|
||||
valueOf(after, UserObjectRoleDO::getRemark));
|
||||
return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private String buildManagerFieldChanges(Long beforeManagerUserId, Long afterManagerUserId) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "managerUserId", beforeManagerUserId, afterManagerUserId);
|
||||
return JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private UserObjectRoleDO cloneMember(UserObjectRoleDO source) {
|
||||
UserObjectRoleDO clone = new UserObjectRoleDO();
|
||||
clone.setId(source.getId());
|
||||
clone.setUserId(source.getUserId());
|
||||
clone.setObjectType(source.getObjectType());
|
||||
clone.setObjectId(source.getObjectId());
|
||||
clone.setRoleId(source.getRoleId());
|
||||
clone.setStatus(source.getStatus());
|
||||
clone.setJoinedTime(source.getJoinedTime());
|
||||
clone.setLeftTime(source.getLeftTime());
|
||||
clone.setRemark(source.getRemark());
|
||||
return clone;
|
||||
}
|
||||
|
||||
private <T> T valueOf(UserObjectRoleDO member, Function<UserObjectRoleDO, T> getter) {
|
||||
return member == null ? null : getter.apply(member);
|
||||
}
|
||||
|
||||
private void appendFieldChange(Map<String, Object> fieldChanges, String fieldName, Object before, Object after) {
|
||||
if (Objects.equals(before, after)) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("before", before);
|
||||
value.put("after", after);
|
||||
fieldChanges.put(fieldName, value);
|
||||
}
|
||||
|
||||
private String normalizeNullableText(String value) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private String defaultText(String value) {
|
||||
return StringUtils.hasText(value) ? value : "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
|
||||
/**
|
||||
@@ -27,6 +29,14 @@ public interface ProductService {
|
||||
*/
|
||||
void updateProduct(ProductSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 更新产品设置页基础信息
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @param reqVO 更新请求
|
||||
*/
|
||||
void updateProductBaseInfo(Long productId, ProductSettingBaseInfoUpdateReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获取产品详情
|
||||
*
|
||||
@@ -35,6 +45,14 @@ public interface ProductService {
|
||||
*/
|
||||
ProductDO getProduct(Long id);
|
||||
|
||||
/**
|
||||
* 获取产品上下文
|
||||
*
|
||||
* @param id 产品编号
|
||||
* @return 产品上下文
|
||||
*/
|
||||
ProductContextRespVO getProductContext(Long id);
|
||||
|
||||
/**
|
||||
* 获取产品分页
|
||||
*
|
||||
|
||||
@@ -5,30 +5,54 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextNavRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextProductRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRoleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemMenuDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.permission.SystemRoleMenuDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.permission.SystemMenuMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.permission.SystemRoleMenuMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductStatusLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
|
||||
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
@@ -40,16 +64,29 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String ROLE_SCOPE_OBJECT = PermissionScopeTypeEnum.OBJECT.getScopeType();
|
||||
private static final String PRODUCT_MANAGER_ROLE_CODE = "product_manager";
|
||||
|
||||
private static final String PRODUCT_ACTIVE_STATUS = "active";
|
||||
private static final String PRODUCT_PAUSED_STATUS = "paused";
|
||||
private static final String PRODUCT_ARCHIVED_STATUS = "archived";
|
||||
private static final String PRODUCT_ABANDONED_STATUS = "abandoned";
|
||||
|
||||
private static final Integer MEMBER_STATUS_ACTIVE = 0;
|
||||
|
||||
private static final String PRODUCT_CREATE_ACTION = "create";
|
||||
private static final String PRODUCT_UPDATE_ACTION = "update";
|
||||
private static final String PRODUCT_DELETE_ACTION = "delete";
|
||||
private static final String PRODUCT_CHANGE_MANAGER_ACTION = "change_manager";
|
||||
private static final String PRODUCT_ADD_MEMBER_ACTION = "add_member";
|
||||
private static final String AUDIT_BIZ_TYPE_MEMBER = "rdms_user_object_role";
|
||||
|
||||
private static final String PRODUCT_CODE_PREFIX = "CNPD";
|
||||
private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
|
||||
private static final String PRODUCT_UPDATE_PERMISSION = "project:product:update";
|
||||
private static final String PRODUCT_STATUS_PERMISSION = "project:product:status";
|
||||
private static final String PRODUCT_DELETE_PERMISSION = "project:product:delete";
|
||||
private static final String PRODUCT_DELETE_CONFIRM_TEXT = "DELETE";
|
||||
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@@ -60,6 +97,14 @@ public class ProductServiceImpl implements ProductService {
|
||||
@Resource
|
||||
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
private SystemRoleMapper systemRoleMapper;
|
||||
@Resource
|
||||
private SystemRoleMenuMapper systemRoleMenuMapper;
|
||||
@Resource
|
||||
private SystemMenuMapper systemMenuMapper;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@Override
|
||||
@@ -75,44 +120,82 @@ public class ProductServiceImpl implements ProductService {
|
||||
product.setName(createReqVO.getName().trim());
|
||||
product.setManagerUserId(createReqVO.getManagerUserId());
|
||||
product.setDescription(normalizeNullableText(createReqVO.getDescription()));
|
||||
product.setRemark(normalizeNullableText(createReqVO.getRemark()));
|
||||
productMapper.insert(product);
|
||||
|
||||
initManagerMemberRelation(product);
|
||||
writeBizAuditLog(product, PRODUCT_CREATE_ACTION, null, PRODUCT_ACTIVE_STATUS,
|
||||
buildFieldChanges(null, product), null);
|
||||
buildProductFieldChanges(null, product), null);
|
||||
return product.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#updateReqVO.id",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
public void updateProduct(ProductSaveReqVO updateReqVO) {
|
||||
if (updateReqVO.getId() == null) {
|
||||
throw invalidParamException("产品编号不能为空");
|
||||
}
|
||||
ProductDO product = validateProductExists(updateReqVO.getId());
|
||||
validateManagerUser(updateReqVO.getManagerUserId());
|
||||
validateProductCodeUnchanged(product, updateReqVO.getCode());
|
||||
validateProductEditable(product, updateReqVO);
|
||||
validateProductNameUnique(updateReqVO.getId(), updateReqVO.getName());
|
||||
|
||||
ProductDO before = BeanUtils.toBean(product, ProductDO.class);
|
||||
|
||||
product.setDirectionCode(updateReqVO.getDirectionCode());
|
||||
product.setName(updateReqVO.getName().trim());
|
||||
product.setManagerUserId(updateReqVO.getManagerUserId());
|
||||
product.setDescription(normalizeNullableText(updateReqVO.getDescription()));
|
||||
product.setRemark(normalizeNullableText(updateReqVO.getRemark()));
|
||||
productMapper.updateById(product);
|
||||
|
||||
writeBizAuditLog(product, PRODUCT_UPDATE_ACTION, product.getStatusCode(), product.getStatusCode(),
|
||||
buildFieldChanges(before, product), null);
|
||||
validateManagerUserUnchanged(product, updateReqVO.getManagerUserId());
|
||||
applyProductBaseInfoUpdate(product, updateReqVO.getDirectionCode(), updateReqVO.getName(), updateReqVO.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
public void updateProductBaseInfo(Long productId, ProductSettingBaseInfoUpdateReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
applyProductBaseInfoUpdate(product, reqVO.getDirectionCode(), reqVO.getName(), reqVO.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#id",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public ProductDO getProduct(Long id) {
|
||||
return validateProductExists(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#id", memberOnly = true)
|
||||
public ProductContextRespVO getProductContext(Long id) {
|
||||
ProductDO product = validateProductExists(id);
|
||||
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
UserObjectRoleDO currentMember = userObjectRoleMapper
|
||||
.selectActiveByObjectAndUserId(PRODUCT_OBJECT_TYPE, id, loginUserId);
|
||||
ProductContextRespVO respVO = new ProductContextRespVO();
|
||||
respVO.setCurrentProduct(buildCurrentProduct(product));
|
||||
if (currentMember == null) {
|
||||
respVO.setNavs(Collections.emptyList());
|
||||
respVO.setButtons(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
|
||||
SystemRoleDO currentRole = systemRoleMapper
|
||||
.selectByIdAndScopeAndObjectType(currentMember.getRoleId(), ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
|
||||
respVO.setCurrentRole(buildCurrentRole(currentMember, currentRole));
|
||||
|
||||
List<SystemRoleMenuDO> roleMenus = systemRoleMenuMapper.selectListByRoleId(currentMember.getRoleId());
|
||||
if (roleMenus.isEmpty()) {
|
||||
respVO.setNavs(Collections.emptyList());
|
||||
respVO.setButtons(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
|
||||
Set<Long> menuIds = roleMenus.stream()
|
||||
.map(SystemRoleMenuDO::getMenuId)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
List<SystemMenuDO> menus = filterEnableProductObjectMenus(
|
||||
systemMenuMapper.selectListByIdsAndScopeAndObjectType(menuIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE));
|
||||
|
||||
respVO.setNavs(buildContextNavs(menus));
|
||||
respVO.setButtons(buildContextButtons(menus));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ProductDO> getProductPage(ProductPageReqVO pageReqVO) {
|
||||
return productMapper.selectPage(pageReqVO);
|
||||
@@ -120,6 +203,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.id",
|
||||
permission = PRODUCT_STATUS_PERMISSION)
|
||||
public void changeProductStatus(ProductStatusActionReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(reqVO.getId());
|
||||
String actionCode = reqVO.getActionCode().trim();
|
||||
@@ -129,9 +214,12 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
String fromStatus = product.getStatusCode();
|
||||
String toStatus = transition.getToStatusCode();
|
||||
int updateCount = productMapper.updateStatusByIdAndStatus(product.getId(), fromStatus, toStatus, reason);
|
||||
if (updateCount != 1) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_CONCURRENT_MODIFIED);
|
||||
}
|
||||
product.setStatusCode(toStatus);
|
||||
product.setLastStatusReason(reason);
|
||||
productMapper.updateById(product);
|
||||
|
||||
writeProductStatusLog(product, actionCode, fromStatus, toStatus, reason);
|
||||
writeBizAuditLog(product, actionCode, fromStatus, toStatus, null, reason);
|
||||
@@ -139,15 +227,21 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.id",
|
||||
permission = PRODUCT_DELETE_PERMISSION)
|
||||
public void deleteProduct(ProductDeleteReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(reqVO.getId());
|
||||
validateDeleteConfirmText(reqVO.getConfirmText());
|
||||
if (!Objects.equals(product.getName(), reqVO.getProductName().trim())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_DELETE_NAME_MISMATCH);
|
||||
}
|
||||
|
||||
String reason = reqVO.getReason().trim();
|
||||
String fromStatus = product.getStatusCode();
|
||||
productMapper.deleteById(reqVO.getId());
|
||||
int deleteCount = productMapper.deleteByIdAndStatus(reqVO.getId(), fromStatus);
|
||||
if (deleteCount != 1) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_CONCURRENT_MODIFIED);
|
||||
}
|
||||
|
||||
writeProductStatusLog(product, PRODUCT_DELETE_ACTION, fromStatus, null, reason);
|
||||
writeBizAuditLog(product, PRODUCT_DELETE_ACTION, fromStatus, null, null, reason);
|
||||
@@ -213,8 +307,20 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateManagerUserUnchanged(ProductDO product, Long managerUserId) {
|
||||
if (!Objects.equals(product.getManagerUserId(), managerUserId)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MANAGER_NOT_MODIFIABLE);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateProductEditable(ProductDO product, ProductSaveReqVO updateReqVO) {
|
||||
validateProductEditable(product, updateReqVO.getDirectionCode(), updateReqVO.getName());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateProductEditable(ProductDO product, String directionCode, String name) {
|
||||
if (PRODUCT_ARCHIVED_STATUS.equals(product.getStatusCode())
|
||||
|| PRODUCT_ABANDONED_STATUS.equals(product.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_NOT_ALLOW_EDIT);
|
||||
@@ -222,8 +328,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
if (!PRODUCT_PAUSED_STATUS.equals(product.getStatusCode())) {
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(product.getDirectionCode(), updateReqVO.getDirectionCode())
|
||||
|| !Objects.equals(product.getName(), updateReqVO.getName().trim())) {
|
||||
if (!Objects.equals(product.getDirectionCode(), directionCode)
|
||||
|| !Objects.equals(product.getName(), name.trim())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_PAUSED_ONLY_ALLOW_LIMITED_UPDATE);
|
||||
}
|
||||
}
|
||||
@@ -245,6 +351,14 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateDeleteConfirmText(String confirmText) {
|
||||
String normalizedConfirmText = normalizeNullableText(confirmText);
|
||||
if (!Objects.equals(PRODUCT_DELETE_CONFIRM_TEXT, normalizedConfirmText)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_DELETE_CONFIRM_TEXT_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateProductCode(String code) {
|
||||
String normalizedCode = normalizeNullableText(code);
|
||||
if (StringUtils.hasText(normalizedCode)) {
|
||||
@@ -271,6 +385,90 @@ public class ProductServiceImpl implements ProductService {
|
||||
return generatedCode;
|
||||
}
|
||||
|
||||
private void initManagerMemberRelation(ProductDO product) {
|
||||
SystemRoleDO managerRole = systemRoleMapper
|
||||
.selectByScopeAndObjectTypeAndCode(ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE, PRODUCT_MANAGER_ROLE_CODE);
|
||||
if (managerRole == null) {
|
||||
throw invalidParamException("未找到产品经理对象角色配置:{}", PRODUCT_MANAGER_ROLE_CODE);
|
||||
}
|
||||
|
||||
UserObjectRoleDO member = new UserObjectRoleDO();
|
||||
member.setUserId(product.getManagerUserId());
|
||||
member.setObjectType(PRODUCT_OBJECT_TYPE);
|
||||
member.setObjectId(product.getId());
|
||||
member.setRoleId(managerRole.getId());
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(LocalDateTime.now());
|
||||
member.setLeftTime(null);
|
||||
userObjectRoleMapper.insert(member);
|
||||
|
||||
writeMemberInitAuditLog(member);
|
||||
writeManagerInitAuditLog(product.getId(), product.getManagerUserId());
|
||||
}
|
||||
|
||||
private List<SystemMenuDO> filterEnableProductObjectMenus(List<SystemMenuDO> menus) {
|
||||
if (menus == null || menus.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return menus.stream()
|
||||
.filter(menu -> Objects.equals(ROLE_SCOPE_OBJECT, menu.getScopeType()))
|
||||
.filter(menu -> Objects.equals(PRODUCT_OBJECT_TYPE, menu.getObjectType()))
|
||||
.filter(menu -> Objects.equals(0, menu.getStatus()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ProductContextProductRespVO buildCurrentProduct(ProductDO product) {
|
||||
return BeanUtils.toBean(product, ProductContextProductRespVO.class);
|
||||
}
|
||||
|
||||
private ProductContextRoleRespVO buildCurrentRole(UserObjectRoleDO currentMember, SystemRoleDO currentRole) {
|
||||
ProductContextRoleRespVO roleRespVO = new ProductContextRoleRespVO();
|
||||
roleRespVO.setRoleId(currentMember.getRoleId());
|
||||
if (currentRole != null) {
|
||||
roleRespVO.setRoleCode(currentRole.getCode());
|
||||
roleRespVO.setRoleName(currentRole.getName());
|
||||
}
|
||||
return roleRespVO;
|
||||
}
|
||||
|
||||
private List<ProductContextNavRespVO> buildContextNavs(List<SystemMenuDO> menus) {
|
||||
if (menus.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ProductContextNavRespVO> navs = menus.stream()
|
||||
.filter(menu -> !MenuTypeEnum.BUTTON.getType().equals(menu.getType()))
|
||||
.filter(menu -> !Boolean.FALSE.equals(menu.getVisible()))
|
||||
.map(menu -> {
|
||||
ProductContextNavRespVO nav = new ProductContextNavRespVO();
|
||||
nav.setId(menu.getId());
|
||||
nav.setName(menu.getName());
|
||||
nav.setPath(menu.getPath());
|
||||
nav.setIcon(menu.getIcon());
|
||||
nav.setSort(menu.getSort());
|
||||
return nav;
|
||||
})
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
navs.sort(Comparator.comparing(ProductContextNavRespVO::getSort, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(ProductContextNavRespVO::getId, Comparator.nullsLast(Long::compareTo)));
|
||||
return navs;
|
||||
}
|
||||
|
||||
private List<String> buildContextButtons(List<SystemMenuDO> menus) {
|
||||
if (menus.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return menus.stream()
|
||||
.filter(menu -> MenuTypeEnum.BUTTON.getType().equals(menu.getType()))
|
||||
.map(SystemMenuDO::getPermission)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(String::trim)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void writeProductStatusLog(ProductDO product, String actionType, String fromStatus,
|
||||
String toStatus, String reason) {
|
||||
ProductStatusLogDO statusLog = new ProductStatusLogDO();
|
||||
@@ -301,7 +499,56 @@ public class ProductServiceImpl implements ProductService {
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private String buildFieldChanges(ProductDO before, ProductDO after) {
|
||||
private void writeMemberInitAuditLog(UserObjectRoleDO member) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(AUDIT_BIZ_TYPE_MEMBER);
|
||||
auditLog.setBizId(member.getId());
|
||||
auditLog.setActionType(PRODUCT_ADD_MEMBER_ACTION);
|
||||
auditLog.setFieldChanges(buildMemberFieldChanges(member));
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private void writeManagerInitAuditLog(Long productId, Long managerUserId) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(PRODUCT_OBJECT_TYPE);
|
||||
auditLog.setBizId(productId);
|
||||
auditLog.setActionType(PRODUCT_CHANGE_MANAGER_ACTION);
|
||||
auditLog.setFieldChanges(buildManagerFieldChanges(null, managerUserId));
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private ProductDO cloneProduct(ProductDO source) {
|
||||
ProductDO target = new ProductDO();
|
||||
target.setId(source.getId());
|
||||
target.setCode(source.getCode());
|
||||
target.setDirectionCode(source.getDirectionCode());
|
||||
target.setStatusCode(source.getStatusCode());
|
||||
target.setName(source.getName());
|
||||
target.setManagerUserId(source.getManagerUserId());
|
||||
target.setDescription(source.getDescription());
|
||||
target.setLastStatusReason(source.getLastStatusReason());
|
||||
return target;
|
||||
}
|
||||
|
||||
private void applyProductBaseInfoUpdate(ProductDO product, String directionCode, String name, String description) {
|
||||
validateProductEditable(product, directionCode, name);
|
||||
validateProductNameUnique(product.getId(), name);
|
||||
|
||||
ProductDO before = cloneProduct(product);
|
||||
product.setDirectionCode(directionCode);
|
||||
product.setName(name.trim());
|
||||
product.setDescription(normalizeNullableText(description));
|
||||
productMapper.updateById(product);
|
||||
|
||||
writeBizAuditLog(product, PRODUCT_UPDATE_ACTION, before.getStatusCode(), product.getStatusCode(),
|
||||
buildProductFieldChanges(before, product), null);
|
||||
}
|
||||
|
||||
private String buildProductFieldChanges(ProductDO before, ProductDO after) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "code", valueOf(before, ProductDO::getCode), valueOf(after, ProductDO::getCode));
|
||||
appendFieldChange(fieldChanges, "directionCode", valueOf(before, ProductDO::getDirectionCode),
|
||||
@@ -315,10 +562,26 @@ public class ProductServiceImpl implements ProductService {
|
||||
valueOf(after, ProductDO::getDescription));
|
||||
appendFieldChange(fieldChanges, "lastStatusReason", valueOf(before, ProductDO::getLastStatusReason),
|
||||
valueOf(after, ProductDO::getLastStatusReason));
|
||||
appendFieldChange(fieldChanges, "remark", valueOf(before, ProductDO::getRemark), valueOf(after, ProductDO::getRemark));
|
||||
return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private String buildMemberFieldChanges(UserObjectRoleDO member) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "userId", null, member.getUserId());
|
||||
appendFieldChange(fieldChanges, "roleId", null, member.getRoleId());
|
||||
appendFieldChange(fieldChanges, "status", null, member.getStatus());
|
||||
appendFieldChange(fieldChanges, "joinedTime", null, member.getJoinedTime());
|
||||
appendFieldChange(fieldChanges, "leftTime", null, member.getLeftTime());
|
||||
appendFieldChange(fieldChanges, "remark", null, member.getRemark());
|
||||
return JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private String buildManagerFieldChanges(Long beforeManagerUserId, Long afterManagerUserId) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "managerUserId", beforeManagerUserId, afterManagerUserId);
|
||||
return JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private <T> T valueOf(ProductDO product, Function<ProductDO, T> getter) {
|
||||
return product == null ? null : getter.apply(product);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingRespVO;
|
||||
|
||||
/**
|
||||
* 产品设置 Service 接口
|
||||
*/
|
||||
public interface ProductSettingService {
|
||||
|
||||
/**
|
||||
* 获取产品设置
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @return 产品设置
|
||||
*/
|
||||
ProductSettingRespVO getProductSettings(Long productId);
|
||||
|
||||
/**
|
||||
* 获取产品动态
|
||||
*
|
||||
* @param productId 产品编号
|
||||
* @param reqVO 查询参数
|
||||
* @return 产品动态分页
|
||||
*/
|
||||
PageResult<ProductActivityRespVO> getProductActivities(Long productId, ProductActivityPageReqVO reqVO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingLifecycleRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingRespVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
@Service
|
||||
public class ProductSettingServiceImpl implements ProductSettingService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
|
||||
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private ProductStatusViewService productStatusViewService;
|
||||
@Resource
|
||||
private ProductActivityQueryService productActivityQueryService;
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public ProductSettingRespVO getProductSettings(Long productId) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
ProductSettingRespVO respVO = new ProductSettingRespVO();
|
||||
respVO.setBaseInfo(buildBaseInfo(product));
|
||||
respVO.setLifecycle(buildLifecycle(product));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public PageResult<ProductActivityRespVO> getProductActivities(Long productId, ProductActivityPageReqVO reqVO) {
|
||||
validateProductExists(productId);
|
||||
return productActivityQueryService.getProductActivities(productId, reqVO);
|
||||
}
|
||||
|
||||
private ProductDO validateProductExists(Long productId) {
|
||||
if (productId == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
ProductDO product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
private ProductSettingBaseInfoRespVO buildBaseInfo(ProductDO product) {
|
||||
ProductSettingBaseInfoRespVO baseInfo = new ProductSettingBaseInfoRespVO();
|
||||
baseInfo.setId(product.getId());
|
||||
baseInfo.setCode(product.getCode());
|
||||
baseInfo.setDirectionCode(product.getDirectionCode());
|
||||
baseInfo.setName(product.getName());
|
||||
baseInfo.setManagerUserId(product.getManagerUserId());
|
||||
baseInfo.setManagerUserNickname(getManagerNickname(product.getManagerUserId()));
|
||||
baseInfo.setDescription(product.getDescription());
|
||||
baseInfo.setStatusCode(product.getStatusCode());
|
||||
baseInfo.setLastStatusReason(product.getLastStatusReason());
|
||||
return baseInfo;
|
||||
}
|
||||
|
||||
private ProductSettingLifecycleRespVO buildLifecycle(ProductDO product) {
|
||||
return productStatusViewService.getLifecycle(product.getStatusCode(), product.getLastStatusReason());
|
||||
}
|
||||
|
||||
private String getManagerNickname(Long managerUserId) {
|
||||
if (managerUserId == null) {
|
||||
return null;
|
||||
}
|
||||
CommonResult<AdminUserRespDTO> result = adminUserApi.getUser(managerUserId);
|
||||
AdminUserRespDTO user = result == null ? null : result.getCheckedData();
|
||||
return user == null ? null : user.getNickname();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingActionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingLifecycleRespVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
@Service
|
||||
public class ProductStatusViewService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
|
||||
|
||||
public ProductSettingLifecycleRespVO getLifecycle(String statusCode, String lastStatusReason) {
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectByObjectTypeAndStatusCodeEnabled(PRODUCT_OBJECT_TYPE, statusCode);
|
||||
if (statusModel == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
|
||||
ProductSettingLifecycleRespVO lifecycle = new ProductSettingLifecycleRespVO();
|
||||
lifecycle.setStatusCode(statusModel.getStatusCode());
|
||||
lifecycle.setStatusName(statusModel.getStatusName());
|
||||
lifecycle.setTerminal(statusModel.getTerminalFlag());
|
||||
lifecycle.setAllowEdit(statusModel.getAllowEdit());
|
||||
lifecycle.setLastStatusReason(lastStatusReason);
|
||||
lifecycle.setAvailableActions(buildAvailableActions(statusCode));
|
||||
return lifecycle;
|
||||
}
|
||||
|
||||
private List<ProductSettingActionRespVO> buildAvailableActions(String statusCode) {
|
||||
List<ObjectStatusTransitionDO> transitions = objectStatusTransitionMapper
|
||||
.selectListByObjectTypeAndFromStatus(PRODUCT_OBJECT_TYPE, statusCode);
|
||||
if (transitions == null || transitions.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return transitions.stream().map(transition -> {
|
||||
ProductSettingActionRespVO action = new ProductSettingActionRespVO();
|
||||
action.setActionCode(transition.getActionCode());
|
||||
action.setActionName(transition.getActionName());
|
||||
action.setNeedReason(transition.getNeedReason());
|
||||
return action;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user