From 73cc12fe65bfc05374b7e1e33b6172b9357e5dcd Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Wed, 10 Dec 2025 16:49:07 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=20Java=20=E7=AB=AF?= =?UTF-8?q?=E9=80=82=E9=85=8D=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PythonAIGCClient 统一客户端设计 - 选题/内容/海报三个 Service 层 - 提示词管理迁移到 Java 端 (数据库) - 灰度切换策略 - 实施步骤和工时估算 --- docs/JAVA_ADAPTER_PLAN.md | 567 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 567 insertions(+) create mode 100644 docs/JAVA_ADAPTER_PLAN.md diff --git a/docs/JAVA_ADAPTER_PLAN.md b/docs/JAVA_ADAPTER_PLAN.md new file mode 100644 index 0000000..0add6f9 --- /dev/null +++ b/docs/JAVA_ADAPTER_PLAN.md @@ -0,0 +1,567 @@ +# Java 端适配方案 + +> 更新时间: 2024-12-10 + +## 概述 + +本文档描述 Java 端如何对接新的 Python AIGC 服务,包括: +1. 新增 AIGC 服务适配层 +2. 提示词管理迁移到 Java 端 +3. 灰度切换策略 + +--- + +## 一、架构设计 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Java 端 (主控) │ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ PromptManager │ │ AIGCService │ │ +│ │ (提示词管理) │ │ (AIGC 服务调用) │ │ +│ └────────┬────────┘ └────────┬────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────────┐ │ +│ │ PythonApiClient │ │ +│ │ (统一 HTTP 客户端) │ │ +│ └─────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Python AIGC 服务 │ +│ │ +│ POST /api/v2/aigc/execute │ +│ - engine: topic_generate / content_generate / poster_smart_v2 │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 二、Java 端新增模块 + +### 2.1 目录结构 + +``` +src/main/java/com/zowoyoo/zwypicture/ +├── aigc/ # 新增: AIGC 模块 +│ ├── client/ +│ │ └── PythonAIGCClient.java # Python API 客户端 +│ ├── service/ +│ │ ├── TopicGenerateService.java +│ │ ├── ContentGenerateService.java +│ │ └── PosterGenerateService.java +│ ├── dto/ +│ │ ├── request/ +│ │ │ ├── TopicGenerateRequest.java +│ │ │ ├── ContentGenerateRequest.java +│ │ │ └── PosterGenerateRequest.java +│ │ └── response/ +│ │ ├── TopicGenerateResponse.java +│ │ ├── ContentGenerateResponse.java +│ │ └── PosterGenerateResponse.java +│ └── controller/ +│ └── AIGCController.java # API 入口 +│ +├── prompt/ # 新增: 提示词管理 +│ ├── entity/ +│ │ └── PromptTemplate.java # 提示词实体 +│ ├── repository/ +│ │ └── PromptTemplateRepository.java +│ ├── service/ +│ │ └── PromptService.java +│ └── controller/ +│ └── PromptController.java +``` + +### 2.2 配置 + +```yaml +# application.yml +aigc: + python: + # 新服务地址 + base-url: http://localhost:8001 + # 旧服务地址 (灰度切换用) + legacy-url: http://localhost:8000 + # 超时配置 + connect-timeout: 5000 + read-timeout: 60000 + # 灰度开关 + use-new-service: true +``` + +--- + +## 三、核心代码设计 + +### 3.1 Python API 客户端 + +```java +package com.zowoyoo.zwypicture.aigc.client; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PythonAIGCClient { + + private final WebClient webClient; + + @Value("${aigc.python.base-url}") + private String baseUrl; + + /** + * 统一执行 AIGC 引擎 + */ + public Mono execute(String engine, Object params, Class responseType) { + AIGCExecuteRequest request = AIGCExecuteRequest.builder() + .engine(engine) + .params(params) + .asyncMode(false) + .build(); + + return webClient.post() + .uri(baseUrl + "/api/v2/aigc/execute") + .bodyValue(request) + .retrieve() + .bodyToMono(responseType) + .doOnError(e -> log.error("AIGC 调用失败: engine={}, error={}", engine, e.getMessage())); + } + + /** + * 执行选题生成 + */ + public Mono generateTopics(TopicGenerateRequest request) { + return execute("topic_generate", request, TopicGenerateResponse.class); + } + + /** + * 执行内容生成 + */ + public Mono generateContent(ContentGenerateRequest request) { + return execute("content_generate", request, ContentGenerateResponse.class); + } + + /** + * 执行海报生成 + */ + public Mono generatePoster(PosterGenerateRequest request) { + return execute("poster_smart_v2", request, PosterGenerateResponse.class); + } +} +``` + +### 3.2 请求/响应 DTO + +```java +// === 选题生成 === +@Data +@Builder +public class TopicGenerateRequest { + private Integer numTopics = 5; + private String month; // "2024-12" + private SubjectInfo subject; // 主体信息 + private StyleInfo style; // 风格 + private AudienceInfo audience; // 人群 + private HotTopics hotTopics; // 热点 +} + +@Data +public class TopicGenerateResponse { + private Boolean success; + private TopicData data; + private String error; + + @Data + public static class TopicData { + private List topics; + private Integer count; + } +} + +// === 内容生成 === +@Data +@Builder +public class ContentGenerateRequest { + private Topic topic; // 选题 + private SubjectInfo subject; // 主体信息 + private StyleInfo style; + private AudienceInfo audience; + private ReferenceContent reference; // 参考内容 + private Boolean enableJudge = true; +} + +@Data +public class ContentGenerateResponse { + private Boolean success; + private ContentData data; + + @Data + public static class ContentData { + private GeneratedContent content; + private Topic topic; + private Boolean judged; + } +} + +// === 海报生成 === +@Data +@Builder +public class PosterGenerateRequest { + private String category; // 景点/美食/酒店/民宿/活动/攻略 + private String name; + private String description; + private String price; + private String location; + private String features; // 逗号分隔 + private String imageUrl; + private String overrideLayout; // 可选 + private String overrideTheme; // 可选 +} + +@Data +public class PosterGenerateResponse { + private Boolean success; + private PosterData data; + + @Data + public static class PosterData { + private String previewBase64; // 预览图 + private Object fabricJson; // Fabric.js JSON + private String layout; + private String theme; + private Map content; + } +} +``` + +### 3.3 Service 层 + +```java +@Slf4j +@Service +@RequiredArgsConstructor +public class TopicGenerateService { + + private final PythonAIGCClient pythonClient; + private final PromptService promptService; // 提示词服务 + + /** + * 生成选题 + */ + public Mono> generateTopics( + Long scenicSpotId, + Long productId, + String styleId, + String audienceId, + String month, + int count + ) { + // 1. 从数据库获取完整对象 + SubjectInfo subject = buildSubjectInfo(scenicSpotId, productId); + StyleInfo style = promptService.getStyle(styleId); + AudienceInfo audience = promptService.getAudience(audienceId); + + // 2. 构建请求 + TopicGenerateRequest request = TopicGenerateRequest.builder() + .numTopics(count) + .month(month) + .subject(subject) + .style(style) + .audience(audience) + .build(); + + // 3. 调用 Python 服务 + return pythonClient.generateTopics(request) + .map(response -> { + if (!response.getSuccess()) { + throw new AIGCException("选题生成失败: " + response.getError()); + } + return response.getData().getTopics(); + }); + } + + private SubjectInfo buildSubjectInfo(Long scenicSpotId, Long productId) { + // 从数据库查询景区和产品信息,组装成 SubjectInfo + // ... + } +} +``` + +### 3.4 Controller 层 + +```java +@Slf4j +@RestController +@RequestMapping("/api/v2/aigc") +@RequiredArgsConstructor +public class AIGCController { + + private final TopicGenerateService topicService; + private final ContentGenerateService contentService; + private final PosterGenerateService posterService; + + /** + * 生成选题 + */ + @PostMapping("/topic/generate") + public Mono>> generateTopics( + @RequestBody TopicGenerateRequestVO request + ) { + return topicService.generateTopics( + request.getScenicSpotId(), + request.getProductId(), + request.getStyleId(), + request.getAudienceId(), + request.getMonth(), + request.getCount() + ).map(ApiResponse::success); + } + + /** + * 生成内容 + */ + @PostMapping("/content/generate") + public Mono> generateContent( + @RequestBody ContentGenerateRequestVO request + ) { + return contentService.generateContent(request) + .map(ApiResponse::success); + } + + /** + * 生成海报 + */ + @PostMapping("/poster/generate") + public Mono> generatePoster( + @RequestBody PosterGenerateRequestVO request + ) { + return posterService.generatePoster(request) + .map(ApiResponse::success); + } +} +``` + +--- + +## 四、提示词管理迁移 + +### 4.1 数据库表设计 + +```sql +CREATE TABLE prompt_template ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(100) NOT NULL COMMENT '模板名称', + type VARCHAR(50) NOT NULL COMMENT '类型: style/audience/system', + version VARCHAR(20) DEFAULT 'v1.0.0', + content TEXT NOT NULL COMMENT '模板内容', + variables JSON COMMENT '变量定义', + model_params JSON COMMENT '模型参数', + status TINYINT DEFAULT 1 COMMENT '状态: 1启用 0禁用', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_name_version (name, version) +) COMMENT '提示词模板'; + +-- 预置风格 +INSERT INTO prompt_template (name, type, content) VALUES +('xiaohongshu', 'style', '小红书种草风格,活泼有趣,善用emoji...'), +('gonglue', 'style', '攻略分享风格,干货满满,条理清晰...'), +('tuijian', 'style', '真诚推荐风格,真实体验,娓娓道来...'); + +-- 预置人群 +INSERT INTO prompt_template (name, type, content) VALUES +('qinzi', 'audience', '亲子家庭,关注安全、趣味、教育...'), +('zhoubianyou', 'audience', '周边游用户,关注性价比、交通便利...'), +('gaoshe', 'audience', '高消费人群,关注品质、私密、独特...'); +``` + +### 4.2 PromptService + +```java +@Service +@RequiredArgsConstructor +public class PromptService { + + private final PromptTemplateRepository repository; + + /** + * 获取风格配置 + */ + public StyleInfo getStyle(String styleId) { + PromptTemplate template = repository.findByNameAndType(styleId, "style") + .orElseThrow(() -> new NotFoundException("风格不存在: " + styleId)); + + return StyleInfo.builder() + .id(styleId) + .name(template.getName()) + .content(template.getContent()) + .build(); + } + + /** + * 获取人群配置 + */ + public AudienceInfo getAudience(String audienceId) { + PromptTemplate template = repository.findByNameAndType(audienceId, "audience") + .orElseThrow(() -> new NotFoundException("人群不存在: " + audienceId)); + + return AudienceInfo.builder() + .id(audienceId) + .name(template.getName()) + .content(template.getContent()) + .build(); + } + + /** + * 获取所有风格列表 (供前端下拉) + */ + public List listStyles() { + return repository.findByTypeAndStatus("style", 1) + .stream() + .map(this::toStyleInfo) + .collect(Collectors.toList()); + } + + /** + * 获取所有人群列表 + */ + public List listAudiences() { + return repository.findByTypeAndStatus("audience", 1) + .stream() + .map(this::toAudienceInfo) + .collect(Collectors.toList()); + } +} +``` + +### 4.3 管理接口 + +```java +@RestController +@RequestMapping("/api/v2/prompt") +@RequiredArgsConstructor +public class PromptController { + + private final PromptService promptService; + + // 列表 + @GetMapping("/styles") + public ApiResponse> listStyles() { + return ApiResponse.success(promptService.listStyles()); + } + + @GetMapping("/audiences") + public ApiResponse> listAudiences() { + return ApiResponse.success(promptService.listAudiences()); + } + + // CRUD + @PostMapping + public ApiResponse create(@RequestBody PromptTemplate template) { + return ApiResponse.success(promptService.create(template)); + } + + @PutMapping("/{id}") + public ApiResponse update( + @PathVariable Long id, + @RequestBody PromptTemplate template + ) { + return ApiResponse.success(promptService.update(id, template)); + } + + @DeleteMapping("/{id}") + public ApiResponse delete(@PathVariable Long id) { + promptService.delete(id); + return ApiResponse.success(); + } +} +``` + +--- + +## 五、灰度切换策略 + +### 5.1 配置开关 + +```java +@Configuration +@ConfigurationProperties(prefix = "aigc") +@Data +public class AIGCConfig { + private boolean useNewService = false; // 灰度开关 + private PythonConfig python = new PythonConfig(); + + @Data + public static class PythonConfig { + private String baseUrl; // 新服务 + private String legacyUrl; // 旧服务 + } +} +``` + +### 5.2 切换逻辑 + +```java +@Service +@RequiredArgsConstructor +public class AIGCRouter { + + private final AIGCConfig config; + private final PythonAIGCClient newClient; + private final LegacyAIGCClient legacyClient; + + public Mono route(String engine, Object params, Class responseType) { + if (config.isUseNewService()) { + return newClient.execute(engine, params, responseType); + } else { + return legacyClient.execute(engine, params, responseType); + } + } +} +``` + +### 5.3 测试流程 + +``` +1. 部署新 Python 服务到 8001 端口 +2. Java 端配置 aigc.use-new-service=false (默认用旧服务) +3. 测试环境手动切换为 true,验证新服务 +4. 验证通过后,生产环境切换为 true +5. 观察一段时间后,移除旧服务 +``` + +--- + +## 六、实施步骤 + +| 阶段 | 任务 | 工时 | +|-----|------|------| +| **Phase 1** | 新增 PythonAIGCClient + DTO | 0.5 天 | +| **Phase 2** | 新增 Service 层 (选题/内容/海报) | 1 天 | +| **Phase 3** | 新增 Controller + 前端对接 | 0.5 天 | +| **Phase 4** | 提示词管理迁移 (数据库 + API) | 1 天 | +| **Phase 5** | 灰度测试 + 切换 | 1 天 | + +**总计: 4 天** + +--- + +## 七、API 对照表 + +| 功能 | Java 新接口 | Python 引擎 | +|-----|-------------|-------------| +| 选题生成 | `POST /api/v2/aigc/topic/generate` | `topic_generate` | +| 内容生成 | `POST /api/v2/aigc/content/generate` | `content_generate` | +| 海报生成 | `POST /api/v2/aigc/poster/generate` | `poster_smart_v2` | +| 风格列表 | `GET /api/v2/prompt/styles` | - (Java 本地) | +| 人群列表 | `GET /api/v2/prompt/audiences` | - (Java 本地) |