feat(system): 新增用户协议版本管理(版本表/多语言/状态机)

This commit is contained in:
DaxPay Dev
2026-06-24 21:36:27 +08:00
parent 15ce168f45
commit c3e6af56dc
22 changed files with 850 additions and 54 deletions

View File

@@ -0,0 +1,5 @@
{
"DRAFT": "Draft",
"PUBLISHED": "Published",
"ARCHIVED": "Archived"
}

View File

@@ -2,5 +2,11 @@
"defaultCannotDelete": "Default protocol cannot be deleted",
"notExist": "User protocol not found",
"itemNotExist": "User protocol item not found",
"defaultItemNotExist": "No default protocol exists for this type"
"defaultItemNotExist": "No default protocol exists for this type",
"versionNotExist": "User protocol version not found",
"draftOnlyCanEdit": "Only draft versions can be edited",
"draftOnlyCanDelete": "Only draft versions can be deleted",
"draftOnlyCanPublish": "Only draft versions can be published",
"publishedOnlyCanArchive": "Only published versions can be archived",
"sameClientType": "Target client type is the same as the source"
}

View File

@@ -404,6 +404,9 @@
"clientType": {
"notBlank": "Client type cannot be blank"
},
"language": {
"notBlank": "Language cannot be blank"
},
"email": {
"notBlank": "Email cannot be blank",
"format": "Invalid email format"

View File

@@ -0,0 +1,5 @@
{
"DRAFT": "草稿",
"PUBLISHED": "已发布",
"ARCHIVED": "已归档"
}

View File

@@ -2,5 +2,11 @@
"defaultCannotDelete": "默认协议不可删除",
"notExist": "用户协议不存在",
"itemNotExist": "用户协议项不存在",
"defaultItemNotExist": "该类型下不存在默认协议"
"defaultItemNotExist": "该类型下不存在默认协议",
"versionNotExist": "用户协议版本不存在",
"draftOnlyCanEdit": "仅草稿状态的版本可编辑",
"draftOnlyCanDelete": "仅草稿状态的版本可删除",
"draftOnlyCanPublish": "仅草稿状态的版本可发布",
"publishedOnlyCanArchive": "仅已发布状态的版本可归档",
"sameClientType": "目标端类型与源端类型一致"
}

View File

