docs(backend): S1 接口文档在线化——补 springdoc 配置 + 39 个 Controller 中文注解
为什么:前端联调依赖在线接口文档。S1 条目要求两个端口的 /v3/api-docs 能看、
且每个接口有中文分组与中文 summary。
诊断(与 PM 初步定位不同,有硬证据):app-api 的 /v3/api-docs、/doc.html、/、
/error 全部"挂住"(8s 超时 0 字节),根因不是 Spring Security 拦截,而是
运行中的进程握着一个被重新打包替换掉的旧 jar——spring-boot:repackage 把原 fat jar
原地改名为 .jar.original(同一 inode)再写新 jar,进程再去加载"启动后未加载过的类"
就必然 NoClassDefFoundError(日志里连 logback 的 ThrowableProxy 都加载不到),
线程直接死掉、既不响应也记不了日志。SecurityConfig.PUBLIC_PATHS 本来就包含
/doc.html、/swagger-ui/**、/v3/api-docs/**,未改任何安全配置。
- app-api 的 application.yml 补 springdoc.swagger-ui.path: /doc.html
- 39 个 Controller(app-api 10 + admin-api 29)补 @Tag(name=中文分组)
与 @Operation(summary=中文描述),共 172 个接口;描述取自 api-phase1.md 与既有 Javadoc
- 两个入口类加 @OpenAPIDefinition:文档标题 + 顶层 tags 清单(springdoc 默认顶层 tags 为空)
- 纯新增 357 行、删除 0 行:业务逻辑、路径、字段零改动
- 实测:mvn test 37 条契约测试全过;两个入口 package BUILD SUCCESS;
两端口 /v3/api-docs 中文 tags + 中文 summary,/doc.html 可打开,
非白名单路径 5ms 内返回 {"code":401,...} 不再挂住
This commit is contained in:
1 parent
fafadfc6ec
commit
c6fcfcc6ea
42 files changed
+357
No files matched your search
@@ -3,12 +3,34 @@ package com.fruitshorts.app;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.annotations.info.Info;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* 客户端接口服务,只暴露 /app/** 与静态媒资 /files/**。
|
||||
* 与 fruitshorts-admin-api 共用同一套领域模块和数据库,但入口、鉴权和限流策略独立。
|
||||
*/
|
||||
/* 显式指定扫描根包:本类在 com.fruitshorts.app 下,默认扫不到 content/system/framework 模块的 Bean */
|
||||
@OpenAPIDefinition(
|
||||
info = @Info(
|
||||
title = "FruitShorts 客户端接口(/app/**)",
|
||||
version = "1.0.0",
|
||||
description = "接口契约以 FruitServer/docs/api-phase1.md 为准"
|
||||
),
|
||||
tags = {
|
||||
@Tag(name = "客户端-账号", description = "验证码、注册、登录、令牌刷新与登出"),
|
||||
@Tag(name = "客户端-内容浏览", description = "首页 / 发现 / 分类聚合接口"),
|
||||
@Tag(name = "客户端-评论", description = "评论发布、列表与删除"),
|
||||
@Tag(name = "客户端-金币与订单", description = "钱包、金币解锁与订单票据"),
|
||||
@Tag(name = "客户端-离线下载", description = "下载授权、下载偏好与断点续传"),
|
||||
@Tag(name = "客户端-短剧与播放", description = "短剧详情与播放票据"),
|
||||
@Tag(name = "客户端-收藏", description = "收藏切换与列表"),
|
||||
@Tag(name = "客户端-点赞与举报", description = "点赞与举报提报"),
|
||||
@Tag(name = "客户端-个人中心", description = "资料、设备、数据导出与注销"),
|
||||
@Tag(name = "客户端-观看进度", description = "观看进度上报与续看位置"),
|
||||
}
|
||||
)
|
||||
@SpringBootApplication(scanBasePackages = "com.fruitshorts")
|
||||
@MapperScan("com.fruitshorts.**.dal.mapper")
|
||||
public class AppApiApplication {
|
||||
|
||||
+8
@@ -11,6 +11,8 @@ import com.fruitshorts.user.vo.OtpSendReqVO;
|
||||
import com.fruitshorts.user.vo.RefreshReqVO;
|
||||
import com.fruitshorts.user.vo.RegisterReqVO;
|
||||
import com.fruitshorts.user.vo.TokenRespVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -20,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 客户端账号:验证码、注册、登录、刷新、登出。 */
|
||||
@Tag(name = "客户端-账号")
|
||||
@RestController
|
||||
@RequestMapping("/app/auth")
|
||||
@RequiredArgsConstructor
|
||||
@@ -29,30 +32,35 @@ public class AppAuthController {
|
||||
private final AppAuthService authService;
|
||||
|
||||
/** 发送验证码。频控在服务端,客户端不需要自己限流。 */
|
||||
@Operation(summary = "发送验证码(频控在服务端)")
|
||||
@PostMapping("/otp")
|
||||
public Result<Void> sendOtp(@Valid @RequestBody OtpSendReqVO req, HttpServletRequest request) {
|
||||
otpService.send(req, WebUtils.getClientIp(request));
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "注册账号(邮箱免验证码 / 手机号需验证码)")
|
||||
@PostMapping("/register")
|
||||
public Result<TokenRespVO> register(@Valid @RequestBody RegisterReqVO req,
|
||||
HttpServletRequest request) {
|
||||
return Result.ok(authService.register(req, WebUtils.getClientIp(request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "登录(验证码或密码)")
|
||||
@PostMapping("/login")
|
||||
public Result<TokenRespVO> login(@Valid @RequestBody LoginReqVO req,
|
||||
HttpServletRequest request) {
|
||||
return Result.ok(authService.login(req, WebUtils.getClientIp(request)));
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新令牌(旧 refresh token 立即失效)")
|
||||
@PostMapping("/refresh")
|
||||
public Result<TokenRespVO> refresh(@Valid @RequestBody RefreshReqVO req) {
|
||||
return Result.ok(authService.refresh(req.getRefreshToken()));
|
||||
}
|
||||
|
||||
/** 登出:默认只吊销当前设备的会话,allDevices=true 时吊销全部。 */
|
||||
@Operation(summary = "登出(默认当前设备,allDevices 吊销全部)")
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout(@RequestBody(required = false) LogoutReqVO req) {
|
||||
authService.logout(SecurityUtils.getUserIdOrNull(), req == null ? new LogoutReqVO() : req);
|
||||
|
||||
+6
@@ -4,6 +4,8 @@ import com.fruitshorts.app.service.AppCatalogService;
|
||||
import com.fruitshorts.app.vo.AppCategoryVO;
|
||||
import com.fruitshorts.app.vo.AppFeedVO;
|
||||
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;
|
||||
@@ -14,6 +16,7 @@ import java.util.List;
|
||||
/**
|
||||
* 客户端目录接口:首页与发现页各一次请求拿全量聚合数据。
|
||||
*/
|
||||
@Tag(name = "客户端-内容浏览")
|
||||
@RestController
|
||||
@RequestMapping("/app")
|
||||
@RequiredArgsConstructor
|
||||
@@ -21,16 +24,19 @@ public class AppCatalogController {
|
||||
|
||||
private final AppCatalogService appCatalogService;
|
||||
|
||||
@Operation(summary = "首页聚合:轮播 + 分区,一次请求渲染整页")
|
||||
@GetMapping("/home/feed")
|
||||
public Result<AppFeedVO> homeFeed() {
|
||||
return Result.ok(appCatalogService.homeFeed());
|
||||
}
|
||||
|
||||
@Operation(summary = "发现页聚合:分区列表")
|
||||
@GetMapping("/discover/feed")
|
||||
public Result<AppFeedVO> discoverFeed() {
|
||||
return Result.ok(appCatalogService.discoverFeed());
|
||||
}
|
||||
|
||||
@Operation(summary = "分类列表")
|
||||
@GetMapping("/categories")
|
||||
public Result<List<AppCategoryVO>> categories() {
|
||||
return Result.ok(appCatalogService.categories());
|
||||
|
||||
+6
@@ -8,6 +8,8 @@ import com.fruitshorts.interaction.service.CommentService;
|
||||
import com.fruitshorts.interaction.vo.CommentPublishReqVO;
|
||||
import com.fruitshorts.interaction.vo.CommentQueryReqVO;
|
||||
import com.fruitshorts.interaction.vo.CommentRespVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
* 不传 `rootId` 时返回顶层评论,传了则返回该楼层的回复。
|
||||
* 列表允许匿名浏览;发布与删除需要登录。
|
||||
*/
|
||||
@Tag(name = "客户端-评论")
|
||||
@RestController
|
||||
@RequestMapping("/app/comments")
|
||||
@RequiredArgsConstructor
|
||||
@@ -34,6 +37,7 @@ public class AppCommentController {
|
||||
|
||||
private final CommentService commentService;
|
||||
|
||||
@Operation(summary = "发布评论")
|
||||
@PostMapping
|
||||
public Result<CommentRespVO> publish(@Valid @RequestBody CommentPublishReqVO req,
|
||||
HttpServletRequest request) {
|
||||
@@ -41,6 +45,7 @@ public class AppCommentController {
|
||||
WebUtils.getClientIp(request), request.getHeader("X-Device-Id")));
|
||||
}
|
||||
|
||||
@Operation(summary = "评论列表(游标翻页,可匿名浏览)")
|
||||
@GetMapping
|
||||
public Result<CursorResult<CommentRespVO>> page(@RequestParam Long dramaId,
|
||||
@RequestParam(required = false) Long episodeId,
|
||||
@@ -56,6 +61,7 @@ public class AppCommentController {
|
||||
return Result.ok(commentService.page(SecurityUtils.getUserIdOrNull(), query));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除自己的评论")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
commentService.delete(SecurityUtils.getAppUserId(), id);
|
||||
|
||||
+7
@@ -6,6 +6,8 @@ import com.fruitshorts.commerce.service.UnlockService;
|
||||
import com.fruitshorts.commerce.service.WalletService;
|
||||
import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
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.PathVariable;
|
||||
@@ -24,6 +26,7 @@ import java.util.Map;
|
||||
* 金币解锁是第一阶段真实的收费方式;真实商店购买走 {@code /app/orders/verify},
|
||||
* 本轮只预留接口(商店未接入时返回明确业务提示,不会产生假订单)。
|
||||
*/
|
||||
@Tag(name = "客户端-金币与订单")
|
||||
@RestController
|
||||
@RequestMapping("/app")
|
||||
@RequiredArgsConstructor
|
||||
@@ -34,6 +37,7 @@ public class AppCommerceController {
|
||||
private final OrderService orderService;
|
||||
|
||||
/** 我的钱包:余额 + 最近流水,客户端一处渲染。 */
|
||||
@Operation(summary = "我的钱包(余额 + 最近流水)")
|
||||
@GetMapping("/wallet")
|
||||
public Result<Map<String, Object>> wallet() {
|
||||
Long userId = SecurityUtils.getAppUserId();
|
||||
@@ -44,17 +48,20 @@ public class AppCommerceController {
|
||||
}
|
||||
|
||||
/** 金币解锁分集:幂等,重复点击不会重复扣币。 */
|
||||
@Operation(summary = "金币解锁分集(幂等,重复点击不重复扣币)")
|
||||
@PostMapping("/episodes/{episodeId}/unlock")
|
||||
public Result<Map<String, Object>> unlock(@PathVariable Long episodeId) {
|
||||
return Result.ok(unlockService.unlock(SecurityUtils.getAppUserId(), episodeId));
|
||||
}
|
||||
|
||||
@Operation(summary = "我的订单列表")
|
||||
@GetMapping("/orders")
|
||||
public Result<List<Orders>> orders() {
|
||||
return Result.ok(orderService.listByUser(SecurityUtils.getAppUserId()));
|
||||
}
|
||||
|
||||
/** 提交商店票据。第一阶段商店未接入,会返回明确的"尚未配置"。 */
|
||||
@Operation(summary = "提交商店票据(第一阶段商店未接入,返回明确提示)")
|
||||
@PostMapping("/orders/verify")
|
||||
public Result<Orders> verify(@RequestBody Map<String, String> body) {
|
||||
return Result.ok(orderService.verifyAndDeliver(SecurityUtils.getAppUserId(),
|
||||
|
||||
+11
@@ -8,6 +8,8 @@ import com.fruitshorts.playback.vo.DownloadLicenseRespVO;
|
||||
import com.fruitshorts.playback.vo.DownloadPolicyRespVO;
|
||||
import com.fruitshorts.playback.vo.DownloadProgressReqVO;
|
||||
import com.fruitshorts.playback.vo.DownloadSettingReqVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -29,6 +31,7 @@ import java.util.List;
|
||||
* <p>服务端只发授权与地址,分片由客户端向 CDN 拉取;进度上报用于断点续传与跨端展示。
|
||||
* 设备标识优先取请求体,其次取请求头 X-Device-Id。
|
||||
*/
|
||||
@Tag(name = "客户端-离线下载")
|
||||
@RestController
|
||||
@RequestMapping("/app/downloads")
|
||||
@RequiredArgsConstructor
|
||||
@@ -37,6 +40,7 @@ public class AppDownloadController {
|
||||
private final DownloadService downloadService;
|
||||
|
||||
/** 申请下载授权(同一剧集重复申请幂等,只刷新有效期)。 */
|
||||
@Operation(summary = "申请下载授权(同一剧集幂等,只刷新有效期)")
|
||||
@PostMapping
|
||||
public Result<DownloadLicenseRespVO> grant(@Valid @RequestBody DownloadGrantReqVO req,
|
||||
HttpServletRequest request) {
|
||||
@@ -44,17 +48,20 @@ public class AppDownloadController {
|
||||
request.getHeader("X-Device-Id")));
|
||||
}
|
||||
|
||||
@Operation(summary = "我的下载授权列表")
|
||||
@GetMapping
|
||||
public Result<List<DownloadLicenseRespVO>> list(@RequestParam(required = false) String deviceId) {
|
||||
return Result.ok(downloadService.list(SecurityUtils.getAppUserId(), deviceId));
|
||||
}
|
||||
|
||||
@Operation(summary = "下载规则(设备上限 / 仅 Wi-Fi / 清晰度上限)")
|
||||
@GetMapping("/policy")
|
||||
public Result<DownloadPolicyRespVO> policy(@RequestParam(required = false) String deviceId) {
|
||||
return Result.ok(downloadService.policy(SecurityUtils.getAppUserId(), deviceId));
|
||||
}
|
||||
|
||||
/** 仅 Wi-Fi 下载、清晰度上限等偏好。 */
|
||||
@Operation(summary = "更新下载偏好设置")
|
||||
@PutMapping("/settings")
|
||||
public Result<Void> updateSettings(@RequestBody DownloadSettingReqVO req) {
|
||||
downloadService.updateSettings(SecurityUtils.getAppUserId(), req);
|
||||
@@ -62,6 +69,7 @@ public class AppDownloadController {
|
||||
}
|
||||
|
||||
/** 断点续传前重新取一次地址:授权不变,只有地址是短时效的。 */
|
||||
@Operation(summary = "重新获取下载地址(断点续传前刷新短时效地址)")
|
||||
@GetMapping("/{episodeId}/url")
|
||||
public Result<DownloadLicenseRespVO> refreshUrl(@PathVariable Long episodeId,
|
||||
HttpServletRequest request) {
|
||||
@@ -69,6 +77,7 @@ public class AppDownloadController {
|
||||
request.getHeader("X-Device-Id")));
|
||||
}
|
||||
|
||||
@Operation(summary = "上报下载进度")
|
||||
@PostMapping("/{episodeId}/progress")
|
||||
public Result<DownloadLicenseRespVO> reportProgress(@PathVariable Long episodeId,
|
||||
@RequestBody DownloadProgressReqVO req,
|
||||
@@ -77,6 +86,7 @@ public class AppDownloadController {
|
||||
request.getHeader("X-Device-Id")));
|
||||
}
|
||||
|
||||
@Operation(summary = "吊销某集的下载授权")
|
||||
@DeleteMapping("/{episodeId}")
|
||||
public Result<Void> revoke(@PathVariable Long episodeId,
|
||||
@RequestParam(required = false) String deviceId,
|
||||
@@ -87,6 +97,7 @@ public class AppDownloadController {
|
||||
}
|
||||
|
||||
/** 清空当前设备的全部下载授权。 */
|
||||
@Operation(summary = "清空当前设备的全部下载授权")
|
||||
@DeleteMapping
|
||||
public Result<Integer> clear(@RequestParam(required = false) String deviceId,
|
||||
HttpServletRequest request) {
|
||||
|
||||
+5
@@ -7,6 +7,8 @@ import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.framework.web.WebUtils;
|
||||
import com.fruitshorts.playback.service.PlaybackService;
|
||||
import com.fruitshorts.playback.vo.PlayRespVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -14,6 +16,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "客户端-短剧与播放")
|
||||
@RestController
|
||||
@RequestMapping("/app")
|
||||
@RequiredArgsConstructor
|
||||
@@ -23,6 +26,7 @@ public class AppDramaController {
|
||||
private final PlaybackService playbackService;
|
||||
|
||||
/** 详情:包含剧集列表,客户端进详情页一次请求即可渲染 */
|
||||
@Operation(summary = "短剧详情(含剧集列表,进详情页一次请求即可渲染)")
|
||||
@GetMapping("/dramas/{id}")
|
||||
public Result<AppDramaDetailVO> detail(@PathVariable Long id) {
|
||||
return Result.ok(appCatalogService.dramaDetail(id));
|
||||
@@ -32,6 +36,7 @@ public class AppDramaController {
|
||||
* 播放票据:服务端判定权益后签发短时效播放地址。
|
||||
* 未登录也能播免费集;付费集未解锁时只返回需要多少金币。
|
||||
*/
|
||||
@Operation(summary = "播放票据:判定权益后签发短时效播放地址")
|
||||
@GetMapping("/episodes/{id}/play")
|
||||
public Result<PlayRespVO> play(@PathVariable Long id, HttpServletRequest request) {
|
||||
return Result.ok(playbackService.play(
|
||||
|
||||
+6
@@ -6,6 +6,8 @@ import com.fruitshorts.common.pojo.Result;
|
||||
import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.playback.service.FavoriteService;
|
||||
import com.fruitshorts.playback.vo.FavoriteToggleReqVO;
|
||||
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.web.bind.annotation.DeleteMapping;
|
||||
@@ -19,6 +21,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
/** 收藏。 */
|
||||
@Tag(name = "客户端-收藏")
|
||||
@RestController
|
||||
@RequestMapping("/app/favorites")
|
||||
@RequiredArgsConstructor
|
||||
@@ -28,18 +31,21 @@ public class AppFavoriteController {
|
||||
private final AppCatalogService appCatalogService;
|
||||
|
||||
/** 切换收藏,返回切换后的状态:true 为已收藏。 */
|
||||
@Operation(summary = "切换收藏(返回切换后的状态)")
|
||||
@PostMapping("/toggle")
|
||||
public Result<Boolean> toggle(@Valid @RequestBody FavoriteToggleReqVO req) {
|
||||
return Result.ok(favoriteService.toggle(SecurityUtils.getAppUserId(), req.getDramaId()));
|
||||
}
|
||||
|
||||
/** 收藏列表,卡片结构与首页一致。 */
|
||||
@Operation(summary = "我的收藏列表")
|
||||
@GetMapping
|
||||
public Result<List<AppDramaCardVO>> list() {
|
||||
return Result.ok(appCatalogService.toCards(
|
||||
favoriteService.listDramas(SecurityUtils.getAppUserId())));
|
||||
}
|
||||
|
||||
@Operation(summary = "取消收藏")
|
||||
@DeleteMapping("/{dramaId}")
|
||||
public Result<Void> remove(@PathVariable Long dramaId) {
|
||||
favoriteService.remove(SecurityUtils.getAppUserId(), dramaId);
|
||||
|
||||
+5
@@ -7,6 +7,8 @@ import com.fruitshorts.interaction.service.ReportService;
|
||||
import com.fruitshorts.interaction.vo.LikeToggleReqVO;
|
||||
import com.fruitshorts.interaction.vo.LikeToggleRespVO;
|
||||
import com.fruitshorts.interaction.vo.ReportCreateReqVO;
|
||||
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.web.bind.annotation.PostMapping;
|
||||
@@ -15,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 点赞与举报。 */
|
||||
@Tag(name = "客户端-点赞与举报")
|
||||
@RestController
|
||||
@RequestMapping("/app")
|
||||
@RequiredArgsConstructor
|
||||
@@ -24,12 +27,14 @@ public class AppInteractionController {
|
||||
private final ReportService reportService;
|
||||
|
||||
/** 点赞/取消点赞,返回切换后的状态与最新计数。 */
|
||||
@Operation(summary = "点赞 / 取消点赞")
|
||||
@PostMapping("/likes/toggle")
|
||||
public Result<LikeToggleRespVO> toggleLike(@Valid @RequestBody LikeToggleReqVO req) {
|
||||
return Result.ok(likeService.toggle(SecurityUtils.getAppUserId(), req));
|
||||
}
|
||||
|
||||
/** 提交举报:同一用户对同一目标只保留一条待处理工单。 */
|
||||
@Operation(summary = "提交举报(同一目标只保留一条待处理工单)")
|
||||
@PostMapping("/reports")
|
||||
public Result<Long> report(@Valid @RequestBody ReportCreateReqVO req) {
|
||||
return Result.ok(reportService.create(SecurityUtils.getAppUserId(), req));
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.fruitshorts.user.service.AppUserService;
|
||||
import com.fruitshorts.user.vo.AppUserRespVO;
|
||||
import com.fruitshorts.user.vo.DeactivateReqVO;
|
||||
import com.fruitshorts.user.vo.ProfileUpdateReqVO;
|
||||
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.web.bind.annotation.GetMapping;
|
||||
@@ -21,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** 客户端个人中心:资料、设备、注销、数据导出。 */
|
||||
@Tag(name = "客户端-个人中心")
|
||||
@RestController
|
||||
@RequestMapping("/app/me")
|
||||
@RequiredArgsConstructor
|
||||
@@ -28,33 +31,39 @@ public class AppMeController {
|
||||
|
||||
private final AppUserService userService;
|
||||
|
||||
@Operation(summary = "我的资料")
|
||||
@GetMapping
|
||||
public Result<AppUserRespVO> me() {
|
||||
return Result.ok(userService.me(SecurityUtils.getAppUserId()));
|
||||
}
|
||||
|
||||
@Operation(summary = "修改资料(昵称 / 头像 / 语言等)")
|
||||
@PutMapping
|
||||
public Result<AppUserRespVO> update(@Valid @RequestBody ProfileUpdateReqVO req) {
|
||||
return Result.ok(userService.updateProfile(SecurityUtils.getAppUserId(), req));
|
||||
}
|
||||
|
||||
@Operation(summary = "我的登录设备列表")
|
||||
@GetMapping("/devices")
|
||||
public Result<List<AppUserDevice>> devices() {
|
||||
return Result.ok(userService.devices(SecurityUtils.getAppUserId()));
|
||||
}
|
||||
|
||||
/** 商店与隐私法规要求的数据导出,返回账号侧数据;内容侧数据由客户端自行下载。 */
|
||||
@Operation(summary = "数据导出(合规要求)")
|
||||
@GetMapping("/export")
|
||||
public Result<Map<String, Object>> export() {
|
||||
return Result.ok(userService.exportAccount(SecurityUtils.getAppUserId()));
|
||||
}
|
||||
|
||||
/** 申请注销:立即登出全部设备,进入冷静期,期间可撤销。 */
|
||||
@Operation(summary = "申请注销(进入冷静期并登出全部设备)")
|
||||
@PostMapping("/deactivate")
|
||||
public Result<LocalDateTime> deactivate(@RequestBody(required = false) DeactivateReqVO req) {
|
||||
return Result.ok(userService.requestDeactivation(SecurityUtils.getAppUserId(), req));
|
||||
}
|
||||
|
||||
@Operation(summary = "撤销注销申请")
|
||||
@PostMapping("/deactivate/cancel")
|
||||
public Result<Void> cancelDeactivate() {
|
||||
userService.cancelDeactivation(SecurityUtils.getAppUserId());
|
||||
|
||||
+8
@@ -5,6 +5,8 @@ import com.fruitshorts.framework.security.SecurityUtils;
|
||||
import com.fruitshorts.playback.service.WatchProgressService;
|
||||
import com.fruitshorts.playback.vo.ProgressRespVO;
|
||||
import com.fruitshorts.playback.vo.ProgressSaveReqVO;
|
||||
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.web.bind.annotation.DeleteMapping;
|
||||
@@ -19,6 +21,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
/** 观看进度与观看记录。 */
|
||||
@Tag(name = "客户端-观看进度")
|
||||
@RestController
|
||||
@RequestMapping("/app/watch")
|
||||
@RequiredArgsConstructor
|
||||
@@ -27,28 +30,33 @@ public class AppWatchController {
|
||||
private final WatchProgressService progressService;
|
||||
|
||||
/** 上报进度:客户端每 10 秒一次,切集时立即上报一次。 */
|
||||
@Operation(summary = "上报观看进度(客户端每 10 秒一次)")
|
||||
@PostMapping("/progress")
|
||||
public Result<ProgressRespVO> save(@Valid @RequestBody ProgressSaveReqVO req) {
|
||||
return Result.ok(progressService.save(SecurityUtils.getAppUserId(), req));
|
||||
}
|
||||
|
||||
/** 某部剧的续看位置,进详情页时用它决定"续看第 N 集"。 */
|
||||
@Operation(summary = "某部剧的续看位置")
|
||||
@GetMapping("/progress")
|
||||
public Result<ProgressRespVO> ofDrama(@RequestParam Long dramaId) {
|
||||
return Result.ok(progressService.ofDrama(SecurityUtils.getAppUserId(), dramaId));
|
||||
}
|
||||
|
||||
@Operation(summary = "最近观看列表")
|
||||
@GetMapping("/recent")
|
||||
public Result<List<ProgressRespVO>> recent(@RequestParam(defaultValue = "20") int limit) {
|
||||
return Result.ok(progressService.recent(SecurityUtils.getAppUserId(), limit));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除某部剧的观看记录")
|
||||
@DeleteMapping("/progress/{dramaId}")
|
||||
public Result<Void> remove(@PathVariable Long dramaId) {
|
||||
progressService.remove(SecurityUtils.getAppUserId(), dramaId);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "清空观看记录")
|
||||
@DeleteMapping("/progress")
|
||||
public Result<Void> clear() {
|
||||
progressService.clear(SecurityUtils.getAppUserId());
|
||||
|
||||
@@ -55,6 +55,11 @@ fruitshorts:
|
||||
cors:
|
||||
allowed-origins: "*"
|
||||
|
||||
# 接口文档:/doc.html 打开 Swagger UI,/v3/api-docs 是机器可读契约(前端联调按它写)
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /doc.html
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.fruitshorts: info
|
||||
Reference in new issue
Block a user