feat(website): 新增应用发布管理功能

- 在CmsWebsite实体中添加发布状态、定价模式、应用描述等相关字段
- 实现应用上架审核流程:提交审核、撤回申请、下架、管理员审批等功能
- 添加审核列表分页查询接口,支持按发布状态筛选
- 更新路由配置,调整开发者资源路径
- 添加数据库表结构变更SQL脚本,增加发布管理相关字段
This commit is contained in:
2026-04-01 00:59:06 +08:00
parent 5029be1f1f
commit f1dde97538
7 changed files with 275 additions and 4 deletions

View File

@@ -26,7 +26,7 @@ import java.util.Map;
@Slf4j @Slf4j
@Tag(name = "开发者资源管理") @Tag(name = "开发者资源管理")
@RestController @RestController
@RequestMapping("/api/_modules/app/developer-resource") @RequestMapping("/api/app/developer-resource")
public class AppResourceController extends BaseController { public class AppResourceController extends BaseController {
@Resource @Resource

View File

@@ -562,4 +562,85 @@ public class CmsWebsiteController extends BaseController {
return success("清除成功"); return success("清除成功");
} }
// ─── 发布管理 ────────────────────────────────────────────────────────
@Operation(summary = "提交上架审核")
@PostMapping("/submitReview")
public ApiResult<?> submitReview(@RequestBody Map<String, Object> body) {
Integer websiteId = body.get("websiteId") instanceof Number ? ((Number) body.get("websiteId")).intValue() : null;
if (websiteId == null) return fail("websiteId 不能为空");
String priceType = (String) body.get("priceType");
Integer price = body.get("price") instanceof Number ? ((Number) body.get("price")).intValue() : 0;
String subscriptionPeriod = (String) body.get("subscriptionPeriod");
String appDescription = (String) body.get("appDescription");
String detailDescription = (String) body.get("detailDescription");
String screenshots = (String) body.get("screenshots");
Integer userId = getLoginUserId();
try {
cmsWebsiteService.submitReview(websiteId, priceType, price, subscriptionPeriod,
appDescription, detailDescription, screenshots, userId);
return success("上架申请提交成功,等待审核");
} catch (Exception e) {
return fail(e.getMessage());
} }
}
@Operation(summary = "撤回审核申请")
@PostMapping("/withdrawReview/{websiteId}")
public ApiResult<?> withdrawReview(@PathVariable Integer websiteId) {
try {
cmsWebsiteService.withdrawReview(websiteId, getLoginUserId());
return success("已撤回审核申请");
} catch (Exception e) {
return fail(e.getMessage());
}
}
@Operation(summary = "下架应用")
@PostMapping("/unpublish/{websiteId}")
public ApiResult<?> unpublish(@PathVariable Integer websiteId) {
try {
cmsWebsiteService.unpublish(websiteId, getLoginUserId());
return success("已下架");
} catch (Exception e) {
return fail(e.getMessage());
}
}
@PreAuthorize("hasAuthority('cms:website:update')")
@Operation(summary = "管理员审核通过")
@PostMapping("/approveReview/{websiteId}")
public ApiResult<?> approveReview(@PathVariable Integer websiteId) {
try {
cmsWebsiteService.approveReview(websiteId, getLoginUserId());
return success("审核通过,应用已上架");
} catch (Exception e) {
return fail(e.getMessage());
}
}
@PreAuthorize("hasAuthority('cms:website:update')")
@Operation(summary = "管理员审核拒绝")
@PostMapping("/rejectReview")
public ApiResult<?> rejectReview(@RequestBody Map<String, Object> body) {
Integer websiteId = body.get("websiteId") instanceof Number ? ((Number) body.get("websiteId")).intValue() : null;
String rejectReason = (String) body.get("rejectReason");
if (websiteId == null) return fail("websiteId 不能为空");
if (rejectReason == null || rejectReason.isBlank()) return fail("请填写拒绝原因");
try {
cmsWebsiteService.rejectReview(websiteId, rejectReason, getLoginUserId());
return success("已拒绝该应用上架申请");
} catch (Exception e) {
return fail(e.getMessage());
}
}
@PreAuthorize("hasAuthority('cms:website:update')")
@Operation(summary = "获取审核列表(管理员)")
@GetMapping("/pageReviews")
public ApiResult<PageResult<CmsWebsite>> pageReviews(CmsWebsiteParam param) {
return success(cmsWebsiteService.pageReviews(param));
}
}

View File