@@ -407,6 +407,9 @@
"clientType": {
"notBlank": "端类型不能为空"
},
"language": {
"notBlank": "语言不能为空"
},
"email": {
"notBlank": "邮箱不可为空",
"format": "邮箱格式不正确"

View File

@@ -4,6 +4,7 @@ import cn.daxpay.open.platform.system.enums.UserProtocolClientTypeEnum;
import cn.daxpay.open.platform.system.enums.UserProtocolTypeEnum;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolParam;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolQuery;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolContentResult;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolResult;
import cn.daxpay.open.platform.system.service.protocol.UserProtocolService;
import cn.daxpay.open.platform.core.annotation.IgnoreAuth;
@@ -88,16 +89,19 @@ public class UserProtocolController {
return Res.ok(userProtocolService.findById(id));
}
/// 查询默认协议
/// 查询默认协议内容(对外展示, 各端通过 type+clientType+language 拉取当前生效版本)
///
/// @param type 协议类型
/// @return 默认协议信息
/// @param clientType 端类型
/// @param language 语言(可选, 为空时回退到协议默认语言)
/// @return 协议内容
@IgnoreAuth
@Operation(summary = "查询默认协议")
@Operation(summary = "查询默认协议内容")
@GetMapping("/find-default")
public Result<UserProtocolResult> findDefault(@NotNull(message = "{validation.field.type.notBlank}") String type,
@NotNull(message = "{validation.field.clientType.notBlank}") String clientType){
return Res.ok(userProtocolService.findDefault(type, clientType));
public Result<UserProtocolContentResult> findDefault(@NotNull(message = "{validation.field.type.notBlank}") String type,
@NotNull(message = "{validation.field.clientType.notBlank}") String clientType,
@RequestParam(required = false) String language){
return Res.ok(userProtocolService.findDefault(type, clientType, language));
}
@Operation(summary = "协议类型列表")
@@ -138,6 +142,16 @@ public class UserProtocolController {
return Res.ok();
}
/// 复制协议到其他端(连同各语言的当前生效版本一起复制)
///
/// @param id 源协议ID
/// @param clientType 目标端类型
/// @return 目标协议ID
@Operation(summary = "复制到其他端")
@PostMapping("/copy-to-client")
public Result<Long> copyToClient(@NotNull(message = "{validation.field.id.notNull}") Long id,
@NotNull(message = "{validation.field.clientType.notBlank}") String clientType){
return Res.ok(userProtocolService.copyToClient(id, clientType));
}
}

View File

@@ -0,0 +1,99 @@
package cn.daxpay.open.platform.system.controller.protocol;
import cn.daxpay.open.platform.core.rest.Res;
import cn.daxpay.open.platform.core.rest.param.PageParam;
import cn.daxpay.open.platform.core.rest.result.PageResult;
import cn.daxpay.open.platform.core.rest.result.Result;
import cn.daxpay.open.platform.core.validation.ValidationGroup;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolVersionParam;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolVersionQuery;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolVersionResult;
import cn.daxpay.open.platform.system.service.protocol.UserProtocolVersionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/// # 用户协议版本控制器
///
@Validated
@Tag(name = "用户协议版本")
@RestController
@RequestMapping("/user/protocol/version")
@RequiredArgsConstructor
public class UserProtocolVersionController {
private final UserProtocolVersionService userProtocolVersionService;
/// 分页查询版本
///
/// @param pageParam 分页参数
/// @param query 查询条件
/// @return 版本分页结果
@Operation(summary = "分页")
@GetMapping("/page")
public Result<PageResult<UserProtocolVersionResult>> page(PageParam pageParam, UserProtocolVersionQuery query){
return Res.ok(userProtocolVersionService.page(pageParam, query));
}
/// 新建草稿
///
/// @param param 版本参数
@Operation(summary = "新建草稿")
@PostMapping("/add")
public Result<Void> add(@RequestBody @Validated(ValidationGroup.add.class) UserProtocolVersionParam param){
userProtocolVersionService.add(param);
return Res.ok();
}
/// 编辑草稿内容(仅草稿可编辑)
///
/// @param param 版本参数
@Operation(summary = "编辑草稿")
@PostMapping("/update")
public Result<Void> update(@RequestBody @Validated(ValidationGroup.edit.class) UserProtocolVersionParam param){
userProtocolVersionService.update(param);
return Res.ok();
}
/// 删除草稿(仅草稿可删除)
///
/// @param id 版本ID
@Operation(summary = "删除草稿")
@PostMapping("/delete")
public Result<Void> delete(@NotNull(message = "{validation.field.id.notNull}") Long id){
userProtocolVersionService.delete(id);
return Res.ok();
}
/// 根据ID查询版本
///
/// @param id 版本ID
/// @return 版本信息
@Operation(summary = "查询")
@GetMapping("/get")
public Result<UserProtocolVersionResult> findById(@NotNull(message = "{validation.field.id.notNull}") Long id){
return Res.ok(userProtocolVersionService.findById(id));
}
/// 发布版本(草稿 -> 已发布, 同协议同语言原已发布自动归档)
///
/// @param id 版本ID
@Operation(summary = "发布版本")
@PostMapping("/publish")
public Result<Void> publish(@NotNull(message = "{validation.field.id.notNull}") Long id){
userProtocolVersionService.publish(id);
return Res.ok();
}
/// 归档版本(已发布 -> 归档)
///
/// @param id 版本ID
@Operation(summary = "归档版本")
@PostMapping("/archive")
public Result<Void> archive(@NotNull(message = "{validation.field.id.notNull}") Long id){
userProtocolVersionService.archive(id);
return Res.ok();
}
}

View File

@@ -0,0 +1,36 @@
package cn.daxpay.open.platform.system.convert.protocol;
import cn.daxpay.open.platform.system.entity.protocol.UserProtocolVersion;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolVersionParam;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolVersionResult;
import org.mapstruct.BeanMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.factory.Mappers;
/// # 用户协议版本转换类
///
@Mapper
public interface UserProtocolVersionConvert {
UserProtocolVersionConvert CONVERT = Mappers.getMapper(UserProtocolVersionConvert.class);
/// 实体转结果
///
/// @param entity 用户协议版本实体
/// @return 用户协议版本结果
UserProtocolVersionResult toResult(UserProtocolVersion entity);
/// 参数转实体
///
/// @param param 用户协议版本参数
/// @return 用户协议版本实体
UserProtocolVersion toEntity(UserProtocolVersionParam param);
/// 复制属性
///
/// @param param 用户协议版本参数
/// @param entity 用户协议版本实体
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void copy(UserProtocolVersionParam param, @MappingTarget UserProtocolVersion entity);
}

View File

@@ -0,0 +1,96 @@
package cn.daxpay.open.platform.system.dao.protocol;
import cn.daxpay.open.platform.common.mybatisplus.base.MpRealDelEntity;
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
import cn.daxpay.open.platform.common.mybatisplus.query.generator.QueryGenerator;
import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil;
import cn.daxpay.open.platform.core.rest.param.PageParam;
import cn.daxpay.open.platform.system.entity.protocol.UserProtocolVersion;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolVersionQuery;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
/// # 用户协议版本管理
///
@Slf4j
@Repository
@RequiredArgsConstructor
public class UserProtocolVersionManager extends BaseManager<UserProtocolVersionMapper, UserProtocolVersion> {
/// 分页
public Page<UserProtocolVersion> page(PageParam pageParam, UserProtocolVersionQuery query){
Page<UserProtocolVersion> mpPage = MpUtil.getMpPage(pageParam, UserProtocolVersion.class);
QueryWrapper<UserProtocolVersion> generator = QueryGenerator.generator(query);
return this.page(mpPage,generator);
}
/// 查询当前生效版本(已发布)
public Optional<UserProtocolVersion> findPublished(Long protocolId, String language){
return this.lambdaQuery()
.eq(UserProtocolVersion::getProtocolId,protocolId)
.eq(UserProtocolVersion::getLanguage,language)
.eq(UserProtocolVersion::getStatus, "PUBLISHED")
.oneOpt();
}
/// 查询某协议下所有版本(级联删除/复制用)
public List<UserProtocolVersion> findAllByProtocol(Long protocolId){
return this.lambdaQuery()
.eq(UserProtocolVersion::getProtocolId,protocolId)
.list();
}
/// 获取下一个版本号(同协议同语言自增)
public Integer nextVersionNo(Long protocolId, String language){
Long count = this.lambdaQuery()
.eq(UserProtocolVersion::getProtocolId,protocolId)
.eq(UserProtocolVersion::getLanguage,language)
.count();
return count.intValue() + 1;
}
/// 将同协议同语言的原已发布版本归档
public void archivePublished(Long protocolId, String language){
this.lambdaUpdate()
.eq(UserProtocolVersion::getProtocolId,protocolId)
.eq(UserProtocolVersion::getLanguage,language)
.eq(UserProtocolVersion::getStatus, "PUBLISHED")
.set(UserProtocolVersion::getStatus, "ARCHIVED")
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
/// 发布版本
public void publish(Long id, Integer versionNo){
this.lambdaUpdate()
.eq(UserProtocolVersion::getId,id)
.set(UserProtocolVersion::getStatus, "PUBLISHED")
.set(UserProtocolVersion::getVersionNo, versionNo)
.set(UserProtocolVersion::getEffectiveTime, OffsetDateTime.now())
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
/// 归档版本
public void archive(Long id){
this.lambdaUpdate()
.eq(UserProtocolVersion::getId,id)
.set(UserProtocolVersion::getStatus, "ARCHIVED")
.setIncrBy(MpRealDelEntity::getVersion, 1)
.update();
}
/// 根据协议ID删除全部版本(级联)
public void deleteByProtocolId(Long protocolId){
this.lambdaUpdate()
.eq(UserProtocolVersion::getProtocolId,protocolId)
.remove();
}
}

View File

@@ -0,0 +1,11 @@
package cn.daxpay.open.platform.system.dao.protocol;
import cn.daxpay.open.platform.system.entity.protocol.UserProtocolVersion;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Mapper;
/// # 用户协议版本Mapper
///
@Mapper
public interface UserProtocolVersionMapper extends MPJBaseMapper<UserProtocolVersion> {
}

View File

@@ -30,15 +30,12 @@ public class UserProtocol extends MpBaseEntity implements ToResult<UserProtocolR
/// 端类型
private String clientType;
/// 内容格式
private String contentFormat;
/// 协议内容
private String content;
/// 默认协议
private Boolean defaultProtocol;
/// 默认语言
private String defaultLanguage;
@Override
public UserProtocolResult toResult() {
return UserProtocolConvert.CONVERT.toResult(this);

View File

@@ -0,0 +1,64 @@
package cn.daxpay.open.platform.system.entity.protocol;
import cn.daxpay.open.platform.common.mybatisplus.base.MpBaseEntity;
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
import cn.daxpay.open.platform.system.convert.protocol.UserProtocolVersionConvert;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolVersionParam;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolVersionResult;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.OffsetDateTime;
/// # 用户协议版本
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@TableName("base_user_protocol_version")
public class UserProtocolVersion extends MpBaseEntity implements ToResult<UserProtocolVersionResult> {
/// 协议ID
private Long protocolId;
/// 语言
private String language;
/// 版本号
private Integer versionNo;
/// 版本标签
private String versionLabel;
/// 标题
private String title;
/// 内容(Markdown)
private String content;
/// 渲染后的HTML
private String contentHtml;
/// 内容格式
private String contentFormat;
/// 状态
private String status;
/// 生效时间
private OffsetDateTime effectiveTime;
/// 变更说明
private String summary;
@Override
public UserProtocolVersionResult toResult() {
return UserProtocolVersionConvert.CONVERT.toResult(this);
}
public static UserProtocolVersion init(UserProtocolVersionParam param) {
return UserProtocolVersionConvert.CONVERT.toEntity(param);
}
}

View File

@@ -0,0 +1,36 @@
package cn.daxpay.open.platform.system.enums;
import cn.daxpay.open.platform.core.exception.BizException;
import cn.daxpay.open.platform.core.i18n.I18nSupport;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/// 用户协议版本状态
@Getter
@RequiredArgsConstructor
public enum UserProtocolVersionStatusEnum implements I18nSupport {
/// 草稿
DRAFT("DRAFT"),
/// 已发布
PUBLISHED("PUBLISHED"),
/// 已归档
ARCHIVED("ARCHIVED");
private final String code;
@Override
public String getI18nPrefix() {
return "enum.user_protocol_status";
}
public static UserProtocolVersionStatusEnum findByCode(String code) {
return Arrays.stream(values())
.filter(e -> e.getCode().equalsIgnoreCase(code))
.findFirst()
// 通用: 未知的用户协议版本状态
.orElseThrow(() -> new BizException("error.common.enumUnknown", "UserProtocolVersionStatus"));
}
}

View File

@@ -40,13 +40,9 @@ public class UserProtocolParam {
@Schema(description = "端类型")
private String clientType;
/// 内容格式
@Schema(description = "内容格式")
private String contentFormat;
/// 内容
@NotBlank(message = "{validation.field.content.notBlank}")
@Schema(description = "内容")
private String content;
/// 默认语言
@NotBlank(message = "{validation.field.language.notBlank}")
@Schema(description = "默认语言")
private String defaultLanguage;
}

View File

@@ -0,0 +1,58 @@
package cn.daxpay.open.platform.system.param.protocol;
import cn.daxpay.open.platform.core.validation.ValidationGroup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import lombok.Data;
import lombok.experimental.Accessors;
/// # 用户协议版本
///
@Data
@Accessors(chain = true)
@Schema(title = "用户协议版本")
public class UserProtocolVersionParam {
@Null(message = "{validation.field.id.mustBeNullOnAdd}", groups = ValidationGroup.add.class)
@NotNull(message = "{validation.field.id.notNull}", groups = ValidationGroup.edit.class)
@Schema(description = "主键")
private Long id;
/// 协议ID
@NotNull(message = "{validation.field.protocolId.notNull}")
@Schema(description = "协议ID")
private Long protocolId;
/// 语言
@NotBlank(message = "{validation.field.language.notBlank}")
@Schema(description = "语言")
private String language;
/// 版本标签
@Schema(description = "版本标签")
private String versionLabel;
/// 标题
@NotBlank(message = "{validation.field.title.notBlank}")
@Schema(description = "标题")
private String title;
/// 内容
@NotBlank(message = "{validation.field.content.notBlank}")
@Schema(description = "内容")
private String content;
/// 渲染后的HTML
@Schema(description = "渲染后的HTML")
private String contentHtml;
/// 内容格式
@Schema(description = "内容格式")
private String contentFormat;
/// 变更说明
@Schema(description = "变更说明")
private String summary;
}

View File

@@ -0,0 +1,28 @@
package cn.daxpay.open.platform.system.param.protocol;
import cn.daxpay.open.platform.common.mybatisplus.query.entity.SortParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/// # 用户协议版本查询
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "用户协议版本查询")
public class UserProtocolVersionQuery extends SortParam {
/// 协议ID
@Schema(description = "协议ID")
private Long protocolId;
/// 语言
@Schema(description = "语言")
private String language;
/// 状态
@Schema(description = "状态")
private String status;
}

View File

@@ -0,0 +1,66 @@
package cn.daxpay.open.platform.system.result.protocol;
import cn.daxpay.open.platform.core.result.BaseResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.OffsetDateTime;
/// # 用户协议内容(对外展示)
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "用户协议内容")
public class UserProtocolContentResult extends BaseResult {
/// 名称
@Schema(description = "名称")
private String name;
/// 显示名称
@Schema(description = "显示名称")
private String showName;
/// 类型
@Schema(description = "类型")
private String type;
/// 端类型
@Schema(description = "端类型")
private String clientType;
/// 语言
@Schema(description = "语言")
private String language;
/// 版本号
@Schema(description = "版本号")
private Integer versionNo;
/// 版本标签
@Schema(description = "版本标签")
private String versionLabel;
/// 标题
@Schema(description = "标题")
private String title;
/// 内容
@Schema(description = "内容")
private String content;
/// 渲染后的HTML
@Schema(description = "渲染后的HTML")
private String contentHtml;
/// 内容格式
@Schema(description = "内容格式")
private String contentFormat;
/// 生效时间
@Schema(description = "生效时间")
private OffsetDateTime effectiveTime;
}

View File

@@ -30,16 +30,12 @@ public class UserProtocolResult extends BaseResult {
@Schema(description = "端类型")
private String clientType;
/// 内容格式
@Schema(description = "内容格式")
private String contentFormat;
/// 默认协议
@Schema(description = "默认协议")
private Boolean defaultProtocol;
/// 内容
@Schema(description = "内容")
private String content;
/// 默认语言
@Schema(description = "默认语言")
private String defaultLanguage;
}

View File

@@ -0,0 +1,62 @@
package cn.daxpay.open.platform.system.result.protocol;
import cn.daxpay.open.platform.core.result.BaseResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.OffsetDateTime;
/// # 用户协议版本
///
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "用户协议版本")
public class UserProtocolVersionResult extends BaseResult {
/// 协议ID
@Schema(description = "协议ID")
private Long protocolId;
/// 语言
@Schema(description = "语言")
private String language;
/// 版本号
@Schema(description = "版本号")
private Integer versionNo;
/// 版本标签
@Schema(description = "版本标签")
private String versionLabel;
/// 标题
@Schema(description = "标题")
private String title;
/// 内容
@Schema(description = "内容")
private String content;
/// 渲染后的HTML
@Schema(description = "渲染后的HTML")
private String contentHtml;
/// 内容格式
@Schema(description = "内容格式")
private String contentFormat;
/// 状态
@Schema(description = "状态")
private String status;
/// 生效时间
@Schema(description = "生效时间")
private OffsetDateTime effectiveTime;
/// 变更说明
@Schema(description = "变更说明")
private String summary;
}

View File

@@ -1,25 +1,31 @@
package cn.daxpay.open.platform.system.service.protocol;
import cn.daxpay.open.platform.system.convert.protocol.UserProtocolConvert;
import cn.daxpay.open.platform.system.dao.protocol.UserProtocolManager;
import cn.daxpay.open.platform.system.entity.protocol.UserProtocol;
import cn.daxpay.open.platform.system.enums.UserProtocolClientTypeEnum;
import cn.daxpay.open.platform.system.enums.UserProtocolContentFormatEnum;
import cn.daxpay.open.platform.system.enums.UserProtocolTypeEnum;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolParam;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolQuery;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolResult;
import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil;
import cn.daxpay.open.platform.core.code.CommonCode;
import cn.daxpay.open.platform.core.exception.BizException;
import cn.daxpay.open.platform.core.exception.DataNotExistException;
import cn.daxpay.open.platform.core.rest.param.PageParam;
import cn.daxpay.open.platform.core.rest.result.PageResult;
import cn.daxpay.open.platform.system.convert.protocol.UserProtocolConvert;
import cn.daxpay.open.platform.system.dao.protocol.UserProtocolManager;
import cn.daxpay.open.platform.system.dao.protocol.UserProtocolVersionManager;
import cn.daxpay.open.platform.system.entity.protocol.UserProtocol;
import cn.daxpay.open.platform.system.entity.protocol.UserProtocolVersion;
import cn.daxpay.open.platform.system.enums.UserProtocolClientTypeEnum;
import cn.daxpay.open.platform.system.enums.UserProtocolTypeEnum;
import cn.daxpay.open.platform.system.enums.UserProtocolVersionStatusEnum;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolParam;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolQuery;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolContentResult;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolResult;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cn.daxpay.open.platform.core.code.CommonCode;
import java.util.HashMap;
import java.util.Map;
/// # 用户协议管理服务
///
@@ -28,6 +34,11 @@ import cn.daxpay.open.platform.core.code.CommonCode;
@RequiredArgsConstructor
public class UserProtocolService {
private final UserProtocolManager userProtocolManager;
private final UserProtocolVersionManager userProtocolVersionManager;
private final UserProtocolVersionService userProtocolVersionService;
/// 默认语言
private static final String DEFAULT_LANGUAGE = "zh-CN";
/// 分页
public PageResult<UserProtocolResult> page(PageParam pageParam, UserProtocolQuery query){
@@ -39,7 +50,7 @@ public class UserProtocolService {
public void add(UserProtocolParam param){
this.validateParam(param);
var userProtocol = UserProtocol.init(param);
userProtocol.setContentFormat(this.getContentFormat(param.getContentFormat()));
userProtocol.setDefaultLanguage(this.getLanguage(param.getDefaultLanguage()));
userProtocolManager.save(userProtocol);
}
@@ -50,11 +61,12 @@ public class UserProtocolService {
// 系统: 协议不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.notExist"));
UserProtocolConvert.CONVERT.copy(param, userProtocol);
userProtocol.setContentFormat(this.getContentFormat(param.getContentFormat()));
userProtocol.setDefaultLanguage(this.getLanguage(param.getDefaultLanguage()));
userProtocolManager.updateById(userProtocol);
}
/// 删除
/// 删除(级联删除版本)
@Transactional(rollbackFor = Exception.class)
public void delete(Long id){
// 默认不可被删除
var userProtocol = userProtocolManager.findById(id)
@@ -64,6 +76,8 @@ public class UserProtocolService {
// 默认协议不可删除
throw new BizException(CommonCode.FAIL_CODE, "error.system.protocol.defaultCannotDelete");
}
// 级联删除版本
userProtocolVersionManager.deleteByProtocolId(id);
userProtocolManager.deleteById(id);
}
@@ -75,14 +89,33 @@ public class UserProtocolService {
.orElseThrow(() -> new DataNotExistException("error.system.protocol.notExist"));
}
/// 根据分类查询默认协议
public UserProtocolResult findDefault(String type, String clientType){
/// 根据分类查询默认协议内容(对外展示), language 为空时回退到协议默认语言
public UserProtocolContentResult findDefault(String type, String clientType, String language){
UserProtocolTypeEnum.findByCode(type);
UserProtocolClientTypeEnum.findByCode(clientType);
return userProtocolManager.findDefault(type, clientType)
.map(UserProtocol::toResult)
var protocol = userProtocolManager.findDefault(type, clientType)
// 系统: 协议不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.notExist"));
String lang = StrUtil.isBlank(language) ? protocol.getDefaultLanguage() : language;
var version = userProtocolVersionManager.findPublished(protocol.getId(), lang).orElse(null);
// 组装对外内容结果
var result = new UserProtocolContentResult();
result.setId(protocol.getId());
result.setName(protocol.getName());
result.setShowName(protocol.getShowName());
result.setType(protocol.getType());
result.setClientType(protocol.getClientType());
result.setLanguage(lang);
if (version != null){
result.setVersionNo(version.getVersionNo());
result.setVersionLabel(version.getVersionLabel());
result.setTitle(version.getTitle());
result.setContent(version.getContent());
result.setContentHtml(version.getContentHtml());
result.setContentFormat(version.getContentFormat());
result.setEffectiveTime(version.getEffectiveTime());
}
return result;
}
/// 设置默认协议
@@ -103,16 +136,47 @@ public class UserProtocolService {
userProtocolManager.cancelDefault(id);
}
/// 复制协议到目标端(含每种语言的当前生效版本)
@Transactional(rollbackFor = Exception.class)
public Long copyToClient(Long sourceProtocolId, String targetClientType){
var source = userProtocolManager.findById(sourceProtocolId)
// 系统: 协议不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.notExist"));
UserProtocolClientTypeEnum.findByCode(targetClientType);
if (targetClientType.equalsIgnoreCase(source.getClientType())){
// 系统: 目标端类型与源端一致
throw new BizException(CommonCode.FAIL_CODE, "error.system.protocol.sameClientType");
}
// 创建目标协议
var target = new UserProtocol();
target.setName(source.getName());
target.setShowName(source.getShowName());
target.setType(source.getType());
target.setClientType(targetClientType);
target.setDefaultProtocol(false);
target.setDefaultLanguage(source.getDefaultLanguage());
userProtocolManager.save(target);
// 复制每种语言的已发布版本(同语言仅一条已发布)
var versions = userProtocolVersionService.findAllByProtocol(source.getId());
Map<String, UserProtocolVersion> publishedByLang = new HashMap<>();
for (var v : versions){
if (UserProtocolVersionStatusEnum.PUBLISHED.getCode().equals(v.getStatus())){
publishedByLang.put(v.getLanguage(), v);
}
}
for (var v : publishedByLang.values()){
userProtocolVersionService.copyVersion(v, target.getId());
}
return target.getId();
}
private void validateParam(UserProtocolParam param) {
UserProtocolTypeEnum.findByCode(param.getType());
UserProtocolClientTypeEnum.findByCode(param.getClientType());
this.getContentFormat(param.getContentFormat());
this.getLanguage(param.getDefaultLanguage());
}
private String getContentFormat(String contentFormat) {
if (StrUtil.isBlank(contentFormat)) {
return UserProtocolContentFormatEnum.MARKDOWN.getCode();
}
return UserProtocolContentFormatEnum.findByCode(contentFormat).getCode();
private String getLanguage(String language) {
return StrUtil.isBlank(language) ? DEFAULT_LANGUAGE : language;
}
}

View File

@@ -0,0 +1,145 @@
package cn.daxpay.open.platform.system.service.protocol;
import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil;
import cn.daxpay.open.platform.core.code.CommonCode;
import cn.daxpay.open.platform.core.exception.BizException;
import cn.daxpay.open.platform.core.exception.DataNotExistException;
import cn.daxpay.open.platform.core.rest.param.PageParam;
import cn.daxpay.open.platform.core.rest.result.PageResult;
import cn.daxpay.open.platform.system.convert.protocol.UserProtocolVersionConvert;
import cn.daxpay.open.platform.system.dao.protocol.UserProtocolVersionManager;
import cn.daxpay.open.platform.system.entity.protocol.UserProtocolVersion;
import cn.daxpay.open.platform.system.enums.UserProtocolContentFormatEnum;
import cn.daxpay.open.platform.system.enums.UserProtocolVersionStatusEnum;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolVersionParam;
import cn.daxpay.open.platform.system.param.protocol.UserProtocolVersionQuery;
import cn.daxpay.open.platform.system.result.protocol.UserProtocolVersionResult;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/// # 用户协议版本管理服务
///
@Slf4j
@Service
@RequiredArgsConstructor
public class UserProtocolVersionService {
private final UserProtocolVersionManager userProtocolVersionManager;
/// 分页
public PageResult<UserProtocolVersionResult> page(PageParam pageParam, UserProtocolVersionQuery query){
return MpUtil.toPageResult(userProtocolVersionManager.page(pageParam,query));
}
/// 创建草稿
public void add(UserProtocolVersionParam param){
var version = UserProtocolVersion.init(param);
version.setContentFormat(this.getContentFormat(param.getContentFormat()));
version.setStatus(UserProtocolVersionStatusEnum.DRAFT.getCode());
userProtocolVersionManager.save(version);
}
/// 更新草稿内容(仅草稿可编辑)
public void update(UserProtocolVersionParam param){
var version = userProtocolVersionManager.findById(param.getId())
// 系统: 协议版本不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.versionNotExist"));
if (!UserProtocolVersionStatusEnum.DRAFT.getCode().equals(version.getStatus())) {
// 系统: 仅草稿状态可编辑
throw new BizException(CommonCode.FAIL_CODE, "error.system.protocol.draftOnlyCanEdit");
}
UserProtocolVersionConvert.CONVERT.copy(param, version);
version.setContentFormat(this.getContentFormat(param.getContentFormat()));
userProtocolVersionManager.updateById(version);
}
/// 删除(仅草稿可删除)
public void delete(Long id){
var version = userProtocolVersionManager.findById(id)
// 系统: 协议版本不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.versionNotExist"));
if (!UserProtocolVersionStatusEnum.DRAFT.getCode().equals(version.getStatus())) {
// 系统: 仅草稿状态可删除
throw new BizException(CommonCode.FAIL_CODE, "error.system.protocol.draftOnlyCanDelete");
}
userProtocolVersionManager.deleteById(id);
}
/// 详情
public UserProtocolVersionResult findById(Long id){
return userProtocolVersionManager.findById(id)
.map(UserProtocolVersion::toResult)
// 系统: 协议版本不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.versionNotExist"));
}
/// 发布版本: 草稿 -> 已发布, 同协议同语言原已发布 -> 归档
@Transactional(rollbackFor = Exception.class)
public void publish(Long id){
var version = userProtocolVersionManager.findById(id)
// 系统: 协议版本不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.versionNotExist"));
if (!UserProtocolVersionStatusEnum.DRAFT.getCode().equals(version.getStatus())) {
// 系统: 仅草稿状态可发布
throw new BizException(CommonCode.FAIL_CODE, "error.system.protocol.draftOnlyCanPublish");
}
// 分配版本号
Integer versionNo = userProtocolVersionManager.nextVersionNo(version.getProtocolId(), version.getLanguage());
// 旧生效版本归档
userProtocolVersionManager.archivePublished(version.getProtocolId(), version.getLanguage());
// 发布当前版本
userProtocolVersionManager.publish(id, versionNo);
}
/// 归档版本(已发布 -> 归档)
public void archive(Long id){
var version = userProtocolVersionManager.findById(id)
// 系统: 协议版本不存在
.orElseThrow(() -> new DataNotExistException("error.system.protocol.versionNotExist"));
if (!UserProtocolVersionStatusEnum.PUBLISHED.getCode().equals(version.getStatus())) {
// 系统: 仅已发布状态可归档
throw new BizException(CommonCode.FAIL_CODE, "error.system.protocol.publishedOnlyCanArchive");
}
userProtocolVersionManager.archive(id);
}
/// 根据协议ID和语言查询当前生效版本
public UserProtocolVersionResult findPublished(Long protocolId, String language){
return userProtocolVersionManager.findPublished(protocolId, language)
.map(UserProtocolVersion::toResult)
.orElse(null);
}
/// 复制版本到目标协议(用于复制到其他端, 直接以已发布状态写入)
@Transactional(rollbackFor = Exception.class)
public void copyVersion(UserProtocolVersion source, Long targetProtocolId){
var copy = new UserProtocolVersion();
copy.setProtocolId(targetProtocolId);
copy.setLanguage(source.getLanguage());
copy.setVersionNo(userProtocolVersionManager.nextVersionNo(targetProtocolId, source.getLanguage()));
copy.setVersionLabel(source.getVersionLabel());
copy.setTitle(source.getTitle());
copy.setContent(source.getContent());
copy.setContentHtml(source.getContentHtml());
copy.setContentFormat(source.getContentFormat());
copy.setStatus(UserProtocolVersionStatusEnum.PUBLISHED.getCode());
copy.setSummary(source.getSummary());
userProtocolVersionManager.save(copy);
}
/// 根据协议ID查询全部版本(复制时用)
public List<UserProtocolVersion> findAllByProtocol(Long protocolId){
return userProtocolVersionManager.findAllByProtocol(protocolId);
}
private String getContentFormat(String contentFormat) {
if (StrUtil.isBlank(contentFormat)) {
return UserProtocolContentFormatEnum.MARKDOWN.getCode();
}
return UserProtocolContentFormatEnum.findByCode(contentFormat).getCode();
}
}