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
+8
-26
@@ -2,48 +2,30 @@
|
||||
|
||||
package com.fruitshorts.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fruitshorts.app.vo.AppNoticeVO;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.system.dal.entity.OpsNotice;
|
||||
import com.fruitshorts.system.dal.mapper.OpsNoticeMapper;
|
||||
import com.fruitshorts.system.service.OpsNoticeService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* C 端系统公告(`GET /app/notices`,匿名,只读)。
|
||||
*
|
||||
* <p>口径:`status=1` + `target ∈ {ALL, USER}` + 已到发布时间 + 未过期;
|
||||
* 按 `publish_at desc, id desc` 排序。**没有数据就是空列表**(不是 404)——
|
||||
* 公告当前也没有后台产出方,客户端拿到空列表属正常状态。
|
||||
* <p>筛选口径**不在本类实现**:统一走 `OpsNoticeService.pageForApp(...)` ——
|
||||
* 后台公告管理(`/admin/notices`)用的是同一套条件,避免"后台发布后 C 端到底能不能看到"两处条件漂移。
|
||||
*
|
||||
* <p>没有数据返回空列表(不是 404);本类只负责把实体转成 C 端字段(不暴露后台字段)。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AppNoticeService {
|
||||
|
||||
private static final String TARGET_ALL = "ALL";
|
||||
private static final String TARGET_USER = "USER";
|
||||
private static final int MAX_PAGE_SIZE = 50;
|
||||
|
||||
private final OpsNoticeMapper opsNoticeMapper;
|
||||
private final OpsNoticeService opsNoticeService;
|
||||
|
||||
public PageResult<AppNoticeVO> page(int pageIndex, int pageSize) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Page<OpsNotice> page = new Page<>(pageIndex < 1 ? 1 : pageIndex,
|
||||
pageSize < 1 ? 20 : Math.min(pageSize, MAX_PAGE_SIZE));
|
||||
IPage<OpsNotice> result = opsNoticeMapper.selectPage(page, Wrappers.<OpsNotice>lambdaQuery()
|
||||
.eq(OpsNotice::getStatus, 1)
|
||||
.in(OpsNotice::getTarget, List.of(TARGET_ALL, TARGET_USER))
|
||||
.and(w -> w.isNull(OpsNotice::getPublishAt).or().le(OpsNotice::getPublishAt, now))
|
||||
.and(w -> w.isNull(OpsNotice::getExpireAt).or().gt(OpsNotice::getExpireAt, now))
|
||||
.orderByDesc(OpsNotice::getPublishAt)
|
||||
.orderByDesc(OpsNotice::getId));
|
||||
return new PageResult<>(result.getTotal(), result.getRecords().stream().map(this::toVO).toList());
|
||||
PageResult<OpsNotice> page = opsNoticeService.pageForApp(pageIndex, pageSize);
|
||||
return new PageResult<>(page.getTotal(), page.getList().stream().map(this::toVO).toList());
|
||||
}
|
||||
|
||||
private AppNoticeVO toVO(OpsNotice notice) {
|
||||
|
||||
+4
-3
@@ -12,6 +12,7 @@ import com.fruitshorts.commerce.dal.mapper.StoreReceiptMapper;
|
||||
import com.fruitshorts.commerce.service.DisabledStoreVerifier;
|
||||
import com.fruitshorts.commerce.service.EntitlementService;
|
||||
import com.fruitshorts.commerce.service.OrderService;
|
||||
import com.fruitshorts.commerce.service.ProductService;
|
||||
import com.fruitshorts.commerce.service.StoreVerifier;
|
||||
import com.fruitshorts.commerce.service.UnlockService;
|
||||
import com.fruitshorts.commerce.service.WalletService;
|
||||
@@ -65,8 +66,8 @@ class AppOrdersVerifyContractTest {
|
||||
storeReceiptMapper = mock(StoreReceiptMapper.class);
|
||||
verifier = new StubStoreVerifier();
|
||||
OrderService orderService = new OrderService(ordersMapper, mock(OrderEventMapper.class),
|
||||
productMapper, storeReceiptMapper, verifier, mock(WalletService.class),
|
||||
mock(EntitlementService.class));
|
||||
new ProductService(productMapper), storeReceiptMapper, verifier,
|
||||
mock(WalletService.class), mock(EntitlementService.class));
|
||||
mockMvc = ControllerContractSupport.mockMvcOf(new AppCommerceController(
|
||||
mock(WalletService.class), mock(UnlockService.class), orderService));
|
||||
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||
@@ -131,7 +132,7 @@ class AppOrdersVerifyContractTest {
|
||||
void should_keep_disabled_behaviour_when_store_is_not_configured() throws Exception {
|
||||
// 默认实现(未配凭据):对外仍是"尚未配置",不产生订单
|
||||
OrderService disabled = new OrderService(ordersMapper, mock(OrderEventMapper.class),
|
||||
productMapper, storeReceiptMapper, new DisabledStoreVerifier(),
|
||||
new ProductService(productMapper), storeReceiptMapper, new DisabledStoreVerifier(),
|
||||
mock(WalletService.class), mock(EntitlementService.class));
|
||||
MockMvc disabledMvc = ControllerContractSupport.mockMvcOf(new AppCommerceController(
|
||||
mock(WalletService.class), mock(UnlockService.class), disabled));
|
||||
|
||||
Reference in new issue
Block a user