@@ -321,6 +321,50 @@ public class CmsWebsite implements Serializable {
@TableField(exist = false) @TableField(exist = false)
private CmsWebsiteSetting setting; private CmsWebsiteSetting setting;
// ─── 发布管理字段 ──────────────────────────────────────────────────
@Schema(description = "发布状态: developing开发中 pending_review待审核 published已上架 rejected审核未通过 deprecated已下架")
private String publishStatus;
@Schema(description = "定价模式: free免费 one_time一次性 subscription订阅")
private String priceType;
@Schema(description = "订阅周期: month按月 year按年")
private String subscriptionPeriod;
@Schema(description = "应用简介(市场展示用)")
private String appDescription;
@Schema(description = "详细说明(富文本)")
private String detailDescription;
@Schema(description = "应用截图JSON数组字符串")
private String screenshots;
@Schema(description = "安装/使用次数")
private Integer installCount;
@Schema(description = "评分1-5")
private java.math.BigDecimal rating;
@Schema(description = "审核拒绝原因")
private String rejectReason;
@Schema(description = "提交审核时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.util.Date publishApplyTime;
@Schema(description = "正式发布上架时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.util.Date publishTime;
@Schema(description = "审核人用户ID")
private Integer reviewerId;
@Schema(description = "审核操作时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private java.util.Date reviewTime;
public String getPhone(){ public String getPhone(){
return DesensitizedUtil.mobilePhone(this.phone); return DesensitizedUtil.mobilePhone(this.phone);
} }

View File

@@ -225,4 +225,7 @@ public class CmsWebsiteParam extends BaseParam {
@QueryField(type = QueryType.EQ) @QueryField(type = QueryType.EQ)
private String adminPhone; private String adminPhone;
@Schema(description = "发布状态筛选: developing/pending_review/published/rejected/deprecated")
private String publishStatus;
} }

View File

@@ -67,4 +67,26 @@ public interface CmsWebsiteService extends IService<CmsWebsite> {
* @param tenantId 租户ID * @param tenantId 租户ID
*/ */
void clearSiteInfoCache(Integer tenantId); void clearSiteInfoCache(Integer tenantId);
// ─── 发布管理 ──────────────────────────────────────────────────
/** 提交上架审核 */
void submitReview(Integer websiteId, String priceType, Integer price,
String subscriptionPeriod, String appDescription,
String detailDescription, String screenshots, Integer userId);
/** 撤回审核申请(回到开发中) */
void withdrawReview(Integer websiteId, Integer userId);
/** 下架应用 */
void unpublish(Integer websiteId, Integer userId);
/** 管理员审核通过 */
void approveReview(Integer websiteId, Integer reviewerId);
/** 管理员审核拒绝 */
void rejectReview(Integer websiteId, String rejectReason, Integer reviewerId);
/** 分页查询审核列表(管理员) */
PageResult<CmsWebsite> pageReviews(CmsWebsiteParam param);
} }

View File

@@ -417,9 +417,113 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
} }
} }
/** // ─── 发布管理 ──────────────────────────────────────────────────────────
* 从数据库获取网站信息
*/ @Override
public void submitReview(Integer websiteId, String priceType, Integer price,
String subscriptionPeriod, String appDescription,
String detailDescription, String screenshots, Integer userId) {
CmsWebsite website = getById(websiteId);
if (website == null) throw new RuntimeException("应用不存在");
// 只有开发中 / 审核未通过 才能提交
String cur = website.getPublishStatus();
if (!"developing".equals(cur) && !"rejected".equals(cur) && cur != null) {
throw new RuntimeException("当前状态不允许提交审核");
}
CmsWebsite update = new CmsWebsite();
update.setWebsiteId(websiteId);
update.setPublishStatus("pending_review");
update.setPriceType(priceType != null ? priceType : "free");
update.setPrice(price != null ? new java.math.BigDecimal(price).movePointLeft(2) : java.math.BigDecimal.ZERO);
update.setSubscriptionPeriod(subscriptionPeriod);
update.setAppDescription(appDescription);
update.setDetailDescription(detailDescription);
update.setScreenshots(screenshots);
update.setPublishApplyTime(new java.util.Date());
update.setRejectReason(null);
updateById(update);
}
@Override
public void withdrawReview(Integer websiteId, Integer userId) {
CmsWebsite website = getById(websiteId);
if (website == null) throw new RuntimeException("应用不存在");
if (!"pending_review".equals(website.getPublishStatus())) {
throw new RuntimeException("当前状态不是待审核,无法撤回");
}
CmsWebsite update = new CmsWebsite();
update.setWebsiteId(websiteId);
update.setPublishStatus("developing");
updateById(update);
}
@Override
public void unpublish(Integer websiteId, Integer userId) {
CmsWebsite website = getById(websiteId);
if (website == null) throw new RuntimeException("应用不存在");
if (!"published".equals(website.getPublishStatus())) {
throw new RuntimeException("应用未上架,无法下架");
}
CmsWebsite update = new CmsWebsite();
update.setWebsiteId(websiteId);
update.setPublishStatus("deprecated");
updateById(update);
}
@Override
public void approveReview(Integer websiteId, Integer reviewerId) {
CmsWebsite website = getById(websiteId);
if (website == null) throw new RuntimeException("应用不存在");
if (!"pending_review".equals(website.getPublishStatus())) {
throw new RuntimeException("当前状态不是待审核");
}
CmsWebsite update = new CmsWebsite();
update.setWebsiteId(websiteId);
update.setPublishStatus("published");
update.setMarket(true);
update.setReviewerId(reviewerId);
update.setReviewTime(new java.util.Date());
update.setPublishTime(new java.util.Date());
update.setRejectReason(null);
updateById(update);
}
@Override
public void rejectReview(Integer websiteId, String rejectReason, Integer reviewerId) {
CmsWebsite website = getById(websiteId);
if (website == null) throw new RuntimeException("应用不存在");
if (!"pending_review".equals(website.getPublishStatus())) {
throw new RuntimeException("当前状态不是待审核");
}
CmsWebsite update = new CmsWebsite();
update.setWebsiteId(websiteId);
update.setPublishStatus("rejected");
update.setRejectReason(rejectReason);
update.setReviewerId(reviewerId);
update.setReviewTime(new java.util.Date());
updateById(update);
}
@Override
public PageResult<CmsWebsite> pageReviews(CmsWebsiteParam param) {
com.baomidou.mybatisplus.extension.plugins.pagination.Page<CmsWebsite> page =
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(param.getCurrent(), param.getSize() > 0 ? param.getSize() : 20);
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CmsWebsite> wrapper =
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CmsWebsite>()
.eq(CmsWebsite::getDeleted, 0)
.isNotNull(CmsWebsite::getPublishStatus)
.ne(CmsWebsite::getPublishStatus, "developing")
.eq(cn.hutool.core.util.ObjectUtil.isNotEmpty(param.getPublishStatus()),
CmsWebsite::getPublishStatus, param.getPublishStatus())
.and(cn.hutool.core.util.ObjectUtil.isNotEmpty(param.getKeywords()), q ->
q.like(CmsWebsite::getWebsiteName, param.getKeywords())
.or().like(CmsWebsite::getWebsiteCode, param.getKeywords()))
.last("ORDER BY FIELD(publish_status,'pending_review','rejected','published','deprecated'), publish_apply_time DESC");
baseMapper.selectPage(page, wrapper);
return new com.gxwebsoft.common.core.web.PageResult<>(page.getRecords(), page.getTotal());
}
private CmsWebsite getWebsiteFromDatabase(Integer tenantId) { private CmsWebsite getWebsiteFromDatabase(Integer tenantId) {
return getByTenantId(tenantId); return getByTenantId(tenantId);
} }

