feat(backend): 补齐 C 端商业化与账号缺口(金币充值/会员/搜索/语言筛选/Google登录/短信/商店可配置接入)
This commit is contained in:
1 parent
212834d59d
commit
63b086998e
25 files changed
+859
-29
No files matched your search
+7
@@ -2,6 +2,7 @@
|
||||
|
||||
package com.fruitshorts.app.controller;
|
||||
|
||||
import com.fruitshorts.app.vo.GoogleLoginReqVO;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.framework.web.WebUtils;
|
||||
@@ -55,6 +56,12 @@ public class AppAuthController {
|
||||
return Result.ok(authService.login(req, WebUtils.getClientIp(request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "Google 登录(未配置 clientId 时返回明确错误)")
|
||||
@PostMapping("/google")
|
||||
public Result<TokenRespVO> google(@Valid @RequestBody GoogleLoginReqVO req) {
|
||||
return Result.ok(authService.googleLogin(req.getIdToken()));
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新令牌(旧 refresh token 立即失效)")
|
||||
@PostMapping("/refresh")
|
||||
public Result<TokenRespVO> refresh(@Valid @RequestBody RefreshReqVO req) {
|
||||
|
||||
+20
@@ -4,8 +4,10 @@ package com.fruitshorts.app.controller;
|
||||
|
||||
import com.fruitshorts.commerce.dal.entity.Orders;
|
||||
import com.fruitshorts.commerce.service.OrderService;
|
||||
import com.fruitshorts.commerce.service.ProductService;
|
||||
import com.fruitshorts.commerce.service.UnlockService;
|
||||
import com.fruitshorts.commerce.service.WalletService;
|
||||
import com.fruitshorts.commerce.vo.ProductRespVO;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -16,6 +18,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
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;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -37,6 +40,7 @@ public class AppCommerceController {
|
||||
private final WalletService walletService;
|
||||
private final UnlockService unlockService;
|
||||
private final OrderService orderService;
|
||||
private final ProductService productService;
|
||||
|
||||
/** 我的钱包:余额 + 最近流水,客户端一处渲染。 */
|
||||
@Operation(summary = "我的钱包(余额 + 最近流水)")
|
||||
@@ -49,6 +53,22 @@ public class AppCommerceController {
|
||||
return Result.ok(data);
|
||||
}
|
||||
|
||||
/** 充值商品列表:只返回上架商品,type 可选(COIN_PACK/VIP)。 */
|
||||
@Operation(summary = "充值商品列表(只返回上架商品,type 可选 COIN_PACK/VIP)")
|
||||
@GetMapping("/products")
|
||||
public Result<List<ProductRespVO>> products(@RequestParam(required = false) String type) {
|
||||
return Result.ok(productService.listForApp(type));
|
||||
}
|
||||
|
||||
/** 创建充值订单:body 传 productId,返回 CREATED 状态的订单,等待后续支付。 */
|
||||
@Operation(summary = "创建充值订单(body 传 productId)")
|
||||
@PostMapping("/orders")
|
||||
public Result<Orders> createOrder(@RequestBody Map<String, Object> body) {
|
||||
Object productId = body.get("productId");
|
||||
Long pid = productId == null ? null : Long.valueOf(productId.toString());
|
||||
return Result.ok(orderService.createOrder(SecurityUtils.getAppUserId(), pid));
|
||||
}
|
||||
|
||||
/** 金币解锁分集:幂等,重复点击不会重复扣币。 */
|
||||
@Operation(summary = "金币解锁分集(幂等,重复点击不重复扣币)")
|
||||
@PostMapping("/episodes/{episodeId}/unlock")
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.app.controller;
|
||||
|
||||
import com.fruitshorts.app.service.AppSearchService;
|
||||
import com.fruitshorts.app.vo.AppDramaCardVO;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 客户端搜索:按标题或简介模糊匹配已上线短剧。 */
|
||||
@Tag(name = "客户端-搜索")
|
||||
@RestController
|
||||
@RequestMapping("/app")
|
||||
@RequiredArgsConstructor
|
||||
public class AppSearchController {
|
||||
|
||||
private final AppSearchService appSearchService;
|
||||
|
||||
@Operation(summary = "搜索短剧(按标题/简介模糊匹配,只返回已上线)")
|
||||
@GetMapping("/search")
|
||||
public Result<PageResult<AppDramaCardVO>> search(@RequestParam String keyword,
|
||||
@RequestParam(defaultValue = "1") int pageIndex,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
return Result.ok(appSearchService.search(keyword, pageIndex, pageSize));
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,7 @@ public class AppCatalogService {
|
||||
card.setTotalEpisodes(drama.getTotalEpisodes());
|
||||
card.setIsHot(drama.getIsHot() != null && drama.getIsHot() == 1);
|
||||
card.setViewCount(drama.getViewCount());
|
||||
card.setLanguage(drama.getLanguage());
|
||||
card.setPreview(preview == null ? null : toEpisode(preview));
|
||||
return card;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.app.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fruitshorts.app.vo.AppDramaCardVO;
|
||||
import com.fruitshorts.common.pojo.PageResult;
|
||||
import com.fruitshorts.content.dal.entity.Drama;
|
||||
import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.enums.ContentStatus;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 客户端搜索:只搜已上线短剧,命中标题或简介。
|
||||
* 结果复用 {@link AppCatalogService#toCards} 的卡片转换,保证与首页卡片字段一致。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AppSearchService {
|
||||
|
||||
private static final int MAX_PAGE_SIZE = 50;
|
||||
|
||||
private final DramaMapper dramaMapper;
|
||||
private final AppCatalogService appCatalogService;
|
||||
|
||||
public PageResult<AppDramaCardVO> search(String keyword, int pageIndex, int pageSize) {
|
||||
if (!StringUtils.hasText(keyword)) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
int safeIndex = Math.max(pageIndex, 1);
|
||||
int safeSize = Math.min(Math.max(pageSize, 1), MAX_PAGE_SIZE);
|
||||
Page<Drama> page = new Page<>(safeIndex, safeSize);
|
||||
Page<Drama> result = dramaMapper.selectPage(page, Wrappers.<Drama>lambdaQuery()
|
||||
.eq(Drama::getStatus, ContentStatus.PUBLISHED.getCode())
|
||||
.and(w -> w.like(Drama::getTitle, keyword.trim())
|
||||
.or().like(Drama::getSynopsis, keyword.trim()))
|
||||
.orderByDesc(Drama::getSort)
|
||||
.orderByDesc(Drama::getId));
|
||||
return new PageResult<>(result.getTotal(), appCatalogService.toCards(result.getRecords()));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
package com.fruitshorts.app.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/** 列表卡片,用于首页/发现页/分类分区 */
|
||||
@@ -17,6 +18,9 @@ public class AppDramaCardVO {
|
||||
private Integer totalEpisodes;
|
||||
private Boolean isHot;
|
||||
private Long viewCount;
|
||||
/** 语种,供客户端按语言筛选(如 zh-CN / id)。 */
|
||||
@Schema(description = "短剧语种,如 zh-CN / id")
|
||||
private String language;
|
||||
/**
|
||||
* 首个可播免费集。首页竖屏播放需要在列表阶段就能起播,
|
||||
* 带上这一集可以省掉「点开→请求详情→再请求播放地址」的往返。
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.app.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/** Google 登录请求。 */
|
||||
@Data
|
||||
public class GoogleLoginReqVO {
|
||||
|
||||
@NotBlank(message = "缺少 Google idToken")
|
||||
private String idToken;
|
||||
}
|
||||
Reference in new issue
Block a user