fullData = leaiApiClient.fetchWorkDetail(remoteWorkId);
+ if (fullData != null) {
+ savePageList(workId, fullData.get("pageList"));
+ } else {
+ log.warn("[乐读派] 补充拉取页面数据失败: workId={}, remoteWorkId={}", workId, remoteWorkId);
+ }
+ }
+
/**
* 通过 remoteWorkId 查找本地作品
*/
diff --git a/backend-java/src/main/java/com/competition/modules/leai/task/LeaiReconcileTask.java b/backend-java/src/main/java/com/competition/modules/leai/task/LeaiReconcileTask.java
index 0eab89d..ef9a739 100644
--- a/backend-java/src/main/java/com/competition/modules/leai/task/LeaiReconcileTask.java
+++ b/backend-java/src/main/java/com/competition/modules/leai/task/LeaiReconcileTask.java
@@ -16,7 +16,7 @@ import java.util.Map;
/**
* 定时任务:B3 对账 + Webhook 失败重试
*
- * 1. 每 30 分钟调用 B3 接口对账,补偿 Webhook 遗漏
+ * 1. B3 对账:开发/测试环境每 1 分钟,生产环境每 30 分钟(配置项 leai.reconcile-interval)
* 2. 每 10 分钟重试失败的 Webhook 事件(最多 3 次)
*/
@Slf4j
@@ -35,9 +35,9 @@ public class LeaiReconcileTask {
/**
* B3 定时对账
- * 每 30 分钟执行,初始延迟 60 秒
+ * 间隔由 leai.reconcile-interval 配置(开发/测试: 1分钟,生产: 30分钟)
*/
- @Scheduled(fixedRate = 30 * 60 * 1000, initialDelay = 60 * 1000)
+ @Scheduled(fixedRateString = "${leai.reconcile-interval:1800000}", initialDelayString = "${leai.reconcile-initial-delay:60000}")
public void reconcile() {
log.info("[B3对账] 开始执行...");
diff --git a/backend-java/src/main/java/com/competition/modules/leai/vo/LeaiTokenVO.java b/backend-java/src/main/java/com/competition/modules/leai/vo/LeaiTokenVO.java
index 7d766c4..bec3c40 100644
--- a/backend-java/src/main/java/com/competition/modules/leai/vo/LeaiTokenVO.java
+++ b/backend-java/src/main/java/com/competition/modules/leai/vo/LeaiTokenVO.java
@@ -15,10 +15,4 @@ public class LeaiTokenVO {
@Schema(description = "机构ID(对应本项目的租户 code,即 tenant_code)")
private String orgId;
-
- @Schema(description = "H5 前端地址")
- private String h5Url;
-
- @Schema(description = "用户手机号")
- private String phone;
}
diff --git a/backend-java/src/main/java/com/competition/modules/pub/config/SmsConfig.java b/backend-java/src/main/java/com/competition/modules/pub/config/SmsConfig.java
new file mode 100644
index 0000000..cc42252
--- /dev/null
+++ b/backend-java/src/main/java/com/competition/modules/pub/config/SmsConfig.java
@@ -0,0 +1,29 @@
+package com.competition.modules.pub.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 阿里云短信服务配置
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "aliyun.sms")
+public class SmsConfig {
+
+ /** 阿里云短信 AccessKey ID */
+ private String accessKeyId;
+
+ /** 阿里云短信 AccessKey Secret */
+ private String accessKeySecret;
+
+ /** 短信签名名称 */
+ private String signName = "乐绘世界";
+
+ /** 验证码模板 CODE */
+ private String templateCode;
+
+ /** 是否启用真实短信发送(false 时验证码输出到日志) */
+ private boolean enabled = false;
+}
diff --git a/backend-java/src/main/java/com/competition/modules/pub/controller/PublicAuthController.java b/backend-java/src/main/java/com/competition/modules/pub/controller/PublicAuthController.java
index 6a85a11..963fb92 100644
--- a/backend-java/src/main/java/com/competition/modules/pub/controller/PublicAuthController.java
+++ b/backend-java/src/main/java/com/competition/modules/pub/controller/PublicAuthController.java
@@ -5,7 +5,9 @@ import com.competition.common.result.Result;
import com.competition.common.util.SecurityUtil;
import com.competition.modules.pub.dto.PublicLoginDto;
import com.competition.modules.pub.dto.PublicRegisterDto;
+import com.competition.modules.pub.dto.SendSmsCodeDto;
import com.competition.modules.pub.service.PublicAuthService;
+import com.competition.modules.pub.service.SmsCodeService;
import com.competition.security.annotation.Public;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,6 +24,7 @@ import java.util.Map;
public class PublicAuthController {
private final PublicAuthService publicAuthService;
+ private final SmsCodeService smsCodeService;
@Public
@PostMapping("/register")
@@ -39,6 +42,15 @@ public class PublicAuthController {
return Result.success(publicAuthService.login(dto));
}
+ @Public
+ @PostMapping("/sms/send")
+ @RateLimit(permits = 1, duration = 60)
+ @Operation(summary = "发送短信验证码")
+ public Result sendSmsCode(@Valid @RequestBody SendSmsCodeDto dto) {
+ smsCodeService.sendCode(dto.getPhone());
+ return Result.success(null);
+ }
+
@PostMapping("/switch-child")
@Operation(summary = "切换到子女账号")
public Result