View File

@@ -0,0 +1,17 @@
-- 为 cms_website 表新增发布管理相关字段
-- 执行前请确认已连接正确的数据库
ALTER TABLE `cms_website`
ADD COLUMN `publish_status` VARCHAR(20) NULL DEFAULT 'developing' COMMENT '发布状态: developing开发中 pending_review待审核 published已上架 rejected审核未通过 deprecated已下架' AFTER `market`,
ADD COLUMN `price_type` VARCHAR(20) NULL COMMENT '定价模式: free免费 one_time一次性 subscription订阅' AFTER `publish_status`,
ADD COLUMN `subscription_period` VARCHAR(10) NULL COMMENT '订阅周期: month按月 year按年' AFTER `price_type`,
ADD COLUMN `app_description` VARCHAR(500) NULL COMMENT '应用简介(市场展示用)' AFTER `subscription_period`,
ADD COLUMN `detail_description` TEXT NULL COMMENT '详细说明(富文本)' AFTER `app_description`,
ADD COLUMN `screenshots` TEXT NULL COMMENT '应用截图JSON数组字符串' AFTER `detail_description`,
ADD COLUMN `install_count` INT NULL DEFAULT 0 COMMENT '安装/使用次数' AFTER `screenshots`,
ADD COLUMN `rating` DECIMAL(3,1) NULL DEFAULT 0.0 COMMENT '评分1-5' AFTER `install_count`,
ADD COLUMN `reject_reason` VARCHAR(500) NULL COMMENT '审核拒绝原因' AFTER `rating`,
ADD COLUMN `publish_apply_time` DATETIME NULL COMMENT '提交审核时间' AFTER `reject_reason`,
ADD COLUMN `publish_time` DATETIME NULL COMMENT '正式发布上架时间' AFTER `publish_apply_time`,
ADD COLUMN `reviewer_id` INT NULL COMMENT '审核人用户ID' AFTER `publish_time`,
ADD COLUMN `review_time` DATETIME NULL COMMENT '审核操作时间' AFTER `reviewer_id`;