feat(admin): 商品配置与系统公告管理接口,id 以字符串下发
- /admin/products 六个接口 + 校验(code 唯一、store_product_id 同平台唯一、 COIN_PACK 需 coin_amount>0、VIP 需 vip_days>0、VIP 只建不启) - /admin/notices 六个接口,与 C 端 /app/notices 共用 OpsNoticeService.pageForApp 一套筛选口径 - 权限点 product:list/product:update/notice:list/notice:update + 菜单 31/32/33(sql/data-phase1.sql 幂等) - 雪花 id 超过 JS 安全整数范围:id 以字符串下发,避免浏览器四舍五入导致回查 200002 - C 端商品查询收敛到 ProductService.findForStore,OrderService 不再自带一份 - 测试:module-commerce 33 / admin-api 16 / app-api 49 / worker 12,共 110 条全过
This commit is contained in:
1 parent
7513ac06bc
commit
8d19a0cc0a
24 files changed
+1761
-48
No files matched your search
@@ -36,6 +36,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@Tag(name = "后台-菜单管理", description = "菜单树维护"),
|
||||
@Tag(name = "后台-操作日志", description = "操作日志检索"),
|
||||
@Tag(name = "后台-订单管理", description = "订单检索、补发与退款"),
|
||||
@Tag(name = "后台-商品配置", description = "商品(金币包)配置与上下架"),
|
||||
@Tag(name = "后台-系统公告", description = "系统公告发布与管理(与 C 端口径一致)"),
|
||||
@Tag(name = "后台-权限点", description = "权限点查询"),
|
||||
@Tag(name = "后台-分集收费", description = "分集价格配置"),
|
||||
@Tag(name = "后台-对账", description = "对账任务与手动对账"),
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.log.OperationLog;
|
||||
import com.fruitshorts.system.service.OpsNoticeService;
|
||||
import com.fruitshorts.system.vo.NoticeQueryReqVO;
|
||||
import com.fruitshorts.system.vo.NoticeRespVO;
|
||||
import com.fruitshorts.system.vo.NoticeSaveReqVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 后台系统公告管理。
|
||||
*
|
||||
* <p>发布后的可见性与 C 端 `GET /app/notices` **共用同一套筛选实现**
|
||||
* (`OpsNoticeService.pageForApp`):`status=1` + `target ∈ {ALL, USER}` + 已到发布时间 + 未过期。
|
||||
*/
|
||||
@Tag(name = "后台-系统公告")
|
||||
@RestController
|
||||
@RequestMapping("/admin/notices")
|
||||
@RequiredArgsConstructor
|
||||
public class NoticeAdminController {
|
||||
|
||||
private final OpsNoticeService opsNoticeService;
|
||||
|
||||
@Operation(summary = "公告分页查询")
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('notice:list')")
|
||||
public Result<PageResult<NoticeRespVO>> page(@Validated NoticeQueryReqVO req) {
|
||||
return Result.ok(opsNoticeService.pageForAdmin(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "公告详情")
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('notice:list')")
|
||||
public Result<NoticeRespVO> detail(@PathVariable Long id) {
|
||||
return Result.ok(opsNoticeService.detail(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "新建公告(默认发布,C 端立即可见;返回新公告 id,字符串)")
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPermission('notice:update')")
|
||||
@OperationLog(module = "系统公告", action = "新建公告")
|
||||
public Result<String> create(@Valid @RequestBody NoticeSaveReqVO req) {
|
||||
// 雪花 id 用字符串回,避免前端拿到被四舍五入的 id
|
||||
return Result.ok(String.valueOf(opsNoticeService.create(req)));
|
||||
}
|
||||
|
||||
@Operation(summary = "编辑公告")
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('notice:update')")
|
||||
@OperationLog(module = "系统公告", action = "编辑公告")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody NoticeSaveReqVO req) {
|
||||
opsNoticeService.update(id, req);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "发布 / 下架(下架后 C 端立即不可见)")
|
||||
@PutMapping("/{id}/status")
|
||||
@PreAuthorize("@ss.hasPermission('notice:update')")
|
||||
@OperationLog(module = "系统公告", action = "公告上下架")
|
||||
public Result<Void> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
|
||||
opsNoticeService.updateStatus(id, status);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除公告")
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('notice:update')")
|
||||
@OperationLog(module = "系统公告", action = "删除公告")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
opsNoticeService.delete(id);
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.admin.controller;
|
||||
|
||||
import com.fruitshorts.commerce.service.ProductService;
|
||||
import com.fruitshorts.commerce.vo.ProductQueryReqVO;
|
||||
import com.fruitshorts.commerce.vo.ProductRespVO;
|
||||
import com.fruitshorts.commerce.vo.ProductSaveReqVO;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.log.OperationLog;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 后台商品(金币包)配置。
|
||||
*
|
||||
* <p>商品是 C 端 `POST /app/orders/verify` 的发币依据(按 `store_product_id` 对齐商店商品),
|
||||
* 所以 `code` 与"同平台 store_product_id"的唯一性、以及 type 与金额的匹配都在服务端强校验。
|
||||
*/
|
||||
@Tag(name = "后台-商品配置")
|
||||
@RestController
|
||||
@RequestMapping("/admin/products")
|
||||
@RequiredArgsConstructor
|
||||
public class ProductAdminController {
|
||||
|
||||
private final ProductService productService;
|
||||
|
||||
@Operation(summary = "商品分页查询")
|
||||
@GetMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermission('product:list')")
|
||||
public Result<PageResult<ProductRespVO>> page(@Validated ProductQueryReqVO req) {
|
||||
return Result.ok(productService.page(req));
|
||||
}
|
||||
|
||||
@Operation(summary = "商品详情")
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('product:list')")
|
||||
public Result<ProductRespVO> detail(@PathVariable Long id) {
|
||||
return Result.ok(productService.detail(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "新建商品(默认下架,需显式上架;返回新商品 id,字符串)")
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPermission('product:update')")
|
||||
@OperationLog(module = "商品配置", action = "新建商品")
|
||||
public Result<String> create(@Valid @RequestBody ProductSaveReqVO req) {
|
||||
// 雪花 id 用字符串回,避免前端拿到被四舍五入的 id
|
||||
return Result.ok(String.valueOf(productService.create(req)));
|
||||
}
|
||||
|
||||
@Operation(summary = "编辑商品")
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('product:update')")
|
||||
@OperationLog(module = "商品配置", action = "编辑商品")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody ProductSaveReqVO req) {
|
||||
productService.update(id, req);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "上架 / 下架(会员商品在订阅上线前不能启用)")
|
||||
@PutMapping("/{id}/status")
|
||||
@PreAuthorize("@ss.hasPermission('product:update')")
|
||||
@OperationLog(module = "商品配置", action = "商品上下架")
|
||||
public Result<Void> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
|
||||
productService.updateStatus(id, status);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除商品")
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("@ss.hasPermission('product:update')")
|
||||
@OperationLog(module = "商品配置", action = "删除商品")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
productService.delete(id);
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user